maint: improve error messages upon failed read, write, access, close
[platform/upstream/coreutils.git] / src / truncate.c
1 /* truncate -- truncate or extend the length of files.
2    Copyright (C) 2008-2013 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* Written by Pádraig Brady
18
19    This is backwards compatible with the FreeBSD utility, but is more
20    flexible wrt the size specifications and the use of long options,
21    to better fit the "GNU" environment.  */
22
23 #include <config.h>             /* sets _FILE_OFFSET_BITS=64 etc. */
24 #include <stdio.h>
25 #include <getopt.h>
26 #include <sys/types.h>
27
28 #include "system.h"
29 #include "error.h"
30 #include "quote.h"
31 #include "stat-size.h"
32 #include "xstrtol.h"
33
34 /* The official name of this program (e.g., no 'g' prefix).  */
35 #define PROGRAM_NAME "truncate"
36
37 #define AUTHORS proper_name_utf8 ("Padraig Brady", "P\303\241draig Brady")
38
39 /* (-c) If true, don't create if not already there */
40 static bool no_create;
41
42 /* (-o) If true, --size refers to blocks not bytes */
43 static bool block_mode;
44
45 /* (-r) Reference file to use size from */
46 static char const *ref_file;
47
48 static struct option const longopts[] =
49 {
50   {"no-create", no_argument, NULL, 'c'},
51   {"io-blocks", no_argument, NULL, 'o'},
52   {"reference", required_argument, NULL, 'r'},
53   {"size", required_argument, NULL, 's'},
54   {GETOPT_HELP_OPTION_DECL},
55   {GETOPT_VERSION_OPTION_DECL},
56   {NULL, 0, NULL, 0}
57 };
58
59 typedef enum
60 { rm_abs = 0, rm_rel, rm_min, rm_max, rm_rdn, rm_rup } rel_mode_t;
61
62 /* Set size to the value of STR, interpreted as a decimal integer,
63    optionally multiplied by various values.
64    Return -1 on error, 0 on success.
65
66    This supports dd BLOCK size suffixes + lowercase g,t,m for bsd compat
67    Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats.  */
68 static int
69 parse_len (char const *str, off_t *size)
70 {
71   enum strtol_error e;
72   intmax_t tmp_size;
73   e = xstrtoimax (str, NULL, 10, &tmp_size, "EgGkKmMPtTYZ0");
74   if (e == LONGINT_OK
75       && !(OFF_T_MIN <= tmp_size && tmp_size <= OFF_T_MAX))
76     e = LONGINT_OVERFLOW;
77
78   if (e == LONGINT_OK)
79     {
80       errno = 0;
81       *size = tmp_size;
82       return 0;
83     }
84
85   errno = (e == LONGINT_OVERFLOW ? EOVERFLOW : 0);
86   return -1;
87 }
88
89 void
90 usage (int status)
91 {
92   if (status != EXIT_SUCCESS)
93     emit_try_help ();
94   else
95     {
96       printf (_("Usage: %s OPTION... FILE...\n"), program_name);
97       fputs (_("\
98 Shrink or extend the size of each FILE to the specified size\n\
99 \n\
100 A FILE argument that does not exist is created.\n\
101 \n\
102 If a FILE is larger than the specified size, the extra data is lost.\n\
103 If a FILE is shorter, it is extended and the extended part (hole)\n\
104 reads as zero bytes.\n\
105 "), stdout);
106
107       emit_mandatory_arg_note ();
108
109       fputs (_("\
110   -c, --no-create        do not create any files\n\
111 "), stdout);
112       fputs (_("\
113   -o, --io-blocks        treat SIZE as number of IO blocks instead of bytes\n\
114 "), stdout);
115       fputs (_("\
116   -r, --reference=RFILE  base size on RFILE\n\
117   -s, --size=SIZE        set or adjust the file size by SIZE\n"), stdout);
118       fputs (HELP_OPTION_DESCRIPTION, stdout);
119       fputs (VERSION_OPTION_DESCRIPTION, stdout);
120       emit_size_note ();
121       fputs (_("\n\
122 SIZE may also be prefixed by one of the following modifying characters:\n\
123 '+' extend by, '-' reduce by, '<' at most, '>' at least,\n\
124 '/' round down to multiple of, '%' round up to multiple of.\n"), stdout);
125       emit_ancillary_info ();
126     }
127   exit (status);
128 }
129
130 /* return true on success, false on error.  */
131 static bool
132 do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
133               rel_mode_t rel_mode)
134 {
135   struct stat sb;
136   off_t nsize;
137
138   if ((block_mode || (rel_mode && rsize < 0)) && fstat (fd, &sb) != 0)
139     {
140       error (0, errno, _("cannot fstat %s"), quote (fname));
141       return false;
142     }
143   if (block_mode)
144     {
145       off_t const blksize = ST_BLKSIZE (sb);
146       if (ssize < OFF_T_MIN / blksize || ssize > OFF_T_MAX / blksize)
147         {
148           error (0, 0,
149                  _("overflow in %" PRIdMAX
150                    " * %" PRIdMAX " byte blocks for file %s"),
151                  (intmax_t) ssize, (intmax_t) blksize,
152                  quote (fname));
153           return false;
154         }
155       ssize *= blksize;
156     }
157   if (rel_mode)
158     {
159       uintmax_t fsize;
160
161       if (0 <= rsize)
162         fsize = rsize;
163       else
164         {
165           off_t file_size;
166           if (usable_st_size (&sb))
167             {
168               file_size = sb.st_size;
169               if (file_size < 0)
170                 {
171                   /* Sanity check.  Overflow is the only reason I can think
172                      this would ever go negative. */
173                   error (0, 0, _("%s has unusable, apparently negative size"),
174                          quote (fname));
175                   return false;
176                 }
177             }
178           else
179             {
180               file_size = lseek (fd, 0, SEEK_END);
181               if (file_size < 0)
182                 {
183                   error (0, errno, _("cannot get the size of %s"),
184                          quote (fname));
185                   return false;
186                 }
187             }
188           fsize = file_size;
189         }
190
191       if (rel_mode == rm_min)
192         nsize = MAX (fsize, (uintmax_t) ssize);
193       else if (rel_mode == rm_max)
194         nsize = MIN (fsize, (uintmax_t) ssize);
195       else if (rel_mode == rm_rdn)
196         /* 0..ssize-1 -> 0 */
197         nsize = (fsize / ssize) * ssize;
198       else if (rel_mode == rm_rup)
199         /* 1..ssize -> ssize */
200         {
201           /* Here ssize>=1 && fsize>=0 */
202           uintmax_t const overflow = ((fsize + ssize - 1) / ssize) * ssize;
203           if (overflow > OFF_T_MAX)
204             {
205               error (0, 0, _("overflow rounding up size of file %s"),
206                      quote (fname));
207               return false;
208             }
209           nsize = overflow;
210         }
211       else
212         {
213           if (ssize > OFF_T_MAX - (off_t)fsize)
214             {
215               error (0, 0, _("overflow extending size of file %s"),
216                      quote (fname));
217               return false;
218             }
219           nsize = fsize + ssize;
220         }
221     }
222   else
223     nsize = ssize;
224   if (nsize < 0)
225     nsize = 0;
226
227   if (ftruncate (fd, nsize) == -1)      /* note updates mtime & ctime */
228     {
229       error (0, errno,
230              _("failed to truncate %s at %" PRIdMAX " bytes"), quote (fname),
231              (intmax_t) nsize);
232       return false;
233     }
234
235   return true;
236 }
237
238 int
239 main (int argc, char **argv)
240 {
241   bool got_size = false;
242   bool errors = false;
243   off_t size IF_LINT ( = 0);
244   off_t rsize = -1;
245   rel_mode_t rel_mode = rm_abs;
246   int c, fd = -1, oflags;
247   char const *fname;
248
249   initialize_main (&argc, &argv);
250   set_program_name (argv[0]);
251   setlocale (LC_ALL, "");
252   bindtextdomain (PACKAGE, LOCALEDIR);
253   textdomain (PACKAGE);
254
255   atexit (close_stdout);
256
257   while ((c = getopt_long (argc, argv, "cor:s:", longopts, NULL)) != -1)
258     {
259       switch (c)
260         {
261         case 'c':
262           no_create = true;
263           break;
264
265         case 'o':
266           block_mode = true;
267           break;
268
269         case 'r':
270           ref_file = optarg;
271           break;
272
273         case 's':
274           /* skip any whitespace */
275           while (isspace (to_uchar (*optarg)))
276             optarg++;
277           switch (*optarg)
278             {
279             case '<':
280               rel_mode = rm_max;
281               optarg++;
282               break;
283             case '>':
284               rel_mode = rm_min;
285               optarg++;
286               break;
287             case '/':
288               rel_mode = rm_rdn;
289               optarg++;
290               break;
291             case '%':
292               rel_mode = rm_rup;
293               optarg++;
294               break;
295             }
296           /* skip any whitespace */
297           while (isspace (to_uchar (*optarg)))
298             optarg++;
299           if (*optarg == '+' || *optarg == '-')
300             {
301               if (rel_mode)
302                 {
303                   error (0, 0, _("multiple relative modifiers specified"));
304                   /* Note other combinations are flagged as invalid numbers */
305                   usage (EXIT_FAILURE);
306                 }
307               rel_mode = rm_rel;
308             }
309           if (parse_len (optarg, &size) == -1)
310             error (EXIT_FAILURE, errno, _("invalid number %s"),
311                    quote (optarg));
312           /* Rounding to multiple of 0 is nonsensical */
313           if ((rel_mode == rm_rup || rel_mode == rm_rdn) && size == 0)
314             error (EXIT_FAILURE, 0, _("division by zero"));
315           got_size = true;
316           break;
317
318         case_GETOPT_HELP_CHAR;
319
320         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
321
322         default:
323           usage (EXIT_FAILURE);
324         }
325     }
326
327   argv += optind;
328   argc -= optind;
329
330   /* must specify either size or reference file */
331   if (!ref_file && !got_size)
332     {
333       error (0, 0, _("you must specify either %s or %s"),
334              quote_n (0, "--size"), quote_n (1, "--reference"));
335       usage (EXIT_FAILURE);
336     }
337   /* must specify a relative size with a reference file */
338   if (ref_file && got_size && !rel_mode)
339     {
340       error (0, 0, _("you must specify a relative %s with %s"),
341              quote_n (0, "--size"), quote_n (1, "--reference"));
342       usage (EXIT_FAILURE);
343     }
344   /* block_mode without size is not valid */
345   if (block_mode && !got_size)
346     {
347       error (0, 0, _("%s was specified but %s was not"),
348              quote_n (0, "--io-blocks"), quote_n (1, "--size"));
349       usage (EXIT_FAILURE);
350     }
351   /* must specify at least 1 file */
352   if (argc < 1)
353     {
354       error (0, 0, _("missing file operand"));
355       usage (EXIT_FAILURE);
356     }
357
358   if (ref_file)
359     {
360       struct stat sb;
361       off_t file_size = -1;
362       if (stat (ref_file, &sb) != 0)
363         error (EXIT_FAILURE, errno, _("cannot stat %s"), quote (ref_file));
364       if (usable_st_size (&sb))
365         file_size = sb.st_size;
366       else
367         {
368           int ref_fd = open (ref_file, O_RDONLY);
369           if (0 <= ref_fd)
370             {
371               off_t file_end = lseek (ref_fd, 0, SEEK_END);
372               int saved_errno = errno;
373               close (ref_fd); /* ignore failure */
374               if (0 <= file_end)
375                 file_size = file_end;
376               else
377                 {
378                   /* restore, in case close clobbered it. */
379                   errno = saved_errno;
380                 }
381             }
382         }
383       if (file_size < 0)
384         error (EXIT_FAILURE, errno, _("cannot get the size of %s"),
385                quote (ref_file));
386       if (!got_size)
387         size = file_size;
388       else
389         rsize = file_size;
390     }
391
392   oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
393
394   while ((fname = *argv++) != NULL)
395     {
396       if ((fd = open (fname, oflags, MODE_RW_UGO)) == -1)
397         {
398           /* 'truncate -s0 -c no-such-file'  shouldn't gen error
399              'truncate -s0 no-such-dir/file' should gen ENOENT error
400              'truncate -s0 no-such-dir/' should gen EISDIR error
401              'truncate -s0 .' should gen EISDIR error */
402           if (!(no_create && errno == ENOENT))
403             {
404               error (0, errno, _("cannot open %s for writing"),
405                      quote (fname));
406               errors = true;
407             }
408           continue;
409         }
410
411
412       if (fd != -1)
413         {
414           errors |= !do_ftruncate (fd, fname, size, rsize, rel_mode);
415           if (close (fd) != 0)
416             {
417               error (0, errno, _("failed to close %s"), quote (fname));
418               errors = true;
419             }
420         }
421     }
422
423   return errors ? EXIT_FAILURE : EXIT_SUCCESS;
424 }