Hi Helmut,

Helmut Grohne wrote:

> At present, /bin/sh is always diverted. The diversion takes one of three
> forms:
>
>  A. diverted by package dash, pointing to dash
>  B. diverted by package bash (but performed by dash.postinst), pointing
>     to bash
>  C. local diversion
>
> The local diversion gives freedom to administrators to manually point
> their /bin/sh at bash and seems to be still in use. dash.postinst takes
> care not to mess with such diversions and that should continue to work.

Sure, that makes sense (re local diversions).

> Unless there is a local diversion, dash.postinst sets up either A or B
> depending on a debconf question defaulting to A. Since /bin/sh normally
> points to dash when not diverted, the diversion A seems unnecessary. B
> will have to continue to exist for as long as dash supports changing
> /bin/sh via debconf.

My ideal would be to go even further and not include /bin/sh in any
shell's package (in other words, to treat the symlink something like a
configuration file).  That way, one could bootstrap with any choice of
shell instead of dash needing to be essential.

We're already closer to that ideal than we used to be, since bash
doesn't ship /bin/sh nowadays; only dash does.

What would be required to make that work?

 1. On initial install (bootstrap), we need to be able to run preinst
    for essential packages.  Most preinsts are shell scripts with
    /bin/sh shebang, so this means that one of the following would
    have to happen:

     1a. run preinst for dash before any other package; it could use
         e.g. a #!/usr/bin/perl script to install the /bin/sh symlink

     1b. as custom logic in the bootstrap utility, write the /bin/sh
         symlink

    1b'. introduce a new maintainer script that is specifically for
         bootstrapping.  (I would love this; it's probably worth a
         separate DEP, but it's overkill for this application alone.)

     1c. include the /bin/sh symlink in a separate "sh-is-dash"
         package

Of these, 1b seems simple enough so it's surmountable.

 2. On upgrade of dash in existing systems, we need to avoid removing
    the /bin/sh symlink:

    - if another package is diverting /bin/sh, this happens
      automatically (good)

    - if dash is diverting /bin/sh, preinst can handle this by passing
      ownership of the diversion to another package

    - after the patch proposed below, there is a third case: if no
      package is diverting /bin/sh, dash's preinst can still handle
      this by creating a diversion on behalf of another package

So the patch below doesn't lose flexibility in that dimension (good).

 3. After an upgrade, we want to end up with a clean state with no
    diversions.  We could handle that by removing the diversion in
    dash's postinst.

Upshot: the proposal below doesn't bring us closer to that ideal, but
it doesn't bring us further away either.  And I like the idea of
making the default configuration simpler.

[...]
> --- dash-0.5.11+git20210120+802ebd4/debian/dash.postinst      2021-03-04 
> 10:22:32.000000000 +0100
> +++ dash-0.5.11+git20210120+802ebd4/debian/dash.postinst      2021-06-03 
> 20:19:40.000000000 +0200
> @@ -25,7 +25,7 @@
>       diverter=$(dpkg-divert --listpackage $dfile)
>       truename=$(dpkg-divert --truename $dfile)
>  
> -     if [ "$diverter" = dash ]; then
> +     if [ -z "$diverter" ]; then
>               # good.
>               return
>       fi
> @@ -35,7 +35,7 @@
>               return
>       fi
>  
> -     if [ -n "$diverter" ] && [ "$diverter" != bash ]; then
> +     if [ "$diverter" != bash ]; then
>               # Let dpkg-divert error out; we are not taking
>               # over the diversion, unless we added it

nit: this should say "removing" instead of "taking over".

>               # ourselves on behalf of bash.
>               dpkg-divert --package dash --no-rename --remove $dfile
>               echo "This should never be reached"
>               exit 1

Isn't this reachable now, in the "$diverter" = dash case?  Or is the idea that
the drop_obsolete_diversion case is meant to have taken care of that?  I think
treating this as reachable would make the code easier to reason about (and then
we can simplify further after this code has been live in one stable release).

> @@ -45,7 +45,6 @@
>       fi
>  
>       dpkg-divert --package bash --no-rename --remove $dfile
> -     dpkg-divert --package dash --no-rename --divert $distrib --add $dfile

dash is the only package that ships /bin/sh, so this has no effect on what
happens during unpacking.  Good.

[...]
> @@ -58,18 +57,20 @@
>       diverter=$(dpkg-divert --listpackage $dfile)
>       truename=$(dpkg-divert --truename $dfile)
>  
> -     if [ "$diverter" != dash ]; then
> +     if [ "$diverter" = dash ]; then
> +             # Should not happen. Handle anyway.
> +             dpkg-divert --package dash --no-rename --remove "$dfile"
> +             if [ -n "$truename" ]; then
> +                     rm -f "$truename"
> +             fi
> +     elif [ -n "$diverter" ]; then
>               # good.
>               return
>       fi
>  
>       # Donate the diversion and sh symlink to the bash package.

The previous control flow was

        if <already in intended state>; then
                # good.
                return
        fi

        get into intended state

The new control flow is

        if <case 1>; then
                partly handle it
        elif <already in intended state>; then
                return
        fi

        handle the rest

I find the new control flow harder to follow: it takes longer to get
the "what do we do if we're already in a good state?" case out of the
way, and it splits up the other cases.

Could this say something like the following instead?

        if [ -n "$diverter" ] && [ "$diverter" != dash ]; then
                # good.
                return
        fi

        # Donate the diversion and sh symlink to the bash package.
        ltarget=$(echo $ltarget | sed s/dash/bash/)
        if [ -n "$diverter" ]; then
                dpkg-divert --package dash --no-rename --remove $dfile
                if [ -n "$truename" ]; then
                        rm -f "$truename"
                fi
        fi
        dpkg-divert --package bash --no-rename --divert $distrib --add $dfile
        replace_with_link $dfile $ltarget $distrib

Or if we want to make this briefer, the first test can be written as

        if [ "${diverter:-dash}" != dash ]; then

[...]
> @@ -104,6 +104,20 @@
>       fi
>  }
>  
> +drop_obsolete_diversion() {
> +     dfile=$1 ltarget=$2 distrib=${3:-$dfile.distrib}
> +     diverter=$(dpkg-divert --listpackage "$dfile")
> +     truename=$(dpkg-divert --truename "$dfile")
> +     actualtarget=$(readlink "$dfile")
> +
> +     if [ "$diverter" != dash ] || [ "$truename" != "$distrib" ] || [ 
> "$actualtarget" != "$ltarget" ]; then
> +             # Not our diversion or a non-trivial one.
> +             return
> +     fi
> +     dpkg-divert --package dash --no-rename --remove "$dfile"
> +     rm -f "$truename"
> +}
> +
>  add_shell() {
>       if ! type add-shell > /dev/null 2>&1; then
>               return
> @@ -124,6 +138,10 @@
>               /usr/share/man/man1/sh.distrib.1.gz \
>               /usr/share/man/man1/ash.1.gz
>       add_shell
> +elif [ "$1" = configure ] && dpkg --compare-versions "$2" lt 
> 0.5.11+git20210120+802ebd4-1.1~; then
> +     drop_obsolete_diversion /bin/sh dash
> +     drop_obsolete_diversion /usr/share/man/man1/sh.1.gz dash.1.gz 
> /usr/share/man/man1/sh.distrib.1.gz
> +     add_shell
>  elif [ "$1" = configure ] && dpkg --compare-versions "$2" lt 0.4.18; then

Doesn't this make this 'elif' clause unreachable, because 0.4.18 < 0.5.11?

Summary: I like what this patch aims to do; I think it needs some tweaking to
be easier to read but then it should be good to apply.

Thanks,
Jonathan

Reply via email to