xref: /freebsd/usr.bin/stat/tests/stat_test.sh (revision 1a7a067da456f8962ef87bfdf75c94cd12988615)
1#
2# Copyright (c) 2017 Dell EMC
3# All rights reserved.
4# Copyright (c) 2025 Klara, Inc.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26#
27
28atf_test_case F_flag
29F_flag_head()
30{
31	atf_set	"descr" "Verify the output format for -F"
32}
33F_flag_body()
34{
35	# TODO: socket, whiteout file
36	atf_check touch a
37	atf_check mkdir b
38	atf_check install -m 0777 /dev/null c
39	atf_check ln -s a d
40	atf_check mkfifo f
41
42	atf_check -o match:'.* a' stat -Fn a
43	atf_check -o match:'.* b/' stat -Fn b
44	atf_check -o match:'.* c\*' stat -Fn c
45	atf_check -o match:'.* d@' stat -Fn d
46	atf_check -o match:'.* f\|' stat -Fn f
47}
48
49atf_test_case h_flag cleanup
50h_flag_head()
51{
52	atf_set "descr" "Verify the output format for -h"
53	atf_set "require.user" "root"
54}
55h_flag_body()
56{
57	# POSIX defines a hole as “[a] contiguous region of bytes
58	# within a file, all having the value of zero” and requires
59	# that “all seekable files shall have a virtual hole starting
60	# at the current size of the file” but says “it is up to the
61	# implementation to define when sparse files can be created
62	# and with what granularity for the size of holes”.  It also
63	# defines a sparse file as “[a] file that contains more holes
64	# than just the virtual hole at the end of the file”.  That's
65	# pretty much the extent of its discussion of holes, apart
66	# from the description of SEEK_HOLE and SEEK_DATA in the lseek
67	# manual page.  In other words, there is no portable way to
68	# reliably create a hole in a file on any given file system.
69	#
70	# On FreeBSD, this test is likely to run on either tmpfs, ufs
71	# (ffs2), or zfs.  Of those three, only tmpfs has predictable
72	# semantics and supports all possible configurations (the
73	# minimum hole size on zfs is variable for small files, and
74	# ufs will not allow a file to end in a hole).
75	atf_check mkdir mnt
76	atf_check mount -t tmpfs tmpfs mnt
77	cd mnt
78
79	# For a directory, prints the minimum hole size, which on
80	# tmpfs is the system page size.
81	ps=$(sysctl -n hw.pagesize)
82	atf_check -o inline:"$((ps)) .\n" stat -h .
83	atf_check -o inline:"$((ps)) ." stat -hn .
84
85	# For a file, prints a list of holes.
86	atf_check truncate -s 0 foo
87	atf_check -o inline:"0 foo" \
88	    stat -hn foo
89	atf_check truncate -s "$((ps))" foo
90	atf_check -o inline:"0-$((ps-1)) foo" \
91	    stat -hn foo
92	atf_check dd status=none if=/COPYRIGHT of=foo \
93	    oseek="$((ps))" bs=1 count=1
94	atf_check -o inline:"0-$((ps-1)),$((ps+1)) foo" \
95	    stat -hn foo
96	atf_check truncate -s "$((ps*3))" foo
97	atf_check -o inline:"0-$((ps-1)),$((ps*2))-$((ps*3-1)) foo" \
98	    stat -hn foo
99
100	# Test multiple files.
101	atf_check dd status=none if=/COPYRIGHT of=bar
102	sz=$(stat -f%z bar)
103	atf_check -o inline:"0-$((ps-1)),$((ps*2))-$((ps*3-1)) foo
104$((sz)) bar
105" \
106	    stat -h foo bar
107
108	# For a device, fail.
109	atf_check -s exit:1 -e match:"/dev/null: Illegal seek" \
110	    stat -h /dev/null
111}
112h_flag_cleanup()
113{
114	if [ -d mnt ]; then
115		umount mnt || true
116	fi
117}
118
119atf_test_case l_flag
120l_flag_head()
121{
122	atf_set	"descr" "Verify the output format for -l"
123}
124l_flag_body()
125{
126	atf_check touch a
127	atf_check ln a b
128	atf_check ln -s a c
129	atf_check mkdir d
130
131	paths="a b c d"
132
133	ls_out=ls.output
134	stat_out=stat.output
135
136	# NOTE:
137	# - Even though stat -l claims to be equivalent to `ls -lT`, the
138	#   whitespace is a bit more liberal in the `ls -lT` output.
139	# - `ls -ldT` is used to not recursively list the contents of
140	#   directories.
141	for path in $paths; do
142		atf_check -o save:$ls_out ls -ldT $path
143		cat $ls_out
144		atf_check -o save:$stat_out stat -l $path
145		cat $stat_out
146		echo "Comparing normalized whitespace"
147		atf_check sed -i '' -E -e 's/[[:space:]]+/ /g' $ls_out
148		atf_check sed -i '' -E -e 's/[[:space:]]+/ /g' $stat_out
149		atf_check cmp $ls_out $stat_out
150	done
151}
152
153atf_test_case n_flag
154n_flag_head()
155{
156	atf_set	"descr" "Verify that -n suppresses newline output for lines"
157}
158n_flag_body()
159{
160	atf_check touch a b
161	atf_check -o inline:"$(stat a | tr -d '\n')" stat -n a
162	atf_check -o inline:"$(stat a b | tr -d '\n')" stat -n a b
163}
164
165atf_test_case q_flag
166q_flag_head()
167{
168	atf_set	"descr" "Verify that -q suppresses error messages from l?stat(2)"
169}
170q_flag_body()
171{
172	ln -s nonexistent broken-link
173
174	atf_check -s exit:1 stat -q nonexistent
175	atf_check -s exit:1 stat -q nonexistent
176	atf_check -o not-empty stat -q broken-link
177	atf_check -o not-empty stat -qL broken-link
178}
179
180atf_test_case r_flag
181r_flag_head()
182{
183	atf_set	"descr" "Verify that -r displays output in 'raw mode'"
184}
185r_flag_body()
186{
187	atf_check touch a
188	# TODO: add more thorough checks.
189	atf_check -o not-empty stat -r a
190}
191
192atf_test_case s_flag
193s_flag_head()
194{
195	atf_set	"descr" "Verify the output format for -s"
196}
197s_flag_body()
198{
199	atf_check touch a
200	atf_check ln a b
201	atf_check ln -s a c
202	atf_check mkdir d
203
204	paths="a b c d"
205
206	# The order/name of each of the fields is specified by stat(1) manpage.
207	fields="st_dev st_ino st_mode st_nlink"
208	fields="$fields st_uid st_gid st_rdev st_size"
209	fields="$fields st_uid st_gid st_mode"
210	fields="$fields st_atime st_mtime st_ctime st_birthtime"
211	fields="$fields st_blksize st_blocks st_flags"
212
213	# NOTE: the following...
214	# - ... relies on set -eu to ensure that the fields are set, as
215	#       documented, in stat(1).
216	# - ... uses a subshell to ensure that the eval'ed variables don't
217	#	pollute the next iteration's behavior.
218	for path in $paths; do
219		(
220		set -eu
221		eval $(stat -s $path)
222		for field in $fields; do
223			eval "$field=\$$field"
224		done
225		) || atf_fail 'One or more fields not set by stat(1)'
226	done
227}
228
229atf_test_case t_flag
230t_flag_head()
231{
232	atf_set	"descr" "Verify the output format for -t"
233}
234
235t_flag_body()
236{
237	atf_check touch foo
238	atf_check touch -d 1970-01-01T00:00:42 foo
239	atf_check -o inline:'42\n' \
240	    stat -t '%s' -f '%a' foo
241	atf_check -o inline:'1970-01-01 00:00:42\n' \
242	    stat -t '%F %H:%M:%S' -f '%Sa' foo
243}
244
245x_output_date()
246{
247	local date_format='%a %b %e %H:%M:%S %Y'
248
249	stat -t "$date_format" "$@"
250}
251
252x_output()
253{
254	local path=$1; shift
255
256	local atime_s=$(x_output_date -f '%Sa' $path)
257	local btime_s=$(x_output_date -f '%SB' $path)
258	local ctime_s=$(x_output_date -f '%Sc' $path)
259	local devid=$(stat -f '%Hd,%Ld' $path)
260	local file_type_s=$(stat -f '%HT' $path)
261	local gid=$(stat -f '%5g' $path)
262	local groupname=$(stat -f '%8Sg' $path)
263	local inode=$(stat -f '%i' $path)
264	local mode=$(stat -f '%Mp%Lp' $path)
265	local mode_s=$(stat -f '%Sp' $path)
266	local mtime_s=$(x_output_date -f '%Sm' $path)
267	local nlink=$(stat -f '%l' $path)
268	local size_a=$(stat -f '%-11z' $path)
269	local uid=$(stat -f '%5u' $path)
270	local username=$(stat -f '%8Su' $path)
271
272	cat <<EOF
273  File: "$path"
274  Size: $size_a  FileType: $file_type_s
275  Mode: ($mode/$mode_s)         Uid: ($uid/$username)  Gid: ($gid/$groupname)
276Device: $devid   Inode: $inode    Links: $nlink
277Access: $atime_s
278Modify: $mtime_s
279Change: $ctime_s
280 Birth: $btime_s
281EOF
282}
283
284atf_test_case x_flag
285x_flag_head()
286{
287	atf_set	"descr" "Verify the output format for -x"
288}
289x_flag_body()
290{
291	atf_check touch a
292	atf_check ln a b
293	atf_check ln -s a c
294	atf_check mkdir d
295
296	paths="a b c d"
297
298	for path in $paths; do
299		atf_check -o "inline:$(x_output $path)\n" stat -x $path
300	done
301}
302
303atf_init_test_cases()
304{
305	atf_add_test_case F_flag
306	#atf_add_test_case H_flag
307	atf_add_test_case h_flag
308	#atf_add_test_case L_flag
309	#atf_add_test_case f_flag
310	atf_add_test_case l_flag
311	atf_add_test_case n_flag
312	atf_add_test_case q_flag
313	atf_add_test_case r_flag
314	atf_add_test_case s_flag
315	atf_add_test_case t_flag
316	atf_add_test_case x_flag
317}
318