tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Import truncate(1) from FreeBSD



Hi,

I have attached a patch for importing truncate(1) from FreeBSD.
Please let me know if you come across any issues while testing it.

Regards,
zerous


# HG changeset patch
# User Naveen Narayanan <nnaveen%posteo.net@localhost>
# Date 1761077270 -7200
#      Tue Oct 21 22:07:50 2025 +0200
# Branch trunk
# Node ID 18356b65cc89a38396f66914a1f4fcfa3b2681ba
# Parent  03d10f218fce5630dbd63fdd135fec096db9f6ca
Import truncate(1) from FreeBSD

diff -r 03d10f218fce -r 18356b65cc89 distrib/sets/lists/base/mi
--- a/distrib/sets/lists/base/mi	Sun Oct 19 18:56:19 2025 +0000
+++ b/distrib/sets/lists/base/mi	Tue Oct 21 22:07:50 2025 +0200
@@ -886,6 +886,7 @@
 ./usr/bin/tpm_version				base-tpm-bin		tpm
 ./usr/bin/tput					base-util-bin
 ./usr/bin/tr					base-util-bin
+./usr/bin/truncate                              base-util-bin
 ./usr/bin/tradcpp				base-util-bin
 ./usr/bin/true					base-util-bin
 ./usr/bin/tset					base-util-bin
diff -r 03d10f218fce -r 18356b65cc89 distrib/sets/lists/man/mi
--- a/distrib/sets/lists/man/mi	Sun Oct 19 18:56:19 2025 +0000
+++ b/distrib/sets/lists/man/mi	Tue Oct 21 22:07:50 2025 +0200
@@ -4263,6 +4263,7 @@
 ./usr/share/man/man1/tpm_version.1		man-tpm-man		tpm,.man
 ./usr/share/man/man1/tput.1			man-util-man		.man
 ./usr/share/man/man1/tr.1			man-util-man		.man
+./usr/share/man/man1/truncate.1			man-util-man		.man
 ./usr/share/man/man1/tradcpp.1			man-util-man		.man
 ./usr/share/man/man1/true.1			man-util-man		.man
 ./usr/share/man/man1/tset.1			man-util-man		.man
diff -r 03d10f218fce -r 18356b65cc89 distrib/sets/lists/manhtml/mi
--- a/distrib/sets/lists/manhtml/mi	Sun Oct 19 18:56:19 2025 +0000
+++ b/distrib/sets/lists/manhtml/mi	Tue Oct 21 22:07:50 2025 +0200
@@ -617,6 +617,7 @@
 ./usr/share/man/html1/tpm_version.html		man-tpm-htmlman		tpm,html
 ./usr/share/man/html1/tput.html			man-util-htmlman	html
 ./usr/share/man/html1/tr.html			man-util-htmlman	html
+./usr/share/man/html1/truncate.html		man-util-htmlman	html
 ./usr/share/man/html1/tradcpp.html		man-util-htmlman	html
 ./usr/share/man/html1/true.html			man-util-htmlman	html
 ./usr/share/man/html1/tset.html			man-util-htmlman	html
diff -r 03d10f218fce -r 18356b65cc89 distrib/sets/lists/tests/mi
--- a/distrib/sets/lists/tests/mi	Sun Oct 19 18:56:19 2025 +0000
+++ b/distrib/sets/lists/tests/mi	Tue Oct 21 22:07:50 2025 +0200
@@ -6745,6 +6745,11 @@
 ./usr/tests/usr.bin/tr/Atffile				tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/tr/Kyuafile				tests-usr.bin-tests	compattestfile,atf,kyua
 ./usr/tests/usr.bin/tr/t_basic				tests-usr.bin-tests	compattestfile,atf
+./usr/tests/usr.bin/truncate                           tests-usr.bin-tests     compattestfile,atf
+./usr/tests/usr.bin/truncate/Atffile                    tests-usr.bin-tests     compattestfile,atf
+./usr/tests/usr.bin/truncate/t_grow                     tests-usr.bin-tests     compattestfile,atf
+./usr/tests/usr.bin/truncate/t_misc                     tests-usr.bin-tests     compattestfile,atf
+./usr/tests/usr.bin/truncate/t_shrink                   tests-usr.bin-tests     compattestfile,atf
 ./usr/tests/usr.bin/unifdef				tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/unifdef/Atffile			tests-usr.bin-tests	compattestfile,atf
 ./usr/tests/usr.bin/unifdef/Kyuafile			tests-usr.bin-tests	compattestfile,atf,kyua
diff -r 03d10f218fce -r 18356b65cc89 etc/mtree/NetBSD.dist.tests
--- a/etc/mtree/NetBSD.dist.tests	Sun Oct 19 18:56:19 2025 +0000
+++ b/etc/mtree/NetBSD.dist.tests	Tue Oct 21 22:07:50 2025 +0200
@@ -479,6 +479,7 @@
 ./usr/tests/usr.bin/printf
 ./usr/tests/usr.bin/pwhash
 ./usr/tests/usr.bin/realpath
+./usr/tests/usr.bin/truncate
 ./usr/tests/usr.bin/rump_server
 ./usr/tests/usr.bin/sdiff
 ./usr/tests/usr.bin/sed
diff -r 03d10f218fce -r 18356b65cc89 tests/usr.bin/Makefile
--- a/tests/usr.bin/Makefile	Sun Oct 19 18:56:19 2025 +0000
+++ b/tests/usr.bin/Makefile	Tue Oct 21 22:07:50 2025 +0200
@@ -9,7 +9,7 @@
 		diff dirname error find fstat ftp gcov gdb grep gzip id indent \
 		infocmp jot ld locale m4 make mixerctl mkdep mtree nbperf \
 		netpgpverify patch pkill pr printf pwhash realpath rump_server \
-		shmif_dumpbus shmif_pcapin sdiff sed sort stat tar tmux tr \
+		shmif_dumpbus shmif_pcapin sdiff sed sort stat tar tmux tr truncate \
 		unifdef uniq vmstat xlint ztest
 
 .if ${MKCXX} != "no"
diff -r 03d10f218fce -r 18356b65cc89 tests/usr.bin/truncate/Makefile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/usr.bin/truncate/Makefile	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,11 @@
+#     $NetBSD$
+
+.include <bsd.own.mk>
+
+TESTSDIR= ${TESTSBASE}/usr.bin/truncate
+
+TESTS_SH+= t_grow
+TESTS_SH+= t_shrink
+TESTS_SH+= t_misc
+
+.include <bsd.test.mk>
diff -r 03d10f218fce -r 18356b65cc89 tests/usr.bin/truncate/t_grow.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/usr.bin/truncate/t_grow.sh	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,117 @@
+# $NetBSD$
+# Copyright (c) 2021 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+server="rump_server -lrumpvfs -lrumpdev_disk -lrumpfs_ffs -d key=/dev/dk,hostpath=ffs.img,size=host"
+
+export RUMP_SERVER=unix://ffs
+
+exmount='rumpfs on / type rumpfs (local)
+/dev/dk on /mnt type ffs (local)
+'
+
+t_init()
+{
+      atf_check -s exit:0 -o ignore -e ignore newfs -b 32k -f 4k -F -s 10M ffs.img
+
+      atf_check -s exit:0 -o ignore -e ignore ${server} ${RUMP_SERVER}
+
+      export LD_PRELOAD=/usr/lib/librumphijack.so
+      atf_check -s exit:0 -o ignore -e ignore mkdir /rump/mnt
+      export RUMPHIJACK=vfs=all,blanket=/dev/dk:/mnt
+      atf_check -s exit:0 -o ignore -e ignore mount /dev/dk /mnt
+      atf_check -s exit:0 -o inline:"${exmount}" mount
+}
+
+atf_test_case t_cleanup cleanup
+t_cleanup()
+{
+      rump.halt
+}
+
+atf_test_case abs_grow
+abs_grow_head()
+{
+      atf_set "descr" "Tests that truncate can grow a file to absolute length"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+abs_grow_body()
+{
+      t_init
+
+      # truncate should allocate the first and the last block only
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 10M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((10 * 1024 * 1024))
+
+      # truncate should allocate 3 blocks in this case since it extends the file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 20M /mnt/test_file
+      atf_check_equal $(du /mnt/test_file | awk '{print $1}') 192
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((20 * 1024 * 1024))
+}
+
+atf_test_case rela_grow
+rela_grow_head()
+{
+      atf_set "descr" "Check truncate can grow file to relative length"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+rela_grow_body()
+{
+      t_init
+
+      # truncate should allocate the first and the last block only
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 10M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((10 * 1024 * 1024))
+
+      # truncate should allocate 3 blocks in this case since it extends the file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s +10M /mnt/test_file
+      atf_check_equal $(du /mnt/test_file | awk '{print $1}') 192
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((20 * 1024 * 1024))
+}
+
+atf_test_case nocreate
+nocreate_head()
+{
+      atf_set "descr" "Check file is not created with -c"
+      atf_set "use.fs" "true"
+}
+nocreate_body()
+{
+      t_init
+
+      # test_file should not exist
+      atf_check -s exit:0 -o ignore -e ignore truncate -c -s 10M /mnt/test_file
+      atf_check -s not-exit:0 -e ignore 'ls /mnt/test_file'
+}
+
+atf_init_test_cases() {
+      atf_add_test_case abs_grow
+      atf_add_test_case rela_grow
+      atf_add_test_case nocreate
+}
diff -r 03d10f218fce -r 18356b65cc89 tests/usr.bin/truncate/t_misc.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/usr.bin/truncate/t_misc.sh	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,146 @@
+# $NetBSD$
+# Copyright (c) 2021 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+server="rump_server -lrumpvfs -lrumpdev_disk -lrumpfs_ffs -d key=/dev/dk,hostpath=ffs.img,size=host"
+
+export RUMP_SERVER=unix://ffs
+
+exmount='rumpfs on / type rumpfs (local)
+/dev/dk on /mnt type ffs (local)
+'
+
+t_init()
+{
+      atf_check -s exit:0 -o ignore -e ignore newfs -b 32k -f 4k -F -s 10M ffs.img
+      atf_check -s exit:0 -o ignore -e ignore ${server} ${RUMP_SERVER}
+
+      export LD_PRELOAD=/usr/lib/librumphijack.so
+      atf_check -s exit:0 -o ignore -e ignore mkdir /rump/mnt
+      export RUMPHIJACK=vfs=all,blanket=/dev/dk:/mnt
+      atf_check -s exit:0 -o ignore -e ignore mount /dev/dk /mnt
+      atf_check -s exit:0 -o inline:"${exmount}" mount
+}
+
+atf_test_case t_cleanup cleanup
+t_cleanup()
+{
+      rump.halt
+}
+
+atf_test_case reference
+reference_head()
+{
+      atf_set "descr" "Tests that truncate can use a reference file"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+reference_body()
+{
+      t_init
+
+      # create a reference file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 10M /mnt/test_file1
+      atf_check_equal $(du -s /mnt/test_file1 | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file1 | awk '{print $5}') $((10 * 1024 * 1024))
+
+      # truncate should extend the file to the length of the reference file
+      atf_check -s exit:0 -o ignore -e ignore truncate -r /mnt/test_file1 /mnt/test_file2
+      atf_check_equal $(du -s /mnt/test_file2 | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file2 | awk '{print $5}') $((10 * 1024 * 1024))
+}
+
+atf_test_case negative
+negative_head()
+{
+      atf_set "descr" "Tests that truncate treats negative size as zero"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+negative_body()
+{
+      t_init
+
+      # create/extend a file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 10M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((10 * 1024 * 1024))
+
+      # truncate should shrink the file to zero size
+      atf_check -s exit:0 -o ignore -e ignore truncate -s -100M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 0
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') 0
+}
+
+atf_test_case roundup
+roundup_head()
+{
+      atf_set "descr" "Tests that truncate rounds up the size of the file"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+roundup_body()
+{
+      t_init
+
+      # create/extend a file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 6M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((6 * 1024 * 1024))
+
+      # truncate should round up the size of the file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s %5M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 192
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((10 * 1024 * 1024))
+}
+
+atf_test_case rounddown
+rounddown_head()
+{
+      atf_set "descr" "Tests that truncate rounds down the size of the file"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+rounddown_body()
+{
+      t_init
+
+      # create/extend a file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 9M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((9 * 1024 * 1024))
+
+      # truncate should round down the size of the file
+      atf_check -s exit:0 -o ignore -e ignore truncate -s /2M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 64
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((8 * 1024 * 1024))
+}
+
+atf_init_test_cases() {
+      atf_add_test_case reference
+      atf_add_test_case rounddown
+      atf_add_test_case roundup
+      atf_add_test_case negative
+}
diff -r 03d10f218fce -r 18356b65cc89 tests/usr.bin/truncate/t_shrink.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/usr.bin/truncate/t_shrink.sh	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,100 @@
+# $NetBSD$
+# Copyright (c) 2021 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+server="rump_server -lrumpvfs -lrumpdev_disk -lrumpfs_ffs -d key=/dev/dk,hostpath=ffs.img,size=host"
+
+export RUMP_SERVER=unix://ffs
+
+exmount='rumpfs on / type rumpfs (local)
+/dev/dk on /mnt type ffs (local)
+'
+
+t_init()
+{
+      atf_check -s exit:0 -o ignore -e ignore newfs -b 32k -f 4k -F -s 10M ffs.img
+      atf_check -s exit:0 -o ignore -e ignore ${server} ${RUMP_SERVER}
+
+      export LD_PRELOAD=/usr/lib/librumphijack.so
+      atf_check -s exit:0 -o ignore -e ignore mkdir /rump/mnt
+      export RUMPHIJACK=vfs=all,blanket=/dev/dk:/mnt
+      atf_check -s exit:0 -o ignore -e ignore mount /dev/dk /mnt
+      atf_check -s exit:0 -o inline:"${exmount}" mount
+}
+
+atf_test_case t_cleanup cleanup
+t_cleanup()
+{
+      rump.halt
+}
+
+atf_test_case abs_shrink
+abs_shrink_head()
+{
+      atf_set "descr" "Tests that truncate can shrink a file to absolute length"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+abs_shrink_body()
+{
+      t_init
+
+      # truncate should allocate the first and the last block only
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 10M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((10 * 1024 * 1024))
+
+      # truncate should allocate only one block post shrink
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 5M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 64
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((5 * 1024 * 1024))
+}
+
+atf_test_case rela_shrink
+rela_shrink_head()
+{
+      atf_set "descr" "Tests that truncate can shrink a file to relative length"
+      atf_set "use.fs" "true"
+      atf_set "require.progs" "ls awk du"
+}
+rela_shrink_body()
+{
+      t_init
+
+      # truncate should allocate the first and the last block only
+      atf_check -s exit:0 -o ignore -e ignore truncate -s 10M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 128
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((10 * 1024 * 1024))
+
+      # truncate should allocate only one block post shrink
+      atf_check -s exit:0 -o ignore -e ignore truncate -s -5M /mnt/test_file
+      atf_check_equal $(du -s /mnt/test_file | awk '{print $1}') 64
+      atf_check_equal $(ls -l /mnt/test_file | awk '{print $5}') $((5 * 1024 * 1024))
+}
+
+atf_init_test_cases() {
+      atf_add_test_case abs_shrink
+      atf_add_test_case rela_shrink
+}
diff -r 03d10f218fce -r 18356b65cc89 usr.bin/Makefile
--- a/usr.bin/Makefile	Sun Oct 19 18:56:19 2025 +0000
+++ b/usr.bin/Makefile	Tue Oct 21 22:07:50 2025 +0200
@@ -29,7 +29,7 @@
 	spell split stat su sys_info systat \
 	tabs tail talk tcopy tee telnet tftp tic time timeout tip touch \
 	tput \
-	tr true tset tsort tty ul uname unexpand unifdef \
+	tr truncate true tset tsort tty ul uname unexpand unifdef \
 	uniq units unvis unzip usbhidaction usbhidctl users utoppya \
 	uudecode uuencode uuidgen vacation vgrind videoctl vis \
 	vmstat vndcompress w \
diff -r 03d10f218fce -r 18356b65cc89 usr.bin/truncate/Makefile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr.bin/truncate/Makefile	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,8 @@
+#     $NetBSD$
+
+PROG= truncate
+
+DPADD+= ${LIBUTIL}
+LDADD+= -lutil
+
+.include <bsd.prog.mk>
diff -r 03d10f218fce -r 18356b65cc89 usr.bin/truncate/truncate
Binary file usr.bin/truncate/truncate has changed
diff -r 03d10f218fce -r 18356b65cc89 usr.bin/truncate/truncate.1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr.bin/truncate/truncate.1	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,249 @@
+.\"   $NetBSD$
+.\"
+.\" Copyright (c) 2000 Sheldon Hearn <sheldonh%FreeBSD.org@localhost>.
+.\" All rights reserved.
+.\" Copyright (c) 2021 The FreeBSD Foundation
+.\"
+.\" Portions of this manual page were written by Ka Ho Ng <khng%FreeBSD.org@localhost>
+.\" under sponsorship from the FreeBSD Foundation.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd August 19, 2021
+.Dt TRUNCATE 1
+.Os
+.Sh NAME
+.Nm truncate
+.Nd truncate, extend the length of files, or perform space management in files
+.Sh SYNOPSIS
+.Nm
+.Op Fl c
+.Bk -words
+.Fl s Xo
+.Sm off
+.Op Cm + | - | % | /
+.Ar size
+.Op Cm SUFFIX
+.Sm on
+.Xc
+.Ek
+.Ar
+.Nm
+.Op Fl c
+.Bk -words
+.Fl r Ar rfile
+.Ek
+.Ar
+.Nm
+.Op Fl c
+.Bk -words
+.Fl d
+.Oo
+.Fl o Xo
+.Sm off
+.Ar offset
+.Op Cm SUFFIX
+.Sm on
+.Xc
+.Oc
+.Fl l Xo
+.Sm off
+.Ar length
+.Op Cm SUFFIX
+.Sm on
+.Xc
+.Ek
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+utility adjusts the length of each regular file given on the command-line, or
+performs space management with the given offset and the length over a regular
+file given on the command-line.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl c
+Do not create files if they do not exist.
+The
+.Nm
+utility does not treat this as an error.
+No error messages are displayed
+and the exit value is not affected.
+.It Fl r Ar rfile
+Truncate or extend files to the length of the file
+.Ar rfile .
+.It Fl s Xo
+.Sm off
+.Op Cm + | - | % | /
+.Ar size
+.Op Cm SUFFIX
+.Sm on
+.Xc
+If the
+.Ar size
+argument is preceded by a plus sign
+.Pq Cm + ,
+files will be extended by this number of bytes.
+If the
+.Ar size
+argument is preceded by a dash
+.Pq Cm - ,
+file lengths will be reduced by no more than this number of bytes,
+to a minimum length of zero bytes.
+If the
+.Ar size
+argument is preceded by a percent sign
+.Pq Cm % ,
+files will be round up to a multiple of this number of bytes.
+If the
+.Ar size
+argument is preceded by a slash sign
+.Pq Cm / ,
+files will be round down to a multiple of this number of bytes,
+to a minimum length of zero bytes.
+Otherwise, the
+.Ar size
+argument specifies an absolute length to which all files
+should be extended or reduced as appropriate.
+.It Fl d
+Zero a region in the specified file.
+If the underlying file system of the given file supports hole-punching,
+file system space deallocation may be performed in the operation region.
+.It Fl o Ar offset
+The space management operation is performed at the given
+.Ar offset
+bytes in the file.
+If this option is not specified, the operation is performed at the beginning of the file.
+.It Fl l Ar length
+The length of the operation range in bytes.
+This option must always be specified if option
+.Fl d
+is specified, and must be greater than 0.
+.El
+.Pp
+The
+.Ar size ,
+.Ar offset
+and
+.Ar length
+arguments may be suffixed with one of
+.Cm K ,
+.Cm M ,
+.Cm G
+or
+.Cm T
+(either upper or lower case) to indicate a multiple of
+Kilobytes, Megabytes, Gigabytes or Terabytes
+respectively.
+.Pp
+Exactly one of the
+.Fl r ,
+.Fl s
+and
+.Fl d
+options must be specified.
+.Pp
+If a file is made smaller, its extra data is lost.
+If a file is made larger,
+it will be extended as if by writing bytes with the value zero.
+If the file does not exist,
+it is created unless the
+.Fl c
+option is specified.
+.Pp
+Note that,
+while truncating a file causes space on disk to be freed,
+extending a file does not cause space to be allocated.
+To extend a file and actually allocate the space,
+it is necessary to explicitly write data to it,
+using (for example) the shell's
+.Ql >>
+redirection syntax, or
+.Xr dd 1 .
+.Sh EXIT STATUS
+.Ex -std
+If the operation fails for an argument,
+.Nm
+will issue a diagnostic
+and continue processing the remaining arguments.
+.Sh EXAMPLES
+Adjust the size of the file
+.Pa test_file
+to 10 Megabytes but do not create it if it does not exist:
+.Bd -literal -offset indent
+truncate -c -s +10M test_file
+.Ed
+.Pp
+Same as above but create the file if it does not exist:
+.Bd -literal -offset indent
+truncate -s +10M test_file
+ls -l test_file
+-rw-r--r--  1 root  wheel  10485760 Jul 22 18:48 test_file
+.Ed
+.Pp
+Adjust the size of
+.Pa test_file
+to the size of the kernel and create another file
+.Pa test_file2
+with the same size:
+.Bd -literal -offset indent
+truncate -r /boot/kernel/kernel test_file test_file2
+ls -l /boot/kernel/kernel test_file*
+-r-xr-xr-x  1 root  wheel    31352552 May 15 14:18 /boot/kernel/kernel*
+-rw-r--r--  1 root  wheel    31352552 Jul 22 19:15 test_file
+-rw-r--r--  1 root  wheel    31352552 Jul 22 19:15 test_file2
+.Ed
+.Pp
+Downsize
+.Pa test_file
+in 5 Megabytes:
+.Bd -literal -offset indent
+# truncate -s -5M test_file
+ls -l test_file*
+-rw-r--r--  1 root  wheel    26109672 Jul 22 19:17 test_file
+-rw-r--r--  1 root  wheel    31352552 Jul 22 19:15 test_file2
+.Ed
+.Sh SEE ALSO
+.Xr dd 1 ,
+.Xr touch 1 ,
+.Xr fspacectl 2 ,
+.Xr truncate 2
+.Sh STANDARDS
+The
+.Nm
+utility conforms to no known standards.
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Fx 4.2 .
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An Sheldon Hearn Aq Mt sheldonh%starjuice.net@localhost .
+Hole-punching support of this
+utility was developed by
+.An Ka Ho Ng Aq Mt khng%FreeBSD.org@localhost .
diff -r 03d10f218fce -r 18356b65cc89 usr.bin/truncate/truncate.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr.bin/truncate/truncate.c	Tue Oct 21 22:07:50 2025 +0200
@@ -0,0 +1,222 @@
+/* $NetBSD$ */
+/*
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2000 Sheldon Hearn <sheldonh%FreeBSD.org@localhost>.
+ * All rights reserved.
+ *
+ * Copyright (c) 2021 The FreeBSD Foundation
+ *
+ * Portions of this software were developed by Ka Ho Ng <khng%FreeBSD.org@localhost>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+
+__dead static void usage(void);
+
+int
+main(int argc, char **argv)
+{
+      struct stat sb;
+      mode_t omode;
+      off_t oflow, rsize, sz, tsize, round, off, len;
+      int64_t usz;
+      int ch, error, fd, oflags, r;
+      int do_dealloc;
+      int do_truncate;
+      int no_create;
+      int do_relative;
+      int do_round;
+      int do_refer;
+      int got_size;
+      char *fname, *rname;
+
+      fd = -1;
+      rsize = tsize = sz = off = 0;
+      len = -1;
+      do_dealloc = no_create = do_relative = do_round = do_refer =
+          got_size = 0;
+      do_truncate = 1;
+      error = r = 0;
+      rname = NULL;
+      while ((ch = getopt(argc, argv, "cdr:s:o:l:")) != -1)
+              switch (ch) {
+              case 'c':
+                      no_create = 1;
+                      break;
+              case 'd':
+                      do_dealloc = 1;
+                      do_truncate = 0;
+                      break;
+              case 'r':
+                      do_refer = 1;
+                      rname = optarg;
+                      break;
+              case 's':
+                      if (*optarg == '+' || *optarg == '-') {
+                              do_relative = 1;
+                      } else if (*optarg == '%' || *optarg == '/') {
+                              do_round = 1;
+                      }
+                      if (dehumanize_number(do_relative ||
+                          do_round ? optarg + 1 : optarg, &usz) == -1 ||
+                          usz <= 0)
+                              errx(EXIT_FAILURE,
+                                  "invalid size argument `%s'", optarg);
+
+                      sz = (*optarg == '-' || *optarg == '/') ?
+                          -usz : usz;
+                      got_size = 1;
+                      break;
+              case 'o':
+                      if (dehumanize_number(optarg, &usz) == -1 ||
+                          usz < 0)
+                              errx(EXIT_FAILURE,
+                                  "invalid offset argument `%s'", optarg);
+
+                      off = usz;
+                      break;
+              case 'l':
+                      if (dehumanize_number(optarg, &usz) == -1 ||
+                          usz <= 0)
+                              errx(EXIT_FAILURE,
+                                  "invalid length argument `%s'", optarg);
+
+                      len = usz;
+                      break;
+              default:
+                      usage();
+                      /* NOTREACHED */
+              }
+
+      argv += optind;
+      argc -= optind;
+
+      /*
+       * Exactly one of do_refer, got_size or do_dealloc must be specified.
+       * Since do_relative implies got_size, do_relative, do_refer and
+       * do_dealloc are also mutually exclusive.  If do_dealloc is specified,
+       * the length argument must be set.  See usage() for allowed
+       * invocations.
+       */
+      if (argc < 1 || do_refer + got_size + do_dealloc != 1 ||
+          (do_dealloc == 1 && len == -1))
+              usage();
+      if (do_refer == 1) {
+              if (stat(rname, &sb) == -1)
+                      err(EXIT_FAILURE, "%s", rname);
+              tsize = sb.st_size;
+      } else if (do_relative == 1 || do_round == 1)
+              rsize = sz;
+      else if (do_dealloc == 0)
+              tsize = sz;
+
+      if (no_create)
+              oflags = O_WRONLY;
+      else
+              oflags = O_WRONLY | O_CREAT;
+      omode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+      while ((fname = *argv++) != NULL) {
+              if (fd != -1)
+                      close(fd);
+              if ((fd = open(fname, oflags, omode)) == -1) {
+                      if (errno != ENOENT) {
+                              warn("%s", fname);
+                              error++;
+                      }
+                      continue;
+              }
+              if (do_relative == 1) {
+                      if (fstat(fd, &sb) == -1) {
+                              warn("%s", fname);
+                              error++;
+                              continue;
+                      }
+                      oflow = sb.st_size + rsize;
+                      if (oflow < (sb.st_size + rsize)) {
+                              errno = EFBIG;
+                              warn("%s", fname);
+                              error++;
+                              continue;
+                      }
+                      tsize = oflow;
+              }
+              if (do_round == 1) {
+                      if (fstat(fd, &sb) == -1) {
+                              warn("%s", fname);
+                              error++;
+                              continue;
+                      }
+                      sz = rsize;
+                      if (sz < 0)
+                              sz = -sz;
+                      if (sb.st_size % sz) {
+                              round = sb.st_size / sz;
+                              if (rsize > 0)
+                                      round++;
+                              tsize = round * sz;
+                      } else
+                              tsize = sb.st_size;
+              }
+              if (tsize < 0)
+                      tsize = 0;
+
+              if (do_dealloc == 1) {
+                      r = fdiscard(fd, off, len);
+              }
+              if (do_truncate == 1)
+                      r = ftruncate(fd, tsize);
+              if (r == -1) {
+                      warn("%s", fname);
+                      error++;
+              }
+      }
+      if (fd != -1)
+              close(fd);
+
+      return (error ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static void
+usage(void)
+{
+      const char *pnam = getprogname();
+      fprintf(stderr, "usage:\t%s %s\n\t%s %s\n\t%s %s\n",
+          pnam, "[-c] -s [+|-|%|/]size[K|k|M|m|G|g|T|t] file ...",
+          pnam, "[-c] -r rfile file ...",
+          pnam, "[-c] -d [-o offset[K|k|M|m|G|g|T|t]] -l length[K|k|M|m|G|g|T|t] file ...");
+      exit(EXIT_FAILURE);
+}


Home | Main Index | Thread Index | Old Index