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
22 changes: 21 additions & 1 deletion lib/envmodules.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
************************************************************************/

#define _ISOC99_SOURCE
#define _XOPEN_SOURCE
#define _XOPEN_SOURCE 500

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
Expand Down Expand Up @@ -84,7 +85,9 @@ Envmodules_GetFilesInDirectoryObjCmd(
int have_modulerc = 0;
int have_version = 0;
int is_hidden;
int is_dir;
char path[PATH_MAX];
struct stat st;

/* Parse arguments. */
if (objc == 3) {
Expand Down Expand Up @@ -129,6 +132,21 @@ Envmodules_GetFilesInDirectoryObjCmd(
Tcl_ListObjAppendElement(interp, ltmp, Tcl_NewStringObj(path, -1));
is_hidden = (direntry->d_name[0] == '.') ? 1 : 0;
Tcl_ListObjAppendElement(interp, ltmp, Tcl_NewIntObj(is_hidden));
/* Determine if entry is a directory using d_type when available,
* falling back to stat() for DT_UNKNOWN/DT_LNK (symlinks to
* dirs and NFS v3 where d_type is always DT_UNKNOWN) */
#ifdef DT_DIR
if (direntry->d_type == DT_DIR) {
is_dir = 1;
} else if (direntry->d_type != DT_UNKNOWN
&& direntry->d_type != DT_LNK) {
is_dir = 0;
} else
#endif
{
is_dir = (!stat(path, &st) && S_ISDIR(st.st_mode)) ? 1 : 0;
}
Tcl_ListObjAppendElement(interp, ltmp, Tcl_NewIntObj(is_dir));
}
}
/* Do not treat error happening during read to send list of valid files. */
Expand All @@ -150,11 +168,13 @@ Envmodules_GetFilesInDirectoryObjCmd(
snprintf(path, sizeof(path), "%s/%s", dir, ".modulerc");
Tcl_ListObjAppendElement(interp, lres, Tcl_NewStringObj(path, -1));
Tcl_ListObjAppendElement(interp, lres, Tcl_NewIntObj(0));
Tcl_ListObjAppendElement(interp, lres, Tcl_NewIntObj(0));
}
if (have_version) {
snprintf(path, sizeof(path), "%s/%s", dir, ".version");
Tcl_ListObjAppendElement(interp, lres, Tcl_NewStringObj(path, -1));
Tcl_ListObjAppendElement(interp, lres, Tcl_NewIntObj(0));
Tcl_ListObjAppendElement(interp, lres, Tcl_NewIntObj(0));
}
/* Then append regular elements. */
Tcl_ListObjAppendList(interp, lres, ltmp);
Expand Down
29 changes: 23 additions & 6 deletions tcl/modfind.tcl.in
Original file line number Diff line number Diff line change
Expand Up @@ -2904,7 +2904,7 @@ proc __getFilesInDirectory {dir fetch_dotversion} {

# Add each element in the current directory to the list
foreach elt $elt_list {
lappend dir_list $elt 0
lappend dir_list $elt 0 [file isdirectory $elt]
}

# search for hidden files
Expand All @@ -2914,11 +2914,11 @@ proc __getFilesInDirectory {dir fetch_dotversion} {
.modulerc - .version {
if {($fetch_dotversion || $elt ne {.version}) && [file readable\
$dir/$elt]} {
lappend dir_list $dir/$elt 0
lappend dir_list $dir/$elt 0 0
}
}
default {
lappend dir_list $dir/$elt 1
lappend dir_list $dir/$elt 1 [file isdirectory $dir/$elt]
}
}
}
Expand Down Expand Up @@ -2993,12 +2993,19 @@ proc findModulesFromDirsAndFiles {dir full_list depthlvl fetch_mtime\
$element] $element]
} else {
# Add each element in the current directory to the list
foreach {fpelt hid} $elt_list {
foreach {fpelt hid isdir} $elt_list {
lappend full_list $fpelt
# Flag hidden files
if {$hid} {
set hidden_list($fpelt) 1
}
# Record file type to avoid later stat calls
set fpeltname [getModuleNameFromModulepath $fpelt $dir]
if {$isdir} {
set dknown_arr($fpeltname) 1
} else {
set fknown_arr($fpeltname) 1
}
}
}
}
Expand Down Expand Up @@ -3083,19 +3090,29 @@ proc findModules {dir mod depthlvl fetch_mtime} {
# use catch protection to handle non-readable and non-existent dir
if {[catch {
set full_list {}
foreach {fpelt hid} [getFilesInDirectory $dir 0] {
array set init_fknown {}
array set init_dknown {}
foreach {fpelt hid isdir} [getFilesInDirectory $dir 0] {
set elt [file tail $fpelt]
# include any .modulerc file found at the modulepath root
if {$elt eq {.modulerc} || $findall || [modEqStatic $elt match]} {
lappend full_list $fpelt
# record file type to avoid later stat calls
set eltname [getModuleNameFromModulepath $fpelt $dir]
if {$isdir} {
set init_dknown($eltname) 1
} else {
set init_fknown($eltname) 1
}
}
}
}]} {
return {}
}

# walk through list of dirs and files to find modules
findModulesFromDirsAndFiles $dir $full_list $depthlvl $fetch_mtime mod_list
findModulesFromDirsAndFiles $dir $full_list $depthlvl $fetch_mtime \
mod_list {} {} init_fknown init_dknown

reportDebug "found [array names mod_list]"

Expand Down
1 change: 1 addition & 0 deletions testsuite/modulefiles.deep/plainlink
47 changes: 47 additions & 0 deletions testsuite/modules.80-deep/025-symlink-deep.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
##############################################################################
# Modules Revision 3.0
# Providing a flexible user environment
#
# Description: Deep modulefile directories - symlink directory handling
# Command: avail, load
# Modulefiles: plainlink (symlink to plain)
#
# Test that symlinked directories are correctly identified as directories
# when the C extension uses d_type/stat() to pre-compute is_dir. Symlinks
# have d_type=DT_LNK and must be resolved via stat() to detect if target
# is a directory.
#
##############################################################################

# ensure auto symbolic versions are not set for these tests
setenv_var MODULES_ADVANCED_VERSION_SPEC 0

# plainlink is a symlink to the plain directory within modulefiles.deep
# it should be traversable and its modules should be available

# test avail on symlinked deep module (terse output)
set deepmodpath "$env(TESTSUITEDIR)/modulefiles.deep"
set ts_sh "$deepmodpath:\nplainlink/dir1/1.0\nplainlink/dir1/2.0\nplainlink/dir2/1.0\nplainlink/dir2/2.0"
setenv_path_var MODULEPATH $deepmodpath
testouterr_cmd "sh" "avail -t plainlink" "OK" $ts_sh

skip_if_quick_mode

# test load of a specific module through the symlink
set ans [list]
lappend ans [list set TEST "plain/dir1/1.0"]
lappend ans [list set __MODULES_LMCONFLICT \
"plainlink/dir1/1.0&plain/dir1"]
lappend ans [list set _LMFILES_ \
"$deepmodpath/plainlink/dir1/1.0"]
lappend ans [list set LOADEDMODULES "plainlink/dir1/1.0"]
test_cmd_re "sh" "load plainlink/dir1/1.0" $ans

# test avail on a deeper path within the symlink
set ts_sh "$deepmodpath:\nplainlink/dir2/1.0\nplainlink/dir2/2.0"
testouterr_cmd "sh" "avail -t plainlink/dir2" "OK" $ts_sh

# cleanup
setenv_path_var MODULEPATH $modpath
unsetenv_var MODULES_ADVANCED_VERSION_SPEC
unset ts_sh ans deepmodpath
Loading