1*c7509229SRobert Mustacchi#!/usr/bin/ksh 2*c7509229SRobert Mustacchi# 3*c7509229SRobert Mustacchi# This file and its contents are supplied under the terms of the 4*c7509229SRobert Mustacchi# Common Development and Distribution License ("CDDL"), version 1.0. 5*c7509229SRobert Mustacchi# You may only use this file in accordance with the terms of version 6*c7509229SRobert Mustacchi# 1.0 of the CDDL. 7*c7509229SRobert Mustacchi# 8*c7509229SRobert Mustacchi# A full copy of the text of the CDDL should have accompanied this 9*c7509229SRobert Mustacchi# source. A copy of the CDDL is also available via the Internet at 10*c7509229SRobert Mustacchi# http://www.illumos.org/license/CDDL. 11*c7509229SRobert Mustacchi# 12*c7509229SRobert Mustacchi 13*c7509229SRobert Mustacchi# 14*c7509229SRobert Mustacchi# Copyright 2026 Oxide Computer Company 15*c7509229SRobert Mustacchi# 16*c7509229SRobert Mustacchi 17*c7509229SRobert Mustacchi# 18*c7509229SRobert Mustacchi# Test the various flavors of cp, mv, and ln with the -T flag. The -T flag 19*c7509229SRobert Mustacchi# briefly requires that there are always two operands and says to treat the 20*c7509229SRobert Mustacchi# destination like a file with respect to removing and everything else. 21*c7509229SRobert Mustacchi# 22*c7509229SRobert Mustacchi 23*c7509229SRobert Mustacchiunalias -a 24*c7509229SRobert Mustacchiset -o pipefail 25*c7509229SRobert Mustacchiexport LANG=C.UTF-8 26*c7509229SRobert Mustacchi 27*c7509229SRobert Mustacchi# 28*c7509229SRobert Mustacchi# Program paths for interposing while testing. 29*c7509229SRobert Mustacchi# 30*c7509229SRobert MustacchiMV=${MV:-"/usr/bin/mv"} 31*c7509229SRobert MustacchiXMV=${XMV:-"/usr/xpg4/bin/mv"} 32*c7509229SRobert MustacchiCP=${CP:-"/usr/bin/cp"} 33*c7509229SRobert MustacchiXCP=${XCP:-"/usr/xpg4/bin/cp"} 34*c7509229SRobert MustacchiLN=${LN:-"/usr/bin/ln"} 35*c7509229SRobert MustacchiXLN=${XLN:-"/usr/xpg4/bin/ln"} 36*c7509229SRobert Mustacchi 37*c7509229SRobert Mustacchi 38*c7509229SRobert Mustacchint_exit=0 39*c7509229SRobert Mustacchint_arg0=$(basename $0) 40*c7509229SRobert Mustacchint_work="/tmp/$nt_arg0.$$" 41*c7509229SRobert Mustacchi 42*c7509229SRobert Mustacchifunction fatal 43*c7509229SRobert Mustacchi{ 44*c7509229SRobert Mustacchi typeset msg="$*" 45*c7509229SRobert Mustacchi echo "TEST FAILED: $msg" >&2 46*c7509229SRobert Mustacchi exit 1 47*c7509229SRobert Mustacchi} 48*c7509229SRobert Mustacchi 49*c7509229SRobert Mustacchifunction warn 50*c7509229SRobert Mustacchi{ 51*c7509229SRobert Mustacchi typeset msg="$*" 52*c7509229SRobert Mustacchi echo "TEST FAILED: $msg" >&2 53*c7509229SRobert Mustacchi nt_exit=1 54*c7509229SRobert Mustacchi} 55*c7509229SRobert Mustacchi 56*c7509229SRobert Mustacchifunction cleanup 57*c7509229SRobert Mustacchi{ 58*c7509229SRobert Mustacchi rm -rf $nt_work/ 59*c7509229SRobert Mustacchi} 60*c7509229SRobert Mustacchi 61*c7509229SRobert Mustacchifunction setup 62*c7509229SRobert Mustacchi{ 63*c7509229SRobert Mustacchi typeset f 64*c7509229SRobert Mustacchi mkdir "$nt_work" || fatal "failed to make test directory" 65*c7509229SRobert Mustacchi for f in file1 file2 file3; do 66*c7509229SRobert Mustacchi echo $f > "$nt_work/$f" || fatal "failed to create $f" 67*c7509229SRobert Mustacchi done 68*c7509229SRobert Mustacchi 69*c7509229SRobert Mustacchi mkdir -p "$nt_work/dir1/foo/bar" || fatal "failed to create $f" 70*c7509229SRobert Mustacchi echo nested > "$nt_work/dir1/foo/bar/nested" || fatal \ 71*c7509229SRobert Mustacchi "failed to create nested file" 72*c7509229SRobert Mustacchi mkdir -p "$nt_work/empty" || fatal "failed to create $f" 73*c7509229SRobert Mustacchi} 74*c7509229SRobert Mustacchi 75*c7509229SRobert Mustacchifunction exp_fail 76*c7509229SRobert Mustacchi{ 77*c7509229SRobert Mustacchi typeset desc="$1" 78*c7509229SRobert Mustacchi shift 79*c7509229SRobert Mustacchi 80*c7509229SRobert Mustacchi if $* 2>/dev/null; then 81*c7509229SRobert Mustacchi warn "$desc unexpectedly worked" 82*c7509229SRobert Mustacchi return 83*c7509229SRobert Mustacchi fi 84*c7509229SRobert Mustacchi 85*c7509229SRobert Mustacchi printf "TEST PASSED: %s correctly failed\n" "$desc" 86*c7509229SRobert Mustacchi} 87*c7509229SRobert Mustacchi 88*c7509229SRobert Mustacchifunction check_one 89*c7509229SRobert Mustacchi{ 90*c7509229SRobert Mustacchi typeset desc="$1" 91*c7509229SRobert Mustacchi typeset check="$2" 92*c7509229SRobert Mustacchi typeset data="$3" 93*c7509229SRobert Mustacchi typeset comp=$(<$check) 94*c7509229SRobert Mustacchi 95*c7509229SRobert Mustacchi if [[ "$comp" != "$data" ]]; then 96*c7509229SRobert Mustacchi warn "$desc: $check didn't have expected data: " \ 97*c7509229SRobert Mustacchi "found: '$comp', expected: '$data'" 98*c7509229SRobert Mustacchi return 99*c7509229SRobert Mustacchi fi 100*c7509229SRobert Mustacchi 101*c7509229SRobert Mustacchi printf "TEST PASSED: %s: found expected data\n" "$desc" 102*c7509229SRobert Mustacchi} 103*c7509229SRobert Mustacchi 104*c7509229SRobert Mustacchi# 105*c7509229SRobert Mustacchi# Run a command that relates to a file. Check that the target file has the 106*c7509229SRobert Mustacchi# expected contents in the end. 107*c7509229SRobert Mustacchi# 108*c7509229SRobert Mustacchifunction exp_pass 109*c7509229SRobert Mustacchi{ 110*c7509229SRobert Mustacchi typeset desc="$1" 111*c7509229SRobert Mustacchi typeset check="$2" 112*c7509229SRobert Mustacchi typeset data="$3" 113*c7509229SRobert Mustacchi typeset comp= 114*c7509229SRobert Mustacchi 115*c7509229SRobert Mustacchi shift; shift; shift 116*c7509229SRobert Mustacchi if ! $*; then 117*c7509229SRobert Mustacchi warn "$desc: failed to run $*" 118*c7509229SRobert Mustacchi return 119*c7509229SRobert Mustacchi fi 120*c7509229SRobert Mustacchi 121*c7509229SRobert Mustacchi check_one "$desc" "$check" "$data" 122*c7509229SRobert Mustacchi} 123*c7509229SRobert Mustacchi 124*c7509229SRobert Mustacchitrap cleanup EXIT 125*c7509229SRobert Mustacchi 126*c7509229SRobert Mustacchi# 127*c7509229SRobert Mustacchi# First go through and verify cases where we have only one argument or where we 128*c7509229SRobert Mustacchi# have more than one argument. Normally only ln works with only a single file. 129*c7509229SRobert Mustacchi# 130*c7509229SRobert Mustacchisetup 131*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 132*c7509229SRobert Mustacchi exp_fail "$f -T one arg" $f -T "$nt_work/file1" 133*c7509229SRobert Mustacchi exp_fail "$f -T three args" $f -T "$nt_work/file1" "$nt_work/file2" \ 134*c7509229SRobert Mustacchi "$nt_work/dir1" 135*c7509229SRobert Mustacchidone 136*c7509229SRobert Mustacchi 137*c7509229SRobert Mustacchi# 138*c7509229SRobert Mustacchi# First go through and make sure that things do what we expect with basic file 139*c7509229SRobert Mustacchi# to file movement without -T. We clean before every test to make sure that we 140*c7509229SRobert Mustacchi# have a pristine environment. We also do an override pass here using -f to make 141*c7509229SRobert Mustacchi# sure that we can clobber as expected. 142*c7509229SRobert Mustacchi# 143*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 144*c7509229SRobert Mustacchi cleanup; setup 145*c7509229SRobert Mustacchi exp_pass "$f file to file" "$nt_work/targ" "file1" $f "$nt_work/file1" \ 146*c7509229SRobert Mustacchi "$nt_work/targ" 147*c7509229SRobert Mustacchi exp_pass "$f -f file to file" "$nt_work/file3" "file2" $f -f \ 148*c7509229SRobert Mustacchi "$nt_work/file2" "$nt_work/file3" 149*c7509229SRobert Mustacchidone 150*c7509229SRobert Mustacchi 151*c7509229SRobert Mustacchi# 152*c7509229SRobert Mustacchi# Now do the same, but moving contents into directories. Both with and without 153*c7509229SRobert Mustacchi# -n. -n shouldn't care because the target doesn't exist. 154*c7509229SRobert Mustacchi# 155*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 156*c7509229SRobert Mustacchi cleanup; setup 157*c7509229SRobert Mustacchi exp_pass "$f file to dir" "$nt_work/dir1/file1" "file1" $f \ 158*c7509229SRobert Mustacchi "$nt_work/file1" "$nt_work/dir1" 159*c7509229SRobert Mustacchi cleanup; setup 160*c7509229SRobert Mustacchi exp_pass "$f -n file to dir" "$nt_work/dir1/file1" "file1" $f -n \ 161*c7509229SRobert Mustacchi "$nt_work/file1" "$nt_work/dir1" 162*c7509229SRobert Mustacchidone 163*c7509229SRobert Mustacchi 164*c7509229SRobert Mustacchi# 165*c7509229SRobert Mustacchi# Now use -T for file to file, which basically should be the same as before for 166*c7509229SRobert Mustacchi# both overwrite or not. 167*c7509229SRobert Mustacchi# 168*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 169*c7509229SRobert Mustacchi cleanup; setup 170*c7509229SRobert Mustacchi exp_pass "$f -T file to file" "$nt_work/targ" "file1" $f -T \ 171*c7509229SRobert Mustacchi "$nt_work/file1" "$nt_work/targ" 172*c7509229SRobert Mustacchi exp_pass "$f -Tf file to file" "$nt_work/file3" "file2" $f -Tf \ 173*c7509229SRobert Mustacchi "$nt_work/file2" "$nt_work/file3" 174*c7509229SRobert Mustacchidone 175*c7509229SRobert Mustacchi 176*c7509229SRobert Mustacchi# 177*c7509229SRobert Mustacchi# Verify that -n on its own is honored correctly for file to file. Then do the 178*c7509229SRobert Mustacchi# same with -T. The tools are inconsistent about exiting zero or not, so we 179*c7509229SRobert Mustacchi# check file contents instead. 180*c7509229SRobert Mustacchi# 181*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 182*c7509229SRobert Mustacchi cleanup; setup 183*c7509229SRobert Mustacchi $f -n "$nt_work/file1" "$nt_work/file2" >/dev/null 2>/dev/null 184*c7509229SRobert Mustacchi check_one "$f file to file -n source intact" "$nt_work/file1" "file1" 185*c7509229SRobert Mustacchi check_one "$f file to file -n dest intact" "$nt_work/file2" "file2" 186*c7509229SRobert Mustacchi $f -Tn "$nt_work/file1" "$nt_work/file2" >/dev/null 2>/dev/null 187*c7509229SRobert Mustacchi check_one "$f -Tn file to file source intact" "$nt_work/file1" "file1" 188*c7509229SRobert Mustacchi check_one "$f -Tn file to file dest intact" "$nt_work/file2" "file2" 189*c7509229SRobert Mustacchidone 190*c7509229SRobert Mustacchi 191*c7509229SRobert Mustacchi# 192*c7509229SRobert Mustacchi# Now one of the major differences with -T. If we use -Tn with the target of a 193*c7509229SRobert Mustacchi# directory, nothing should happen, there shouldn't be a file inside of the 194*c7509229SRobert Mustacchi# directory, and our original file should still exist. 195*c7509229SRobert Mustacchi# 196*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 197*c7509229SRobert Mustacchi cleanup; setup 198*c7509229SRobert Mustacchi $f -Tn "$nt_work/file1" "$nt_work/dir1" >/dev/null 2>/dev/null 199*c7509229SRobert Mustacchi check_one "$f -Tn file to dir source intact" "$nt_work/file1" "file1" 200*c7509229SRobert Mustacchi if [[ ! -d "$nt_work/dir1" ]]; then 201*c7509229SRobert Mustacchi warn "$f -Tn file to dir incorrectly removed dir" 202*c7509229SRobert Mustacchi else 203*c7509229SRobert Mustacchi printf "TEST PASSED: %s -Tn file to dir dir intact\n" "$f" 204*c7509229SRobert Mustacchi fi 205*c7509229SRobert Mustacchi 206*c7509229SRobert Mustacchi if [[ -e "$nt_work/dir1/file1" ]]; then 207*c7509229SRobert Mustacchi warn "$f -Tn file to dir incorrectly created file" 208*c7509229SRobert Mustacchi else 209*c7509229SRobert Mustacchi printf "TEST PASSED: %s -Tn didn't put file in dir\n" "$f" 210*c7509229SRobert Mustacchi fi 211*c7509229SRobert Mustacchidone 212*c7509229SRobert Mustacchi 213*c7509229SRobert Mustacchi# 214*c7509229SRobert Mustacchi# Verify that if our target is an empty directory, we don't overwrite it when 215*c7509229SRobert Mustacchi# using -T. This should be true regardless of -f. 216*c7509229SRobert Mustacchi# 217*c7509229SRobert Mustacchifor f in "$LN" "$XLN" "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 218*c7509229SRobert Mustacchi cleanup; setup 219*c7509229SRobert Mustacchi $f -Tf "$nt_work/file1" "$nt_work/empty" 2>/dev/null 220*c7509229SRobert Mustacchi check_one "$f -Tf file to dir source intact" "$nt_work/file1" "file1" 221*c7509229SRobert Mustacchi if [[ ! -d "$nt_work/empty" ]]; then 222*c7509229SRobert Mustacchi warn "$f -Tf file to dir incorrectly removed dir" 223*c7509229SRobert Mustacchi else 224*c7509229SRobert Mustacchi printf "TEST PASSED: %s -Tf file to dir dir intact\n" "$f" 225*c7509229SRobert Mustacchi fi 226*c7509229SRobert Mustacchi 227*c7509229SRobert Mustacchi if [[ -e "$nt_work/empty/file1" ]]; then 228*c7509229SRobert Mustacchi warn "$f -Tn file to dir incorrectly created file" 229*c7509229SRobert Mustacchi else 230*c7509229SRobert Mustacchi printf "TEST PASSED: %s -Tf didn't put file in dir\n" "$f" 231*c7509229SRobert Mustacchi fi 232*c7509229SRobert Mustacchidone 233*c7509229SRobert Mustacchi 234*c7509229SRobert Mustacchi# 235*c7509229SRobert Mustacchi# Now that we have done various versions of going file->* we need to turn around 236*c7509229SRobert Mustacchi# and do dir->*. We start with the base case of a directory to a file. The 237*c7509229SRobert Mustacchi# behavior here is tool specific: 238*c7509229SRobert Mustacchi# 239*c7509229SRobert Mustacchi# - mv does not allow this. 240*c7509229SRobert Mustacchi# - cp does not allow this. 241*c7509229SRobert Mustacchi# - We don't want to generate directory hardlinks with ln 242*c7509229SRobert Mustacchi# - ln without -f will refuse because it exists. However, ln with -f will 243*c7509229SRobert Mustacchi# normally touch it. We focus on the non -f case so we have a consistent 244*c7509229SRobert Mustacchi# failure. 245*c7509229SRobert Mustacchi# 246*c7509229SRobert Mustacchicleanup; setup 247*c7509229SRobert Mustacchifor f in "$LN -s" "$XLN -s" "$MV" "$XMV" "$CP" "$XCP"; do 248*c7509229SRobert Mustacchi exp_fail "$f dir to file" $f "$nt_work/empty" "$nt_work/file1" 249*c7509229SRobert Mustacchidone 250*c7509229SRobert Mustacchi 251*c7509229SRobert Mustacchi# 252*c7509229SRobert Mustacchi# Now directory to directory. In the case of non-T operation, everything should 253*c7509229SRobert Mustacchi# happily work and create it inside of the target directory. However, with -T 254*c7509229SRobert Mustacchi# the behavior is different: 255*c7509229SRobert Mustacchi# 256*c7509229SRobert Mustacchi# - mv works if it's an empty target directory, fails otherwise. This is 257*c7509229SRobert Mustacchi# different from when not using -T. 258*c7509229SRobert Mustacchi# - cp does not allow this. 259*c7509229SRobert Mustacchi# - ln -s does not allow it regardless of -f or not. 260*c7509229SRobert Mustacchi# 261*c7509229SRobert Mustacchifor f in "$LN -s" "$XLN -s" "$CP" "$XCP"; do 262*c7509229SRobert Mustacchi cleanup; setup 263*c7509229SRobert Mustacchi exp_fail "$f -T dir to dir" $f -T "$nt_work/dir1" "$nt_work/empty" 264*c7509229SRobert Mustacchidone 265*c7509229SRobert Mustacchi 266*c7509229SRobert Mustacchifor f in "$MV" "$XMV"; do 267*c7509229SRobert Mustacchi cleanup; setup 268*c7509229SRobert Mustacchi exp_pass "$f -T dir to dir (empty)" "$nt_work/empty/foo/bar/nested" \ 269*c7509229SRobert Mustacchi "nested" $f -T "$nt_work/dir1" "$nt_work/empty" 270*c7509229SRobert Mustacchi cleanup; setup 271*c7509229SRobert Mustacchi exp_fail "$f -T dir to dir (non-empty)" $f -T "$nt_work/empty" \ 272*c7509229SRobert Mustacchi "$nt_work/dir1" 273*c7509229SRobert Mustacchidone 274*c7509229SRobert Mustacchi 275*c7509229SRobert Mustacchi# 276*c7509229SRobert Mustacchi# Finally test a case specific to symlinks: where the source is a directory but 277*c7509229SRobert Mustacchi# the target doesn't exist. Without -T the first time it's created it goes in 278*c7509229SRobert Mustacchi# the top-level dir. The second time because it's a symlink to a directory it 279*c7509229SRobert Mustacchi# goes in the directory. Consider 'ln -s amd64 64' like we use in the build 280*c7509229SRobert Mustacchi# system. The first time it creates 64->amd64 in the top level and then 281*c7509229SRobert Mustacchi# afterwards creates amd64/amd64 which points to itself, which doesn't help 282*c7509229SRobert Mustacchi# anyone. While we are using absolute paths in the test, the same behavior 283*c7509229SRobert Mustacchi# holds. 284*c7509229SRobert Mustacchi# 285*c7509229SRobert Mustacchifor f in "$LN -s" "$XLN -s"; do 286*c7509229SRobert Mustacchi cleanup; setup 287*c7509229SRobert Mustacchi ln -s "$nt_work/empty" "$nt_work/64" || fatal "failed to create 64" 288*c7509229SRobert Mustacchi exp_fail "$f -T existing dir symlink" $f -T "$nt_work/empty" \ 289*c7509229SRobert Mustacchi "$nt_work/64" 290*c7509229SRobert Mustacchi if ! $f -Tf "$nt_work/empty" "$nt_work/64"; then 291*c7509229SRobert Mustacchi warn "$f -Tf existing symlink failed" 292*c7509229SRobert Mustacchi else 293*c7509229SRobert Mustacchi printf "TEST PASSED: %s -Tf existing dir symlink failed\n" "$f" 294*c7509229SRobert Mustacchi fi 295*c7509229SRobert Mustacchidone 296*c7509229SRobert Mustacchi 297*c7509229SRobert Mustacchiif (( nt_exit == 0 )); then 298*c7509229SRobert Mustacchi printf "All tests passed successfully\n" 299*c7509229SRobert Mustacchifi 300*c7509229SRobert Mustacchiexit $nt_exit 301