From 2bdec45f62abf102d126f26142fa27d6a5780e89 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Tue, 30 Sep 2025 14:53:22 +0000 Subject: [PATCH 1/6] Add configuration for in progress location --- config/config.go | 5 +++++ docs/parameters.yaml | 9 +++++++++ param/parameters.go | 1 + param/parameters_struct.go | 2 ++ 4 files changed, 17 insertions(+) diff --git a/config/config.go b/config/config.go index c22e4e1a5..fac248aa9 100644 --- a/config/config.go +++ b/config/config.go @@ -1312,6 +1312,11 @@ func SetServerDefaults(v *viper.Viper) error { v.SetDefault(param.LocalCache_Socket.GetName(), filepath.Join(fcRunLocation, "cache.sock")) v.SetDefault(param.LocalCache_DataLocation.GetName(), filepath.Join(fcRunLocation, "cache")) + // Set the default for Origin.InProgressLocation + if v.GetString(param.Origin_StorageType.GetName()) == "posix" { + v.SetDefault(param.Origin_InProgressLocation.GetName(), filepath.Join(v.GetString(param.Origin_RunLocation.GetName()), "in-progress")) + } + // Any platform-specific paths should go here err := InitServerOSDefaults(v) if err != nil { diff --git a/docs/parameters.yaml b/docs/parameters.yaml index 49c577031..61143d8c3 100644 --- a/docs/parameters.yaml +++ b/docs/parameters.yaml @@ -839,6 +839,15 @@ root_default: /run/pelican/xrootd/origin default: $XDG_RUNTIME_DIR/pelican/origin components: ["origin"] --- +name: Origin.InProgressLocation +description: |+ + A directory where objects being uploaded to the origin are temporarily stored until they are committed under the namespace. + + This is only available for origins that have a StorageType of "posix". +type: filename +default: $Origin.RunLocation/in-progress +components: ["origin"] +--- name: Origin.NamespacePrefix description: |+ [Deprecated] Origin.NamespacePrefix is being deprecated and will be removed in a future release. It's configuration is being replaced by either diff --git a/param/parameters.go b/param/parameters.go index 3a4f5dd58..858869f22 100644 --- a/param/parameters.go +++ b/param/parameters.go @@ -237,6 +237,7 @@ var ( Origin_GlobusConfigLocation = StringParam{"Origin.GlobusConfigLocation"} Origin_HttpAuthTokenFile = StringParam{"Origin.HttpAuthTokenFile"} Origin_HttpServiceUrl = StringParam{"Origin.HttpServiceUrl"} + Origin_InProgressLocation = StringParam{"Origin.InProgressLocation"} Origin_Mode = StringParam{"Origin.Mode"} Origin_NamespacePrefix = StringParam{"Origin.NamespacePrefix"} Origin_RunLocation = StringParam{"Origin.RunLocation"} diff --git a/param/parameters_struct.go b/param/parameters_struct.go index a897e3d30..59b88a843 100644 --- a/param/parameters_struct.go +++ b/param/parameters_struct.go @@ -254,6 +254,7 @@ type Config struct { GlobusConfigLocation string `mapstructure:"globusconfiglocation" yaml:"GlobusConfigLocation"` HttpAuthTokenFile string `mapstructure:"httpauthtokenfile" yaml:"HttpAuthTokenFile"` HttpServiceUrl string `mapstructure:"httpserviceurl" yaml:"HttpServiceUrl"` + InProgressLocation string `mapstructure:"inprogresslocation" yaml:"InProgressLocation"` Mode string `mapstructure:"mode" yaml:"Mode"` Multiuser bool `mapstructure:"multiuser" yaml:"Multiuser"` NamespacePrefix string `mapstructure:"namespaceprefix" yaml:"NamespacePrefix"` @@ -631,6 +632,7 @@ type configWithType struct { GlobusConfigLocation struct { Type string; Value string } HttpAuthTokenFile struct { Type string; Value string } HttpServiceUrl struct { Type string; Value string } + InProgressLocation struct { Type string; Value string } Mode struct { Type string; Value string } Multiuser struct { Type string; Value bool } NamespacePrefix struct { Type string; Value string } From f94dcd9588eff7c5a1a6f8994db725c7175b685d Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Tue, 30 Sep 2025 15:27:36 +0000 Subject: [PATCH 2/6] Configure XRootD to use POSC plugin --- xrootd/resources/xrootd-origin.cfg | 5 ++++ xrootd/xrootd_config.go | 45 +++++++++++++++++++----------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/xrootd/resources/xrootd-origin.cfg b/xrootd/resources/xrootd-origin.cfg index 371617fc9..f0da28716 100644 --- a/xrootd/resources/xrootd-origin.cfg +++ b/xrootd/resources/xrootd-origin.cfg @@ -58,6 +58,11 @@ xrootd.mongstream oss throttle use send json dflthdr 127.0.0.1:{{.Xrootd.LocalMo all.adminpath {{.Origin.RunLocation}} all.pidpath {{.Origin.RunLocation}} {{if eq .Origin.StorageType "posix"}} +# enable POSC plugin +ofs.osslib ++ libXrdOssPosc.so +posc.trace {{.Logging.OriginOss}} +posc.prefix {{.Origin.InProgressLocation}} + oss.localroot {{.Xrootd.Mount}} {{else if eq .Origin.StorageType "s3"}} ofs.osslib libXrdS3.so diff --git a/xrootd/xrootd_config.go b/xrootd/xrootd_config.go index 5962bb32d..08ca701bc 100644 --- a/xrootd/xrootd_config.go +++ b/xrootd/xrootd_config.go @@ -95,22 +95,23 @@ enable = true type ( OriginConfig struct { - Multiuser bool - DirectorTest bool - EnableCmsd bool - EnableMacaroons bool - EnableVoms bool - EnablePublicReads bool - EnableListings bool - SelfTest bool - Concurrency int - Port int - FederationPrefix string - HttpServiceUrl string - HttpAuthTokenFile string - XRootServiceUrl string - RunLocation string - StorageType string + Multiuser bool + DirectorTest bool + EnableCmsd bool + EnableMacaroons bool + EnableVoms bool + EnablePublicReads bool + EnableListings bool + SelfTest bool + Concurrency int + Port int + FederationPrefix string + HttpServiceUrl string + HttpAuthTokenFile string + XRootServiceUrl string + RunLocation string + StorageType string + InProgressLocation string // S3 specific options that are kept top-level because // they aren't specific to each export @@ -461,6 +462,18 @@ func CheckXrootdEnv(server server_structs.XRootDServer) error { " to desired daemon user %v", runtimeDir, username) } + // Create the in-progress directory + if param.Origin_StorageType.GetString() == "posix" { + inProgressDir := param.Origin_InProgressLocation.GetString() + if err = config.MkdirAll(inProgressDir, 0755, uid, gid); err != nil { + return errors.Wrapf(err, "Unable to create in-progress directory %v", inProgressDir) + } + if err = os.Chown(inProgressDir, uid, -1); err != nil { + return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ + " to desired daemon user %v", inProgressDir, username) + } + } + // The scitokens library will write its JWKS cache into the user's home direct by // default. By setting $XDG_CACHE_HOME, we move the JWKS cache into our runtime dir. // This makes the Pelican instance more self-contained inside the runtime dir -- and two From 8225778247e3c39d6f0143e71b5efacdd4607e94 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Fri, 3 Oct 2025 19:43:49 +0000 Subject: [PATCH 3/6] Add logic around in-progress directory creation --- xrootd/xrootd_config.go | 59 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/xrootd/xrootd_config.go b/xrootd/xrootd_config.go index 08ca701bc..b2ffafd83 100644 --- a/xrootd/xrootd_config.go +++ b/xrootd/xrootd_config.go @@ -462,15 +462,60 @@ func CheckXrootdEnv(server server_structs.XRootDServer) error { " to desired daemon user %v", runtimeDir, username) } - // Create the in-progress directory + // Create the in-progress directory with appropriate permissions if param.Origin_StorageType.GetString() == "posix" { inProgressDir := param.Origin_InProgressLocation.GetString() - if err = config.MkdirAll(inProgressDir, 0755, uid, gid); err != nil { - return errors.Wrapf(err, "Unable to create in-progress directory %v", inProgressDir) - } - if err = os.Chown(inProgressDir, uid, -1); err != nil { - return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ - " to desired daemon user %v", inProgressDir, username) + + // Check if the directory exists + if stat, err := os.Stat(inProgressDir); os.IsNotExist(err) { + // Directory doesn't exist - create it with mode 1777 (world-writable + sticky bit) + log.Infof("Creating in-progress directory %v with mode 1777 (world-writable + sticky bit)", inProgressDir) + if err = config.MkdirAll(inProgressDir, 0777|os.ModeSticky, uid, gid); err != nil { + return errors.Wrapf(err, "Unable to create in-progress directory %v", inProgressDir) + } + if err = os.Chown(inProgressDir, uid, -1); err != nil { + return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ + " to desired daemon user %v", inProgressDir, username) + } + } else if err != nil { + // Error checking directory + return errors.Wrapf(err, "Unable to stat in-progress directory %v", inProgressDir) + } else if !stat.IsDir() { + // Path exists but is not a directory + return errors.Errorf("In-progress location %v exists but is not a directory", inProgressDir) + } else { + // Directory exists - check its permissions + mode := stat.Mode() + perm := mode.Perm() + + // Check if world-writable (others have write permission) + isWorldWritable := (perm & 0002) != 0 + + if isWorldWritable { + // Check sticky bit + hasStickyBit := (mode & os.ModeSticky) != 0 + + if hasStickyBit { + log.Infof("In-progress directory %v exists with mode %04o - using shared sticky-bit model", + inProgressDir, perm) + } else { + log.Warningf("In-progress directory %v has mode %04o (world-writable without sticky bit). "+ + "This is UNSAFE! Any user can delete others' files. "+ + "Consider setting permissions to 1777 (chmod 1777 %v)", + inProgressDir, perm, inProgressDir) + } + } else { + // Not world-writable - per-user model + log.Infof("In-progress directory %v has mode %04o (per-user model). "+ + "Users must have pre-created subdirectories (%v/$USER) with proper ownership and permissions.", + inProgressDir, perm, inProgressDir) + } + + // Ensure ownership is correct + if err = os.Chown(inProgressDir, uid, -1); err != nil { + return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ + " to desired daemon user %v", inProgressDir, username) + } } } From 3a6ec472b63b0974d01d5060db574b3b0561baf8 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Mon, 27 Oct 2025 17:55:19 +0000 Subject: [PATCH 4/6] Rework POSC integration --- xrootd/resources/xrootd-origin.cfg | 3 +- xrootd/xrootd_config.go | 123 +++++++++++++++-------------- 2 files changed, 66 insertions(+), 60 deletions(-) diff --git a/xrootd/resources/xrootd-origin.cfg b/xrootd/resources/xrootd-origin.cfg index f0da28716..a4e92e8d2 100644 --- a/xrootd/resources/xrootd-origin.cfg +++ b/xrootd/resources/xrootd-origin.cfg @@ -61,7 +61,8 @@ all.pidpath {{.Origin.RunLocation}} # enable POSC plugin ofs.osslib ++ libXrdOssPosc.so posc.trace {{.Logging.OriginOss}} -posc.prefix {{.Origin.InProgressLocation}} +# posc.prefix {{.Origin.InProgressLocation}} +posc.prefix /in-progress oss.localroot {{.Xrootd.Mount}} {{else if eq .Origin.StorageType "s3"}} diff --git a/xrootd/xrootd_config.go b/xrootd/xrootd_config.go index b2ffafd83..9e7540f58 100644 --- a/xrootd/xrootd_config.go +++ b/xrootd/xrootd_config.go @@ -229,7 +229,7 @@ type xrootdMaintenanceState struct { // CheckOriginXrootdEnv is almost a misnomer -- it does both checking and configuring. In partcicular, // it is responsible for setting up the exports and handling all the symlinking we use // to export our directories. -func CheckOriginXrootdEnv(exportPath string, server server_structs.XRootDServer, uid int, gid int, groupname string) error { +func CheckOriginXrootdEnv(exportPath string, server server_structs.XRootDServer, uid int, gid int, username, groupname string) error { // First we check if our config yaml contains the Exports block. If it does, we use that instead of the older legacy // options for all this configuration originExports, err := server_utils.GetOriginExports() @@ -259,6 +259,68 @@ func CheckOriginXrootdEnv(exportPath string, server server_structs.XRootDServer, } // Set the mount to our export path now that everything is symlinked viper.Set("Xrootd.Mount", exportPath) + + // Create the user specified in-progress directory with appropriate permissions + inProgressDir := param.Origin_InProgressLocation.GetString() + + // Check if the directory exists + if stat, err := os.Stat(inProgressDir); os.IsNotExist(err) { + // Directory doesn't exist - create it with mode 1777 (world-writable + sticky bit) + log.Infof("Creating in-progress directory %v with mode 1777 (world-writable + sticky bit)", inProgressDir) + if err = config.MkdirAll(inProgressDir, 0777|os.ModeSticky, uid, gid); err != nil { + return errors.Wrapf(err, "Unable to create in-progress directory %v", inProgressDir) + } + if err = os.Chown(inProgressDir, uid, -1); err != nil { + return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ + " to desired daemon user %v", inProgressDir, username) + } + } else if err != nil { + // Error checking directory + return errors.Wrapf(err, "Unable to stat in-progress directory %v", inProgressDir) + } else if !stat.IsDir() { + // Path exists but is not a directory + return errors.Errorf("In-progress location %v exists but is not a directory", inProgressDir) + } else { + // Directory exists - check its permissions + mode := stat.Mode() + perm := mode.Perm() + + // Check if world-writable (others have write permission) + isWorldWritable := (perm & 0002) != 0 + + if isWorldWritable { + // Check sticky bit + hasStickyBit := (mode & os.ModeSticky) != 0 + + if hasStickyBit { + log.Infof("In-progress directory %v exists with mode %04o - using shared sticky-bit model", + inProgressDir, perm) + } else { + log.Warningf("In-progress directory %v has mode %04o (world-writable without sticky bit). "+ + "This is UNSAFE! Any user can delete others' files. "+ + "Consider setting permissions to 1777 (chmod 1777 %v)", + inProgressDir, perm, inProgressDir) + } + } else { + // Not world-writable - per-user model + log.Infof("In-progress directory %v has mode %04o (per-user model). "+ + "Users must have pre-created subdirectories (%v/$USER) with proper ownership and permissions.", + inProgressDir, perm, inProgressDir) + } + + // Ensure ownership is correct + if err = os.Chown(inProgressDir, uid, -1); err != nil { + return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ + " to desired daemon user %v", inProgressDir, username) + } + } + // At this point, the in-progress directory is created and owned by the user specified in the config + // We need to symlink the user-specified in-progress directory to the actual in-progress directory + + err = os.Symlink(inProgressDir, filepath.Join(exportPath, "in-progress")) + if err != nil { + return errors.Wrapf(err, "Failed to create in-progress symlink from %v to %v", inProgressDir, filepath.Join(exportPath, "in-progress")) + } } if param.Origin_SelfTest.GetBool() { @@ -462,63 +524,6 @@ func CheckXrootdEnv(server server_structs.XRootDServer) error { " to desired daemon user %v", runtimeDir, username) } - // Create the in-progress directory with appropriate permissions - if param.Origin_StorageType.GetString() == "posix" { - inProgressDir := param.Origin_InProgressLocation.GetString() - - // Check if the directory exists - if stat, err := os.Stat(inProgressDir); os.IsNotExist(err) { - // Directory doesn't exist - create it with mode 1777 (world-writable + sticky bit) - log.Infof("Creating in-progress directory %v with mode 1777 (world-writable + sticky bit)", inProgressDir) - if err = config.MkdirAll(inProgressDir, 0777|os.ModeSticky, uid, gid); err != nil { - return errors.Wrapf(err, "Unable to create in-progress directory %v", inProgressDir) - } - if err = os.Chown(inProgressDir, uid, -1); err != nil { - return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ - " to desired daemon user %v", inProgressDir, username) - } - } else if err != nil { - // Error checking directory - return errors.Wrapf(err, "Unable to stat in-progress directory %v", inProgressDir) - } else if !stat.IsDir() { - // Path exists but is not a directory - return errors.Errorf("In-progress location %v exists but is not a directory", inProgressDir) - } else { - // Directory exists - check its permissions - mode := stat.Mode() - perm := mode.Perm() - - // Check if world-writable (others have write permission) - isWorldWritable := (perm & 0002) != 0 - - if isWorldWritable { - // Check sticky bit - hasStickyBit := (mode & os.ModeSticky) != 0 - - if hasStickyBit { - log.Infof("In-progress directory %v exists with mode %04o - using shared sticky-bit model", - inProgressDir, perm) - } else { - log.Warningf("In-progress directory %v has mode %04o (world-writable without sticky bit). "+ - "This is UNSAFE! Any user can delete others' files. "+ - "Consider setting permissions to 1777 (chmod 1777 %v)", - inProgressDir, perm, inProgressDir) - } - } else { - // Not world-writable - per-user model - log.Infof("In-progress directory %v has mode %04o (per-user model). "+ - "Users must have pre-created subdirectories (%v/$USER) with proper ownership and permissions.", - inProgressDir, perm, inProgressDir) - } - - // Ensure ownership is correct - if err = os.Chown(inProgressDir, uid, -1); err != nil { - return errors.Wrapf(err, "Unable to change ownership of in-progress directory %v"+ - " to desired daemon user %v", inProgressDir, username) - } - } - } - // The scitokens library will write its JWKS cache into the user's home direct by // default. By setting $XDG_CACHE_HOME, we move the JWKS cache into our runtime dir. // This makes the Pelican instance more self-contained inside the runtime dir -- and two @@ -569,7 +574,7 @@ func CheckXrootdEnv(server server_structs.XRootDServer) error { } if server.GetServerType().IsEnabled(server_structs.OriginType) { - err = CheckOriginXrootdEnv(exportPath, server, uid, gid, groupname) + err = CheckOriginXrootdEnv(exportPath, server, uid, gid, username, groupname) } else { err = CheckCacheXrootdEnv(server, uid, gid) } From 2334b144955f883cb91dcee863f7ea42a8585574 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Mon, 15 Dec 2025 22:24:24 +0000 Subject: [PATCH 5/6] Fix POSC plugin symlink creation and validation Check for existing symlinks and validate they point to the correct in-progress directory. Ensure symlink targets are always absolute paths. --- xrootd/resources/xrootd-origin.cfg | 4 +- xrootd/xrootd_config.go | 67 ++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/xrootd/resources/xrootd-origin.cfg b/xrootd/resources/xrootd-origin.cfg index a4e92e8d2..83bf3ef3f 100644 --- a/xrootd/resources/xrootd-origin.cfg +++ b/xrootd/resources/xrootd-origin.cfg @@ -61,7 +61,9 @@ all.pidpath {{.Origin.RunLocation}} # enable POSC plugin ofs.osslib ++ libXrdOssPosc.so posc.trace {{.Logging.OriginOss}} -# posc.prefix {{.Origin.InProgressLocation}} +# posc.prefix is set to /in-progress relative to oss.localroot +# The actual directory location is configured via Origin.InProgressLocation +# and symlinked to /in-progress under the export path posc.prefix /in-progress oss.localroot {{.Xrootd.Mount}} diff --git a/xrootd/xrootd_config.go b/xrootd/xrootd_config.go index 9e7540f58..0cffead5f 100644 --- a/xrootd/xrootd_config.go +++ b/xrootd/xrootd_config.go @@ -315,11 +315,70 @@ func CheckOriginXrootdEnv(exportPath string, server server_structs.XRootDServer, } } // At this point, the in-progress directory is created and owned by the user specified in the config - // We need to symlink the user-specified in-progress directory to the actual in-progress directory + // We need to symlink from the export path (where POSC will look) to the user-specified in-progress directory + inProgressSymlinkPath := filepath.Join(exportPath, "in-progress") + + // Check if symlink already exists and remove it if it points to the wrong location + if linkInfo, err := os.Lstat(inProgressSymlinkPath); err == nil { + if linkInfo.Mode()&os.ModeSymlink != 0 { + // It's a symlink - check if it points to the correct location + existingTarget, err := os.Readlink(inProgressSymlinkPath) + if err != nil { + return errors.Wrapf(err, "Failed to read existing in-progress symlink at %v", inProgressSymlinkPath) + } + // Resolve the symlink target to an absolute path + // If the target is relative, resolve it relative to the symlink's directory + var absExistingTarget string + if filepath.IsAbs(existingTarget) { + absExistingTarget = existingTarget + } else { + absExistingTarget = filepath.Join(filepath.Dir(inProgressSymlinkPath), existingTarget) + absExistingTarget = filepath.Clean(absExistingTarget) + } + // Resolve to absolute path (handles any remaining ".." components) + absExistingTarget, err = filepath.Abs(absExistingTarget) + if err != nil { + return errors.Wrapf(err, "Failed to resolve absolute path of existing symlink target %v", existingTarget) + } + absInProgressDir, err := filepath.Abs(inProgressDir) + if err != nil { + return errors.Wrapf(err, "Failed to resolve absolute path of in-progress directory %v", inProgressDir) + } + if absExistingTarget != absInProgressDir { + log.Infof("Removing existing in-progress symlink at %v (points to %v, should point to %v)", + inProgressSymlinkPath, existingTarget, inProgressDir) + if err = os.Remove(inProgressSymlinkPath); err != nil { + return errors.Wrapf(err, "Failed to remove existing in-progress symlink at %v", inProgressSymlinkPath) + } + } else { + // Symlink already points to the correct location, no need to recreate it + log.Debugf("In-progress symlink at %v already points to the correct location %v", inProgressSymlinkPath, inProgressDir) + // Continue with the rest of the function - don't return early + } + } else { + // Path exists but is not a symlink - this is an error + return errors.Errorf("In-progress path %v exists but is not a symlink", inProgressSymlinkPath) + } + } else if !os.IsNotExist(err) { + return errors.Wrapf(err, "Failed to stat in-progress symlink at %v", inProgressSymlinkPath) + } - err = os.Symlink(inProgressDir, filepath.Join(exportPath, "in-progress")) - if err != nil { - return errors.Wrapf(err, "Failed to create in-progress symlink from %v to %v", inProgressDir, filepath.Join(exportPath, "in-progress")) + // Create symlink: the symlink at exportPath/in-progress points to inProgressDir + // This allows POSC (configured with posc.prefix /in-progress) to find the actual directory + // POSC will resolve /in-progress relative to oss.localroot (which is exportPath) + // Only create the symlink if it doesn't already exist or was removed above + if _, err := os.Lstat(inProgressSymlinkPath); os.IsNotExist(err) { + // Ensure we use an absolute path for the symlink target to avoid issues + // if the working directory changes + absInProgressDir, err := filepath.Abs(inProgressDir) + if err != nil { + return errors.Wrapf(err, "Failed to resolve absolute path of in-progress directory %v", inProgressDir) + } + err = os.Symlink(absInProgressDir, inProgressSymlinkPath) + if err != nil { + return errors.Wrapf(err, "Failed to create in-progress symlink from %v to %v", absInProgressDir, inProgressSymlinkPath) + } + log.Debugf("Created in-progress symlink at %v pointing to %v", inProgressSymlinkPath, absInProgressDir) } } From 0d1515a8c3b10a151d7147fd413175f715f55692 Mon Sep 17 00:00:00 2001 From: Patrick Brophy Date: Tue, 16 Dec 2025 20:36:05 +0000 Subject: [PATCH 6/6] Copy over posc plugin to xrootd plugin directory --- github_scripts/osx_install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/github_scripts/osx_install.sh b/github_scripts/osx_install.sh index 44de64d31..e6f3926e6 100755 --- a/github_scripts/osx_install.sh +++ b/github_scripts/osx_install.sh @@ -88,6 +88,7 @@ echo "Will install into: $xrootd_libdir" sudo mkdir -p "$xrootd_libdir" sudo ln -s "$PWD/release_dir/lib/libXrdHTTPServer-5.so" "$xrootd_libdir" sudo ln -s "$PWD/release_dir/lib/libXrdS3-5.so" "$xrootd_libdir" +sudo ln -s "$PWD/release_dir/lib/libXrdOssPosc-5.so" "$xrootd_libdir" popd popd