Skip to content

Commit

Permalink
dcache-xroot: support relative path in URL
Browse files Browse the repository at this point in the history
Motivation:

Other protocols such as HTTP allow the URL path to
be expressed in terms of the user's root, but xroot
has always required the absolute path of the file.

This can create confusion for experiments which
may not have knowledge of the full namespace file tree
but only the paths relative to the VO root.

Modification:

Support both absolute and relative paths for the
targets given to xroot.  This needs to be done
for both source and destination paths in the
case of TPC.

The procedure is as follows:  the path is
resolved first by trying to use the user root;
if that root is undefined, then the `xrootd.root`
root for the door is used.

In the case of TPC with tokens, the TPC client
logs into the source anonymously with a generated
rendezvous key; since a path relative to the user
root may not be constructed correctly from the door
root, we need to pass the user/effective root
determined at the destination door to the TPC client
via a special dcache opaque property, and prefer
that to the locally determined root at the source
when constructing the full path.

Result:

Both full and relative file paths succeed.

Target: master
Patch: https://rb.dcache.org/r/13923
Requires-notes: yes
Acked-by: Dmitry
Acked-by: Tigran
  • Loading branch information
alrossi committed Mar 23, 2023
1 parent eb3713f commit c491f2d
Showing 1 changed file with 56 additions and 31 deletions.
Expand Up @@ -141,6 +141,26 @@ public class XrootdRedirectHandler extends ConcurrentXrootdRequestHandler {
private static final Logger _log =
LoggerFactory.getLogger(XrootdRedirectHandler.class);

private static final String EFFECTIVE_ROOT_NAME = "org.dcache.effectiveRoot";

private static Map<String, String> safelyExtractOpaque(String opaqueString) {
Map<String, String> opaque;
try {
opaque = OpaqueStringParser.getOpaqueMap(opaqueString);
if (opaque.isEmpty()) {
/*
* create a new HashMap as empty opaque map is immutable
*/
opaque = new HashMap<>();
}
} catch (ParseException e) {
_log.warn("Ignoring malformed open opaque {}: {}", opaqueString,
e.getMessage());
opaque = new HashMap<>();
}
return opaque;
}

private class LoginSessionInfo {

private final Subject subject;
Expand Down Expand Up @@ -329,24 +349,10 @@ protected XrootdResponse<OpenRequest> doOnOpen(ChannelHandlerContext ctx, OpenRe
InetSocketAddress remoteAddress = getSourceAddress();
LoginSessionInfo loginSessionInfo = sessionInfo();

Map<String, String> opaque;

try {
opaque = OpaqueStringParser.getOpaqueMap(req.getOpaque());
if (opaque.isEmpty()) {
/*
* create a new HashMap as empty opaque map is immutable
*/
opaque = new HashMap<>();
}
} catch (ParseException e) {
_log.warn("Ignoring malformed open opaque {}: {}", req.getOpaque(),
e.getMessage());
opaque = new HashMap<>();
}
Map<String, String> opaque = safelyExtractOpaque(req.getOpaque());

try {
FsPath path = createFullPath(req.getPath());
FsPath path = createFullPath(req.getPath(), opaque);

XrootdResponse response
= conditionallyHandleThirdPartyRequest(ctx,
Expand Down Expand Up @@ -413,8 +419,7 @@ protected XrootdResponse<OpenRequest> doOnOpen(ChannelHandlerContext ctx, OpenRe
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction(),
persistOnSuccessfulClose,
((loginSessionInfo.isLoggedIn()) ?
loginSessionInfo.getUserRootPath() : _rootPath),
effectiveRoot(),
req.getSession().getDelegatedCredential(),
opaque);
} else {
Expand Down Expand Up @@ -727,6 +732,11 @@ private InetSocketAddress getRedirect(XrootdTransfer transfer) throws IOExceptio
_log.debug("Open request {} from client to door as destination: OK;"
+ "removing info {}.", req, info);
_door.removeTpcPlaceholder(info.getFd());
/*
* The TPC client will need the resolved path in case it logs in to the
* source using the rendezvous key rather than a delegated credential.
*/
opaque.put(EFFECTIVE_ROOT_NAME, effectiveRoot().toString());
return null; // proceed as usual with mover + redirect
}

Expand Down Expand Up @@ -844,7 +854,8 @@ protected XrootdResponse<StatRequest> doOnStat(ChannelHandlerContext ctx, StatRe
LoginSessionInfo loginSessionInfo = sessionInfo();
InetSocketAddress client = getSourceAddress();

return new StatResponse(req, _door.getFileStatus(createFullPath(path),
return new StatResponse(req, _door.getFileStatus(createFullPath(path,
safelyExtractOpaque(req.getOpaque())),
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction(),
client.getAddress().getHostAddress()));
Expand All @@ -870,7 +881,7 @@ protected XrootdResponse<StatxRequest> doOnStatx(ChannelHandlerContext ctx, Stat
try {
FsPath[] paths = new FsPath[req.getPaths().length];
for (int i = 0; i < paths.length; i++) {
paths[i] = createFullPath(req.getPaths()[i]);
paths[i] = createFullPath(req.getPaths()[i], safelyExtractOpaque(req.getOpaques()[i]));
}
LoginSessionInfo loginSessionInfo = sessionInfo();
Subject subject = loginSessionInfo.getSubject();
Expand Down Expand Up @@ -902,7 +913,7 @@ protected XrootdResponse<RmRequest> doOnRm(ChannelHandlerContext ctx, RmRequest

try {
LoginSessionInfo loginSessionInfo = sessionInfo();
_door.deleteFile(createFullPath(req.getPath()),
_door.deleteFile(createFullPath(req.getPath(), safelyExtractOpaque(req.getOpaque())),
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction());
return withOk(req);
Expand Down Expand Up @@ -930,7 +941,7 @@ protected XrootdResponse<RmDirRequest> doOnRmDir(ChannelHandlerContext ctx, RmDi

try {
LoginSessionInfo loginSessionInfo = sessionInfo();
_door.deleteDirectory(createFullPath(req.getPath()),
_door.deleteDirectory(createFullPath(req.getPath(), safelyExtractOpaque(req.getOpaque())),
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction());
return withOk(req);
Expand All @@ -957,7 +968,7 @@ protected XrootdResponse<MkDirRequest> doOnMkDir(ChannelHandlerContext ctx, MkDi

try {
LoginSessionInfo loginSessionInfo = sessionInfo();
_door.createDirectory(createFullPath(req.getPath()),
_door.createDirectory(createFullPath(req.getPath(), safelyExtractOpaque(req.getOpaque())),
req.shouldMkPath(),
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction());
Expand Down Expand Up @@ -992,8 +1003,8 @@ protected XrootdResponse<MvRequest> doOnMv(ChannelHandlerContext ctx, MvRequest

try {
LoginSessionInfo loginSessionInfo = sessionInfo();
_door.moveFile(createFullPath(req.getSourcePath()),
createFullPath(req.getTargetPath()),
_door.moveFile(createFullPath(req.getSourcePath(), safelyExtractOpaque(req.getSourceOpaque())),
createFullPath(req.getTargetPath(), safelyExtractOpaque(req.getTargetOpaque())),
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction());
return withOk(req);
Expand Down Expand Up @@ -1059,7 +1070,8 @@ protected XrootdResponse<QueryRequest> doOnQuery(ChannelHandlerContext ctx, Quer
ChecksumInfo checksumInfo = new ChecksumInfo(msg.getPath(),
msg.getOpaque());
LoginSessionInfo loginSessionInfo = sessionInfo();
Set<Checksum> checksums = _door.getChecksums(createFullPath(msg.getPath()),
Set<Checksum> checksums = _door.getChecksums(createFullPath(msg.getPath(),
safelyExtractOpaque(msg.getOpaque())),
loginSessionInfo.getSubject(),
loginSessionInfo.getRestriction());
return selectChecksum(checksumInfo, checksums, msg);
Expand All @@ -1082,7 +1094,7 @@ protected XrootdResponse<DirListRequest> doOnDirList(ChannelHandlerContext ctx,
}

_log.info("Listing directory {}", listPath);
FsPath fullListPath = createFullPath(listPath);
FsPath fullListPath = createFullPath(listPath, safelyExtractOpaque(request.getOpaque()));

if (!_door.isReadAllowed(fullListPath)) {
throw new PermissionDeniedCacheException("Permission denied.");
Expand Down Expand Up @@ -1359,12 +1371,25 @@ private void loggedIn(LoginEvent event) {
}

/**
* Forms a full PNFS path. The path is created by concatenating the root path and path. The root
* path is guaranteed to be a prefix of the path returned.
* The path is created by concatenating the root path and path. If the path is already absolute,
* the extra root prefix is first stripped. The root path is guaranteed to be a prefix
* of the path returned.
*/
private FsPath createFullPath(String path)
private FsPath createFullPath(String path, Map<String, String> opaque)
throws PermissionDeniedCacheException {
return _rootPath.chroot(path);
String fromOpaque = opaque.get(EFFECTIVE_ROOT_NAME);
FsPath root = fromOpaque != null ? FsPath.create(fromOpaque) : effectiveRoot();
FsPath fullPath = FsPath.create(path);
if (fullPath.hasPrefix(root)) {
path = fullPath.stripPrefix(root);
}
return root.chroot(path);
}

private FsPath effectiveRoot() {
LoginSessionInfo loginSessionInfo = sessionInfo();
FsPath userRoot = loginSessionInfo != null ? loginSessionInfo.getUserRootPath() : null;
return userRoot != null ? userRoot : _rootPath;
}

/**
Expand Down

0 comments on commit c491f2d

Please sign in to comment.