1#!/usr/bin/ksh 2# 3# This file and its contents are supplied under the terms of the 4# Common Development and Distribution License ("CDDL"), version 1.0. 5# You may only use this file in accordance with the terms of version 6# 1.0 of the CDDL. 7# 8# A full copy of the text of the CDDL should have accompanied this 9# source. A copy of the CDDL is also available via the Internet at 10# http://www.illumos.org/license/CDDL. 11# 12 13# 14# Copyright 2026 Oxide Computer Company 15# 16 17# 18# Various tests for rm(1). 19# 20 21unalias -a 22set -o pipefail 23export LANG=C.UTF-8 24 25RM=${RM:-"/usr/bin/rm"} 26XRM=${XRM:-"/usr/xpg4/bin/rm"} 27 28rm_exit=0 29rm_arg0=$(basename $0) 30rm_work="/tmp/$rm_arg0.$$" 31 32function fatal 33{ 34 typeset msg="$*" 35 echo "TEST FAILED: $msg" >&2 36 exit 1 37} 38 39function warn 40{ 41 typeset msg="$*" 42 echo "TEST FAILED: $msg" >&2 43 rm_exit=1 44} 45 46function cleanup 47{ 48 rm -rf $rm_work/ 49} 50 51function setup 52{ 53 mkdir "$rm_work" || fatal "failed to create directory $rm_work" 54 touch "$rm_work/file0" "$rm_work/file1" "$rm_work/file2" || \ 55 fatal "failed to make files" 56 mkdir "$rm_work/dir" || fatal "failed to create $rm_work/dir" 57 mkdir -p "$rm_work/a/b/c" || fatal "failed to create recursive dirs" 58 touch "$rm_work/a/1" "$rm_work/a/b/2" "$rm_work/a/b/c/3" || \ 59 fatal "failed to create files" 60 touch "$rm_work/noperm" || fatal "failed to make file" 61 chmod 0000 "$rm_work/noperm" || fatal "failed to chmod $rm_work/noperm" 62 mkdir "$rm_work/nodir" || fatal "failed to make $rm_work/nodir" 63 chmod 0000 "$rm_work/nodir" || fatal "failed to chmod $rm_work/nodir" 64} 65 66# 67# Attempt an rm command that should fail. 68# 69function test_fail 70{ 71 typeset desc="$1" 72 typeset exp="$2" 73 typeset out= 74 shift; shift 75 76 out=$($* 2>&1 1>/dev/null) 77 if (( $? == 0 )); then 78 warn "$desc: $* worked, but expected it to fail with $exp" 79 return 80 fi 81 82 if ! [[ "$out" =~ "$exp" ]]; then 83 warn "$desc: failed with $out, but wanted $exp" 84 return 85 fi 86 87 printf "TEST PASSED: %s\n" "$desc" 88} 89 90# 91# Verify all files listed have been removed from this world. 92# 93function check_gone 94{ 95 typeset desc="$1" 96 typeset fail=0 97 typeset f 98 shift 99 100 for f in "$@"; do 101 if [[ -e $f ]]; then 102 warn "$desc: expected $f to be removed, but it still" \ 103 "exists" 104 fail=1 105 fi 106 done 107 108 return $fail 109} 110 111# 112# Variant of the above, but existence 113# 114function check_exists 115{ 116 typeset desc="$1" 117 typeset fail=0 118 typeset f 119 shift 120 121 for f in "$@"; do 122 if ! [[ -e $f ]]; then 123 warn "$desc: expected $f to be present, but it was" \ 124 "removed" 125 fail=1 126 fi 127 done 128 129 return $fail 130} 131 132# 133# Run a basic rm command. It should pass. All arguments listed on the command line 134# should not exist, though some of them may never have. It should not require 135# input, but we redirect stdin to /dev/null regardless. 136# 137function test_rm 138{ 139 typeset desc="$1" 140 typeset f 141 shift 142 143 if ! $* >/dev/null </dev/null; then 144 warn "$desc: rm failed, but expected success" 145 return 146 fi 147 148 # 149 # Remove any options. 150 # 151 shift 152 if [[ "$1" =~ -[A-Za-z]+ ]]; then 153 shift 154 fi 155 156 if ! check_gone "$desc" "$@"; then 157 return 158 fi 159 160 printf "TEST PASSED: %s\n" "$desc" 161} 162 163# 164# A variant of the above, but after executing all files should remain. 165# 166function test_remain 167{ 168 typeset desc="$1" 169 shift 170 171 if ! $* >/dev/null 2>/dev/null </dev/null; then 172 warn "$desc: rm failed, but expected success" 173 return 174 fi 175 176 # 177 # Remove any options. 178 # 179 shift 180 if [[ "$1" =~ -[A-Za-z]+ ]]; then 181 shift 182 fi 183 184 if ! check_exists "$desc" "$@"; then 185 return 186 fi 187 188 printf "TEST PASSED: %s\n" "$desc" 189} 190 191# 192# Variant of the above where we say "yes". 193# 194function test_yes 195{ 196 typeset desc="$1" 197 typeset ret 198 shift 199 200 # 201 # We need to disable pipefail briefly so the failure of the yes command 202 # doesn't lead rm to accidentally fail. 203 # 204 set +o pipefail 205 yes | $* >/dev/null 2>/dev/null 206 ret=$? 207 set -o pipefail 208 if (( ret != 0 )); then 209 warn "$desc: rm failed, but expected success" 210 return 211 fi 212 213 # 214 # Remove any options. 215 # 216 shift 217 if [[ "$1" =~ -[A-Za-z]+ ]]; then 218 shift 219 fi 220 221 if ! check_gone "$desc" "$@"; then 222 return 223 fi 224 225 printf "TEST PASSED: %s\n" "$desc" 226 227} 228 229# 230# Run an rm command that should produce verbose output. We employ sort here to 231# put the files in a deterministic order so that way if we ever end up with 232# multiple files in a directory we aren't subject to the dirent iteration order. 233# 234function test_verbose 235{ 236 typeset desc="$1" 237 typeset exp="$2" 238 typeset out 239 shift; shift 240 241 out=$($* | sort) 242 if (( $? != 0 )); then 243 warn "$desc: rm failed, but expected success" 244 return 245 fi 246 247 shift 248 if [[ "$1" =~ -[A-Za-z]+ ]]; then 249 shift 250 fi 251 252 if ! check_gone "$desc" "$@"; then 253 return 254 fi 255 256 if [[ "$out" != "$exp" ]]; then 257 warn "expected verbose output $exp, found $out" 258 return 259 fi 260 261 printf "TEST PASSED: %s\n" "$desc" 262} 263 264trap cleanup EXIT 265 266# 267# Ensure that we're not running as uid 0 to minimize any chance of some of the 268# DAC privileges. 269# 270if (( $(id -u) == 0 )); then 271 echo "This test should be run as a non-privileged user like nobody." >&2 272 exit 1 273fi 274 275# 276# First test various failure scenarios. 277# 278for f in "$RM" "$XRM"; do 279 test_fail "$f with no arguments" "usage: rm" $f 280 test_fail "$f with bad arg -@" "illegal option -- @" $f -@ 281 test_fail "$f on non-existent file fails" "No such file or directory" \ 282 $f "$rm_work/enoent" 283done 284 285# 286# Verify basic rm activity 287# 288for f in "$RM" "$XRM"; do 289 cleanup; setup 290 test_rm "$f basic file" $f "$rm_work/file0" 291 test_rm "$f multiple files" $f "$rm_work/file1" "$rm_work/file2" 292 test_rm "$f recursive dir" $f -r "$rm_work/a" 293 cleanup ; setup 294 test_rm "$f files and dirs" $f -r "$rm_work/a" "$rm_work/dir" \ 295 "$rm_work/file0" "$rm_work/file1" "$rm_work/file2" 296done 297 298# 299# Verify rm removes other operands even if one fails. Similarly, no error on -f. 300# 301for f in "$RM" "$XRM"; do 302 cleanup; setup 303 test_fail "$f fails with present/missing files" \ 304 "No such file or directory" $f "$rm_work/file0" "$rm_work/ENOENT" \ 305 "$rm_work/file1" "$rm_work/file2" 306 check_gone "$f removed other files after ENOENT" "$rm_work/file0" \ 307 "$rm_work/file1" "$rm_work/file2" 308 cleanup; setup 309 test_rm "$f -f passes with present/missing files" $f -f \ 310 "$rm_work/file0" "$rm_work/ENOENT" "$rm_work/file1" \ 311 "$rm_work/file2" 312done 313 314# 315# Test our behavior on directories: rm should fail on an empty directory or a 316# directory with files. Next, rm -d should work on an empty directory; however, 317# it should fail on a non-empty directory. 318# 319for f in "$RM" "$XRM"; do 320 cleanup; setup 321 test_fail "$f fails on empty directory" "is a directory" $f \ 322 "$rm_work/dir" 323 test_fail "$f fails on non-empty directory" "is a directory" $f \ 324 "$rm_work/a" 325 test_rm "$f -d removes empty directory" $f -d "$rm_work/dir" 326 test_fail "$f -d doesn't remove non-empty directory" \ 327 "Directory not empty" $f -d "$rm_work/a" 328 test_fail "$f -df doesn't remove non-empty directory" \ 329 "Directory not empty" $f -df "$rm_work/a" 330done 331 332# 333# rm -i doesn't remove files when told no. 334# 335for f in "$RM" "$XRM"; do 336 cleanup; setup 337 test_remain "$f -i answer no 1 file" $f -i "$rm_work/file0" 338 test_remain "$f -i answer no multi file" $f -i "$rm_work/file0" \ 339 "$rm_work/file1" "$rm_work/file2" 340 test_remain "$f -ri answer no" $f -ri "$rm_work/file0" "$rm_work/dir" \ 341 "$rm_work/a" 342done 343 344# 345# xpg4 rm has -i and -f as the last one wins. /usr/bin/rm has -i trump. 346# 347cleanup; setup 348test_remain "$RM -i overrides all -f" $RM -fif "$rm_work/file0" 349test_remain "$XRM last -if wins (-i)" $XRM -fifi "$rm_work/file0" 350test_rm "$XRM last -if wins (-f)" $XRM -fif "$rm_work/file0" 351 352# 353# Now, test cases where we say yes to -i and verify that everything is gone. 354# 355for f in "$RM" "$XRM"; do 356 cleanup; setup 357 test_yes "$f -i answer yes 1 file" $f -i "$rm_work/file0" 358 test_yes "$f -i answer yes two file" $f -i "$rm_work/file1" \ 359 "$rm_work/file2" 360 cleanup; setup 361 test_yes "$f -ri answer yes, multi" $f -ri "$rm_work/file1" \ 362 "$rm_work/file2" "$rm_work/dir" "$rm_work/a" 363done 364 365# 366# Now onto rm's behavior when there are no write permissions. rm with no flags 367# will prompt for this if we're on a tty, but not otherwise. -i will prompt. -f 368# will never prompt. Do the same with an empty dir. 369# 370for f in "$RM" "$XRM"; do 371 cleanup; setup 372 test_remain "$f -i no perms" $f -i "$rm_work/noperm" 373 test_rm "$f no perms (!tty)" $f "$rm_work/noperm" 374 cleanup; setup 375 test_yes "$f -i no perms (yes)" $f -i "$rm_work/noperm" 376 cleanup; setup 377 test_rm "$f -f no perms" $f -f "$rm_work/noperm" 378 cleanup; setup 379 test_remain "$f -di no perms dir" $f -di "$rm_work/nodir" 380 test_rm "$f -d no perms dir (!tty)" $f -d "$rm_work/nodir" 381 cleanup; setup 382 test_yes "$f -di no perms dir (yes)" $f -di "$rm_work/nodir" 383 cleanup; setup 384 test_rm "$f -df no perms dir" $f -df "$rm_work/nodir" 385 386done 387 388# 389# Finally, rm says it will fail and always print a message if it encounters a 390# directory without write permission in rm -rf. 391# 392for f in "$RM" "$XRM"; do 393 cleanup; setup 394 chmod 0000 "$rm_work/a/b" || fatal "failed to make chmod $rm_work/a/b" 395 test_fail "$f -rf non-empty non-writable dir" "Permission denied" \ 396 $f -rf "$rm_work/a" 397 chmod 755 "$rm_work/a/b" || fatal "failed to restore $rm_work/a/b" 398done 399 400# 401# Different tests for -v. 402# 403for f in "$RM" "$XRM"; do 404 cleanup; setup 405 test_verbose "$f -v single file" "$rm_work/file0" $f -v "$rm_work/file0" 406 exp=$(find $rm_work/file1 $rm_work/file2 | sort) 407 test_verbose "$f -v multiple files" "$exp" $f \ 408 -v "$rm_work/file2" "$rm_work/file1" 409 exp=$(find "$rm_work/noperm" "$rm_work/dir" "$rm_work/a" | sort) 410 test_verbose "$f -rvf multiple" "$exp" $f -rvf "$rm_work/noperm" \ 411 "$rm_work/dir" "$rm_work/a" 412done 413 414if (( rm_exit == 0 )); then 415 printf "All tests passed successfully\n" 416fi 417exit $rm_exit 418