numfmt: prefer signed types
[coreutils.git] / src / truncate.c
blob7ecd595d8619fb2fe6f31bab79b80069af9754a8
1 /* truncate -- truncate or extend the length of files.
2 Copyright (C) 2008-2023 Free Software Foundation, Inc.
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.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Pádraig Brady
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. */
23 #include <config.h> /* sets _FILE_OFFSET_BITS=64 etc. */
24 #include <stdckdint.h>
25 #include <stdio.h>
26 #include <getopt.h>
27 #include <sys/types.h>
29 #include "system.h"
30 #include "quote.h"
31 #include "stat-size.h"
32 #include "xdectoint.h"
34 /* The official name of this program (e.g., no 'g' prefix). */
35 #define PROGRAM_NAME "truncate"
37 #define AUTHORS proper_name ("Padraig Brady")
39 /* (-c) If true, don't create if not already there */
40 static bool no_create;
42 /* (-o) If true, --size refers to blocks not bytes */
43 static bool block_mode;
45 /* (-r) Reference file to use size from */
46 static char const *ref_file;
48 static struct option const longopts[] =
50 {"no-create", no_argument, nullptr, 'c'},
51 {"io-blocks", no_argument, nullptr, 'o'},
52 {"reference", required_argument, nullptr, 'r'},
53 {"size", required_argument, nullptr, 's'},
54 {GETOPT_HELP_OPTION_DECL},
55 {GETOPT_VERSION_OPTION_DECL},
56 {nullptr, 0, nullptr, 0}
59 typedef enum
60 { rm_abs = 0, rm_rel, rm_min, rm_max, rm_rdn, rm_rup } rel_mode_t;
62 void
63 usage (int status)
65 if (status != EXIT_SUCCESS)
66 emit_try_help ();
67 else
69 printf (_("Usage: %s OPTION... FILE...\n"), program_name);
70 fputs (_("\
71 Shrink or extend the size of each FILE to the specified size\n\
72 \n\
73 A FILE argument that does not exist is created.\n\
74 \n\
75 If a FILE is larger than the specified size, the extra data is lost.\n\
76 If a FILE is shorter, it is extended and the sparse extended part (hole)\n\
77 reads as zero bytes.\n\
78 "), stdout);
80 emit_mandatory_arg_note ();
82 fputs (_("\
83 -c, --no-create do not create any files\n\
84 "), stdout);
85 fputs (_("\
86 -o, --io-blocks treat SIZE as number of IO blocks instead of bytes\n\
87 "), stdout);
88 fputs (_("\
89 -r, --reference=RFILE base size on RFILE\n\
90 -s, --size=SIZE set or adjust the file size by SIZE bytes\n"), stdout);
91 fputs (HELP_OPTION_DESCRIPTION, stdout);
92 fputs (VERSION_OPTION_DESCRIPTION, stdout);
93 emit_size_note ();
94 fputs (_("\n\
95 SIZE may also be prefixed by one of the following modifying characters:\n\
96 '+' extend by, '-' reduce by, '<' at most, '>' at least,\n\
97 '/' round down to multiple of, '%' round up to multiple of.\n"), stdout);
98 emit_ancillary_info (PROGRAM_NAME);
100 exit (status);
103 /* return true on success, false on error. */
104 static bool
105 do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize,
106 rel_mode_t rel_mode)
108 struct stat sb;
109 off_t nsize;
111 if ((block_mode || (rel_mode && rsize < 0)) && fstat (fd, &sb) != 0)
113 error (0, errno, _("cannot fstat %s"), quoteaf (fname));
114 return false;
116 if (block_mode)
118 ptrdiff_t blksize = ST_BLKSIZE (sb);
119 intmax_t ssize0 = ssize;
120 if (ckd_mul (&ssize, ssize, blksize))
122 error (0, 0,
123 _("overflow in %" PRIdMAX
124 " * %" PRIdPTR " byte blocks for file %s"),
125 ssize0, blksize, quoteaf (fname));
126 return false;
129 if (rel_mode)
131 off_t fsize;
133 if (0 <= rsize)
134 fsize = rsize;
135 else
137 if (usable_st_size (&sb))
139 fsize = sb.st_size;
140 if (fsize < 0)
142 /* Sanity check. Overflow is the only reason I can think
143 this would ever go negative. */
144 error (0, 0, _("%s has unusable, apparently negative size"),
145 quoteaf (fname));
146 return false;
149 else
151 fsize = lseek (fd, 0, SEEK_END);
152 if (fsize < 0)
154 error (0, errno, _("cannot get the size of %s"),
155 quoteaf (fname));
156 return false;
161 if (rel_mode == rm_min)
162 nsize = MAX (fsize, ssize);
163 else if (rel_mode == rm_max)
164 nsize = MIN (fsize, ssize);
165 else if (rel_mode == rm_rdn)
166 /* 0..ssize-1 -> 0 */
167 nsize = fsize - fsize % ssize;
168 else
170 if (rel_mode == rm_rup)
172 /* 1..ssize -> ssize */
173 off_t r = fsize % ssize;
174 ssize = r == 0 ? 0 : ssize - r;
176 if (ckd_add (&nsize, fsize, ssize))
178 error (0, 0, _("overflow extending size of file %s"),
179 quoteaf (fname));
180 return false;
184 else
185 nsize = ssize;
186 if (nsize < 0)
187 nsize = 0;
189 if (ftruncate (fd, nsize) != 0)
191 intmax_t s = nsize;
192 error (0, errno, _("failed to truncate %s at %"PRIdMAX" bytes"),
193 quoteaf (fname), s);
194 return false;
197 return true;
201 main (int argc, char **argv)
203 bool got_size = false;
204 off_t size IF_LINT ( = 0);
205 off_t rsize = -1;
206 rel_mode_t rel_mode = rm_abs;
207 int c;
209 initialize_main (&argc, &argv);
210 set_program_name (argv[0]);
211 setlocale (LC_ALL, "");
212 bindtextdomain (PACKAGE, LOCALEDIR);
213 textdomain (PACKAGE);
215 atexit (close_stdout);
217 while ((c = getopt_long (argc, argv, "cor:s:", longopts, nullptr)) != -1)
219 switch (c)
221 case 'c':
222 no_create = true;
223 break;
225 case 'o':
226 block_mode = true;
227 break;
229 case 'r':
230 ref_file = optarg;
231 break;
233 case 's':
234 /* skip any whitespace */
235 while (isspace (to_uchar (*optarg)))
236 optarg++;
237 switch (*optarg)
239 case '<':
240 rel_mode = rm_max;
241 optarg++;
242 break;
243 case '>':
244 rel_mode = rm_min;
245 optarg++;
246 break;
247 case '/':
248 rel_mode = rm_rdn;
249 optarg++;
250 break;
251 case '%':
252 rel_mode = rm_rup;
253 optarg++;
254 break;
256 /* skip any whitespace */
257 while (isspace (to_uchar (*optarg)))
258 optarg++;
259 if (*optarg == '+' || *optarg == '-')
261 if (rel_mode)
263 error (0, 0, _("multiple relative modifiers specified"));
264 /* Note other combinations are flagged as invalid numbers */
265 usage (EXIT_FAILURE);
267 rel_mode = rm_rel;
269 /* Support dd BLOCK size suffixes + lowercase g,t,m for bsd compat.
270 Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats. */
271 size = xdectoimax (optarg, OFF_T_MIN, OFF_T_MAX, "EgGkKmMPQRtTYZ0",
272 _("Invalid number"), 0);
273 /* Rounding to multiple of 0 is nonsensical */
274 if ((rel_mode == rm_rup || rel_mode == rm_rdn) && size == 0)
275 error (EXIT_FAILURE, 0, _("division by zero"));
276 got_size = true;
277 break;
279 case_GETOPT_HELP_CHAR;
281 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
283 default:
284 usage (EXIT_FAILURE);
288 argv += optind;
289 argc -= optind;
291 /* must specify either size or reference file */
292 if (!ref_file && !got_size)
294 error (0, 0, _("you must specify either %s or %s"),
295 quote_n (0, "--size"), quote_n (1, "--reference"));
296 usage (EXIT_FAILURE);
298 /* must specify a relative size with a reference file */
299 if (ref_file && got_size && !rel_mode)
301 error (0, 0, _("you must specify a relative %s with %s"),
302 quote_n (0, "--size"), quote_n (1, "--reference"));
303 usage (EXIT_FAILURE);
305 /* block_mode without size is not valid */
306 if (block_mode && !got_size)
308 error (0, 0, _("%s was specified but %s was not"),
309 quote_n (0, "--io-blocks"), quote_n (1, "--size"));
310 usage (EXIT_FAILURE);
312 /* must specify at least 1 file */
313 if (argc < 1)
315 error (0, 0, _("missing file operand"));
316 usage (EXIT_FAILURE);
319 if (ref_file)
321 struct stat sb;
322 off_t file_size = -1;
323 if (stat (ref_file, &sb) != 0)
324 error (EXIT_FAILURE, errno, _("cannot stat %s"), quoteaf (ref_file));
325 if (usable_st_size (&sb))
326 file_size = sb.st_size;
327 else
329 int ref_fd = open (ref_file, O_RDONLY);
330 if (0 <= ref_fd)
332 off_t file_end = lseek (ref_fd, 0, SEEK_END);
333 int saved_errno = errno;
334 close (ref_fd); /* ignore failure */
335 if (0 <= file_end)
336 file_size = file_end;
337 else
339 /* restore, in case close clobbered it. */
340 errno = saved_errno;
344 if (file_size < 0)
345 error (EXIT_FAILURE, errno, _("cannot get the size of %s"),
346 quoteaf (ref_file));
347 if (!got_size)
348 size = file_size;
349 else
350 rsize = file_size;
353 int oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK;
354 bool errors = false;
356 for (char const *fname; (fname = *argv); argv++)
358 int fd = open (fname, oflags, MODE_RW_UGO);
359 if (fd < 0)
361 /* 'truncate -s0 -c no-such-file' shouldn't gen error
362 'truncate -s0 no-such-dir/file' should gen ENOENT error
363 'truncate -s0 no-such-dir/' should gen EISDIR error
364 'truncate -s0 .' should gen EISDIR error */
365 if (!(no_create && errno == ENOENT))
367 error (0, errno, _("cannot open %s for writing"),
368 quoteaf (fname));
369 errors = true;
372 else
374 errors |= !do_ftruncate (fd, fname, size, rsize, rel_mode);
375 if (close (fd) != 0)
377 error (0, errno, _("failed to close %s"), quoteaf (fname));
378 errors = true;
383 return errors ? EXIT_FAILURE : EXIT_SUCCESS;