maint: update all copyright year number ranges
[platform/upstream/coreutils.git] / src / truncate.c
1 /* truncate -- truncate or extend the length of files.
2    Copyright (C) 2008-2011 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 "posixver.h"
31 #include "quote.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     fprintf (stderr, _("Try `%s --help' for more information.\n"),
94              program_name);
95   else
96     {
97       printf (_("Usage: %s OPTION... FILE...\n"), program_name);
98       fputs (_("\
99 Shrink or extend the size of each FILE to the specified size\n\
100 \n\
101 A FILE argument that does not exist is created.\n\
102 \n\
103 If a FILE is larger than the specified size, the extra data is lost.\n\
104 If a FILE is shorter, it is extended and the extended part (hole)\n\
105 reads as zero bytes.\n\
106 \n\
107 "), stdout);
108       fputs (_("\
109 Mandatory arguments to long options are mandatory for short options too.\n\
110 "), stdout);
111       fputs (_("\
112   -c, --no-create        do not create any files\n\
113 "), stdout);
114       fputs (_("\
115   -o, --io-blocks        treat SIZE as number of IO blocks instead of bytes\n\
116 "), stdout);
117       fputs (_("\
118   -r, --reference=RFILE  base size on RFILE\n\
119   -s, --size=SIZE        set or adjust the file size by SIZE\n"), stdout);
120       fputs (HELP_OPTION_DESCRIPTION, stdout);
121       fputs (VERSION_OPTION_DESCRIPTION, stdout);
122       emit_size_note ();
123       fputs (_("\n\
124 SIZE may also be prefixed by one of the following modifying characters:\n\
125 `+' extend by, `-' reduce by, `<' at most, `>' at least,\n\
126 `/' round down to multiple of, `%' round up to multiple of.\n"), stdout);
127       emit_ancillary_info ();
128     }
129   exit (status);
130 }
131
132 /* return true on success, false on error.  */
133 static bool
134 do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
135               rel_mode_t rel_mode)
136 {
137   struct stat sb;
138   off_t nsize;
139
140   if ((block_mode || (rel_mode && rsize < 0)) && fstat (fd, &sb) != 0)
141     {
142       error (0, errno, _("cannot fstat %s"), quote (fname));
143       return false;
144     }
145   if (block_mode)
146     {
147       off_t const blksize = ST_BLKSIZE (sb);
148       if (ssize < OFF_T_MIN / blksize || ssize > OFF_T_MAX / blksize)
149         {
150           error (0, 0,
151                  _("overflow in %" PRIdMAX
152                    " * %" PRIdMAX " byte blocks for file %s"),
153                  (intmax_t) ssize, (intmax_t) blksize,
154                  quote (fname));
155           return false;
156         }
157       ssize *= blksize;
158     }
159   if (rel_mode)
160     {
161       uintmax_t const fsize = rsize < 0 ? sb.st_size : rsize;
162
163       if (rsize < 0) /* fstat used above to get size.  */
164         {
165           if (!S_ISREG (sb.st_mode) && !S_TYPEISSHM (&sb))
166             {
167               error (0, 0, _("cannot get the size of %s"), quote (fname));
168               return false;
169             }
170           if (sb.st_size < 0)
171             {
172               /* Sanity check. Overflow is the only reason I can think
173                  this would ever go negative. */
174               error (0, 0, _("%s has unusable, apparently negative size"),
175                      quote (fname));
176               return false;
177             }
178         }
179
180       if (rel_mode == rm_min)
181         nsize = MAX (fsize, (uintmax_t) ssize);
182       else if (rel_mode == rm_max)
183         nsize = MIN (fsize, (uintmax_t) ssize);
184       else if (rel_mode == rm_rdn)
185         /* 0..ssize-1 -> 0 */
186         nsize = (fsize / ssize) * ssize;
187       else if (rel_mode == rm_rup)
188         /* 1..ssize -> ssize */
189         {
190           /* Here ssize>=1 && fsize>=0 */
191           uintmax_t const overflow = ((fsize + ssize - 1) / ssize) * ssize;
192           if (overflow > OFF_T_MAX)
193             {
194               error (0, 0, _("overflow rounding up size of file %s"),
195                      quote (fname));
196               return false;
197             }
198           nsize = overflow;
199         }
200       else
201         {
202           if (ssize > OFF_T_MAX - (off_t)fsize)
203             {
204               error (0, 0, _("overflow extending size of file %s"),
205                      quote (fname));
206               return false;
207             }
208           nsize = fsize + ssize;
209         }
210     }
211   else
212     nsize = ssize;
213   if (nsize < 0)
214     nsize = 0;
215
216   if (ftruncate (fd, nsize) == -1)      /* note updates mtime & ctime */
217     {
218       error (0, errno,
219              _("failed to truncate %s at %" PRIdMAX " bytes"), quote (fname),
220              (intmax_t) nsize);
221       return false;
222     }
223
224   return true;
225 }
226
227 int
228 main (int argc, char **argv)
229 {
230   bool got_size = false;
231   bool errors = false;
232   off_t size IF_LINT ( = 0);
233   off_t rsize = -1;
234   rel_mode_t rel_mode = rm_abs;
235   mode_t omode;
236   int c, fd = -1, oflags;
237   char const *fname;
238
239   initialize_main (&argc, &argv);
240   set_program_name (argv[0]);
241   setlocale (LC_ALL, "");
242   bindtextdomain (PACKAGE, LOCALEDIR);
243   textdomain (PACKAGE);
244
245   atexit (close_stdout);
246
247   while ((c = getopt_long (argc, argv, "cor:s:", longopts, NULL)) != -1)
248     {
249       switch (c)
250         {
251         case 'c':
252           no_create = true;
253           break;
254
255         case 'o':
256           block_mode = true;
257           break;
258
259         case 'r':
260           ref_file = optarg;
261           break;
262
263         case 's':
264           /* skip any whitespace */
265           while (isspace (to_uchar (*optarg)))
266             optarg++;
267           switch (*optarg)
268             {
269             case '<':
270               rel_mode = rm_max;
271               optarg++;
272               break;
273             case '>':
274               rel_mode = rm_min;
275               optarg++;
276               break;
277             case '/':
278               rel_mode = rm_rdn;
279               optarg++;
280               break;
281             case '%':
282               rel_mode = rm_rup;
283               optarg++;
284               break;
285             }
286           /* skip any whitespace */
287           while (isspace (to_uchar (*optarg)))
288             optarg++;
289           if (*optarg == '+' || *optarg == '-')
290             {
291               if (rel_mode)
292                 {
293                   error (0, 0, _("multiple relative modifiers specified"));
294                   /* Note other combinations are flagged as invalid numbers */
295                   usage (EXIT_FAILURE);
296                 }
297               rel_mode = rm_rel;
298             }
299           if (parse_len (optarg, &size) == -1)
300             error (EXIT_FAILURE, errno, _("invalid number %s"),
301                    quote (optarg));
302           /* Rounding to multiple of 0 is nonsensical */
303           if ((rel_mode == rm_rup || rel_mode == rm_rdn) && size == 0)
304             error (EXIT_FAILURE, 0, _("division by zero"));
305           got_size = true;
306           break;
307
308         case_GETOPT_HELP_CHAR;
309
310         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
311
312         default:
313           usage (EXIT_FAILURE);
314         }
315     }
316
317   argv += optind;
318   argc -= optind;
319
320   /* must specify either size or reference file */
321   if (!ref_file && !got_size)
322     {
323       error (0, 0, _("you must specify either %s or %s"),
324              quote_n (0, "--size"), quote_n (1, "--reference"));
325       usage (EXIT_FAILURE);
326     }
327   /* must specify a relative size with a reference file */
328   if (ref_file && got_size && !rel_mode)
329     {
330       error (0, 0, _("you must specify a relative %s with %s"),
331              quote_n (0, "--size"), quote_n (1, "--reference"));
332       usage (EXIT_FAILURE);
333     }
334   /* block_mode without size is not valid */
335   if (block_mode && !got_size)
336     {
337       error (0, 0, _("%s was specified but %s was not"),
338              quote_n (0, "--io-blocks"), quote_n (1, "--size"));
339       usage (EXIT_FAILURE);
340     }
341   /* must specify at least 1 file */
342   if (argc < 1)
343     {
344       error (0, 0, _("missing file operand"));
345       usage (EXIT_FAILURE);
346     }
347
348   if (ref_file)
349     {
350       /* FIXME: Maybe support getting size of block devices.  */
351       struct stat sb;
352       if (stat (ref_file, &sb) != 0)
353         error (EXIT_FAILURE, errno, _("cannot stat %s"), quote (ref_file));
354       if (!S_ISREG (sb.st_mode) && !S_TYPEISSHM (&sb))
355         error (EXIT_FAILURE, 0, _("cannot get the size of %s"),
356                quote (ref_file));
357       if (!got_size)
358         size = sb.st_size;
359       else
360         rsize = sb.st_size;
361     }
362
363   oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
364   omode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
365
366   while ((fname = *argv++) != NULL)
367     {
368       if ((fd = open (fname, oflags, omode)) == -1)
369         {
370           /* `truncate -s0 -c no-such-file`  shouldn't gen error
371              `truncate -s0 no-such-dir/file` should gen ENOENT error
372              `truncate -s0 no-such-dir/` should gen EISDIR error
373              `truncate -s0 .` should gen EISDIR error */
374           if (!(no_create && errno == ENOENT))
375             {
376               error (0, errno, _("cannot open %s for writing"),
377                      quote (fname));
378               errors = true;
379             }
380           continue;
381         }
382
383
384       if (fd != -1)
385         {
386           errors |= !do_ftruncate (fd, fname, size, rsize, rel_mode);
387           if (close (fd) != 0)
388             {
389               error (0, errno, _("closing %s"), quote (fname));
390               errors = true;
391             }
392         }
393     }
394
395   return errors ? EXIT_FAILURE : EXIT_SUCCESS;
396 }