truncate: improve handling of non regular files
[platform/upstream/coreutils.git] / src / truncate.c
1 /* truncate -- truncate or extend the length of files.
2    Copyright (C) 2008-2010 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 1 on error, 0 on success */
133 static int
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 1;
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 1;
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 1;
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 1;
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 1;
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 1;
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 1;
222     }
223
224   return 0;
225 }
226
227 int
228 main (int argc, char **argv)
229 {
230   bool got_size = 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, errors = 0, 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++;
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++;
390             }
391         }
392     }
393
394   return errors ? EXIT_FAILURE : EXIT_SUCCESS;
395 }