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# This program goes through and tests how ln acts by default and with its -L and 19# -P flags as well as the default behavior. The basic summary is: 20# 21# 1. All of these flags should be ignored when used with -s. 22# 2. -L should cause us to dereference a symlink when making a hardlink. That is 23# we should get a hardlink to the underlying object (if allowed). 24# 3. -P should cause us to get a hardlink to the symlink itself. 25# 4. /usr/bin/ln defaults to -P behavior. /usr/xpg4/bin/ln defaults to -L 26# behavior. 27# 28# Finally, we want to see how this works across a variety of symlinks to the 29# following file types: regular files, directories, doors, fifos, unix domain 30# sockets. We must be very careful not to create hardlinks to directories here 31# as tests are sometimes run by privileged users. 32# 33 34unalias -a 35set -o pipefail 36export LANG=C.UTF-8 37 38LN=${LN:-"/usr/bin/ln"} 39XLN=${XLN:-"/usr/xpg4/bin/ln"} 40 41lnlp_exit=0 42lnlp_arg0=$(basename $0) 43lnlp_tdir=$(dirname $0) 44lnlp_mkobj="$lnlp_tdir/mkobj" 45lnlp_equiv="$lnlp_tdir/equiv" 46lnlp_work="/tmp/$lnlp_arg0.$$" 47 48# 49# The following table describes the files that we're testing against. hardlinks 50# will not work with doors as doors are considered to be on a different file 51# system. Directories will fail because we are running as a non-root user to 52# ensure that we don't create hardlink to directory madness. 53# 54typeset -A lnlp_files=( 55 ["file"]=(make="touch" hard="pass" soft="pass") 56 ["dir"]=(make="mkdir" hard="fail" soft="pass") 57 ["fifo"]=(make="$lnlp_mkobj -f" hard="pass" soft="pass") 58 ["door"]=(make="$lnlp_mkobj -d" hard="fail" soft="pass") 59 ["uds"]=(make="$lnlp_mkobj -s" hard="pass" soft="pass") 60) 61 62function fatal 63{ 64 typeset msg="$*" 65 echo "TEST FAILED: $msg" >&2 66 exit 1 67} 68 69function warn 70{ 71 typeset msg="$*" 72 echo "TEST FAILED: $msg" >&2 73 lnlp_exit=1 74} 75 76function cleanup 77{ 78 rm -rf $lnlp_work/ 79} 80 81# 82# Create the series of objects and symlinks that we expect to exist. 83# 84function setup 85{ 86 mkdir "$lnlp_work" || fatal "failed to make test directory" 87 for f in ${!lnlp_files[@]}; do 88 typeset targ="${lnlp_work}/$f" 89 typeset sym="${targ}_symlink" 90 91 ${lnlp_files[$f].make} $targ || fatal "failed to make $f" 92 ln -s $targ $sym || fatal "failed to create symlink to $f" 93 done 94} 95 96# 97# Run a single ln hardlink invocation. This invocation is expected to pass. $dst 98# should match the contents of $exp. 99# 100function test_one_hl 101{ 102 typeset desc="$1" 103 typeset src="$lnlp_work/${2}_symlink" 104 typeset exp_base="$3" 105 typeset exp="$lnlp_work/$3" 106 typeset dst="$lnlp_work/test" 107 108 # 109 # Remaining arguments after this are the correct ln program and flags to 110 # use. 111 # 112 shift; shift; shift 113 114 rm -f $dst 115 if ! $* "$src" "$dst"; then 116 warn "$desc: $* $src $dst failed unexpectedly" 117 return 118 fi 119 120 if ! $lnlp_equiv $exp $dst; then 121 warn "$desc: ln didn't result in expected file $3" 122 return 123 fi 124 125 printf "TEST PASSED: %s\n" "$desc" 126} 127 128# 129# This is variant where the ln results should fail. This is generally used when 130# using hardlinks on doors and directories. 131# 132function test_one_fail 133{ 134 typeset desc="$1" 135 typeset src="$lnlp_work/${2}_symlink" 136 typeset dst="$lnlp_work/test" 137 138 shift; shift 139 140 rm -f $dst 141 if $* "$src" "$src" 2>/dev/null; then 142 warn "$desc: $* unexpectedly worked?!" 143 return 144 fi 145 146 printf "TEST PASSED: %s failed correctly\n" "$desc" 147} 148 149# 150# For a given version of ln and its options, run through each of the different 151# valid file types and see if it passes or fails. 152# 153function test_series 154{ 155 typeset bdesc="$1" 156 typeset rtype="$2" 157 158 # 159 # Options after this will be the flags and type of ln invocation we 160 # should use. 161 # 162 shift; shift 163 for f in ${!lnlp_files[@]}; do 164 typeset test_exp 165 166 if [[ "$rtype" == "hard" ]]; then 167 test_exp="$f" 168 else 169 test_exp="${f}_symlink" 170 fi 171 172 if [[ ${lnlp_files[$f].[$rtype]} == "pass" ]]; then 173 test_one_hl "$bdesc: $f results in $rtype" $f \ 174 $test_exp $* 175 else 176 test_one_fail "$bdesc: $f ${rtype}link fails" $f $* 177 fi 178 done 179} 180 181# 182# Go through and make a symlink to each file and verify that it is a different 183# inode than one that already exists. We skip doing this for every combination 184# and just do it once for a file and a symlink. 185# 186function test_symlink 187{ 188 typeset dst="$lnlp_work/test" 189 typeset src="$lnlp_work/file" 190 typeset desc="$1" 191 shift 192 193 rm -f $dst 194 if ! $* $src $dst; then 195 warn "$desc: $* $src $dst unexpectedly failed" 196 return 197 fi 198 199 if $lnlp_equiv $src $dst 1>/dev/null 2>/dev/null; then 200 warn "$desc: ln -s somehow ended up with the same inode" 201 return 202 fi 203 204 src="$lnlp_work/file_symlink" 205 rm -f $dst 206 if ! $* $src $dst; then 207 warn "$desc: $* $src $dst unexpectedly failed" 208 return 209 fi 210 211 if $lnlp_equiv $src $dst 1>/dev/null 2>/dev/null; then 212 warn "$desc: ln -s somehow ended up with the same inode" 213 return 214 fi 215 216 printf "TEST PASSED: %s\n" "$desc" 217} 218 219# 220# Sanity check that we're not running as a privileged user. This won't catch 221# some some cases where we have privileges, but this is better than nothing. 222# 223lnlp_uid=$(id -u) 224if (( lnlp_uid == 0 )); then 225 printf "Running as uid 0 is not permitted try nobody instead\n" >&2 226 exit 1 227fi 228 229trap cleanup EXIT 230 231# 232# Create all of our different fields and the symlinks to them. 233# 234setup 235 236# 237# First test the defaults of each command. 238# 239test_series "$LN defaults" soft $LN 240test_series "$XLN defaults" hard $XLN 241 242# 243# Now verify that they do identical thing with a single -L and -P. 244# 245test_series "$LN -P" soft $LN -P 246test_series "$LN -L" hard $LN -L 247 248test_series "$LN -P wins (-LP)" soft $LN -LP 249test_series "$LN -P wins (-PLPLP)" soft $LN -PLPLP 250test_series "$LN -P wins (-LLLP)" soft $LN -LLLP 251test_series "$LN -L wins (-PL)" hard $LN -PL 252test_series "$LN -L wins (-LPLPL)" hard $LN -LPLPL 253test_series "$LN -L wins (-PPPL)" hard $LN -PPPL 254 255test_series "$XLN -P wins (-LP)" soft $XLN -LP 256test_series "$XLN -P wins (-PLPLP)" soft $XLN -PLPLP 257test_series "$XLN -P wins (-LLLP)" soft $XLN -LLLP 258test_series "$XLN -L wins (-PL)" hard $XLN -PL 259test_series "$XLN -L wins (-LPLPL)" hard $XLN -LPLPL 260test_series "$XLN -L wins (-PPPL)" hard $XLN -PPPL 261 262# 263# Go through and manually do a few symlink related tests. 264# 265test_symlink "$LN -s" $LN -s 266test_symlink "$LN -s -L" $LN -s -L 267test_symlink "$LN -s -P" $LN -s -P 268test_symlink "$LN -s -LP" $LN -s -LP 269test_symlink "$LN -s -PL" $LN -s -PL 270 271test_symlink "$XLN -s" $XLN -s 272test_symlink "$XLN -s -L" $XLN -s -L 273test_symlink "$XLN -s -P" $XLN -s -P 274test_symlink "$XLN -s -LP" $XLN -s -LP 275test_symlink "$XLN -s -PL" $XLN -s -PL 276 277if (( lnlp_exit == 0 )); then 278 printf "All tests passed successfully\n" 279fi 280exit $lnlp_exit 281