Skip to content
Merged
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
7 changes: 7 additions & 0 deletions NEWS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ Modules 5.7.0 (not yet released)
by AISLE in partnership with Red Hat)
* Accept abbreviated upper bounds in version ranges such as ``@3.20:3`` to
select versions starting at ``3.20`` within major version ``3``.
* Fix resolution of relative path entries in :envvar:`MODULEPATH` when a
module is loaded during a modulefile evaluation. Such entry was resolved
against the directory of the evaluating modulefile rather than against the
current working directory. A relative *directory* argument of the
:mfcmd:`is-used` modulefile command or of the :subcmd:`unuse` sub-command
is also resolved against the current working directory to recognize such
entry whatever its form.

.. _CVE-2026-85013: https://github.com/envmodules/modules/security/advisories/GHSA-8hrw-p88g-qhmg

Expand Down
16 changes: 15 additions & 1 deletion doc/source/modulefile.rst
Original file line number Diff line number Diff line change
Expand Up @@ -449,10 +449,18 @@ the *modulefile* is being loaded.
is enabled in case no argument is provided. If a list contains more than one
*directory*, then each member acts as a boolean OR operation.

A relative *directory* is resolved against the directory of the modulefile
being evaluated and also against the current working directory, as a
relative path entry in :envvar:`MODULEPATH` refers to the latter.

.. only:: html or latex

.. versionadded:: 4.1

.. versionchanged:: 5.7
A relative *directory* is also resolved against the current working
directory

.. mfcmd:: lsb-release field

Provide certain Linux Standard Base (LSB) information. :mfcmd:`lsb-release`
Expand Down Expand Up @@ -2279,7 +2287,13 @@ reloaded or refreshed. This is especially important when the modulefile
updates an environment variable also altered by other modulefiles like
:envvar:`PATH`. As the order of the path elements in such variable defines
priority, it is important that this order does not change depending on the way
the modulefiles are loaded.
the modulefiles are loaded. Moreover when :mconfig:`conflict_unload` is
enabled, a conflicting loaded modulefile is unloaded at the time the
:mfcmd:`conflict`, :mfcmd:`family` or :mfcmd:`module unload<module>` command
is evaluated. Environment changes made prior to this command by the loading
modulefile are overridden by this unload when they affect variables also
altered by the unloaded modulefile. For instance a variable set by the loading
modulefile ends up unset if it is also set by the unloaded modulefile.

:command:`module` keeps environment consistent which means a modulefile cannot
be loaded if its requirements are not loaded or if a conflicting module is
Expand Down
8 changes: 2 additions & 6 deletions tcl/mfcmd.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -1335,17 +1335,13 @@ proc is-saved {args} {

# test at least one of the directories passed as argument is set in MODULEPATH
proc is-used {args} {
set modpathlist [getModulePathList]
foreach path $args {
# transform given path in an absolute path to compare with dirs
# registered in the MODULEPATH env var which are returned absolute.
set abspath [getAbsolutePath $path]
if {$abspath in $modpathlist} {
if {[getUsedModulePathEntry $path] ne {}} {
return 1
}
}
# is something used whatever it is?
return [expr {![llength $args] && [llength $modpathlist]}]
return [expr {![llength $args] && [llength [getModulePathList]]}]
}

# test at least one of the modulefiles passed as argument exists
Expand Down
32 changes: 25 additions & 7 deletions tcl/modfind.tcl.in
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,7 @@ proc getLoadedModulePropertyEnvVarName {prop} {
# is set. by default an empty list is returned if no MODULEPATH set
# resolv_var param tells if environment variable references in path elements
# should be resolved or passed as-is in result list
# set_abs param applies an absolute path name conversion to path elements
# if enabled
proc getModulePathList {{behavior returnempty} {resolv_var 1} {set_abs 1}} {
proc getModulePathList {{behavior returnempty} {resolv_var 1}} {
if {![isEnvVarDefined MODULEPATH] && $behavior eq {exiterronundef}} {
reportErrorAndExit {No module path defined}
}
Expand All @@ -507,14 +505,34 @@ proc getModulePathList {{behavior returnempty} {resolv_var 1} {set_abs 1}} {
if {$resolv_var} {
set modpath [resolvStringWithEnv $modpath]
}
if {$set_abs} {
set modpath [getAbsolutePath $modpath]
}
lappendNoDup modpath_list $modpath
# relative modulepath refers to current working directory of module
# command, not to the directory of the modulefile being evaluated
lappendNoDup modpath_list [getAbsolutePath $modpath 1]
}
return $modpath_list
}

# return the MODULEPATH entry, as registered in environment variable, that
# corresponds to given path. path is resolved as an absolute path from the
# directory of the modulefile being evaluated and from the current working
# directory, as a relative MODULEPATH entry refers to the latter. an empty
# string is returned if path is not found in MODULEPATH
# resolv_var param tells if environment variable references in MODULEPATH
# entries should be resolved or compared as-is
proc getUsedModulePathEntry {path {resolv_var 1}} {
set abspath [getAbsolutePath $path]
set abspath_from_cwd [getAbsolutePath $path 1]
foreach modpath [getEnvLoadedModulePropertyParsedList modulepath] {
set absmodpath [expr {$resolv_var ? [resolvStringWithEnv $modpath] :\
$modpath}]
set absmodpath [getAbsolutePath $absmodpath 1]
if {$absmodpath eq $abspath || $absmodpath eq $abspath_from_cwd} {
return $modpath
}
}
return {}
}

proc getModulepathLabel {modpath} {
if {[info exists ::g_modulepathLabel($modpath)]} {
return $::g_modulepathLabel($modpath)
Expand Down
24 changes: 7 additions & 17 deletions tcl/subcmd.tcl.in
Original file line number Diff line number Diff line change
Expand Up @@ -1952,11 +1952,6 @@ proc runModuleUse {cmd mode pos args} {
if {$args eq {}} {
showModulePath
} else {
if {$pos eq {remove}} {
# get current module path list
set modpathlist [getModulePathList returnempty 0 0]
}

foreach path $args {
switch -glob -- $path {
--remove-on-unload - --append-on-unload - --prepend-on-unload -\
Expand All @@ -1978,18 +1973,13 @@ proc runModuleUse {cmd mode pos args} {
}
default {
if {$pos eq {remove}} {
if {$path in $modpathlist} {
lappend pathlist $path
# transform given path in an absolute path which should have
# been registered in the MODULEPATH env var. however for
# compatibility with previous behavior where relative paths
# were registered in MODULEPATH given path is first checked
# against current path list
} elseif {[set abspath [getAbsolutePath $path]] in\
$modpathlist} {
lappend pathlist $abspath
# even if not found, transmit this path to remove-path in
# case several path elements have been joined as one string
# transmit path as registered in MODULEPATH env var, where
# variable references are kept as-is. even if not found,
# transmit given path to remove-path in case several path
# elements have been joined as one string
if {[set usedpath [getUsedModulePathEntry $path 0]] ne\
{}} {
lappend pathlist $usedpath
} else {
lappend pathlist $path
}
Expand Down
5 changes: 3 additions & 2 deletions tcl/util.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@ proc runCommand {cmd args} {
}
}

proc getAbsolutePath {path} {
proc getAbsolutePath {path {from_cwd 0}} {
# currently executing a modulefile or rc, so get the directory of this file
if {[currentState modulefile] ne {}} {
# unless resolution from current working directory is requested
if {!$from_cwd && [currentState modulefile] ne {}} {
set curdir [file dirname [currentState modulefile]]
# elsewhere get module command current working directory
} else {
Expand Down
20 changes: 20 additions & 0 deletions testsuite/modulefiles.4/bar/1
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,23 @@ if {[info exists env(TESTSUITE_LCOMPAT)]} {
}
}
}
if {[info exists env(TESTSUITE_RELATIVE_MODULEPATH)]} {
switch -- $env(TESTSUITE_RELATIVE_MODULEPATH) {
load1 {
module load foo/1.0
}
prereq1 {
prereq foo/1.0
}
depon1 {
depends-on foo/1.0
}
isused1 {
setenv TS_ISUSED_RAW [is-used $env(MODULEPATH)]
setenv TS_ISUSED_DOT [is-used ./$env(MODULEPATH)]
setenv TS_ISUSED_SLASH [is-used $env(MODULEPATH)/]
setenv TS_ISUSED_REL [is-used ..]
setenv TS_ISUSED_UNK [is-used [file tail $env(MODULEPATH)]]
}
}
}
16 changes: 16 additions & 0 deletions testsuite/modulefiles.4/conun/1
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ if {[info exists env(TESTSUITE_CONFLICT_UNLOAD)]} {
depun_of_conun_is_sticky1 - depun_of_conun_is_super_sticky1 {
conflict conun
}
setenv_before_conflict1 {
setenv TSCONUN conun
conflict foo
}
setenv_after_conflict1 {
conflict foo
setenv TSCONUN conun
}
setenv_before_unload1 {
setenv TSCONUN conun
module unload foo
}
setenv_before_family1 {
setenv TSCONUN conun
family conun
}
implicit_default_off1 {
conflict conun
}
Expand Down
7 changes: 7 additions & 0 deletions testsuite/modulefiles.4/foo/1.0
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ if {[info exists env(TESTSUITE_CONFLICT_UNLOAD)]} {
depre_of_conun_is_conflict_of_reqlo1 {
conflict bar/1
}
setenv_before_conflict1 - setenv_after_conflict1 - setenv_before_unload1 {
setenv TSCONUN foo
}
setenv_before_family1 {
setenv TSCONUN foo
family conun
}
}
}
if {[info exists env(TESTSUITE_MODULEPATH_OPT)]} {
Expand Down
55 changes: 55 additions & 0 deletions testsuite/modules.50-cmds/660-conflict_unload.exp
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,61 @@ set tserr [msg_top_load_conun conun/1 {} conun/2 {} {} bar/1]
testouterr_cmd bash {load conun/1} $ans $tserr


#
# environment variable set by loading module prior conflict declaration
#

unsetenv_loaded_module
unsetenv_var __MODULES_LMPREREQ
unsetenv_var __MODULES_LMCONFLICT
unsetenv_var __MODULES_LMTAG
unsetenv_var __MODULES_LMALTNAME
unsetenv_var MODULES_FAMILY_CONUN

setenv_loaded_module [list foo/1.0] [list $mp/foo/1.0]
setenv_var TSCONUN foo

# conflicting module unload occurs when conflict is evaluated, so variable
# set by loading module before that is unset by this unload
setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_before_conflict1
set ans [list]
lappend ans [list set __MODULES_LMCONFLICT conun/1&foo]
lappend ans [list set _LMFILES_ $mp/conun/1]
lappend ans [list set LOADEDMODULES conun/1]
lappend ans [list unset TSCONUN]
set tserr [msg_top_load_conun conun/1 {} {foo/1.0} {} {} {} {}]
testouterr_cmd bash {load conun/1} $ans $tserr

setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_before_unload1
testouterr_cmd bash {load conun/1} $ans $tserr

# variable is preserved if set after conflict declaration
setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_after_conflict1
set ans [list]
lappend ans [list set __MODULES_LMCONFLICT conun/1&foo]
lappend ans [list set _LMFILES_ $mp/conun/1]
lappend ans [list set LOADEDMODULES conun/1]
lappend ans [list set TSCONUN conun]
testouterr_cmd bash {load conun/1} $ans $tserr

setenv_var TESTSUITE_CONFLICT_UNLOAD setenv_before_family1
setenv_var MODULES_FAMILY_CONUN foo
setenv_var __MODULES_LMALTNAME foo/1.0&al|conun
set ans [list]
lappend ans [list set MODULES_FAMILY_CONUN conun]
lappend ans [list set __MODULES_LMCONFLICT conun/1&conun]
lappend ans [list set __MODULES_LMALTNAME conun/1&al|conun]
lappend ans [list set _LMFILES_ $mp/conun/1]
lappend ans [list set LOADEDMODULES conun/1]
lappend ans [list unset TSCONUN]
lappend ans [list set LMOD_FAMILY_CONUN conun]
testouterr_cmd bash {load conun/1} $ans $tserr

unsetenv_var MODULES_FAMILY_CONUN
unsetenv_var __MODULES_LMALTNAME
unsetenv_var TSCONUN


#
# Disabled implicit default
#
Expand Down
74 changes: 74 additions & 0 deletions testsuite/modules.50-cmds/745-modulepath-relative.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
##############################################################################
# Modules Revision 3.0
# Providing a flexible user environment
#
# File: modules.50-cmds/%M%
# Revision: %I%
# First Edition: 2026/09/15
# Last Mod.: %U%, %G%
#
# Authors: Xavier Delaruelle, xavier.delaruelle@cea.fr
#
# Description: Testuite testsequence
# Command: load
# Modulefiles: bar, foo
# Sub-Command: is-used
#
# Comment: %C{
# Test module load made during a modulefile evaluation when
# modulepath is set as a relative path
# }C%
#
##############################################################################

skip_if_quick_mode

set mp $modpath.4

# modulepath relative to current working directory of module command
set relmp [file tail $env(TESTSUITEDIR)]/[file tail $mp]

setenv_var MODULES_AUTO_HANDLING 1

set ans [list]
lappend ans [list set __MODULES_LMPREREQ bar/1&foo/1.0]
lappend ans [list set _LMFILES_ $mp/foo/1.0:$mp/bar/1]
lappend ans [list set LOADEDMODULES foo/1.0:bar/1]
lappend ans [list set __MODULES_LMTAG foo/1.0&auto-loaded]
set tserr [msg_top_load bar/1 {} foo/1.0 {}]

# is-used given the relative entry (raw or in a non-normalized form) or a path
# relative to the modulefile directory, which is not the current working
# directory
set ans2 [list]
lappend ans2 [list set TS_ISUSED_UNK 0]
lappend ans2 [list set TS_ISUSED_RAW 1]
lappend ans2 [list set TS_ISUSED_REL 1]
lappend ans2 [list set TS_ISUSED_SLASH 1]
lappend ans2 [list set TS_ISUSED_DOT 1]
lappend ans2 [list set _LMFILES_ $mp/bar/1]
lappend ans2 [list set LOADEDMODULES bar/1]

foreach modulepath [list $relmp ./$relmp] {
setenv_path_var MODULEPATH $modulepath
foreach tscase {load1 prereq1 depon1} {
setenv_var TESTSUITE_RELATIVE_MODULEPATH $tscase
testouterr_cmd bash {load bar/1} $ans $tserr
}

setenv_var TESTSUITE_RELATIVE_MODULEPATH isused1
testouterr_cmd bash {load bar/1} $ans2 {}

# unuse given the relative entry raw or in a non-normalized form
foreach unusepath [list $relmp ./$relmp $relmp/ $mp] {
testouterr_cmd bash "unuse $unusepath" [list [list unset MODULEPATH]]\
{}
}
}


#
# Cleanup
#

reset_test_env
Loading