Skip to content

Commit 2ef074c

Browse files
committed
Additionally search client plugin paths
1 parent d84ad29 commit 2ef074c

2 files changed

Lines changed: 317 additions & 38 deletions

File tree

xrootd/plugin_check.go

Lines changed: 153 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"path/filepath"
2626
"regexp"
2727
"runtime"
28+
"sort"
2829
"strings"
2930
"sync"
3031

@@ -47,6 +48,28 @@ func resetPluginSearchPathsForTesting() {
4748
pluginSearchPaths = nil
4849
}
4950

51+
// deduplicatePaths resolves paths to absolute paths and removes duplicates while preserving order.
52+
func deduplicatePaths(paths []string) []string {
53+
seen := make(map[string]bool)
54+
var result []string
55+
for _, path := range paths {
56+
// Resolve to absolute path
57+
absPath, err := filepath.Abs(path)
58+
if err != nil {
59+
// If we can't resolve, use the original path
60+
absPath = path
61+
}
62+
// Clean the path to normalize it
63+
absPath = filepath.Clean(absPath)
64+
// Add if not seen before
65+
if !seen[absPath] {
66+
seen[absPath] = true
67+
result = append(result, absPath)
68+
}
69+
}
70+
return result
71+
}
72+
5073
// getPluginSearchPaths returns a list of directories to search for XRootD plugins.
5174
// It includes standard library paths and paths from environment variables.
5275
func getPluginSearchPaths() []string {
@@ -87,7 +110,6 @@ func getPluginSearchPaths() []string {
87110
}
88111
}
89112

90-
appendEnvPaths("XRD_PLUGINPATH")
91113
appendEnvPaths("LD_LIBRARY_PATH")
92114
appendEnvPaths("DYLD_LIBRARY_PATH")
93115
appendEnvPaths("DYLD_FALLBACK_LIBRARY_PATH")
@@ -101,25 +123,7 @@ func getPluginSearchPaths() []string {
101123
searchPaths = append(searchPaths, getXRootDRPaths()...)
102124

103125
// Resolve absolute paths and remove duplicates while preserving order
104-
seen := make(map[string]bool)
105-
var result []string
106-
for _, path := range searchPaths {
107-
// Resolve to absolute path
108-
absPath, err := filepath.Abs(path)
109-
if err != nil {
110-
// If we can't resolve, use the original path
111-
absPath = path
112-
}
113-
// Clean the path to normalize it
114-
absPath = filepath.Clean(absPath)
115-
// Add if not seen before
116-
if !seen[absPath] {
117-
seen[absPath] = true
118-
result = append(result, absPath)
119-
}
120-
}
121-
122-
pluginSearchPaths = result
126+
pluginSearchPaths = deduplicatePaths(searchPaths)
123127
})
124128

125129
return pluginSearchPaths
@@ -210,6 +214,118 @@ func getXRootDRPaths() []string {
210214
return paths
211215
}
212216

217+
// getClientPluginPaths parses XRootD client plugin configuration files and returns
218+
// directories containing absolute library paths from enabled plugins.
219+
// Checks in order: /etc/xrootd/client.plugins.d/, ~/.xrootd/client.plugins.d/,
220+
// and directory pointed to by XRD_PLUGINCONFDIR.
221+
func getClientPluginPaths() []string {
222+
paths := []string{}
223+
configDirs := []string{}
224+
225+
// Standard directories
226+
configDirs = append(configDirs, "/etc/xrootd/client.plugins.d/")
227+
228+
// User directory
229+
if homeDir, err := os.UserHomeDir(); err == nil {
230+
configDirs = append(configDirs, filepath.Join(homeDir, ".xrootd", "client.plugins.d"))
231+
}
232+
233+
// XRD_PLUGINCONFDIR environment variable
234+
if pluginConfDir := os.Getenv("XRD_PLUGINCONFDIR"); pluginConfDir != "" {
235+
configDirs = append(configDirs, pluginConfDir)
236+
}
237+
238+
// Parse each directory
239+
for _, dir := range configDirs {
240+
paths = append(paths, parseClientPluginDir(dir)...)
241+
}
242+
243+
return paths
244+
}
245+
246+
// parseClientPluginDir reads plugin configuration files from a directory
247+
// and extracts directories from absolute lib paths of enabled plugins.
248+
func parseClientPluginDir(dir string) []string {
249+
paths := []string{}
250+
251+
entries, err := os.ReadDir(dir)
252+
if err != nil {
253+
return paths
254+
}
255+
256+
// Sort entries alphabetically as per xrootd behavior
257+
fileNames := make([]string, 0, len(entries))
258+
for _, entry := range entries {
259+
if !entry.IsDir() {
260+
fileNames = append(fileNames, entry.Name())
261+
}
262+
}
263+
sort.Strings(fileNames)
264+
265+
// Process files in alphabetical order
266+
for _, fileName := range fileNames {
267+
filePath := filepath.Join(dir, fileName)
268+
libPath, enabled := parsePluginConfigFile(filePath)
269+
270+
// Only consider paths, not filenames; linker will be handed
271+
// filenames and will search in standard paths.
272+
if enabled {
273+
libDir := filepath.Dir(libPath)
274+
// Only add if libPath contains a directory component (not just a bare filename)
275+
if libDir != "." && libDir != "" {
276+
// For absolute paths, use as-is; for relative paths, resolve against config dir
277+
if !filepath.IsAbs(libDir) {
278+
libDir = filepath.Join(dir, libDir)
279+
}
280+
paths = append(paths, libDir)
281+
}
282+
}
283+
}
284+
285+
return paths
286+
}
287+
288+
// parsePluginConfigFile parses a single plugin configuration file
289+
// and returns the lib path and whether it's enabled.
290+
func parsePluginConfigFile(filePath string) (string, bool) {
291+
file, err := os.Open(filePath)
292+
if err != nil {
293+
return "", false
294+
}
295+
defer file.Close()
296+
297+
var libPath string
298+
enabled := false
299+
300+
scanner := bufio.NewScanner(file)
301+
for scanner.Scan() {
302+
line := strings.TrimSpace(scanner.Text())
303+
// Skip empty lines and comments
304+
if line == "" || strings.HasPrefix(line, "#") {
305+
continue
306+
}
307+
308+
// Parse key=value pairs
309+
parts := strings.SplitN(line, "=", 2)
310+
if len(parts) != 2 {
311+
continue
312+
}
313+
314+
key := strings.TrimSpace(parts[0])
315+
value := strings.TrimSpace(parts[1])
316+
317+
switch key {
318+
case "lib":
319+
libPath = value
320+
case "enable":
321+
// Accept various forms of enabled
322+
enabled = value == "true" || value == "1" || value == "yes"
323+
}
324+
}
325+
326+
return libPath, enabled
327+
}
328+
213329
// getXRootDVersion runs 'xrootd -v' and extracts the major version number
214330
func getXRootDVersion() string {
215331
xrootdVersionOnce.Do(func() {
@@ -242,6 +358,9 @@ func getPluginVariants(baseName string) []string {
242358

243359
// XRootD uses .so extension on all platforms, including macOS
244360
exts := []string{".so"}
361+
if runtime.GOOS == "darwin" {
362+
exts = append(exts, ".dylib")
363+
}
245364

246365
// Strip any existing extension from baseName
247366
nameWithoutExt := baseName
@@ -272,10 +391,17 @@ func getPluginVariants(baseName string) []string {
272391
return variants
273392
}
274393

275-
// CheckPluginExists checks if a plugin exists in any of the standard library search paths.
394+
// checkPluginExists checks if a plugin exists in any of the standard library search paths.
395+
// If includeClientPaths is true, also includes XRootD client plugin configuration directories.
276396
// It returns true if the plugin is found, false otherwise.
277-
func CheckPluginExists(pluginName string) bool {
397+
func checkPluginExists(pluginName string, includeClientPaths bool) bool {
278398
searchPaths := getPluginSearchPaths()
399+
400+
if includeClientPaths {
401+
searchPaths = append(searchPaths, getClientPluginPaths()...)
402+
searchPaths = deduplicatePaths(searchPaths)
403+
}
404+
279405
variants := getPluginVariants(pluginName)
280406

281407
for _, dir := range searchPaths {
@@ -298,22 +424,22 @@ func ValidateRequiredPlugins(isOrigin bool, xrdConfig *XrootdConfig) error {
298424
if isOrigin {
299425
// Check for libXrdHttpPelican if drop privileges is enabled
300426
if xrdConfig.Server.DropPrivileges {
301-
if !CheckPluginExists("libXrdHttpPelican.so") {
427+
if !checkPluginExists("libXrdHttpPelican.so", false) {
302428
missingPlugins = append(missingPlugins, "libXrdHttpPelican.so")
303429
}
304430
}
305431

306432
// Check for libXrdS3 if using S3 storage type
307433
if xrdConfig.Origin.StorageType == "s3" {
308-
if !CheckPluginExists("libXrdS3.so") {
434+
if !checkPluginExists("libXrdS3.so", false) {
309435
missingPlugins = append(missingPlugins, "libXrdS3.so")
310436
}
311437
}
312438
} else {
313439
// Cache-specific checks
314440
// Check for libXrdHttpPelican if drop privileges is enabled
315441
if xrdConfig.Server.DropPrivileges {
316-
if !CheckPluginExists("libXrdHttpPelican.so") {
442+
if !checkPluginExists("libXrdHttpPelican.so", false) {
317443
missingPlugins = append(missingPlugins, "libXrdHttpPelican.so")
318444
}
319445
}
@@ -322,7 +448,8 @@ func ValidateRequiredPlugins(isOrigin bool, xrdConfig *XrootdConfig) error {
322448
// The cache configuration writes client plugin settings to the cache-client.plugins.d directory
323449
// (see CheckCacheEnv in xrootd_config.go), which configures XRootD to use libXrdClPelican.so
324450
// for handling pelican:// protocol requests.
325-
if !CheckPluginExists("libXrdClPelican.so") {
451+
// Include client plugin configuration paths for this check.
452+
if !checkPluginExists("libXrdClPelican.so", true) {
326453
missingPlugins = append(missingPlugins, "libXrdClPelican.so")
327454
}
328455
}

0 commit comments

Comments
 (0)