Page MenuHomeFreeBSD

D51207.id158174.diff
No OneTemporary

D51207.id158174.diff

diff --git a/usr.bin/truncate/tests/Makefile b/usr.bin/truncate/tests/Makefile
--- a/usr.bin/truncate/tests/Makefile
+++ b/usr.bin/truncate/tests/Makefile
@@ -1,3 +1,7 @@
+
+BINDIR?= ${TESTSDIR}
+PROGS+= lssparse
+
ATF_TESTS_SH= truncate_test
.include <bsd.test.mk>
diff --git a/usr.bin/truncate/tests/lssparse.c b/usr.bin/truncate/tests/lssparse.c
new file mode 100644
--- /dev/null
+++ b/usr.bin/truncate/tests/lssparse.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2025 Klara, Inc.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/stat.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void
+usage(const char *prog)
+{
+
+ fprintf(stderr, "usage: %s -v <file>\n", prog);
+ exit(1);
+}
+
+static void
+report_hole(off_t start, off_t end)
+{
+
+ printf("[%zu,%zu)\n", start, end);
+}
+
+static int
+lssparse(int fd, off_t filesz)
+{
+ off_t doff = 0, hoff;
+
+ while ((hoff = lseek(fd, doff, SEEK_HOLE)) != filesz) {
+ doff = lseek(fd, hoff, SEEK_DATA);
+ if (doff == -1) {
+ /* Hole to EOF */
+ report_hole(hoff, filesz);
+ break;
+ }
+
+ report_hole(hoff, doff);
+ }
+
+ return (0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct stat sb;
+ const char *prog = argv[0];
+ const char *file;
+ int ch, fd;
+ bool sizeonly = false;
+
+ while ((ch = getopt(argc, argv, "s")) != -1) {
+ switch (ch) {
+ case 's':
+ sizeonly = true;
+ break;
+ default:
+ usage(prog);
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ usage(prog);
+
+ file = argv[0];
+ fd = open(file, O_RDONLY, 0644);
+ if (fd == -1)
+ err(1, "open");
+
+ if (fstat(fd, &sb) == -1)
+ err(1, "fstat");
+
+ /*
+ * Not all systems have _PC_MIN_HOLE_SIZE, so we'll start there. If
+ * that fails, we'll try to guess based on the reported block size.
+ *
+ * Some filesystems may also report a smaller minimum hole size because
+ * it's complicated. Files on ZFS, for instance, can have holes in the
+ * neighborhood of the _PC_MIN_HOLE_SIZE that will later disappear if
+ * non-zero data shows up within the same blksize segment of the file.
+ * Additionally, it won't *complain* if you try to punch a hole that's
+ * smaller than the block size, but it may not actually be able to punch
+ * that hole if the block contains other data. We'll use the larger of
+ * the minimum hole size and the block size to be safe.
+ */
+ if (sizeonly) {
+ size_t holesz = 0;
+
+ holesz = fpathconf(fd, _PC_MIN_HOLE_SIZE);
+ if (holesz == (size_t)-1)
+ err(1, "fpathconf");
+ else if (holesz == 0)
+ errx(1, "filesystem does not report hole support");
+ if ((size_t)sb.st_blksize > holesz)
+ holesz = sb.st_blksize;
+
+ printf("%zu\n", holesz);
+ return (0);
+ }
+
+ return (lssparse(fd, sb.st_size));
+}
diff --git a/usr.bin/truncate/tests/truncate_test.sh b/usr.bin/truncate/tests/truncate_test.sh
--- a/usr.bin/truncate/tests/truncate_test.sh
+++ b/usr.bin/truncate/tests/truncate_test.sh
@@ -438,6 +438,85 @@
[ ${st_size} -eq 0 ] || atf_fail "new file should now be 0 bytes"
}
+atf_test_case deallocate cleanup
+deallocate_head()
+{
+ atf_set "descr" "Verifies that -d punches a hole in the file"
+}
+deallocate_body()
+{
+ lssparse="$(atf_get_srcdir)/lssparse"
+
+ # We mount our own tmpfs here to avoid false positives due to the
+ # native filesystem. ZFS with compression enabled, in particular,
+ # will helpfully compress zero blocks down transparently and fail our
+ # first (empty) lssparse check below.
+ atf_check mkdir fs
+ atf_check mount -t tmpfs -o size=8m tmp fs
+
+ :> fs/sparse
+ blocksz=$("$lssparse" -s fs/sparse)
+ atf_check test -n ${blocksz}
+
+ # Data block, hole (but real zeroes), data block
+ atf_check -x "jot -nb 'A' -s '' ${blocksz} > fs/sparse"
+ atf_check -e not-empty dd if=/dev/zero of=fs/sparse \
+ bs=${blocksz} oseek=1 count=1 conv=notrunc
+ atf_check -x "jot -nb 'A' -s '' ${blocksz} >> fs/sparse"
+
+ atf_check cp fs/sparse fs/sparse.orig
+ atf_check cp fs/sparse fs/sparse.orig.orig
+
+ # Punch a hole in the middle, ensure the file hasn't changed.
+ hstart=${blocksz}
+ hend=$((blocksz + blocksz))
+ atf_check -o empty "${lssparse}" fs/sparse
+ atf_check truncate -d -o ${blocksz} -l ${blocksz} fs/sparse
+ atf_check -o inline:"[${hstart},${hend})\n" "${lssparse}" fs/sparse
+ atf_check cmp -s fs/sparse fs/sparse.orig
+
+ # cmp(1) above may have caused us to page-in the holes in fs/sparse and
+ # thus, it becomes non-sparse. Just cheat and redo the previously
+ # sparsified segment to setup the next test (which confirms that we can
+ # create holes without disturbing the rest).
+ atf_check truncate -d -o ${blocksz} -l ${blocksz} fs/sparse
+
+ # Clobber the end part of the original file and punch a hole in
+ # the same spot on the new file, ensure that it has zeroed out that
+ # portion.
+ hend=$((blocksz * 3))
+ atf_check -e not-empty dd if=/dev/zero of=fs/sparse.orig \
+ bs=${blocksz} oseek=2 count=1 conv=notrunc
+ # Still no real holes...
+ atf_check -o empty "${lssparse}" fs/sparse.orig
+ atf_check truncate -d -o $((blocksz * 2)) -l ${blocksz} fs/sparse
+ atf_check cmp -s fs/sparse fs/sparse.orig
+
+ # Cheat one last time to try it at the beginning.
+ atf_check truncate -d -o ${blocksz} -l $((blocksz * 2)) fs/sparse
+
+ hstart="0"
+ atf_check -e not-empty dd if=/dev/zero of=fs/sparse.orig \
+ bs=${blocksz} oseek=0 count=1 conv=notrunc
+ atf_check -o empty "${lssparse}" fs/sparse.orig
+ atf_check truncate -d -l ${blocksz} fs/sparse
+ atf_check cmp -s fs/sparse fs/sparse.orig
+
+ # Now bring the original file back and make sure that punching a hole
+ # in data at the beginning doesn't disturb the data at the end.
+ atf_check mv fs/sparse.orig.orig fs/sparse.orig
+ atf_check -o empty "${lssparse}" fs/sparse.orig
+ atf_check cp fs/sparse.orig fs/sparse
+ atf_check -e not-empty dd if=/dev/zero of=fs/sparse.orig \
+ bs=${blocksz} oseek=0 count=1 conv=notrunc
+ atf_check truncate -d -l ${blocksz} fs/sparse
+ atf_check cmp -s fs/sparse fs/sparse.orig
+}
+deallocate_cleanup()
+{
+ umount fs || true
+}
+
atf_init_test_cases()
{
atf_add_test_case illegal_option
@@ -459,4 +538,5 @@
atf_add_test_case roundup
atf_add_test_case rounddown
atf_add_test_case rounddown_zero
+ atf_add_test_case deallocate
}
diff --git a/usr.bin/truncate/truncate.c b/usr.bin/truncate/truncate.c
--- a/usr.bin/truncate/truncate.c
+++ b/usr.bin/truncate/truncate.c
@@ -62,7 +62,6 @@
int do_refer;
int got_size;
char *fname, *rname;
- struct spacectl_range sr;
fd = -1;
rsize = tsize = sz = off = 0;
@@ -198,6 +197,8 @@
tsize = 0;
if (do_dealloc == 1) {
+ struct spacectl_range sr;
+
sr.r_offset = off;
sr.r_len = len;
r = fspacectl(fd, SPACECTL_DEALLOC, &sr, 0, &sr);

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 2, 5:26 AM (1 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29143763
Default Alt Text
D51207.id158174.diff (6 KB)

Event Timeline