From 8f8c0e7416568b7f5555a35e171da0a54ce5a27d Mon Sep 17 00:00:00 2001 From: Nigel Tan Date: Sat, 17 Oct 2026 18:42:43 +0800 Subject: [PATCH] [core] Fix shell detection with multiline commands A newline in a shell argument makes cut return text from later lines. This can select the wrong setup path and leave ROOTSYS unset. Read the first line after converting the NUL separators to newlines. Keep reading the stream so long commands also work with pipefail. Add regression coverage for bash and zsh. Fixes #23746 Assisted-by: codex:gpt-6 --- config/thisroot.sh | 2 +- test/CMakeLists.txt | 11 +++++++++++ test/testThisroot.sh | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 test/testThisroot.sh diff --git a/config/thisroot.sh b/config/thisroot.sh index d2ce449a5ea08..15357095d8cbe 100644 --- a/config/thisroot.sh +++ b/config/thisroot.sh @@ -173,7 +173,7 @@ getTrueShellExeName() { # mklement0 https://stackoverflow.com/a/23011530/7471760 local trueExe nextTarget 2>/dev/null # ignore error in shells without `local` # Determine the shell executable filename. if [ -r "/proc/$$/cmdline" ]; then - trueExe=$(cut -d '' -f1 /proc/$$/cmdline 2>/dev/null) || trueExe=$(xargs -0 -n 1 < /proc/$$/cmdline | head -n 1) || return 1 + trueExe=$(tr '\0' '\n' < /proc/$$/cmdline 2>/dev/null | sed -n '1p') || trueExe=$(xargs -0 -n 1 < /proc/$$/cmdline | head -n 1) || return 1 # Qemu emulation has cmdline start with the emulator if [ "${trueExe##*qemu*}" != "${trueExe}" ]; then # but qemu sets comm to the emulated command diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2eebde410858f..331fad5c0f1ac 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,17 @@ set(root_exe $) #---environment------------------------------------------------------------------------------- ROOT_ADD_TEST(show-environment COMMAND ${CMAKE_COMMAND} -E environment) +if(thisroot_scripts AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + foreach(shell IN ITEMS bash zsh) + find_program(THISROOT_${shell}_EXECUTABLE NAMES ${shell}) + if(THISROOT_${shell}_EXECUTABLE) + ROOT_ADD_TEST(test-thisroot-${shell} + COMMAND ${THISROOT_${shell}_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testThisroot.sh + ${THISROOT_${shell}_EXECUTABLE} ${CMAKE_BINARY_DIR}) + endif() + endforeach() +endif() + #---hworld------------------------------------------------------------------------------------ ROOT_EXECUTABLE(hworld hworld.cxx LIBRARIES Gpad) diff --git a/test/testThisroot.sh b/test/testThisroot.sh new file mode 100644 index 0000000000000..cf30ede31163e --- /dev/null +++ b/test/testThisroot.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +set -eu + +shell=$1 +root_dir=$2 +shell_name=${shell##*/} +unset ROOTSYS + +# Variables are expanded by the child shell. +# shellcheck disable=SC2016 +shell_command=' +if [ -n "${BASH_VERSION-}" ]; then shopt -s extdebug; fi; +. "$1/bin/thisroot.sh" || exit 1; +test "$ROOTSYS" = "$1" || exit 1; +test "$SHELLNAME" = "$2" || exit 1; +' + +single_line_command=$(printf '%s' "$shell_command" | tr '\n' ' ') + +for layout in single multiline long; do + printf 'Checking %s with %s commands\n' "$shell_name" "$layout" + case "$layout" in + single) command_text=$single_line_command ;; + multiline) command_text=$shell_command ;; + long) + padding=$(printf '%65536s' '') + command_text="set -o pipefail; $single_line_command #$padding" + ;; + esac + "$shell" -c "$command_text" thisroot-test "$root_dir" "$shell_name" +done