Splux Linux

source-based Linux using SPS

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

b7beac1


pkdel/pkin: keep shared directories owned by other packages

Date
Commitb7beac119f4793ffa320b5045d1126dadfc9e6e8
GitHubmirror

Parents

Stat

 bin/pkdel              | 19 +++++++++++++
 bin/pkin               | 17 ++++++++++++
 tests/test_packages.sh | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 109 insertions(+)

Diff

diff --git a/bin/pkdel b/bin/pkdel
index 43a02ec..f8b5360 100755
--- a/bin/pkdel
+++ b/bin/pkdel
@@ -267,6 +267,21 @@ while IFS="$sps_tab" read -r sps_entry sps_owner || [ -n "$sps_entry" ]; do
     fi
 done <"$sps_work/manifest.owners"
 
+# Directories are shared resources, not exclusively owned by any one package.
+# A directory may only be removed once no other installed package still claims
+# it in its manifest. Collect every directory referenced by the remaining
+# installed packages so we never delete a directory the system still needs.
+: >"$sps_work/other-dirs"
+for sps_other_record in "$sps_installed"/*; do
+    [ -d "$sps_other_record" ] && [ ! -L "$sps_other_record" ] || continue
+    sps_other_name=${sps_other_record##*/}
+    case $sps_other_name in .|..) continue ;; esac
+    [ "$sps_other_name" = "$sps_name" ] && continue
+    [ -f "$sps_other_record/files" ] && [ ! -L "$sps_other_record/files" ] || continue
+    awk '/\/$/ { print }' "$sps_other_record/files" >>"$sps_work/other-dirs"
+done
+LC_ALL=C sort -u "$sps_work/other-dirs" -o "$sps_work/other-dirs"
+
 # Reverse sort puts children before parents; rmdir leaves shared/non-empty
 # directories alone.
 LC_ALL=C sort -r "$sps_record/files" >"$sps_work/files.reverse"
@@ -274,6 +289,10 @@ while IFS= read -r sps_entry || [ -n "$sps_entry" ]; do
     case $sps_entry in
         */)
             sps_path=${sps_entry%/}
+            if awk -v wanted="$sps_entry" '$0 == wanted { found=1 } END { exit !found }' \
+                "$sps_work/other-dirs"; then
+                continue
+            fi
             sps_safe_parents "$sps_path" || continue
             sps_target_path "$sps_path"
             if [ -d "$sps_target" ] && [ ! -L "$sps_target" ]; then
diff --git a/bin/pkin b/bin/pkin
index 88d84e0..b29e85e 100755
--- a/bin/pkin
+++ b/bin/pkin
@@ -644,11 +644,28 @@ while IFS= read -r sps_path || [ -n "$sps_path" ]; do
     fi
 done <"$sps_work/obsolete"
 if [ -d "$sps_old_record" ]; then
+    # Directories are shared. A directory the new version drops must still be
+    # preserved if any other installed package still claims it.
+    : >"$sps_work/other-dirs"
+    for sps_other_record in "$sps_installed"/*; do
+        [ -d "$sps_other_record" ] && [ ! -L "$sps_other_record" ] || continue
+        sps_other_name=${sps_other_record##*/}
+        case $sps_other_name in .|..) continue ;; esac
+        [ "$sps_other_name" = "$sps_name" ] && continue
+        [ -f "$sps_other_record/files" ] && [ ! -L "$sps_other_record/files" ] || continue
+        awk '/\/$/ { print }' "$sps_other_record/files" >>"$sps_work/other-dirs"
+    done
+    LC_ALL=C sort -u "$sps_work/other-dirs" -o "$sps_work/other-dirs"
+
     LC_ALL=C sort -r "$sps_old_record/files" >"$sps_work/old-files.reverse"
     while IFS= read -r sps_entry || [ -n "$sps_entry" ]; do
         case $sps_entry in
             */)
                 sps_path=${sps_entry%/}
+                if awk -v wanted="$sps_entry" '$0 == wanted { found=1 } END { exit !found }' \
+                    "$sps_work/other-dirs"; then
+                    continue
+                fi
                 if ! awk -v wanted="$sps_path" '
                     { value=$0; sub(/\/$/, "", value); if (value == wanted) found=1 }
                     END { exit !found }
diff --git a/tests/test_packages.sh b/tests/test_packages.sh
index d875d12..763da3b 100755
--- a/tests/test_packages.sh
+++ b/tests/test_packages.sh
@@ -160,4 +160,77 @@ run_sps "$project_dir/bin/pkdel" libfoo >/dev/null
 run_sps "$project_dir/bin/pkcheck" --database | grep -qx 'database: ok' ||
     fail 'empty package database did not validate'
 
+# Removing one package must never delete a directory that a remaining package
+# still claims in its manifest. A fonts-like package shares /usr/share with a
+# filesystem-like base package; only the fonts subdirectories are exclusive.
+shared_root=$tmp/shared-root
+shared_db=$tmp/shared-db
+shared_cache=$tmp/shared-cache
+shared_build=$tmp/shared-build
+mkdir -p "$shared_root" "$shared_cache" "$shared_build" \
+    "$tmp/shared-stage-base/.SPS" "$tmp/shared-stage-base/usr/share" \
+    "$tmp/shared-stage-base/etc" \
+    "$tmp/shared-stage-fonts/.SPS" \
+    "$tmp/shared-stage-fonts/usr/share/fonts/dejavu" \
+    "$tmp/shared-stage-fonts/usr/share/licenses/dejavu-fonts"
+{
+    printf 'format\t1\n'
+    printf 'name\tbasefs\nversion\t1\nrelease\t1\narch\tany\ndescription\tbase hierarchy\n'
+} >"$tmp/shared-stage-base/.SPS/meta"
+printf '%s\n' 'NAME="SPS"' >"$tmp/shared-stage-base/etc/os-release"
+printf '%s\n' 'fontdata' >"$tmp/shared-stage-fonts/usr/share/fonts/dejavu/font.ttf"
+printf '%s\n' 'license' >"$tmp/shared-stage-fonts/usr/share/licenses/dejavu-fonts/LICENSE"
+{
+    printf 'format\t1\n'
+    printf 'name\tsharedfonts\nversion\t1\nrelease\t1\narch\tany\ndescription\tfont payload\n'
+} >"$tmp/shared-stage-fonts/.SPS/meta"
+for shared_stage in "$tmp/shared-stage-base" "$tmp/shared-stage-fonts"; do
+    (
+        CDPATH= cd "$shared_stage" &&
+        find . -type d -print | awk '
+            $0 != "." && $0 !~ /^\.\/\.SPS($|\/)/ { sub(/^\.\//, ""); print $0 "/" }'
+        find . ! -type d -print | awk '
+            $0 !~ /^\.\/\.SPS($|\/)/ { sub(/^\.\//, ""); print }'
+    ) | LC_ALL=C sort >"$shared_stage/.SPS/files"
+    : >"$shared_stage/.SPS/hashes"
+    while IFS= read -r shared_entry || [ -n "$shared_entry" ]; do
+        case $shared_entry in */) continue ;; esac
+        [ -f "$shared_stage/$shared_entry" ] &&
+            printf 'sha256\t%s\t%s\n' \
+                "$(sha256sum "$shared_stage/$shared_entry" | awk '{ print $1 }')" \
+                "$shared_entry" >>"$shared_stage/.SPS/hashes"
+    done <"$shared_stage/.SPS/files"
+done
+tar -C "$tmp/shared-stage-base" -cf "$tmp/shared-base.pkg.tar" .
+tar -C "$tmp/shared-stage-fonts" -cf "$tmp/shared-fonts.pkg.tar" .
+SPS_ROOT=$shared_root SPS_DB=$shared_db SPS_CACHE=$shared_cache \
+SPS_BUILD=$shared_build SPS_CONFIG=/dev/null SPS_REPOS_CONFIG=/dev/null \
+SPS_LIBDIR=$project_dir/lib \
+    "$project_dir/bin/pkin" --dependency "$tmp/shared-base.pkg.tar" >/dev/null ||
+    fail 'shared base package failed to install'
+SPS_ROOT=$shared_root SPS_DB=$shared_db SPS_CACHE=$shared_cache \
+SPS_BUILD=$shared_build SPS_CONFIG=/dev/null SPS_REPOS_CONFIG=/dev/null \
+SPS_LIBDIR=$project_dir/lib \
+    "$project_dir/bin/pkin" "$tmp/shared-fonts.pkg.tar" >/dev/null ||
+    fail 'shared fonts package failed to install'
+[ -d "$shared_root/usr/share/fonts/dejavu" ] ||
+    fail 'shared fonts payload was not installed'
+SPS_ROOT=$shared_root SPS_DB=$shared_db SPS_CACHE=$shared_cache \
+SPS_BUILD=$shared_build SPS_CONFIG=/dev/null SPS_REPOS_CONFIG=/dev/null \
+SPS_LIBDIR=$project_dir/lib \
+    "$project_dir/bin/pkdel" sharedfonts >/dev/null ||
+    fail 'shared fonts package failed to remove'
+[ ! -e "$shared_root/usr/share/fonts" ] &&
+[ ! -e "$shared_root/usr/share/licenses" ] ||
+    fail 'font-only directories were not pruned on removal'
+[ -d "$shared_root/usr/share" ] ||
+    fail 'removal deleted a shared directory still claimed by another package'
+[ -d "$shared_root/usr" ] ||
+    fail 'removal deleted a base directory still claimed by another package'
+SPS_ROOT=$shared_root SPS_DB=$shared_db SPS_CACHE=$shared_cache \
+SPS_BUILD=$shared_build SPS_CONFIG=/dev/null SPS_REPOS_CONFIG=/dev/null \
+SPS_LIBDIR=$project_dir/lib \
+    "$project_dir/bin/pkcheck" --database | grep -qx 'database: ok' ||
+    fail 'database did not validate after shared-directory removal'
+
 printf '%s\n' 'test_packages: ok'