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