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