xref: /illumos-gate/usr/src/test/util-tests/tests/cpmvln/ln-LP.ksh (revision 0f1762c0ba4c6b14fab6b990e30c1f3a82f5ac10)
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