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