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