diff options
Diffstat (limited to '')
-rw-r--r-- | src/truncate.c | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/truncate.c b/src/truncate.c new file mode 100644 index 0000000..cd44eda --- /dev/null +++ b/src/truncate.c @@ -0,0 +1,385 @@ +/* truncate -- truncate or extend the length of files. + Copyright (C) 2008-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Pádraig Brady + + This is backwards compatible with the FreeBSD utility, but is more + flexible wrt the size specifications and the use of long options, + to better fit the "GNU" environment. */ + +#include <config.h> /* sets _FILE_OFFSET_BITS=64 etc. */ +#include <stdio.h> +#include <getopt.h> +#include <sys/types.h> + +#include "system.h" +#include "die.h" +#include "error.h" +#include "quote.h" +#include "stat-size.h" +#include "xdectoint.h" + +/* The official name of this program (e.g., no 'g' prefix). */ +#define PROGRAM_NAME "truncate" + +#define AUTHORS proper_name ("Padraig Brady") + +/* (-c) If true, don't create if not already there */ +static bool no_create; + +/* (-o) If true, --size refers to blocks not bytes */ +static bool block_mode; + +/* (-r) Reference file to use size from */ +static char const *ref_file; + +static struct option const longopts[] = +{ + {"no-create", no_argument, NULL, 'c'}, + {"io-blocks", no_argument, NULL, 'o'}, + {"reference", required_argument, NULL, 'r'}, + {"size", required_argument, NULL, 's'}, + {GETOPT_HELP_OPTION_DECL}, + {GETOPT_VERSION_OPTION_DECL}, + {NULL, 0, NULL, 0} +}; + +typedef enum +{ rm_abs = 0, rm_rel, rm_min, rm_max, rm_rdn, rm_rup } rel_mode_t; + +void +usage (int status) +{ + if (status != EXIT_SUCCESS) + emit_try_help (); + else + { + printf (_("Usage: %s OPTION... FILE...\n"), program_name); + fputs (_("\ +Shrink or extend the size of each FILE to the specified size\n\ +\n\ +A FILE argument that does not exist is created.\n\ +\n\ +If a FILE is larger than the specified size, the extra data is lost.\n\ +If a FILE is shorter, it is extended and the sparse extended part (hole)\n\ +reads as zero bytes.\n\ +"), stdout); + + emit_mandatory_arg_note (); + + fputs (_("\ + -c, --no-create do not create any files\n\ +"), stdout); + fputs (_("\ + -o, --io-blocks treat SIZE as number of IO blocks instead of bytes\n\ +"), stdout); + fputs (_("\ + -r, --reference=RFILE base size on RFILE\n\ + -s, --size=SIZE set or adjust the file size by SIZE bytes\n"), stdout); + fputs (HELP_OPTION_DESCRIPTION, stdout); + fputs (VERSION_OPTION_DESCRIPTION, stdout); + emit_size_note (); + fputs (_("\n\ +SIZE may also be prefixed by one of the following modifying characters:\n\ +'+' extend by, '-' reduce by, '<' at most, '>' at least,\n\ +'/' round down to multiple of, '%' round up to multiple of.\n"), stdout); + emit_ancillary_info (PROGRAM_NAME); + } + exit (status); +} + +/* return true on success, false on error. */ +static bool +do_ftruncate (int fd, char const *fname, off_t ssize, off_t rsize, + rel_mode_t rel_mode) +{ + struct stat sb; + off_t nsize; + + if ((block_mode || (rel_mode && rsize < 0)) && fstat (fd, &sb) != 0) + { + error (0, errno, _("cannot fstat %s"), quoteaf (fname)); + return false; + } + if (block_mode) + { + ptrdiff_t blksize = ST_BLKSIZE (sb); + intmax_t ssize0 = ssize; + if (INT_MULTIPLY_WRAPV (ssize, blksize, &ssize)) + { + error (0, 0, + _("overflow in %" PRIdMAX + " * %" PRIdPTR " byte blocks for file %s"), + ssize0, blksize, quoteaf (fname)); + return false; + } + } + if (rel_mode) + { + off_t fsize; + + if (0 <= rsize) + fsize = rsize; + else + { + if (usable_st_size (&sb)) + { + fsize = sb.st_size; + if (fsize < 0) + { + /* Sanity check. Overflow is the only reason I can think + this would ever go negative. */ + error (0, 0, _("%s has unusable, apparently negative size"), + quoteaf (fname)); + return false; + } + } + else + { + fsize = lseek (fd, 0, SEEK_END); + if (fsize < 0) + { + error (0, errno, _("cannot get the size of %s"), + quoteaf (fname)); + return false; + } + } + } + + if (rel_mode == rm_min) + nsize = MAX (fsize, ssize); + else if (rel_mode == rm_max) + nsize = MIN (fsize, ssize); + else if (rel_mode == rm_rdn) + /* 0..ssize-1 -> 0 */ + nsize = fsize - fsize % ssize; + else + { + if (rel_mode == rm_rup) + { + /* 1..ssize -> ssize */ + off_t r = fsize % ssize; + ssize = r == 0 ? 0 : ssize - r; + } + if (INT_ADD_WRAPV (fsize, ssize, &nsize)) + { + error (0, 0, _("overflow extending size of file %s"), + quoteaf (fname)); + return false; + } + } + } + else + nsize = ssize; + if (nsize < 0) + nsize = 0; + + if (ftruncate (fd, nsize) != 0) + { + intmax_t s = nsize; + error (0, errno, _("failed to truncate %s at %"PRIdMAX" bytes"), + quoteaf (fname), s); + return false; + } + + return true; +} + +int +main (int argc, char **argv) +{ + bool got_size = false; + off_t size IF_LINT ( = 0); + off_t rsize = -1; + rel_mode_t rel_mode = rm_abs; + int c; + + initialize_main (&argc, &argv); + set_program_name (argv[0]); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + atexit (close_stdout); + + while ((c = getopt_long (argc, argv, "cor:s:", longopts, NULL)) != -1) + { + switch (c) + { + case 'c': + no_create = true; + break; + + case 'o': + block_mode = true; + break; + + case 'r': + ref_file = optarg; + break; + + case 's': + /* skip any whitespace */ + while (isspace (to_uchar (*optarg))) + optarg++; + switch (*optarg) + { + case '<': + rel_mode = rm_max; + optarg++; + break; + case '>': + rel_mode = rm_min; + optarg++; + break; + case '/': + rel_mode = rm_rdn; + optarg++; + break; + case '%': + rel_mode = rm_rup; + optarg++; + break; + } + /* skip any whitespace */ + while (isspace (to_uchar (*optarg))) + optarg++; + if (*optarg == '+' || *optarg == '-') + { + if (rel_mode) + { + error (0, 0, _("multiple relative modifiers specified")); + /* Note other combinations are flagged as invalid numbers */ + usage (EXIT_FAILURE); + } + rel_mode = rm_rel; + } + /* Support dd BLOCK size suffixes + lowercase g,t,m for bsd compat. + Note we don't support dd's b=512, c=1, w=2 or 21x512MiB formats. */ + size = xdectoimax (optarg, OFF_T_MIN, OFF_T_MAX, "EgGkKmMPtTYZ0", + _("Invalid number"), 0); + /* Rounding to multiple of 0 is nonsensical */ + if ((rel_mode == rm_rup || rel_mode == rm_rdn) && size == 0) + die (EXIT_FAILURE, 0, _("division by zero")); + got_size = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } + } + + argv += optind; + argc -= optind; + + /* must specify either size or reference file */ + if (!ref_file && !got_size) + { + error (0, 0, _("you must specify either %s or %s"), + quote_n (0, "--size"), quote_n (1, "--reference")); + usage (EXIT_FAILURE); + } + /* must specify a relative size with a reference file */ + if (ref_file && got_size && !rel_mode) + { + error (0, 0, _("you must specify a relative %s with %s"), + quote_n (0, "--size"), quote_n (1, "--reference")); + usage (EXIT_FAILURE); + } + /* block_mode without size is not valid */ + if (block_mode && !got_size) + { + error (0, 0, _("%s was specified but %s was not"), + quote_n (0, "--io-blocks"), quote_n (1, "--size")); + usage (EXIT_FAILURE); + } + /* must specify at least 1 file */ + if (argc < 1) + { + error (0, 0, _("missing file operand")); + usage (EXIT_FAILURE); + } + + if (ref_file) + { + struct stat sb; + off_t file_size = -1; + if (stat (ref_file, &sb) != 0) + die (EXIT_FAILURE, errno, _("cannot stat %s"), quoteaf (ref_file)); + if (usable_st_size (&sb)) + file_size = sb.st_size; + else + { + int ref_fd = open (ref_file, O_RDONLY); + if (0 <= ref_fd) + { + off_t file_end = lseek (ref_fd, 0, SEEK_END); + int saved_errno = errno; + close (ref_fd); /* ignore failure */ + if (0 <= file_end) + file_size = file_end; + else + { + /* restore, in case close clobbered it. */ + errno = saved_errno; + } + } + } + if (file_size < 0) + die (EXIT_FAILURE, errno, _("cannot get the size of %s"), + quoteaf (ref_file)); + if (!got_size) + size = file_size; + else + rsize = file_size; + } + + int oflags = O_WRONLY | (no_create ? 0 : O_CREAT) | O_NONBLOCK; + bool errors = false; + + for (char const *fname; (fname = *argv); argv++) + { + int fd = open (fname, oflags, MODE_RW_UGO); + if (fd < 0) + { + /* 'truncate -s0 -c no-such-file' shouldn't gen error + 'truncate -s0 no-such-dir/file' should gen ENOENT error + 'truncate -s0 no-such-dir/' should gen EISDIR error + 'truncate -s0 .' should gen EISDIR error */ + if (!(no_create && errno == ENOENT)) + { + error (0, errno, _("cannot open %s for writing"), + quoteaf (fname)); + errors = true; + } + } + else + { + errors |= !do_ftruncate (fd, fname, size, rsize, rel_mode); + if (close (fd) != 0) + { + error (0, errno, _("failed to close %s"), quoteaf (fname)); + errors = true; + } + } + } + + return errors ? EXIT_FAILURE : EXIT_SUCCESS; +} |