Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F146263159
D51207.id158174.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
D51207.id158174.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D51207: truncate: fix a minor nit + add a hole-punching test
Attached
Detach File
Event Timeline
Log In to Comment