#!/bin/bash
# Symlink lab. Run it in a throwaway container, never on a machine you care about:
#   docker run --rm -v "$PWD/symlink-lab.sh:/lab.sh:ro" debian:trixie-slim bash /lab.sh
apt-get update -qq >/dev/null && apt-get install -y -qq strace e2fsprogs python3 python3-venv >/dev/null 2>&1
mkdir -p /tmp/lab && cd /tmp/lab
h() { echo; echo "### $*"; }

h "1 a symlink's size is the length of its target path"
ln -s /usr/lib/python3/dist-packages pkgs
stat -c '%F size=%s' pkgs
echo -n /usr/lib/python3/dist-packages | wc -c

h "2 fast and slow symlinks on ext4"
mkdir src && ln -s short src/short && ln -s "$(printf 'x%.0s' {1..70})" src/long
truncate -s 8M img && mkfs.ext4 -q -d src img
debugfs -R 'stat /short' img 2>/dev/null | grep -E 'Fast link|Blockcount'
debugfs -R 'stat /long' img 2>/dev/null | grep -E 'Fast link|Blockcount|EXTENTS'

h "3 a dangling link"
ln -s nowhere dang
cat dang
[ -L dang ] && echo "-L dang: true"
[ -e dang ] || echo "-e dang: false"

h "4 40 hops resolve, 41 fail"
echo hi > t0
for i in $(seq 1 41); do ln -s "t$((i-1))" "t$i"; done
cat t40
cat t41

h "5 a relative target resolves from the link's folder"
mkdir -p a b && echo data > a/file && ln -s file a/rel
cat a/rel
mv a/rel b/rel
cat b/rel

h "6 chmod on a link changes the target"
echo s > secret && chmod 600 secret && ln -s secret sl
chmod 644 sl
stat -c '%n %A' sl secret

h "7 hard link vs symlink after rm"
echo orig > o && ln o hard && ln -s o soft
stat -c '%n inode=%i links=%h' o hard soft
rm o
cat hard
cat soft

h "8 how GNU ln -sfn replaces a link"
mkdir r1 r2 && ln -s r1 current
strace -e trace=symlinkat,unlinkat,renameat,renameat2 ln -sfn r2 current 2>&1 | grep -v '^+++'
readlink current
ln --version | head -1

h "9 mv --exchange swaps two paths in one step"
echo A > x && echo B > y
mv --exchange x y
echo "x=$(cat x) y=$(cat y)"

h "10 the shell's .. and the kernel's .. differ"
mkdir -p real/deep && ln -s real/deep shortcut
cd shortcut && echo "pwd=$(pwd)  pwd -P=$(pwd -P)" && cd ..
(cd shortcut/.. && echo "cd shortcut/.. lands in: $(pwd -P)")
echo "realpath shortcut/.. = $(realpath shortcut/..)"

h "11 /proc magic links"
cp /bin/sleep /tmp/mysleep
/tmp/mysleep 30 &
P=$!
sleep 0.3
rm /tmp/mysleep
readlink "/proc/$P/exe"
cp "/proc/$P/exe" /tmp/recovered && cmp /bin/sleep /tmp/recovered && echo "deleted binary recovered byte for byte"
echo gone > f && exec 3<f && rm f
readlink /proc/$$/fd/3
cat /proc/$$/fd/3

h "12 protected_symlinks"
cat /proc/sys/fs/protected_symlinks

h "13 python resolves its own symlink"
ln -s /usr/bin/python3 /tmp/py
/tmp/py -c 'import sys,os; print("executable", sys.executable, "| real", os.path.realpath(sys.executable), "| prefix", sys.prefix)'
python3 -m venv --without-pip /tmp/venv
cd /tmp/venv/bin && for f in python python3 python3.*; do echo "venv/bin/$f -> $(readlink "$f")"; done && cd /tmp/lab
head -1 /tmp/venv/pyvenv.cfg

h "14 alternatives are two hops"
echo "/usr/bin/awk -> $(readlink /usr/bin/awk) -> $(readlink /etc/alternatives/awk)"
