summaryrefslogtreecommitdiffstats
path: root/src/truncate.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:11:47 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:11:47 +0000
commit758f820bcc0f68aeebac1717e537ca13a320b909 (patch)
tree48111ece75cf4f98316848b37a7e26356e00669e /src/truncate.c
parentInitial commit. (diff)
downloadcoreutils-upstream.tar.xz
coreutils-upstream.zip
Adding upstream version 9.1.upstream/9.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/truncate.c')
-rw-r--r--src/truncate.c385
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;
+}