From dbbc874fc1300b5a8045f720b4d311601cf4f9a9 Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Thu, 15 Oct 2026 06:53:33 -0700 Subject: [PATCH] fix(rc.d): list ~/.rc.d with `command ls` in the login bridge The login ~/.profile sources ~/.bashrc before the rc.d block, so in an interactive login shell a user's `ls` alias or function is already defined when the block lists ~/.rc.d. Depending on the alias, snippets then run in the wrong order (`ls -r`), are skipped (`ls -F` on an executable snippet, `ls -Q`), or are found only because their names appear among the words of a long listing (`ls -l`). `command ls` skips aliases and functions; the C collation and the block's line count are unchanged. Fixes #587 --- acq.backends/common.sh | 5 +++-- test/bats/72-msb-agent-user.bats | 27 +++++++++++++++++++++++++-- test/bats/90-sbx-startup-kit.bats | 4 ++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/acq.backends/common.sh b/acq.backends/common.sh index 008a1d75..9d3223e2 100644 --- a/acq.backends/common.sh +++ b/acq.backends/common.sh @@ -1185,10 +1185,11 @@ acq_guest_shell_script() { # # Emitted lines are evaluated by the guest login shell. They gate to bash, # list snippets with C collation for deterministic order, preserve the readable -# and charset guards, and clean up the loop variable after sourcing. +# and charset guards, and clean up the loop variable after sourcing. `command ls` +# because ~/.profile sources ~/.bashrc first, which may alias or wrap ls. acq_login_profile_rc_block() { printf '%s\n' 'if [ -n "$BASH_VERSION" ] && [ -d "$HOME/.rc.d" ]; then' - printf '%s\n' ' for _acq_rc in $(LC_ALL=C ls "$HOME"/.rc.d 2>/dev/null); do' + printf '%s\n' ' for _acq_rc in $(LC_ALL=C command ls "$HOME"/.rc.d 2>/dev/null); do' printf '%s\n' ' _acq_rc="$HOME/.rc.d/$_acq_rc"' printf '%s\n' ' case "$_acq_rc" in *.sh) ;; *) continue ;; esac' printf '%s\n' ' [ -r "$_acq_rc" ] || continue' diff --git a/test/bats/72-msb-agent-user.bats b/test/bats/72-msb-agent-user.bats index 757f4870..9cb90b5c 100644 --- a/test/bats/72-msb-agent-user.bats +++ b/test/bats/72-msb-agent-user.bats @@ -314,9 +314,9 @@ _attach() { # PRE_SNIPPET NAME _provision rcdbox shell 'export ACQ_SECRET_STORE_DIR="'"$STUBDIR"'/rcd-secrets"' local log; log=$(cat "$CALLS") # The bridge sources ~/.rc.d in deterministic C-collation order (ADR-0030): - # it must iterate an `LC_ALL=C ls "$HOME"/.rc.d` list, NOT a bare locale- + # it must iterate an `LC_ALL=C command ls "$HOME"/.rc.d` list, NOT a bare locale- # dependent `*.sh` glob whose order can vary by guest locale. - assert_regex "$log" 'LC_ALL=C ls' + assert_regex "$log" 'LC_ALL=C command ls' assert_regex "$log" '\$HOME./.rc.d' assert_regex "$log" 'for _acq_rc in' assert_regex "$log" 'SC1090' @@ -340,6 +340,29 @@ _attach() { # PRE_SNIPPET NAME assert_output $'10-a\n2-b' } +@test "rc.d: login-profile block ignores an ls alias or function from .bashrc (#587)" { + # The bridge runs after ~/.profile sources ~/.bashrc, so an interactive + # login shell already has the user's `ls` alias or function when the block + # lists ~/.rc.d. The listing must come from the real ls. + local home="$STUBDIR/rc-alias-home" + mkdir -p "$home/.rc.d" + printf '%s\n' 'printf "%s\n" 10-a >> "$HOME/order"' > "$home/.rc.d/10-a.sh" + printf '%s\n' 'printf "%s\n" 20-b >> "$HOME/order"' > "$home/.rc.d/20-b.sh" + acq_login_profile_rc_block > "$home/profile" + + # An alias that reverses the order. + run env HOME="$home" bash -c 'shopt -s expand_aliases; alias ls="ls -r" +. "$HOME/profile"; cat "$HOME/order"' + assert_success + assert_output $'10-a\n20-b' + + # A function whose output names one snippet twice and omits the other. + rm -f "$home/order" + run env HOME="$home" bash -c 'ls() { echo 20-b.sh 20-b.sh; }; . "$HOME/profile"; cat "$HOME/order"' + assert_success + assert_output $'10-a\n20-b' +} + @test "msb #426: the heal only rewrites a .profile acq owns outright (appended lines survive)" { # Tools like rustup append to ~/.profile below acq's bridge. The rewrite # condition must be marker-present AND still just the bridge (line-count diff --git a/test/bats/90-sbx-startup-kit.bats b/test/bats/90-sbx-startup-kit.bats index b10342c3..12da0721 100644 --- a/test/bats/90-sbx-startup-kit.bats +++ b/test/bats/90-sbx-startup-kit.bats @@ -239,8 +239,8 @@ SPEC local log; log=$(cat "$CALLS") # The bridge is written via a post-create `sbx exec` (not a startup kit). assert_regex "$log" 'acq-login-profile-rc' - # Deterministic lexical order: an LC_ALL=C ls list, NOT a locale-dependent glob. - assert_regex "$log" 'LC_ALL=C ls' + # Deterministic lexical order: an LC_ALL=C command ls list, NOT a locale-dependent glob. + assert_regex "$log" 'LC_ALL=C command ls' assert_regex "$log" 'for _acq_rc in' assert_regex "$log" 'SC1090' assert_regex "$log" 'unset _acq_rc'