logo

utils-std

Collection of commonly available Unix tools
commit: 9481c3ea2b9838bde439b25f12f5147754a26152
parent 04fb603b59922be031ca64b01fe21084296aeea0
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Sat, 20 Apr 2024 07:37:57 +0200

cmd/truncate: new

Diffstat:

MMakefile9++++++++-
Acmd/truncate.177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/truncate.c129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcoreutils.txt2+-
Alib/truncation.c152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/truncation.h25+++++++++++++++++++++++++
Atest-cmd/truncate.t78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest-lib/Kyuafile1+
Atest-lib/truncation.c89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 560 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile @@ -16,7 +16,7 @@ all: $(EXE) $(MAN1SO) rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} $(CC) -std=c99 $(CFLAGS) -c -o $@ $< -TEST_LIBS = test-lib/mode test-lib/strtodur test-lib/symbolize_mode +TEST_LIBS = test-lib/mode test-lib/strtodur test-lib/symbolize_mode test-lib/truncation .PHONY: check check: all $(TEST_LIBS) check-man MALLOC_CHECK_=3 POSIX_ME_HARDER=1 POSIXLY_CORRECT=1 LC_ALL=C.UTF-8 LDSTATIC=$(LDSTATIC) kyua test || (kyua report --verbose --results-filter=broken,failed; false) @@ -83,6 +83,9 @@ test-lib/strtodur: test-lib/strtodur.c lib/strtodur.c Makefile test-lib/symbolize_mode: test-lib/symbolize_mode.c lib/symbolize_mode.c Makefile $(CC) -std=c99 $(CFLAGS) $(ATF_CFLAGS) -o $@ test-lib/symbolize_mode.c lib/symbolize_mode.c $(LDFLAGS) $(ATF_LIBS) +test-lib/truncation: test-lib/truncation.c lib/truncation.c Makefile + $(CC) -std=c99 $(CFLAGS) $(ATF_CFLAGS) -o $@ test-lib/truncation.c lib/truncation.c $(LDFLAGS) $(ATF_LIBS) + cmd/df: cmd/df.c lib/humanize.c Makefile rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} $(CC) -std=c99 $(CFLAGS) -o $@ cmd/df.c lib/humanize.c $(LDFLAGS) $(LDSTATIC) @@ -106,3 +109,7 @@ cmd/seq: cmd/seq.c Makefile cmd/rm: cmd/rm.c lib/consent.c lib/consent.h Makefile rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} $(CC) -std=c99 $(CFLAGS) -o $@ cmd/rm.c lib/consent.c $(LDFLAGS) $(LDSTATIC) + +cmd/truncate: cmd/truncate.c lib/truncation.c lib/truncation.h Makefile + rm -f ${<:=.gcov} ${@:=.gcda} ${@:=.gcno} + $(CC) -std=c99 $(CFLAGS) -o $@ cmd/truncate.c lib/truncation.c $(LDFLAGS) $(LDSTATIC) diff --git a/cmd/truncate.1 b/cmd/truncate.1 @@ -0,0 +1,77 @@ +.\" utils-std: Collection of commonly available Unix tools +.\" Copyright 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +.\" SPDX-License-Identifier: MPL-2.0 +.Dd 2024-04-23 +.Dt TRUNCATE 1 +.Os +.Sh NAME +.Nm truncate +.Nd change the size of files +.Sh SYNOPSIS +.Nm +.Op Fl c +.Fl r Ar ref_file +.Ar file... +.Nm +.Op Fl c +.Fl s [ Ar + Ns | Ns Ar - Ns | Ns Ar % Ns | Ns Ar / ] Ns Ar size Ns Op Ar suffix +.Ar file... +.Sh DESCRIPTION +.Nm +changes the size of each given +.Ar file . +Either with a size operation given via the +.Fl s +option or via a reference file given by +.Fl r Ar ref_file . +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl c +Prevent creating new files. +.It Fl r Ar ref_file +Set the size of +.Ar file +based on the file size of +.Ar ref_file . +.It Fl s [ Ar + Ns | Ns Ar - Ns | Ns Ar % Ns | Ns Ar / ] Ns Ar size Ns Op Ar suffix +Size operation. +.Bl -tag -width _ -compact +.It Ar + +Extend +.Ar file +by +.Ar size +.It Ar - +Reduce +.Ar file +by +.Ar size +.It Ar % +Round up +.Ar file +into a multiple of +.Ar size , +useful for chunks. +.It Ar / +Round down +.Ar file +into a multiple of +.Ar size , +useful for chunks. +.El +.Pp +.Ar size +is an integer optionally followed by a +.Ar suffix +being one of KMGTP (Kilo, Mega, Giga, Tera, ...) using power of 1024 by default, which can be either explicit via adding iB (like MiB), or turned into powers of 1000 by adding B (like MB). +.El +.Sh EXIT STATUS +.Ex -std +.Sh STANDARDS +None known. +.Sh HISTORY +A +.Nm +utility appeared in FreeBSD 4.2. +.Sh AUTHORS +.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me diff --git a/cmd/truncate.c b/cmd/truncate.c @@ -0,0 +1,129 @@ +// utils-std: Collection of commonly available Unix tools +// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _POSIX_C_SOURCE 200809L + +#include "../lib/bitmasks.h" // FIELD_CLR +#include "../lib/truncation.h" // parse_size + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> // open +#include <stdbool.h> +#include <stdio.h> // fprintf +#include <stdlib.h> // abort +#include <string.h> // strerror +#include <unistd.h> // getopt +#include <sys/stat.h> + +char *argv0 = "truncate"; + +static void +usage() +{ + fprintf(stderr, "Usage: truncate [-co] [-r ref_file] [-s size] file...\n"); +} + +int +main(int argc, char *argv[]) +{ + int open_flags = O_WRONLY | O_CREAT | O_NONBLOCK | O_LARGEFILE; + bool size_set = false; + + struct truncation tr; + char *ref_file = NULL; + + int c = -1; + while((c = getopt(argc, argv, ":cr:s:")) != -1) + { + switch(c) + { + case 'c': + FIELD_CLR(open_flags, O_CREAT); + break; + case 'r': + if(size_set) + { + fprintf(stderr, "truncate: Error: Truncation size can only be set once\n"); + usage(); + return 1; + } + ref_file = optarg; + size_set = true; + break; + case 's': + if(size_set) + { + fprintf(stderr, "truncate: Error: Truncation size can only be set once\n"); + usage(); + return 1; + } + if(parse_size(optarg, &tr) < 0) + { + usage(); + return 1; + } + size_set = true; + break; + case ':': + fprintf(stderr, "truncate: Error: Missing operand for option: '-%c'\n", optopt); + usage(); + return 1; + case '?': + fprintf(stderr, "truncate: Error: Unrecognised option: '-%c'\n", optopt); + usage(); + return 1; + default: + abort(); + } + } + + argc -= optind; + argv += optind; + + if(argc < 1) usage(); + + if(!size_set) + { + fprintf(stderr, + "truncate: Error: target size wasn't set, you need to pass one of -d / -r / -s\n"); + return 1; + } + + if(ref_file != NULL) + { + assert(errno == 0); + struct stat ref_stats; + if(stat(optarg, &ref_stats) < 0) + { + fprintf(stderr, "truncate: Error: Couldn't get status for file '%s': %s\n", optarg, strerror(errno)); + return 1; + } + tr.op = OP_SET; + + tr.size = ref_stats.st_size; + } + + for(int argi = 0; argi < argc; argi++) + { + assert(errno == 0); + char *arg = argv[argi]; + + int fd = open(arg, open_flags); + if(fd < 0) + { + fprintf(stderr, "truncate: Error: Failed to open '%s': %s\n", arg, strerror(errno)); + return 1; + } + assert(errno == 0); + + if(apply_truncation(fd, tr, arg) < 0) return 1; + + if(close(fd) < 0) + { + fprintf(stderr, "truncate: Error: Failed closing fd for '%s': %s\n", arg, strerror(errno)); + return 1; + } + } +} diff --git a/coreutils.txt b/coreutils.txt @@ -91,7 +91,7 @@ timeout: ? touch: Done tr: Todo true: Done -truncate: Todo +truncate: Done tsort: ? tty: Done uname: Done diff --git a/lib/truncation.c b/lib/truncation.c @@ -0,0 +1,152 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _POSIX_C_SOURCE 200809L +#include "truncation.h" + +#include <assert.h> +#include <errno.h> +#include <stdio.h> // fprintf +#include <stdlib.h> // strtol +#include <string.h> // strerror +#include <sys/stat.h> // fstat +#include <unistd.h> // ftruncate + +extern char *argv0; + +int +apply_truncation(int fd, struct truncation tr, char *arg) +{ + if(tr.op != OP_SET) + { + struct stat stats; + assert(errno == 0); + if(fstat(fd, &stats) < 0) + { + fprintf(stderr, "truncate: Error: Failed to get status of file '%s': %s\n", arg, strerror(errno)); + return -1; + } + + int q; + switch(tr.op) + { + case OP_INC: + tr.size += stats.st_size; + break; + case OP_DEC: + tr.size = stats.st_size - tr.size; + if(tr.size < 0) tr.size = 0; + break; + case OP_CHK_DOWN: + q = stats.st_size / tr.size; + tr.size = tr.size * q; + break; + case OP_CHK_UP: + q = stats.st_size / tr.size; + if(stats.st_size % tr.size) q++; + tr.size = tr.size * q; + break; + default: + abort(); + } + } + + if(ftruncate(fd, tr.size) < 0) + { + fprintf(stderr, "truncate: Error: Failed to truncate '%s' to %ld: %s\n", arg, tr.size, strerror(errno)); + return -1; + } + + return 0; +} + +static int +apply_size_suffix(long *size, char *endptr) +{ + char units[] = "KMGTPEZ"; + + if(endptr[0] == 0) return 0; + + size_t i = 0; + long si = 1, iec = 1; + for(; i < sizeof(units); i++) + { + si *= 1000; + iec *= 1024; + if(units[i] == endptr[0]) break; + } + if(i >= sizeof(units) || si == 1) + { + fprintf(stderr, "%s: Unrecognised unit '%s'\n", argv0, endptr); + return -1; + } + + if(endptr[1] == 0 || (endptr[1] == 'i' && endptr[2] == 'B' && endptr[3] == 0)) + { + *size *= iec; + return 0; + } + if(endptr[1] == 'B' && endptr[2] == 0) + { + *size *= si; + return 0; + } + + fprintf(stderr, "%s: Unrecognised suffix '%s'\n", argv0, endptr); + return -1; +} + +int +parse_size(char *arg, struct truncation *buf) +{ + assert(arg != NULL); + assert(buf != NULL); + + if(arg[0] == 0) + { + fprintf(stderr, "%s: Got empty size argument\n", argv0); + buf->size = 0; + return -1; + } + + enum operation_e op = OP_SET; + + switch(arg[0]) + { + case '+': + op = OP_INC; + arg++; + break; + case '-': + op = OP_DEC; + arg++; + break; + case '/': + op = OP_CHK_DOWN; + arg++; + break; + case '%': + op = OP_CHK_UP; + arg++; + break; + } + + assert(errno == 0); + char *endptr = NULL; + long size = strtol(arg, &endptr, 10); + if(errno != 0) + { + fprintf(stderr, "%s: Failed parsing '%s' as a number: %s\n", argv0, arg, strerror(errno)); + return -1; + } + + if(endptr != NULL) + { + if(apply_size_suffix(&size, endptr) < 0) return -1; + } + + buf->size = size; + buf->op = op; + return 0; +} diff --git a/lib/truncation.h b/lib/truncation.h @@ -0,0 +1,25 @@ +// Collection of Unix tools, comparable to coreutils +// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _POSIX_C_SOURCE 200809L + +#include <sys/types.h> // off_t + +enum operation_e +{ + OP_SET, + OP_INC, + OP_DEC, + OP_CHK_DOWN, + OP_CHK_UP, +}; + +struct truncation +{ + enum operation_e op; + off_t size; +}; + +int parse_size(char *arg, struct truncation *buf); +int apply_truncation(int fd, struct truncation tr, char *arg); diff --git a/test-cmd/truncate.t b/test-cmd/truncate.t @@ -0,0 +1,78 @@ +#!/usr/bin/env cram +# SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +# SPDX-License-Identifier: MPL-2.0 + + $ export PATH="$TESTDIR/../cmd:$PATH" + + $ test "$(command -v truncate)" = "$TESTDIR/../cmd/truncate" + + $ touch foo + + $ truncate -s 0 foo + $ wc -c foo + 0\sfoo (re) + + $ truncate -s 666 foo + $ wc -c foo + 666\sfoo (re) + + $ truncate -s 666K foo + $ wc -c foo + 681984\sfoo (re) + + $ truncate -s 666KB foo + $ wc -c foo + 666000\sfoo (re) + + $ truncate -s 666KiB foo + $ wc -c foo + 681984\sfoo (re) + + $ truncate -s 1024 foo + $ wc -c foo + 1024\sfoo (re) + $ truncate -s -666 foo + $ wc -c foo + 358\sfoo (re) + + $ truncate -s 666 foo + $ wc -c foo + 666\sfoo (re) + $ truncate -s -1024 foo + $ wc -c foo + 0\sfoo (re) + + $ truncate -s 666 foo + $ wc -c foo + 666\sfoo (re) + $ truncate -s +666 foo + $ wc -c foo + 1332\sfoo (re) + + $ truncate -s 15 foo + $ wc -c foo + 15\sfoo (re) + $ truncate -s /10 foo + $ wc -c foo + 10\sfoo (re) + + $ truncate -s 15 foo + $ wc -c foo + 15\sfoo (re) + $ truncate -s %10 foo + $ wc -c foo + 20\sfoo (re) + + $ truncate -s 15KB foo + $ wc -c foo + 15000\sfoo (re) + $ truncate -s /10KB foo + $ wc -c foo + 10000\sfoo (re) + + $ truncate -s 15KB foo + $ wc -c foo + 15000\sfoo (re) + $ truncate -s %10KB foo + $ wc -c foo + 20000\sfoo (re) diff --git a/test-lib/Kyuafile b/test-lib/Kyuafile @@ -8,3 +8,4 @@ test_suite("utils-std libs") atf_test_program{name="mode"} atf_test_program{name="strtodur"} atf_test_program{name="symbolize_mode"} +atf_test_program{name="truncation"} diff --git a/test-lib/truncation.c b/test-lib/truncation.c @@ -0,0 +1,89 @@ +// utils-std: Collection of commonly available Unix tools +// SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> +// SPDX-License-Identifier: MPL-2.0 + +#define _POSIX_C_SOURCE 200809L +#include <atf-c.h> + +#include "../lib/truncation.h" + +#define CHECK_EQ_SIZE(expect, got) ATF_CHECK_EQ_MSG(expect, got, "Expected %ld; Got %ld\n", expect, got) + +#define TEST_SIZE_OP(str, expect_size, expect_op) \ + ATF_CHECK(parse_size(str, &tr) == 0); \ + CHECK_EQ_SIZE(expect_size, tr.size); \ + ATF_CHECK_EQ(expect_op, tr.op); + +char *argv0 = "test-lib/truncation"; + +ATF_TC(empty); +ATF_TC_HEAD(empty, tc) +{ + atf_tc_set_md_var(tc, "descr", "Empty string"); +} +ATF_TC_BODY(empty, tc) +{ + struct truncation tr; + + ATF_CHECK(parse_size("", &tr) == -1); + CHECK_EQ_SIZE(0L, tr.size); +} + +ATF_TC(set); +ATF_TC_HEAD(set, tc) +{ + atf_tc_set_md_var(tc, "descr", "Set"); +} +ATF_TC_BODY(set, tc) +{ + struct truncation tr; + + TEST_SIZE_OP("0", 0L, OP_SET); + TEST_SIZE_OP("666", 666L, OP_SET); + TEST_SIZE_OP("666M", 666*1024*1024L, OP_SET); + TEST_SIZE_OP("666MB", 666*1000*1000L, OP_SET); + TEST_SIZE_OP("666MiB", 666*1024*1024L, OP_SET); +} + +ATF_TC(inc); +ATF_TC_HEAD(inc, tc) +{ + atf_tc_set_md_var(tc, "descr", "Increase"); +} +ATF_TC_BODY(inc, tc) +{ + struct truncation tr; + + TEST_SIZE_OP("+0", 0L, OP_INC); + TEST_SIZE_OP("+666", 666L, OP_INC); + TEST_SIZE_OP("+666M", 666*1024*1024L, OP_INC); + TEST_SIZE_OP("+666MB", 666*1000*1000L, OP_INC); + TEST_SIZE_OP("+666MiB", 666*1024*1024L, OP_INC); +} + +ATF_TC(dec); +ATF_TC_HEAD(dec, tc) +{ + atf_tc_set_md_var(tc, "descr", "Decrease"); +} +ATF_TC_BODY(dec, tc) +{ + struct truncation tr; + + TEST_SIZE_OP("-0", 0L, OP_DEC); + TEST_SIZE_OP("-666", 666L, OP_DEC); + TEST_SIZE_OP("-666M", 666*1024*1024L, OP_DEC); + TEST_SIZE_OP("-666MB", 666*1000*1000L, OP_DEC); + TEST_SIZE_OP("-666MiB", 666*1024*1024L, OP_DEC); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, empty); + + ATF_TP_ADD_TC(tp, set); + ATF_TP_ADD_TC(tp, inc); + ATF_TP_ADD_TC(tp, dec); + + return atf_no_error(); +}