SPS · log · files · refs · tags · releases · people · languages · GitHub · commit
b7beac1
pkdel/pkin: keep shared directories owned by other packages
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'