add "const" attribute, where possible
[platform/upstream/coreutils.git] / src / truncate.c
1 /* truncate -- truncate or extend the length of files.
2    Copyright (C) 2008 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    Note if !defined(HAVE_FTRUNCATE) then the --skip-ftruncate configure flag
24    was specified or we're in a mingw environment. In these cases gnulib
25    emulation will be used and GNULIB_FTRUNCATE is defined. Note if emulation
26    can't even be provided ftruncate() will return EIO.  */
27
28 #include <config.h>             /* sets _FILE_OFFSET_BITS=64 etc. */
29 #include <stdio.h>
30 #include <getopt.h>
31 #include <sys/types.h>
32
33 #include "system.h"
34 #include "error.h"
35 #include "posixver.h"
36 #include "quote.h"
37 #include "xstrtol.h"
38
39 /* The official name of this program (e.g., no `g' prefix).  */
40 #define PROGRAM_NAME "truncate"
41
42 #define AUTHORS proper_name_utf8 ("Padraig Brady", "P\303\241draig Brady")
43
44 /* (-c) If true, don't create if not already there */
45 static bool no_create;
46
47 /* (-o) If true, --size refers to blocks not bytes */
48 static bool block_mode;
49
50 /* (-r) Reference file to use size from */
51 static char const *ref_file;
52
53 static const struct option const longopts[] =
54 {
55   {"no-create", no_argument, NULL, 'c'},
56   {"io-blocks", no_argument, NULL, 'o'},
57   {"reference", required_argument, NULL, 'r'},
58   {"size", required_argument, NULL, 's'},
59   {GETOPT_HELP_OPTION_DECL},
60   {GETOPT_VERSION_OPTION_DECL},
61   {NULL, 0, NULL, 0}
62 };
63
64 typedef enum
65 { rm_abs = 0, rm_rel, rm_min, rm_max, rm_rdn, rm_rup } rel_mode_t;
66
67 /* Set size to the value of STR, interpreted as a decimal integer,
68    optionally multiplied by various values.
69    Return -1 on error, 0 on success.
70
71    This supports dd BLOCK size suffixes + lowercase g,t,m for bsd compat
72    Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats.  */
73 static int
74 parse_len (char const *str, off_t *size)
75 {
76   enum strtol_error e;
77   /* OFF_T_MAX = INTMAX_MAX */
78   e = xstrtoimax (str, NULL, 10, size, "EgGkKmMPtTYZ0");
79   errno = (e == LONGINT_OVERFLOW) ? EOVERFLOW : 0;
80   return (e == LONGINT_OK) ? 0 : -1;
81 }
82
83 static void
84 usage (int status)
85 {
86   if (status != EXIT_SUCCESS)
87     fprintf (stderr, _("Try `%s --help' for more information.\n"),
88              program_name);
89   else
90     {
91       printf (_("Usage: %s OPTION... FILE...\n"), program_name);
92       fputs (_("\
93 Shrink or extend the size of each FILE to the specified size\n\
94 \n\
95 A FILE argument that does not exist is created.\n\
96 \n\
97 If a FILE is larger than the specified size, the extra data is lost.\n\
98 If a FILE is shorter, it is extended and the extended part (hole)\n\
99 reads as zero bytes.\n\
100 \n\
101 "), stdout);
102       fputs (_("\
103 Mandatory arguments to long options are mandatory for short options too.\n\
104 "), stdout);
105       fputs (_("\
106   -c, --no-create        do not create any files\n\
107 "), stdout);
108       fputs (_("\
109   -o, --io-blocks        Treat SIZE as number of IO blocks instead of bytes\n\
110 "), stdout);
111       fputs (_("\
112   -r, --reference=FILE   use this FILE's size\n\
113   -s, --size=SIZE        use this SIZE\n"), stdout);
114       fputs (HELP_OPTION_DESCRIPTION, stdout);
115       fputs (VERSION_OPTION_DESCRIPTION, stdout);
116       fputs (_("\n\
117 SIZE is a number which may be followed by one of the following suffixes:\n\
118 KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.\n\
119 "), stdout);
120       fputs (_("\n\
121 SIZE may also be prefixed by one of the following modifying characters:\n\
122 `+' extend by, `-' reduce by, `<' at most, `>' at least,\n\
123 `/' round down to multiple of, `%' round up to multiple of.\n"), stdout);
124       fputs (_("\
125 \n\
126 Note that the -r and -s options are mutually exclusive.\n\
127 "), stdout);
128       emit_bug_reporting_address ();
129     }
130   exit (status);
131 }
132
133 /* return 1 on error, 0 on success */
134 static int
135 do_ftruncate (int fd, char const *fname, off_t ssize, rel_mode_t rel_mode)
136 {
137   struct stat sb;
138   off_t nsize;
139
140   if ((block_mode || rel_mode) && fstat (fd, &sb) != 0)
141     {
142       error (0, errno, _("cannot fstat %s"), quote (fname));
143       return 1;
144     }
145   if (block_mode)
146     {
147       size_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                    " * %zu byte blocks for file %s"), ssize, blksize,
153                  quote (fname));
154           return 1;
155         }
156       ssize *= blksize;
157     }
158   if (rel_mode)
159     {
160       uintmax_t const fsize = sb.st_size;
161
162       if (sb.st_size < 0)
163         {
164           /* Complain only for a regular file, a directory,
165              or a shared memory object, as POSIX 1003.1-2004 specifies
166              ftruncate's behavior only for these file types.  */
167           if (S_ISREG (sb.st_mode) || S_ISDIR (sb.st_mode)
168               || S_TYPEISSHM (&sb))
169             {
170               /* overflow is the only reason I can think
171                  this would ever go negative for the above types */
172               error (0, 0, _("%s has unusable, apparently negative size"),
173                      quote (fname));
174               return 1;
175             }
176           return 0;
177         }
178
179       if (rel_mode == rm_min)
180         nsize = MAX (fsize, ssize);
181       else if (rel_mode == rm_max)
182         nsize = MIN (fsize, 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 1;
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 1;
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       /* Complain only when ftruncate fails on a regular file, a
218          directory, or a shared memory object, as POSIX 1003.1-2004
219          specifies ftruncate's behavior only for these file types.
220          For example, do not complain when Linux 2.4 ftruncate
221          fails on /dev/fd0.  */
222       int const ftruncate_errno = errno;
223       if (fstat (fd, &sb) != 0)
224         {
225           error (0, errno, _("cannot fstat %s"), quote (fname));
226           return 1;
227         }
228       else if (S_ISREG (sb.st_mode) || S_ISDIR (sb.st_mode)
229                || S_TYPEISSHM (&sb))
230         {
231           error (0, ftruncate_errno,
232                  _("truncating %s at %" PRIdMAX " bytes"), quote (fname),
233                  nsize);
234           return 1;
235         }
236       return 0;
237     }
238
239   return 0;
240 }
241
242 int
243 main (int argc, char **argv)
244 {
245   bool got_size = false;
246   off_t size;
247   rel_mode_t rel_mode = rm_abs;
248   mode_t omode;
249   int c, errors = 0, fd = -1, oflags;
250   char const *fname;
251
252   initialize_main (&argc, &argv);
253   set_program_name (argv[0]);
254   setlocale (LC_ALL, "");
255   bindtextdomain (PACKAGE, LOCALEDIR);
256   textdomain (PACKAGE);
257
258   atexit (close_stdout);
259
260   while ((c = getopt_long (argc, argv, "cor:s:", longopts, NULL)) != -1)
261     {
262       switch (c)
263         {
264         case 'c':
265           no_create = true;
266           break;
267
268         case 'o':
269           block_mode = true;
270           break;
271
272         case 'r':
273           ref_file = optarg;
274           break;
275
276         case 's':
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           if (*optarg == '+' || *optarg == '-')
297             {
298               if (rel_mode)
299                 {
300                   error (0, 0, _("multiple relative modifiers specified"));
301                   /* Note other combinations are flagged as invalid numbers */
302                   usage (EXIT_FAILURE);
303                 }
304               rel_mode = rm_rel;
305             }
306           if (parse_len (optarg, &size) == -1)
307             error (EXIT_FAILURE, errno, _("invalid number %s"),
308                    quote (optarg));
309           /* Rounding to multiple of 0 is nonsensical */
310           if ((rel_mode == rm_rup || rel_mode == rm_rdn) && size == 0)
311             error (EXIT_FAILURE, 0, _("division by zero"));
312           got_size = true;
313           break;
314
315         case_GETOPT_HELP_CHAR;
316
317         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
318
319         default:
320           usage (EXIT_FAILURE);
321         }
322     }
323
324   argv += optind;
325   argc -= optind;
326
327   /* must specify either size or reference file */
328   if ((ref_file && got_size) || (!ref_file && !got_size))
329     {
330       error (0, 0, _("you must specify one of %s or %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       struct stat sb;
351       if (stat (ref_file, &sb) != 0)
352         error (EXIT_FAILURE, errno, _("cannot stat %s"), quote (ref_file));
353       size = sb.st_size;
354     }
355
356   oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
357   omode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
358
359   while ((fname = *argv++) != NULL)
360     {
361       if ((fd = open (fname, oflags, omode)) == -1)
362         {
363           /* `truncate -s0 -c no-such-file`  shouldn't gen error
364              `truncate -s0 no-such-dir/file` should gen ENOENT error
365              `truncate -s0 no-such-dir/` should gen EISDIR error
366              `truncate -s0 .` should gen EISDIR error */
367           if (!(no_create && errno == ENOENT))
368             {
369               int const open_errno = errno;
370               struct stat sb;
371               if (stat (fname, &sb) == 0)
372                 {
373                   /* Complain only for a regular file, a directory,
374                      or a shared memory object, as POSIX 1003.1-2004 specifies
375                      ftruncate's behavior only for these file types.  */
376                   if (!S_ISREG (sb.st_mode) && !S_ISDIR (sb.st_mode)
377                       && !S_TYPEISSHM (&sb))
378                     continue;
379                 }
380               error (0, open_errno, _("cannot open %s for writing"),
381                      quote (fname));
382               errors++;
383             }
384           continue;
385         }
386
387
388       if (fd != -1)
389         {
390           errors += do_ftruncate (fd, fname, size, rel_mode);
391           if (close (fd) != 0)
392             {
393               error (0, errno, _("closing %s"), quote (fname));
394               errors++;
395             }
396         }
397     }
398
399   return errors ? EXIT_FAILURE : EXIT_SUCCESS;
400 }
401
402 /*
403  * Local variables:
404  *  indent-tabs-mode: nil
405  * End:
406  */