Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions rt/termWidthFallback/err.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
===========================
step 1
lua ProjectDIR/src/lmod.in.lua shell --regression_testing avail
===========================
ProjectDIR/rt/termWidthFallback/mf
alpha/1.0 beta/1.0 gamma/1.0
If the avail list is too long consider trying:
"module --default avail" or "ml -d av" to just list the default modules.
"module overview" or "ml ov" to display the number of modules for each name.
Use "module spider" to find all possible modules and extensions.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".
===========================
step 2
lua ProjectDIR/src/lmod.in.lua shell --regression_testing avail
===========================
ProjectDIR/rt/termWidthFallback/mf
alpha/1.0 beta/1.0 gamma/1.0
If the avail list is too long consider trying:
"module --default avail" or "ml -d av" to just list the default modules.
"module overview" or "ml ov" to display the number of modules for each name.
Use "module spider" to find all possible modules and extensions.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".
===========================
step 3
lua ProjectDIR/src/lmod.in.lua shell --regression_testing avail
===========================
ProjectDIR/rt/termWidthFallback/mf
alpha/1.0 beta/1.0 gamma/1.0
If the avail list is too long consider trying:
"module --default avail" or "ml -d av" to just list the default modules.
"module overview" or "ml ov" to display the number of modules for each name.
Use "module spider" to find all possible modules and extensions.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".
1 change: 1 addition & 0 deletions rt/termWidthFallback/mf/alpha/1.0.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setenv("TW_ALPHA", "1")
1 change: 1 addition & 0 deletions rt/termWidthFallback/mf/beta/1.0.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setenv("TW_BETA", "1")
1 change: 1 addition & 0 deletions rt/termWidthFallback/mf/gamma/1.0.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setenv("TW_GAMMA", "1")
30 changes: 30 additions & 0 deletions rt/termWidthFallback/out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
===========================
step 1
lua ProjectDIR/src/lmod.in.lua shell --regression_testing avail
===========================
__LMOD_REF_COUNT_MODULEPATH=ProjectDIR/rt/termWidthFallback/mf:1;
export __LMOD_REF_COUNT_MODULEPATH;
MODULEPATH=ProjectDIR/rt/termWidthFallback/mf;
export MODULEPATH;
_ModuleTable_='_ModuleTable_={MTversion=3,depthT={},family={},mT={},mpathA={"ProjectDIR/rt/termWidthFallback/mf",},systemBaseMPATH="ProjectDIR/rt/termWidthFallback/mf",}';
export _ModuleTable_;
===========================
step 2
lua ProjectDIR/src/lmod.in.lua shell --regression_testing avail
===========================
MODULEPATH=ProjectDIR/rt/termWidthFallback/mf;
export MODULEPATH;
_ModuleTable_='_ModuleTable_={MTversion=3,depthT={},family={},mT={},mpathA={"ProjectDIR/rt/termWidthFallback/mf",},systemBaseMPATH="ProjectDIR/rt/termWidthFallback/mf",}';
export _ModuleTable_;
===========================
step 3
lua ProjectDIR/src/lmod.in.lua shell --regression_testing avail
===========================
MODULEPATH=ProjectDIR/rt/termWidthFallback/mf;
export MODULEPATH;
_ModuleTable_='_ModuleTable_={MTversion=3,depthT={},family={},mT={},mpathA={"ProjectDIR/rt/termWidthFallback/mf",},systemBaseMPATH="ProjectDIR/rt/termWidthFallback/mf",}';
export _ModuleTable_;
scenario-C wedged-stty completed: yes
scenario-C elapsed seconds: 1
scenario-A probe invocations: 0
scenario-B probe invocations: nonzero
116 changes: 116 additions & 0 deletions rt/termWidthFallback/termWidthFallback.tdesc
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
-- -*- lua -*-
local testName = "termWidthFallback"

testdescript = {
owner = "rtm",
product = "modules",
description = [[
Verify that TermWidth.lua honors $COLUMNS instead of probing
stty/tput, and that the stty/tput probes themselves are bounded
by SIGALRM so a wedged PTY cannot hang module commands.
Regression coverage for GH-832.
]],
keywords = { testName },

active = true,
testName = testName,
job_submit_method = "INTERACTIVE",

runScript = [[

. $(projectDir)/rt/common_funcs.sh

unsetMT
initStdEnvVars
export MODULEPATH=$(testDir)/mf
unset LMOD_TERM_WIDTH

# Install fake stty/tput on PATH that report a sentinel width (999)
# and log every invocation. When $COLUMNS is honored these MUST NOT
# be called. Sentinel-vs-COLUMNS lets us tell the two paths apart
# in the debug output without depending on real terminal sizing.
mkdir -p _fakebin
cat > _fakebin/stty <<'FAKE'
#!/bin/sh
echo "stty $*" >> "$TW_PROBE_LOG"
[ "$1" = "size" ] && { echo "24 999"; exit 0; }
exec /usr/bin/stty "$@" 2>/dev/null || true
FAKE
cat > _fakebin/tput <<'FAKE'
#!/bin/sh
echo "tput $*" >> "$TW_PROBE_LOG"
[ "$1" = "cols" ] && { echo "999"; exit 0; }
exec /usr/bin/tput "$@" 2>/dev/null || true
FAKE
chmod +x _fakebin/stty _fakebin/tput
export PATH="$PWD/_fakebin:$PATH"
export TW_PROBE_LOG="$PWD/probe.log"

remove_generated_lmod_files

# Scenario A: $COLUMNS set -> probes MUST NOT be invoked.
: > "$TW_PROBE_LOG"
export COLUMNS=120
runLmod avail 2>/dev/null # 1
if [ -s "$TW_PROBE_LOG" ]; then
echo "scenario-A probe invocations: nonzero" > _stdout.998
else
echo "scenario-A probe invocations: 0" > _stdout.998
fi

# Scenario B: $COLUMNS unset -> probes run, sentinel 999 is used.
: > "$TW_PROBE_LOG"
unset COLUMNS
export TERM=xterm
runLmod avail 2>/dev/null # 2
if [ -s "$TW_PROBE_LOG" ]; then
echo "scenario-B probe invocations: nonzero" > _stdout.999
else
echo "scenario-B probe invocations: 0" > _stdout.999
fi

# Scenario C: wedged stty size -> alarm must return without hanging.
cat > _fakebin/stty <<'FAKE'
#!/bin/sh
echo "stty $*" >> "$TW_PROBE_LOG"
[ "$1" = "size" ] && sleep 30
exec /usr/bin/stty "$@" 2>/dev/null || true
FAKE
chmod +x _fakebin/stty
: > "$TW_PROBE_LOG"
unset COLUMNS
export TERM=xterm
SECONDS=0
runLmod avail 2>/dev/null # 3
elapsedC=$SECONDS
if [ "$elapsedC" -lt 5 ]; then
echo "scenario-C wedged-stty completed: yes" > _stdout.997
else
echo "scenario-C wedged-stty completed: no" > _stdout.997
fi
echo "scenario-C elapsed seconds: $elapsedC" >> _stdout.997

HOME=$ORIG_HOME

cat _stdout.[0-9][0-9][0-9] > _stdout.orig
joinBase64Results -bash _stdout.orig _stdout.new
cleanUp _stdout.new out.txt

cat _stderr.[0-9][0-9][0-9] > _stderr.orig
cleanUp _stderr.orig err.txt

rm -f results.csv
wrapperDiff --csv results.csv $(testDir)/out.txt out.txt
wrapperDiff --csv results.csv $(testDir)/err.txt err.txt
testFinish -r $(resultFn) -t $(runtimeFn) results.csv
]],

blessScript = [[
# perform what is needed
]],

tests = {
{ id='t1'},
},

}
138 changes: 124 additions & 14 deletions tools/TermWidth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,137 @@ require("strict")
--------------------------------------------------------------------------


_G._DEBUG = false -- Required by luaposix 33
local posix = require("posix")
local unistd = posix.unistd
local wait = posix.sys.wait
-- Pull the signal API from the posix.signal submodule. On the merged `posix`
-- table `posix.signal` is the signal() *function* (e.g. luaposix 35 on EL9), so
-- `posix.signal.kill` errors at load time and Lmod fails to start; requiring the
-- submodule gives a table with kill/signal and the SIG* constants on every
-- supported luaposix.
local psignal = require("posix.signal")
local kill = psignal.kill
local SIGALRM = psignal.SIGALRM
local SIGKILL = psignal.SIGKILL
local setenv_posix = posix.setenv

require("haveTermSupport")
require("capture")
local capture = capture or function (s) return nil end
local getenv = os.getenv
local term = false
local s_width = false
local min = math.min
local s_DFLT = 80

-- Bound stty/tput probes below. A wedged PTY (ioctl(TIOCGWINSZ) that
-- never returns) would otherwise leave `module` blocked forever inside
-- io.popen :read("*all") -- see GH-832. Kept local to TermWidth so
-- general capture() callers are unaffected.
local s_termWidthTimeout = 1

local function l_withCaptureEnv(fn)
local cosmic = require("Cosmic"):singleton()
local newT = {}
local envT = {}
local env_ldT = {
LMOD_LD_LIBRARY_PATH = "LD_LIBRARY_PATH",
LMOD_LD_PRELOAD = "LD_PRELOAD",
}

for k, v in pairs(env_ldT) do
local value = cosmic:get(k, "")
if (value ~= "") then
envT[v] = value
end
end

for k, v in pairs(envT) do
newT[k] = getenv(k) or false
setenv_posix(k, v, true)
end

local result = fn()

for k, myValue in pairs(newT) do
local v = myValue
if (v == false) then v = nil end
setenv_posix(k, v, true)
end

return result
end

local function l_timedCapture(cmd, sec)
return l_withCaptureEnv(function ()
local r, w = unistd.pipe()
if (not r) then
return nil
end

local pid = unistd.fork()
if (pid == 0) then
unistd.close(r)
unistd.dup2(w, 1)
unistd.close(w)
os.execute("sh -c " .. string.format("%q", cmd))
os.exit(0)
end

unistd.close(w)

local timed = false
local cpid = pid
local oldSig = psignal.signal(SIGALRM, function ()
timed = true
kill(cpid, SIGKILL)
end)

unistd.alarm(sec)

local outA = {}
while (true) do
local chunk = unistd.read(r, 4096)
if (not chunk or chunk == "") then
break
end
outA[#outA + 1] = chunk
end

unistd.alarm(0)
psignal.signal(SIGALRM, oldSig)
unistd.close(r)
wait.wait(cpid)

if (timed) then
return nil
end

return table.concat(outA)
end)
end

------------------------------------------------------------------------
-- Ask system for width.

local function l_askSystem(width)

-- try stty size
local r_c = capture("stty size 2> /dev/null")
local i, j, rows, columns = r_c:find('(%d+)%s+(%d+)')
if (i) then
return tonumber(columns)
local r_c = l_timedCapture("stty size 2> /dev/null", s_termWidthTimeout)
if (r_c) then
local i, j, rows, columns = r_c:find('(%d+)%s+(%d+)')
if (i) then
return tonumber(columns)
end
end

-- Try tput cols
if (getenv("TERM")) then
local result = capture("tput cols 2> /dev/null")
i, j, columns = result:find("^(%d+)")
if (i) then
return tonumber(columns)
local result = l_timedCapture("tput cols 2> /dev/null", s_termWidthTimeout)
if (result) then
local i, j, columns = result:find("^(%d+)")
if (i) then
return tonumber(columns)
end
end
end

Expand All @@ -80,10 +184,16 @@ function TermWidth()
s_width = ltw
return s_width
end
s_DFLT = ltw or s_DFLT
s_width = s_DFLT
if (haveTermSupport()) then
s_width = l_askSystem(s_width)

local cols = tonumber(getenv("COLUMNS"))
if (cols) then
s_width = cols
else
s_DFLT = ltw or s_DFLT
s_width = s_DFLT
if (haveTermSupport()) then
s_width = l_askSystem(s_width)
end
end

local maxW = ltw or math.huge
Expand Down