Sindbad~EG File Manager
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright 2021 Helmut Grohne <helmut@subdivi.de>
# A "hashset" is a shell variable containing a sequence of elements separated
# and surrounded by hash (#) characters. None of the elements may contain a
# hash character. The character is thus chosen, because it initiates a comment
# in /etc/shells. All variables ending in _SHELLS in this file are hashsets.
set -e
# Check whether hashset $1 contains element $2.
hashset_contains() {
case "$1" in
*"#$2#"*) return 0 ;;
*) return 1 ;;
esac
}
log() {
if [ "$VERBOSE" = 1 ]; then
echo "$*"
fi
}
ROOT=
VERBOSE=0
NOACT=0
while [ $# -gt 0 ]; do
case "$1" in
--help)
cat <<EOF
usage: $0 [options]
--no-act Do not move the actual update into place
--verbose Be more verbose
--root DIR Operate on the given chroot, defaults to /
EOF
exit 0
;;
--no-act)
NOACT=1
;;
--root)
shift
if [ "$#" -lt 1 ]; then
echo "missing argument to --root" 1>&2
exit 1
fi
ROOT=$1
;;
--verbose)
VERBOSE=1
;;
*)
echo "unrecognized option $1" 1>&2
exit 1
;;
esac
shift
done
PKG_DIR="$ROOT/usr/share/debianutils/shells.d"
STATE_FILE="$ROOT/var/lib/shells.state"
TARGET_ETC_FILE="$ROOT/etc/shells"
SOURCE_ETC_FILE="$TARGET_ETC_FILE"
NEW_ETC_FILE="$TARGET_ETC_FILE.tmp"
NEW_STATE_FILE="$STATE_FILE.tmp"
if ! test -e "$SOURCE_ETC_FILE"; then
SOURCE_ETC_FILE="$ROOT/usr/share/debianutils/shells"
fi
PKG_SHELLS='#'
LC_COLLATE=C.UTF-8 # glob in reproducible order
for f in "$PKG_DIR/"*; do
[ "$f" = "$PKG_DIR/*" ] && break
while IFS='#' read -r line _; do
[ -n "$line" ] || continue
PKG_SHELLS="$PKG_SHELLS$line#"
realshell=$(dpkg-realpath --root "$ROOT" "$line")
if [ "$line" != "$realshell" ]; then
PKG_SHELLS="$PKG_SHELLS$realshell#"
fi
done < "$f"
done
STATE_SHELLS='#'
if [ -e "$STATE_FILE" ] ; then
while IFS='#' read -r line _; do
[ -n "$line" ] && STATE_SHELLS="$STATE_SHELLS$line#"
done < "$STATE_FILE"
fi
cleanup() {
rm -f "$NEW_ETC_FILE" "$NEW_STATE_FILE"
}
trap cleanup EXIT
: > "$NEW_ETC_FILE"
ETC_SHELLS='#'
while IFS= read -r line; do
shell=${line%%#*}
# copy all comment lines, packaged shells and local additions
if [ -z "$shell" ] ||
hashset_contains "$PKG_SHELLS" "$shell" ||
! hashset_contains "$STATE_SHELLS" "$shell"; then
echo "$line" >> "$NEW_ETC_FILE"
ETC_SHELLS="$ETC_SHELLS$shell#"
else
log "removing shell $shell"
fi
done < "$SOURCE_ETC_FILE"
: > "$NEW_STATE_FILE"
saved_IFS=$IFS
IFS='#'
set -f
# shellcheck disable=SC2086 # word splitting intended, globbing disabled
set -- ${PKG_SHELLS###}
set +f
IFS=$saved_IFS
for shell; do
echo "$shell" >> "$NEW_STATE_FILE"
# add shells that are neither already present nor locally removed
if ! hashset_contains "$ETC_SHELLS" "$shell" &&
! hashset_contains "$STATE_SHELLS" "$shell"; then
echo "$shell" >> "$NEW_ETC_FILE"
log "adding shell $shell"
fi
done
if [ "$NOACT" = 0 ]; then
if [ -e "$STATE_FILE" ]; then
chmod --reference="${STATE_FILE}" "${NEW_STATE_FILE}" || chmod $(stat -c %a "${STATE_FILE}") "${NEW_STATE_FILE}"
chown --reference="${STATE_FILE}" "${NEW_STATE_FILE}" || chown $(stat -c %U "${STATE_FILE}") "${NEW_STATE_FILE}"
else
chmod 0644 "$NEW_STATE_FILE"
fi
chmod --reference="${SOURCE_ETC_FILE}" "${NEW_ETC_FILE}" || chmod $(stat -c %a "${SOURCE_ETC_FILE}") "${NEW_ETC_FILE}"
chown --reference="${SOURCE_ETC_FILE}" "${NEW_ETC_FILE}" || chown $(stat -c %U "${SOURCE_ETC_FILE}") "${NEW_ETC_FILE}"
sync -d "$NEW_ETC_FILE" "$NEW_STATE_FILE"
mv -Z "${NEW_ETC_FILE}" "${TARGET_ETC_FILE}" || mv "${NEW_ETC_FILE}" "${TARGET_ETC_FILE}"
sync "$TARGET_ETC_FILE"
sync "$(dirname "$TARGET_ETC_FILE")"
mv "$NEW_STATE_FILE" "$STATE_FILE"
sync "$STATE_FILE"
sync "$(dirname "$STATE_FILE")"
trap "" EXIT
fi
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists