Package: debianutils
Version: 4.11.2
Severity: wishlist
Tags: patch
User: [email protected]
Usertags: rebootstrap
We've had a discussion on the expectations on /etc/shells on
[email protected] recently. You can find the entry point at:
https://lists.debian.org/[email protected]
The present management of /etc/shells using maintainer scripts running
add-shell and remove-shell is suboptimal for a number of reasons.
* Running maintainer scripts during initial bootstrap is known to be
difficult due to ordering and other issues. Our present approach is
to turn it declarative as much as possible.
* The add-shell and remove-shell tools do not mesh well with DPKG_ROOT.
* The contents of /etc/shells depend on the order of running maintainer
scripts.
* The contents of /etc/shells depend on when a /usr-merge is being
performed.
As a result of the discussion, we propose that client packages can
replace their add-shell invocations with adding a file
/usr/share/debianutils/shells.d/$PKG containing the shells to be added.
debianutils will be triggered and update /etc/shells when a package
containing this file is installed or removed. As such, the only package
that needs to carry a maintainer script will be debianutils. The
proposed mechanism will not break existing uses, so there is no flag
day.
Please refer to the mailing list discussion for details.
I would like to thank the following people for their notable
contributions:
* Étienne Mollier
* Felix C. Stegerman
* Guillem Jover
* Johannes Schauer Marin Rodrigues
* Mattia Rizzolo
* Sam Hartman
As a result, I am attaching a patch for the proposed solution. While it
does not fully fix all mentioned issues (in particular, reproducibility
and /usr-merge may need improvement), it provides a significant
improvement and makes fixing those other issues simpler.
Given the lack of participation of debianutils maintainers in the
discussion, I intend to NMU this patch post bullseye barring a
maintainer reply. Once debianutils in unstable support triggers, I
intend to file patches converting shell providers to use the new
mechanism.
Helmut
diff --minimal -Nru debianutils-4.11.2/Makefile.am
debianutils-4.11.2+nmu1/Makefile.am
--- debianutils-4.11.2/Makefile.am 2011-05-18 21:01:01.000000000 +0200
+++ debianutils-4.11.2+nmu1/Makefile.am 2021-06-28 20:02:03.000000000 +0200
@@ -9,9 +9,9 @@
bin_SCRIPTS = which savelog
-sbin_SCRIPTS = installkernel add-shell remove-shell
+sbin_SCRIPTS = installkernel add-shell remove-shell update-shells
man_MANS = run-parts.8 \
installkernel.8 savelog.8 \
tempfile.1 which.1 add-shell.8 \
- remove-shell.8 ischroot.1
+ remove-shell.8 update-shells.8 ischroot.1
diff --minimal -Nru debianutils-4.11.2/debian/changelog
debianutils-4.11.2+nmu1/debian/changelog
--- debianutils-4.11.2/debian/changelog 2020-09-27 19:25:47.000000000 +0200
+++ debianutils-4.11.2+nmu1/debian/changelog 2021-06-28 20:14:17.000000000
+0200
@@ -1,3 +1,10 @@
+debianutils (4.11.2+nmu1) UNRELEASED; urgency=medium
+
+ * Non-maintainer upload.
+ * Add tool update-shells. closes: #-1.
+
+ -- Helmut Grohne <[email protected]> Mon, 28 Jun 2021 20:14:17 +0200
+
debianutils (4.11.2) unstable; urgency=medium
* ischroot: send usage and version to stdout. closes: #961872.
diff --minimal -Nru debianutils-4.11.2/debian/postinst
debianutils-4.11.2+nmu1/debian/postinst
--- debianutils-4.11.2/debian/postinst 2020-05-25 14:51:26.000000000 +0200
+++ debianutils-4.11.2+nmu1/debian/postinst 2021-06-28 20:14:17.000000000
+0200
@@ -6,7 +6,8 @@
fi
case "$1" in
- configure)
+ configure|triggered)
+ update-shells --root "${DPKG_ROOT:-}"
;;
abort-upgrade|abort-remove|abort-deconfigure)
diff --minimal -Nru debianutils-4.11.2/debian/postrm
debianutils-4.11.2+nmu1/debian/postrm
--- debianutils-4.11.2/debian/postrm 2020-05-25 14:51:32.000000000 +0200
+++ debianutils-4.11.2+nmu1/debian/postrm 2021-06-28 20:14:17.000000000
+0200
@@ -3,9 +3,12 @@
set -e
case "$1" in
+ purge)
+ rm -f "${DPKG_ROOT:-}/etc/shells" "${DPKG_ROOT:-}/var/lib/shells.state"
+ ;;
remove|disappear)
;;
- upgrade|failed-upgrade|purge|abort-install|abort-upgrade)
+ upgrade|failed-upgrade|abort-install|abort-upgrade)
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
diff --minimal -Nru debianutils-4.11.2/debian/rules
debianutils-4.11.2+nmu1/debian/rules
--- debianutils-4.11.2/debian/rules 2020-05-25 14:54:02.000000000 +0200
+++ debianutils-4.11.2+nmu1/debian/rules 2021-06-28 20:14:17.000000000
+0200
@@ -56,6 +56,7 @@
debian/tmp/sbin \
debian/tmp/usr/bin \
debian/tmp/usr/sbin \
+ debian/tmp/usr/share/debianutils/shells.d \
debian/tmp/usr/share/man/man1 \
debian/tmp/usr/share/man/man8 \
debian/tmp/usr/share/doc/$(package) \
@@ -93,6 +94,7 @@
$(INSTALL_FILE) debian/copyright debian/tmp/usr/share/doc/$(package)
$(INSTALL_SCRIPT) debian/postinst debian/tmp/DEBIAN/
$(INSTALL_SCRIPT) debian/postrm debian/tmp/DEBIAN/
+ $(INSTALL_FILE) debian/triggers debian/tmp/DEBIAN/
cd debian/tmp && find * -type f ! -regex '^DEBIAN/.*' -print0 |
LC_ALL=C sort -z | xargs -r0 md5sum > DEBIAN/md5sums
diff --minimal -Nru debianutils-4.11.2/debian/triggers
debianutils-4.11.2+nmu1/debian/triggers
--- debianutils-4.11.2/debian/triggers 1970-01-01 01:00:00.000000000 +0100
+++ debianutils-4.11.2+nmu1/debian/triggers 2021-06-28 20:14:17.000000000
+0200
@@ -0,0 +1 @@
+interest-noawait /usr/share/debianutils/shells.d
diff --minimal -Nru debianutils-4.11.2/update-shells
debianutils-4.11.2+nmu1/update-shells
--- debianutils-4.11.2/update-shells 1970-01-01 01:00:00.000000000 +0100
+++ debianutils-4.11.2+nmu1/update-shells 2021-06-28 20:14:17.000000000
+0200
@@ -0,0 +1,144 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright 2021 Helmut Grohne <[email protected]>
+
+# 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"
+ETC_FILE="$ROOT/etc/shells"
+NEW_ETC_FILE="$ETC_FILE.tmp"
+NEW_STATE_FILE="$STATE_FILE.tmp"
+
+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 < "$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"
+ chown --reference="$STATE_FILE" "$NEW_STATE_FILE"
+ else
+ chmod 0644 "$NEW_STATE_FILE"
+ fi
+ chmod --reference="$ETC_FILE" "$NEW_ETC_FILE"
+ chown --reference="$ETC_FILE" "$NEW_ETC_FILE"
+ sync --data "$NEW_ETC_FILE" "$NEW_STATE_FILE"
+ mv "$NEW_ETC_FILE" "$ETC_FILE"
+ sync "$ETC_FILE"
+ mv "$NEW_STATE_FILE" "$STATE_FILE"
+ sync "$STATE_FILE"
+ trap "" EXIT
+fi
diff --minimal -Nru debianutils-4.11.2/update-shells.8
debianutils-4.11.2+nmu1/update-shells.8
--- debianutils-4.11.2/update-shells.8 1970-01-01 01:00:00.000000000 +0100
+++ debianutils-4.11.2+nmu1/update-shells.8 2021-06-28 18:17:57.000000000
+0200
@@ -0,0 +1,31 @@
+.TH UPDATE-SHELLS 8 "28 Jun 2021"
+.SH NAME
+update-shells \- update the list of valid login shells
+.SH SYNOPSIS
+.B update-shells
+.RI [ options ]
+.SH DESCRIPTION
+.B update-shells
+locates the shells provided by packages from
+.I /usr/share/debianutils/shells.d
+and updates
+.I /etc/shells
+with newly added or removed shells.
+To track changes made by the administrator, it consults a state file in
+.I /var/lib/shells.state .
+.SH OPTIONS
+.TP
+.B \-\-no\-act
+Do not actually perform the changes to
+.I /etc/shells .
+.TP
+.B \-\-root
+.I ROOT
+
+Operate on a chroot at
+.I ROOT .
+.TP
+.B \-\-verbose
+Print the shells that are being added or removed.
+.SH SEE ALSO
+.BR shells (5)