SPS · log · files · refs · tags · releases · people · languages · GitHub · commit
4d306f4
mkiso: keep live /bin/sh as busybox and copy ELF closures
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