Splux Linux

source-based Linux using SPS

SPS · log · files · refs · tags · releases · people · languages · GitHub · commit

4d306f4


mkiso: keep live /bin/sh as busybox and copy ELF closures

Date
Commit4d306f4ec53243d4a83e31d2a35ef78b31df7411
GitHubmirror

Parents

Stat

 bin/mkiso              | 202 +++++++++++++++++++++++++++++++++++++++++++++++--
 lib/mkiso/live-bins    |   6 ++
 lib/mkiso/live-plasma  |   2 +-
 lib/mkiso/live-rc      |   2 +-
 lib/mkiso/plasma-bins  |   6 ++
 lib/mkiso/plasma-trees |   3 +
 lib/mkiso/seed-bins    |   4 +-
 man/mkiso.1            |   7 ++
 tests/test_mkiso.sh    |  37 +++++++++
 9 files changed, 258 insertions(+), 11 deletions(-)

Diff

diff --git a/bin/mkiso b/bin/mkiso
index 5c41d50..4ce3d60 100755
--- a/bin/mkiso
+++ b/bin/mkiso
@@ -222,31 +222,45 @@ mkdir -p "$mkiso_live" ||
 
 # --- copy helpers -------------------------------------------------------
 
+# Copy a host path into the live root. Directories are merged (src/. into
+# dest/) so a second copy of /usr/lib64/qt6 cannot nest as qt6/qt6.
+# Symlinks are copied as links, then the chain is followed so sonames like
+# libtinfo.so.6 are not left dangling.
 mkiso_copy_path()
 {
 	mkiso_src=$1
+	mkiso_depth=${2:-0}
+	[ "$mkiso_depth" -lt 16 ] || return 0
 	[ -e "$mkiso_src" ] || [ -L "$mkiso_src" ] || return 0
 	mkiso_dest=$mkiso_live$mkiso_src
-	mkdir -p "$(dirname "$mkiso_dest")" || return 1
+	mkdir -p "$(dirname "$mkiso_dest")" || return 0
 	if [ -d "$mkiso_src" ] && [ ! -L "$mkiso_src" ]; then
 		mkdir -p "$mkiso_dest"
-		cp -a "$mkiso_src" "$mkiso_dest" 2>/dev/null ||
-			cp -R "$mkiso_src" "$mkiso_dest" 2>/dev/null || :
+		cp -a "$mkiso_src"/. "$mkiso_dest"/ 2>/dev/null ||
+			cp -R "$mkiso_src"/. "$mkiso_dest"/ 2>/dev/null || :
 	else
 		cp -a "$mkiso_src" "$mkiso_dest" 2>/dev/null ||
 			cp "$mkiso_src" "$mkiso_dest" 2>/dev/null || :
+		if [ -L "$mkiso_src" ]; then
+			mkiso_link=$(readlink "$mkiso_src") || return 0
+			case $mkiso_link in
+				/*) mkiso_next=$mkiso_link ;;
+				*) mkiso_next=$(dirname "$mkiso_src")/$mkiso_link ;;
+			esac
+			mkiso_copy_path "$mkiso_next" $((mkiso_depth + 1))
+		fi
 	fi
 }
 
 mkiso_copy_libs()
 {
 	mkiso_bin=$1
-	[ -f "$mkiso_bin" ] || return 0
+	[ -f "$mkiso_bin" ] || [ -L "$mkiso_bin" ] || return 0
 	command -v ldd >/dev/null 2>&1 || return 0
 	ldd "$mkiso_bin" 2>/dev/null | while IFS= read -r mkiso_line; do
 		mkiso_lib=
 		case $mkiso_line in
-			*linux-vdso*) continue ;;
+			*linux-vdso*|*'not a dynamic'*) continue ;;
 		esac
 		# "libx.so => /lib64/libx.so (0x...)"
 		mkiso_lib=${mkiso_line#*=> }
@@ -269,6 +283,160 @@ mkiso_copy_libs()
 	done
 }
 
+# glibc, libtinfo, and the loader. Seed and Plasma both need these; a
+# missing libtinfo.so.6 (the real file behind the soname symlink) is what
+# made getty respawn `/bin/sh` forever on the Plasma ISO.
+mkiso_copy_runtime_libs()
+{
+	for mkiso_lib in \
+		/lib64/ld-linux-x86-64.so.2 \
+		/lib/ld-linux-x86-64.so.2 \
+		/lib64/libc.so.6 \
+		/lib64/libm.so.6 \
+		/lib64/libdl.so.2 \
+		/lib64/libpthread.so.0 \
+		/lib64/librt.so.1 \
+		/lib64/libutil.so.1 \
+		/lib64/libresolv.so.2 \
+		/lib64/libnss_files.so.2 \
+		/lib64/libnss_dns.so.2 \
+		/lib64/libtinfo.so.6 \
+		/lib64/libncursesw.so.6 \
+		/lib64/libncurses.so.6 \
+		/lib64/libreadline.so.8 \
+		/lib64/libgcc_s.so.1 \
+		/usr/lib64/libgcc_s.so.1 \
+		/usr/lib64/libstdc++.so.6
+	do
+		[ -e "$mkiso_lib" ] || [ -L "$mkiso_lib" ] || continue
+		mkiso_copy_path "$mkiso_lib"
+	done
+	for mkiso_lib in /lib64/libtinfo.so.* /usr/lib64/libtinfo.so.* \
+		/lib64/libncursesw.so.* /lib64/libc.so.* \
+		/lib64/ld-linux-x86-64.so.*
+	do
+		[ -e "$mkiso_lib" ] || [ -L "$mkiso_lib" ] || continue
+		mkiso_copy_path "$mkiso_lib"
+	done
+	mkiso_copy_path /etc/ld.so.conf
+	mkiso_copy_path /etc/ld.so.conf.d
+	if [ ! -f "$mkiso_live/etc/ld.so.conf" ]; then
+		mkdir -p "$mkiso_live/etc"
+		printf '%s\n' '/lib64' '/usr/lib64' '/lib' '/usr/lib' \
+			>"$mkiso_live/etc/ld.so.conf"
+	fi
+	mkiso_copy_path /usr/share/terminfo
+	mkiso_copy_path /etc/terminfo
+	mkiso_copy_path /lib/terminfo
+}
+
+# ldd every ELF already in the live root so dlopened plugins (Qt, KWin)
+# pull their DT_NEEDED closure too.
+mkiso_close_libs()
+{
+	command -v ldd >/dev/null 2>&1 || return 0
+	mkiso_find=
+	for mkiso_d in bin sbin usr/bin usr/sbin lib64 usr/lib64 lib usr/lib
+	do
+		[ -d "$mkiso_live/$mkiso_d" ] || continue
+		mkiso_find="$mkiso_find $mkiso_live/$mkiso_d"
+	done
+	[ -n "$mkiso_find" ] || return 0
+	# shellcheck disable=SC2086
+	find $mkiso_find \
+		\( -path "$mkiso_live/usr/lib64/gcc" -o \
+		   -path "$mkiso_live/usr/libexec/gcc" -o \
+		   -path "$mkiso_live/usr/lib64/python3.12" -o \
+		   -path "$mkiso_live/usr/lib64/rustlib" -o \
+		   -path "$mkiso_live/usr/lib64/go1.27.0" \) -prune -o \
+		-type f -print 2>/dev/null |
+	while IFS= read -r mkiso_elf || [ -n "$mkiso_elf" ]; do
+		[ -n "$mkiso_elf" ] || continue
+		mkiso_copy_libs "$mkiso_elf"
+	done
+}
+
+mkiso_ldconfig_live()
+{
+	command -v ldconfig >/dev/null 2>&1 || return 0
+	ldconfig -r "$mkiso_live" 2>/dev/null || :
+}
+
+# Getty and live-rc exec /bin/sh. That must be static busybox, never bash.
+mkiso_force_busybox_sh()
+{
+	[ -x "$mkiso_live/bin/busybox" ] || return 0
+	ln -sf busybox "$mkiso_live/bin/sh"
+}
+
+mkiso_stamp_os_release()
+{
+	mkdir -p "$mkiso_live/etc"
+	printf '%s\n' \
+		'NAME="Splux"' \
+		'PRETTY_NAME="Splux Linux"' \
+		'ID=splux' \
+		'VERSION_ID="1.0"' \
+		'LOGO=splux' \
+		'HOME_URL="https://github.com/RobertFlexx/SPS"' \
+		>"$mkiso_live/etc/os-release"
+}
+
+# Fail the ISO build rather than ship a disc that respawns getty with
+# "error while loading shared libraries: libtinfo.so.6".
+mkiso_verify_live()
+{
+	[ -x "$mkiso_live/bin/busybox" ] || return 0
+	if [ -L "$mkiso_live/bin/sh" ]; then
+		mkiso_sh=$(readlink "$mkiso_live/bin/sh")
+		case $mkiso_sh in
+			busybox|./busybox|/bin/busybox) ;;
+			*)
+				sps_die "$SPS_EX_BUILD" \
+					"live /bin/sh must be busybox (got $mkiso_sh)"
+				;;
+		esac
+	else
+		sps_die "$SPS_EX_BUILD" "live /bin/sh is not a busybox symlink"
+	fi
+
+	mkiso_interp=
+	for mkiso_p in "$mkiso_live/lib64/ld-linux-x86-64.so.2" \
+		"$mkiso_live/lib/ld-linux-x86-64.so.2" \
+		"$mkiso_live/usr/lib64/ld-linux-x86-64.so.2"
+	do
+		if [ -e "$mkiso_p" ]; then
+			mkiso_interp=$mkiso_p
+			break
+		fi
+	done
+	mkiso_lp=$mkiso_live/lib64:$mkiso_live/usr/lib64:$mkiso_live/lib:$mkiso_live/usr/lib
+
+	if [ -x "$mkiso_live/bin/bash" ] && [ ! -L "$mkiso_live/bin/bash" ]; then
+		if [ ! -e "$mkiso_live/lib64/libtinfo.so.6" ] &&
+			[ ! -e "$mkiso_live/usr/lib64/libtinfo.so.6" ]; then
+			sps_die "$SPS_EX_BUILD" \
+				"live bash is present but libtinfo.so.6 is not"
+		fi
+		if [ -n "$mkiso_interp" ]; then
+			if ! "$mkiso_interp" --library-path "$mkiso_lp" \
+				"$mkiso_live/bin/bash" -c ':' >/dev/null 2>&1; then
+				sps_die "$SPS_EX_BUILD" \
+					"live /bin/bash cannot execute (missing libraries)"
+			fi
+		fi
+	fi
+
+	# chroot needs privileges; skip it in unprivileged tests.
+	if [ "$(id -u 2>/dev/null)" = 0 ]; then
+		if ! "$mkiso_live/bin/busybox" chroot "$mkiso_live" /bin/sh -c ':' \
+			>/dev/null 2>&1; then
+			sps_die "$SPS_EX_BUILD" \
+				"live /bin/sh cannot execute inside the image"
+		fi
+	fi
+}
+
 # BusyBox --install -s writes absolute symlinks to the host path. Relative
 # links keep applets working inside the squashfs and initramfs.
 mkiso_busybox_links()
@@ -313,6 +481,7 @@ mkiso_copy_plasma()
 		for mkiso_so in $mkiso_pat; do
 			[ -e "$mkiso_so" ] || continue
 			mkiso_copy_path "$mkiso_so"
+			mkiso_copy_libs "$mkiso_so"
 		done
 	done
 }
@@ -357,9 +526,7 @@ mkiso_install_pid1()
 		cp -a "$mkiso_busybox" "$mkiso_live/bin/busybox"
 		chmod 755 "$mkiso_live/bin/busybox"
 		mkiso_busybox_links "$mkiso_live/bin"
-		if [ -x "$mkiso_live/bin/bash" ]; then
-			ln -sf bash "$mkiso_live/bin/sh"
-		fi
+		mkiso_force_busybox_sh
 		cp -a "$mkiso_live/bin/busybox" "$mkiso_live/sbin/init"
 		chmod 755 "$mkiso_live/sbin/init"
 	fi
@@ -462,6 +629,7 @@ if [ -n "$mkiso_busybox" ] && [ -x "$mkiso_busybox" ]; then
 	cp -a "$mkiso_busybox" "$mkiso_live/bin/busybox"
 	chmod 755 "$mkiso_live/bin/busybox"
 	mkiso_busybox_links "$mkiso_live/bin"
+	mkiso_force_busybox_sh
 	ln -sf ../bin/busybox "$mkiso_live/sbin/busybox" 2>/dev/null || :
 fi
 
@@ -469,6 +637,7 @@ install -m 0644 "$mkiso_libdir/motd" "$mkiso_live/etc/motd"
 install -m 0644 "$mkiso_libdir/issue" "$mkiso_live/etc/issue"
 printf '%s\n' live >"$mkiso_live/etc/sps/live"
 printf '%s\n' splux-live >"$mkiso_live/etc/hostname"
+mkiso_stamp_os_release
 printf '%s\n' 'root:x:0:0:root:/root:/bin/sh' >"$mkiso_live/etc/passwd"
 printf '%s\n' 'root:x:0:' >"$mkiso_live/etc/group"
 # Empty password on the live console only.
@@ -511,6 +680,18 @@ git extra https://github.com/RobertFlexx/sps-extra.git 80
 EOF
 fi
 
+# dialog and glibc/libtinfo on every image, including --no-seed.
+if [ -f "$mkiso_libdir/live-bins" ]; then
+	while IFS= read -r mkiso_bin || [ -n "$mkiso_bin" ]; do
+		case $mkiso_bin in ''|\#*) continue ;; esac
+		[ -e "$mkiso_bin" ] || continue
+		mkiso_copy_path "$mkiso_bin"
+		mkiso_copy_libs "$mkiso_bin"
+	done <"$mkiso_libdir/live-bins"
+fi
+mkiso_copy_runtime_libs
+mkiso_force_busybox_sh
+
 if [ "$mkiso_copy_seed" -eq 1 ] && [ -f "$mkiso_libdir/seed-bins" ]; then
 	printf '%s: copying host seed tools\n' "$sps_prog" >&2
 	while IFS= read -r mkiso_bin || [ -n "$mkiso_bin" ]; do
@@ -572,6 +753,11 @@ fi
 
 # PID 1 last, after seed, so a copied /sbin/init cannot stick around.
 mkiso_install_pid1
+mkiso_copy_runtime_libs
+mkiso_close_libs
+mkiso_force_busybox_sh
+mkiso_ldconfig_live
+mkiso_verify_live
 
 if [ -n "$mkiso_layout" ] && [ -z "$mkiso_output" ]; then
 	printf '%s\n' "$mkiso_live"
diff --git a/lib/mkiso/live-bins b/lib/mkiso/live-bins
new file mode 100644
index 0000000..d1c6d20
--- /dev/null
+++ b/lib/mkiso/live-bins
@@ -0,0 +1,6 @@
+# Copied into every live image, including --no-seed. Busybox covers most
+# applets; these are the host tools setup(1) still needs on a slim disc.
+/usr/bin/dialog
+/sbin/ldconfig
+/usr/sbin/ldconfig
+/usr/bin/ldd
diff --git a/lib/mkiso/live-plasma b/lib/mkiso/live-plasma
index 6595087..999dd62 100644
--- a/lib/mkiso/live-plasma
+++ b/lib/mkiso/live-plasma
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/busybox sh
 # Plasma live session. Respawned by busybox init on tty1. Open Konsole
 # and run setup(1). If Plasma is missing, a shell stays on this tty.
 
diff --git a/lib/mkiso/live-rc b/lib/mkiso/live-rc
index 4515cac..1697202 100644
--- a/lib/mkiso/live-rc
+++ b/lib/mkiso/live-rc
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/busybox sh
 # Sysinit for the live image. Child of busybox init, or a systemd oneshot.
 # Must not replace PID 1 and must not exec a console. The old live TTY did
 # that and the kernel reported "Attempted to kill init" (exit 127).
diff --git a/lib/mkiso/plasma-bins b/lib/mkiso/plasma-bins
index 86aaf59..63586df 100644
--- a/lib/mkiso/plasma-bins
+++ b/lib/mkiso/plasma-bins
@@ -31,3 +31,9 @@
 /usr/bin/pipewire
 /usr/bin/pipewire-pulse
 /usr/bin/wireplumber
+/usr/bin/kpackagetool6
+/usr/bin/qdbus
+/usr/bin/qdbus6
+/usr/libexec/kf6/kconf_update
+/usr/libexec/kf6/kio_http_cache_cleaner
+
diff --git a/lib/mkiso/plasma-trees b/lib/mkiso/plasma-trees
index e745617..2494e0e 100644
--- a/lib/mkiso/plasma-trees
+++ b/lib/mkiso/plasma-trees
@@ -31,6 +31,9 @@
 /usr/lib/dri
 /usr/lib64/gbm
 /usr/lib64/libkwin.so
+/usr/libexec/kf6
+/usr/libexec/kde
+/usr/libexec/polkit-1
 /lib/firmware/nvidia
 /lib/firmware/nouveau
 /usr/lib64/pipewire-0.3
diff --git a/lib/mkiso/seed-bins b/lib/mkiso/seed-bins
index 8517465..57f8231 100644
--- a/lib/mkiso/seed-bins
+++ b/lib/mkiso/seed-bins
@@ -1,4 +1,6 @@
-/bin/sh
+# Do not copy /bin/sh. Host /bin/sh is bash, and getty runs /bin/sh.
+# Live /bin/sh stays a busybox symlink. /bin/bash is copied for users
+# who want it, only after its library closure is on the image.
 /bin/bash
 /bin/busybox
 /usr/bin/dialog
diff --git a/man/mkiso.1 b/man/mkiso.1
index 92b0f99..a9e1ff2 100644
--- a/man/mkiso.1
+++ b/man/mkiso.1
@@ -22,6 +22,13 @@ Flash it to USB, boot, and run
 .B setup .
 .B --session plasma
 copies a host Plasma/Wayland stack; NVIDIA live graphics use nouveau.
+Live
+.B /bin/sh
+is always busybox. Host bash is copied as
+.B /bin/bash
+only with its library closure, including the real
+.B libtinfo
+file behind the soname symlink.
 .B --layout
 writes the live root only, for tests and inspection.
 .PP
diff --git a/tests/test_mkiso.sh b/tests/test_mkiso.sh
index ac20ce3..c22eb3b 100755
--- a/tests/test_mkiso.sh
+++ b/tests/test_mkiso.sh
@@ -69,3 +69,40 @@ if [ -x "$layout/bin/busybox" ] && [ -L "$layout/bin/mkdir" ]; then
 		*) fail "busybox applet mkdir should be a relative link, got $target" ;;
 	esac
 fi
+
+[ -f "$layout/etc/os-release" ] || fail 'os-release missing'
+grep -qx 'ID=splux' "$layout/etc/os-release" || fail 'os-release is not Splux'
+grep -qx 'NAME="Splux"' "$layout/etc/os-release" || fail 'os-release NAME is not Splux'
+
+# Host /bin/sh is bash. Getty runs /bin/sh. A bash symlink without libtinfo
+# is the Plasma ISO boot loop.
+if [ -L "$layout/bin/sh" ]; then
+	sh_target=$(readlink "$layout/bin/sh")
+	case $sh_target in
+		busybox|./busybox|/bin/busybox) ;;
+		*) fail "live /bin/sh must be busybox, got $sh_target" ;;
+	esac
+else
+	fail 'live /bin/sh is not a symlink to busybox'
+fi
+if grep -q 'ln -sf bash .*/bin/sh' "$mkiso"; then
+	fail 'mkiso must not point /bin/sh at bash'
+fi
+if [ -x /usr/bin/dialog ]; then
+	[ -e "$layout/usr/bin/dialog" ] || fail 'dialog missing from slim layout'
+fi
+if [ -e "$layout/lib64/libtinfo.so.6" ] || [ -e "$layout/usr/lib64/libtinfo.so.6" ]
+then
+	for tinfo in "$layout/lib64/libtinfo.so.6" "$layout/usr/lib64/libtinfo.so.6"
+	do
+		[ -e "$tinfo" ] || continue
+		if [ -L "$tinfo" ]; then
+			tinfo_real=$(readlink -f "$tinfo")
+			[ -f "$tinfo_real" ] || fail "libtinfo.so.6 is a dangling symlink"
+			case $tinfo_real in
+				"$layout"/*) ;;
+				*) fail "libtinfo real file is outside the live root" ;;
+			esac
+		fi
+	done
+fi