Package: debianutils Version: 4.11.2 Severity: wishlist Tags: patch User: helm...@debian.org Usertags: rebootstrap
We've had a discussion on the expectations on /etc/shells on d-devel@l.d.o recently. You can find the entry point at: https://lists.debian.org/ymjtirkqbjdjy...@alf.mars 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 <hel...@subdivi.de> 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 <hel...@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" +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)