Java tutorial
/* dCache - http://www.dcache.org/ * * Copyright (C) 2014 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.xrootd.door; import com.google.common.net.InetAddresses; import io.netty.channel.ChannelHandlerContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.security.auth.Subject; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.ClosedChannelException; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.OptionalLong; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; import diskCacheV111.util.CacheException; import diskCacheV111.util.FileExistsCacheException; import diskCacheV111.util.FileIsNewCacheException; import diskCacheV111.util.FileNotFoundCacheException; import diskCacheV111.util.FsPath; import diskCacheV111.util.NotFileCacheException; import diskCacheV111.util.PermissionDeniedCacheException; import diskCacheV111.util.TimeoutCacheException; import dmg.cells.nucleus.CellPath; import org.dcache.auth.LoginReply; import org.dcache.auth.Subjects; import org.dcache.auth.attributes.LoginAttributes; import org.dcache.auth.attributes.Restriction; import org.dcache.auth.attributes.Restrictions; import org.dcache.auth.attributes.RootDirectory; import org.dcache.cells.AbstractMessageCallback; import org.dcache.namespace.FileAttribute; import org.dcache.util.Checksum; import org.dcache.util.Checksums; import org.dcache.util.list.DirectoryEntry; import org.dcache.vehicles.PnfsListDirectoryMessage; import org.dcache.xrootd.core.XrootdException; import org.dcache.xrootd.core.XrootdSession; import org.dcache.xrootd.protocol.XrootdProtocol; import org.dcache.xrootd.protocol.XrootdProtocol.*; import org.dcache.xrootd.protocol.messages.AwaitAsyncResponse; import org.dcache.xrootd.protocol.messages.CloseRequest; import org.dcache.xrootd.protocol.messages.DirListRequest; import org.dcache.xrootd.protocol.messages.DirListResponse; import org.dcache.xrootd.protocol.messages.MkDirRequest; import org.dcache.xrootd.protocol.messages.MvRequest; import org.dcache.xrootd.protocol.messages.OpenRequest; import org.dcache.xrootd.protocol.messages.OpenResponse; import org.dcache.xrootd.protocol.messages.PrepareRequest; import org.dcache.xrootd.protocol.messages.QueryRequest; import org.dcache.xrootd.protocol.messages.QueryResponse; import org.dcache.xrootd.protocol.messages.RedirectResponse; import org.dcache.xrootd.protocol.messages.RmDirRequest; import org.dcache.xrootd.protocol.messages.RmRequest; import org.dcache.xrootd.protocol.messages.StatRequest; import org.dcache.xrootd.protocol.messages.StatResponse; import org.dcache.xrootd.protocol.messages.StatxRequest; import org.dcache.xrootd.protocol.messages.StatxResponse; import org.dcache.xrootd.protocol.messages.XrootdResponse; import org.dcache.xrootd.tpc.XrootdTpcInfo; import org.dcache.xrootd.tpc.XrootdTpcInfo.Status; import org.dcache.xrootd.util.FileStatus; import org.dcache.xrootd.util.OpaqueStringParser; import org.dcache.xrootd.util.ParseException; import static org.dcache.xrootd.protocol.XrootdProtocol.*; /** * Channel handler which redirects all open requests to a pool. */ public class XrootdRedirectHandler extends ConcurrentXrootdRequestHandler { private static final Logger _log = LoggerFactory.getLogger(XrootdRedirectHandler.class); private final XrootdDoor _door; private Restriction _authz = Restrictions.denyAll(); private OptionalLong _maximumUploadSize = OptionalLong.empty(); private final Map<String, String> _appIoQueues; private FsPath _rootPath; private FsPath _userRootPath; private boolean _isLoggedIn; /** * Custom entries for kXR_Qconfig requests. */ private final Map<String, String> _queryConfig; public XrootdRedirectHandler(XrootdDoor door, FsPath rootPath, ExecutorService executor, Map<String, String> queryConfig, Map<String, String> appIoQueues) { super(executor); _door = door; _rootPath = rootPath; _queryConfig = queryConfig; _appIoQueues = appIoQueues; } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception { if (event instanceof LoginEvent) { loggedIn((LoginEvent) event); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) { if (t instanceof ClosedChannelException) { _log.info("Connection closed"); } else if (t instanceof RuntimeException || t instanceof Error) { Thread me = Thread.currentThread(); me.getUncaughtExceptionHandler().uncaughtException(me, t); } else if (!isHealthCheck() || !(t instanceof IOException)) { _log.warn(t.toString()); } } /** * For client-server read and write, the open, if successful, will always * result in a redirect response to the proper pool; hence no subsequent * requests like sync, read, write or close are expected at the door. * * For third-party copy where dCache is the source, the interactions are as * follows: * * 1. The client opens the file to check availability (the 'placement' * stage). An OK response is followed by the client closing the file. * 2. The client opens the file again with rendezvous metadata. The * client will close the file only when notified by the destination * server that the transfer has completed. * 3. The destination server will open the file for the actual read. * * The order of 2, 3 is not deterministic; hence the response here must * provide for the possibility that the destination server attempts an * open before the client specifies a time-to-live on the rendezvous * point. * * The strategy adopted is therefore as follows: response to (1) is simply * to check file permissions. No metadata is generated and a "dummy" * file handle is returned. For 2 and 3, whichever occurs first * will cause a metadata object to be stored. If the destination server * open occurs first, a wait response will tell the server to try again * in a maximum of 3 seconds; otherwise, if the request matches and occurs * within the ttl, the mover will be started and the destination * redirected to the pool. Response to the client will carry a file * handle but will not actually open a mover. The close from the client * is handled at the door by removing the rendezvous information. * * Third-party copy where dCache is the destination should proceed with * the usual upload transfer creation, but when the client is redirected * to the pool and calls kXR_open there, a third-party client will * be started which does read requests from the source and then writes * the data to the mover channel. */ @Override protected XrootdResponse<OpenRequest> doOnOpen(ChannelHandlerContext ctx, OpenRequest req) throws XrootdException { /* * TODO * * We ought to process this asynchronously to not block the calling thread during * staging or queuing. We should also switch to an asynchronous reply model if * the request is nearline or is queued on a pool. The naive approach to always * use an asynchronous reply model doesn't work because the xrootd 3.x client * introduces an artificial 1 second delay when processing such a response. */ InetSocketAddress localAddress = getDestinationAddress(); InetSocketAddress remoteAddress = getSourceAddress(); 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<>(); } try { FsPath path = createFullPath(req.getPath()); XrootdResponse response = conditionallyHandleThirdPartyRequest(req, opaque, path, remoteAddress.getHostName()); if (response != null) { return response; } FilePerm neededPerm = req.getRequiredPermission(); _log.info("Opening {} for {}", req.getPath(), neededPerm.xmlText()); if (_log.isDebugEnabled()) { logDebugOnOpen(req); } String ioQueue = appSpecificQueue(req); Long size = null; try { String value = opaque.get("oss.asize"); if (value != null) { size = Long.valueOf(value); } } catch (NumberFormatException exception) { _log.warn("Ignoring malformed oss.asize: {}", exception.getMessage()); } UUID uuid = UUID.randomUUID(); opaque.put(UUID_PREFIX, uuid.toString()); /* * In case this is a third-party open as destination, * pass the client information to the pool. */ opaque.put("org.dcache.xrootd.client", getTpcClientId(req.getSession())); String opaqueString = OpaqueStringParser.buildOpaqueString(opaque); /* * Interact with core dCache to open the requested file. */ XrootdTransfer transfer; if (neededPerm == FilePerm.WRITE) { boolean createDir = req.isMkPath(); boolean overwrite = req.isDelete() && !req.isNew(); boolean persistOnSuccessfulClose = (req.getOptions() & XrootdProtocol.kXR_posc) == XrootdProtocol.kXR_posc; // TODO: replace with req.isPersistOnSuccessfulClose() with the latest xrootd4j transfer = _door.write(remoteAddress, path, ioQueue, uuid, createDir, overwrite, size, _maximumUploadSize, localAddress, req.getSubject(), _authz, persistOnSuccessfulClose, ((_isLoggedIn) ? _userRootPath : _rootPath), req.getSession().getDelegatedCredential()); } else { /* * If this is a tpc transfer, then dCache is source here. * * Since we accept (from the destination server) any * valid form of authentication, but without requiring * the associated user to be mapped, we can override * file permission restrictions (since we possess the * 'token' rendezvous key, and the client file permissions * have been checked during its open request). */ Subject subject; if (opaque.get("tpc.key") == null) { subject = req.getSubject(); } else { subject = Subjects.ROOT; } transfer = _door.read(remoteAddress, path, ioQueue, uuid, localAddress, subject, _authz); /* * Again, if this is a tpc transfer, then dCache is source here. * The transfer is initiated by the destination server * (= current session). However, we wish the doorinfo * client in billing to reflect the original user connection, * so we overwrite the transfer client address, which * is unused by the mover. */ String client = opaque.get("tpc.org"); if (client != null) { int index = client.indexOf("@"); if (index != -1 && index < client.length() - 1) { client = client.substring(index + 1); transfer.setClientAddress(new InetSocketAddress(client, 0)); } } } // ok, open was successful InetSocketAddress address = transfer.getRedirect(); _log.info("Redirecting to {}", address); /* xrootd developers say that IPv6 addresses must always be URI quoted. * The spec doesn't require this, but clients depend on it. */ return new RedirectResponse<>(req, InetAddresses.toUriString(address.getAddress()), address.getPort(), opaqueString, ""); } catch (FileNotFoundCacheException e) { return withError(req, kXR_NotFound, "No such file"); } catch (FileExistsCacheException e) { return withError(req, kXR_NotAuthorized, "File already exists"); } catch (TimeoutCacheException e) { return withError(req, kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { return withError(req, kXR_NotAuthorized, e.getMessage()); } catch (FileIsNewCacheException e) { return withError(req, kXR_FileLocked, "File is locked by upload"); } catch (NotFileCacheException e) { return withError(req, kXR_NotFile, "Not a file"); } catch (CacheException e) { return withError(req, kXR_ServerError, String.format("Failed to open file (%s [%d])", e.getMessage(), e.getRc())); } catch (InterruptedException e) { /* Interrupt may be caused by cell shutdown or client * disconnect. If the client disconnected, then the error * message will never reach the client, so saying that the * server shut down is okay. */ return withError(req, kXR_ServerError, "Server shutdown"); } } /** * <p>Special handling of third-party requests. Distinguishes among * several different cases for the open and either returns a response * directly to the caller or proceeds with the usual mover open and * redirect to the pool by returning <code>null</code>. * Also verifies the rendezvous information in the case of the destination * server contacting dCache as source.</p> */ private XrootdResponse<OpenRequest> conditionallyHandleThirdPartyRequest(OpenRequest req, Map<String, String> opaque, FsPath fsPath, String remoteHost) throws CacheException { if (!_door.isReadAllowed(fsPath)) { throw new PermissionDeniedCacheException("Read permission denied"); } if ("placement".equals(opaque.get("tpc.stage"))) { FileStatus status = _door.getFileStatus(fsPath, req.getSubject(), _authz, remoteHost); int fd = _door.nextTpcPlaceholder(); _log.debug("placement response to {} sent to {} with fhandle {}.", req, remoteHost, fd); return new OpenResponse(req, fd, null, null, status); } String tpcKey = opaque.get("tpc.key"); if (tpcKey == null) { _log.debug("{} not a third-party request.", req); return null; // proceed as usual with mover + redirect } String slfn = req.getPath(); XrootdTpcInfo info = _door.createOrGetRendezvousInfo(tpcKey); /* * The request originated from the TPC destination server. * If the client has not yet opened the file here, * tells the destination to wait. If the verification, including * time to live, fails, the request is cancelled. Otherwise, * the destination is allowed to open the mover and get the * normal redirect response. * * Note that the tpc info is created by either the client or the * server, whichever gets here first. Verification of the key * itself is implicit (it has been found in the map); correctness is * further satisfied by matching org, host and file name. */ if (opaque.containsKey("tpc.org")) { info.addInfoFromOpaque(slfn, opaque); switch (info.verify(remoteHost, slfn, opaque.get("tpc.org"))) { case READY: _log.debug("Open request {} from destination server, info {}: " + "OK to proceed.", req, info); /* * This means that the destination server open arrived * second, the client server open succeeded with * the correct permissions; proceed as usual * with mover + redirect. */ return null; case PENDING: _log.debug("Open request {} from destination server, info {}: " + "PENDING client open.", req, info); /* * This means that the destination server open arrived * first; return a wait-retry reply. */ return new AwaitAsyncResponse<>(req, 3); case CANCELLED: String error = info.isExpired() ? "ttl expired" : "dst, path or org" + " did not match"; _log.warn("Open request {} from destination server, info {}: " + "CANCELLED: {}.", req, info, error); _door.removeTpcPlaceholder(info.getFd()); return withError(req, kXR_InvalidRequest, "tpc rendezvous for " + tpcKey + ": " + error); case ERROR: /* * This means that the destination server requested open * before the client did, and the client did not have * read permissions on this file. */ error = "invalid open request (file permissions)."; _log.warn("Open request {} from destination server, info {}: " + "ERROR: {}.", req, info, error); _door.removeTpcPlaceholder(info.getFd()); return withError(req, kXR_InvalidRequest, "tpc rendezvous for " + tpcKey + ": " + error); } } /* * The request originated from the TPC client, indicating dCache * is the source. */ if (opaque.containsKey("tpc.dst")) { _log.debug("Open request {} from client to door as source, " + "info {}: OK.", req, info); FileStatus status = _door.getFileStatus(fsPath, req.getSubject(), _authz, remoteHost); int flags = status.getFlags(); if ((flags & kXR_readable) != kXR_readable) { /* * Update the info with ERROR, so when the destination checks * it, an error can be returned. */ info.setStatus(Status.ERROR); return withError(req, kXR_InvalidRequest, "not allowed to read file."); } info.addInfoFromOpaque(slfn, opaque); return new OpenResponse(req, info.getFd(), null, null, status); } /* * The request originated from the TPC client, indicating dCache * is the destination. Remove the rendezvous info (not needed), * allow mover to start and redirect the client to the pool. * * It is not necessary to delegate the tpc information through the * protocol, particularly the rendezvous key, because it is part of * the opaque data, and if any of the opaque tpc info is missing * from redirected call to the pool, the transfer will fail. * * However, the calling method will need to fetch a delegated * proxy credential and add that to the protocol. */ if (opaque.containsKey("tpc.src")) { _log.debug("Open request {} from client to door as destination: OK;" + "removing info {}.", req, info); _door.removeTpcPlaceholder(info.getFd()); return null; // proceed as usual with mover + redirect } /* * Something went wrong. */ String error = String.format("Request metadata is invalid: %s: %s, %s.", req, fsPath, remoteHost); throw new CacheException(CacheException.THIRD_PARTY_TRANSFER_FAILED, error); } private String getTpcClientId(XrootdSession session) { int pid = session.getPID(); String uname = session.getUserName(); String clientHost; SocketAddress remoteAddress = session.getChannel().remoteAddress(); if (remoteAddress instanceof InetSocketAddress) { clientHost = ((InetSocketAddress) remoteAddress).getHostName(); } else { clientHost = "localhost"; } return uname + "." + pid + "@" + clientHost; } private String appSpecificQueue(OpenRequest req) { String ioqueue = null; String token = req.getSession().getToken(); try { Map<String, String> attr = OpaqueStringParser.getOpaqueMap(token); ioqueue = _appIoQueues.get(attr.get("xrd.appname")); } catch (ParseException e) { _log.debug("Ignoring malformed login token {}: {}", token, e.getMessage()); } return ioqueue; } /** * Will only occur on third-party-copy where dCache acts as source. The * client closes here (whereas the destination server has been redirected * to the pool). */ @Override protected XrootdResponse<CloseRequest> doOnClose(ChannelHandlerContext ctx, CloseRequest msg) throws XrootdException { int fd = msg.getFileHandle(); _log.debug("doOnClose: removing tpc info for {}.", fd); if (_door.removeTpcPlaceholder(fd)) { return withOk(msg); } else { return withError(msg, kXR_InvalidRequest, "Invalid file handle " + fd + " for tpc source close."); } } @Override protected XrootdResponse<StatRequest> doOnStat(ChannelHandlerContext ctx, StatRequest req) throws XrootdException { String path = req.getPath(); try { InetSocketAddress client = getSourceAddress(); return new StatResponse(req, _door.getFileStatus(createFullPath(path), req.getSubject(), _authz, client.getAddress().getHostAddress())); } catch (FileNotFoundCacheException e) { throw new XrootdException(kXR_NotFound, "No such file"); } catch (TimeoutCacheException e) { throw new XrootdException(kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, String.format("Failed to open file (%s [%d])", e.getMessage(), e.getRc())); } } @Override protected XrootdResponse<StatxRequest> doOnStatx(ChannelHandlerContext ctx, StatxRequest req) throws XrootdException { if (req.getPaths().length == 0) { throw new XrootdException(kXR_ArgMissing, "no paths specified"); } try { FsPath[] paths = new FsPath[req.getPaths().length]; for (int i = 0; i < paths.length; i++) { paths[i] = createFullPath(req.getPaths()[i]); } return new StatxResponse(req, _door.getMultipleFileStatuses(paths, req.getSubject(), _authz)); } catch (TimeoutCacheException e) { throw new XrootdException(kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, String.format("Failed to open file (%s [%d])", e.getMessage(), e.getRc())); } } @Override protected XrootdResponse<RmRequest> doOnRm(ChannelHandlerContext ctx, RmRequest req) throws XrootdException { if (req.getPath().isEmpty()) { throw new XrootdException(kXR_ArgMissing, "no path specified"); } _log.info("Trying to delete {}", req.getPath()); try { _door.deleteFile(createFullPath(req.getPath()), req.getSubject(), _authz); return withOk(req); } catch (TimeoutCacheException e) { throw new XrootdException(kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (FileNotFoundCacheException e) { throw new XrootdException(kXR_NotFound, "No such file"); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, String.format("Failed to delete file (%s [%d])", e.getMessage(), e.getRc())); } } @Override protected XrootdResponse<RmDirRequest> doOnRmDir(ChannelHandlerContext ctx, RmDirRequest req) throws XrootdException { if (req.getPath().isEmpty()) { throw new XrootdException(kXR_ArgMissing, "no path specified"); } _log.info("Trying to delete directory {}", req.getPath()); try { _door.deleteDirectory(createFullPath(req.getPath()), req.getSubject(), _authz); return withOk(req); } catch (TimeoutCacheException e) { throw new XrootdException(kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (FileNotFoundCacheException e) { throw new XrootdException(kXR_NotFound, e.getMessage()); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, String.format("Failed to delete directory " + "(%s [%d]).", e.getMessage(), e.getRc())); } } @Override protected XrootdResponse<MkDirRequest> doOnMkDir(ChannelHandlerContext ctx, MkDirRequest req) throws XrootdException { if (req.getPath().isEmpty()) { throw new XrootdException(kXR_ArgMissing, "no path specified"); } _log.info("Trying to create directory {}", req.getPath()); try { _door.createDirectory(createFullPath(req.getPath()), req.shouldMkPath(), req.getSubject(), _authz); return withOk(req); } catch (TimeoutCacheException e) { throw new XrootdException(kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (FileNotFoundCacheException | FileExistsCacheException e) { throw new XrootdException(kXR_FSError, e.getMessage()); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, String.format("Failed to create directory " + "(%s [%d]).", e.getMessage(), e.getRc())); } } @Override protected XrootdResponse<MvRequest> doOnMv(ChannelHandlerContext ctx, MvRequest req) throws XrootdException { String sourcePath = req.getSourcePath(); if (sourcePath.isEmpty()) { throw new XrootdException(kXR_ArgMissing, "no source path specified"); } String targetPath = req.getTargetPath(); if (targetPath.isEmpty()) { throw new XrootdException(kXR_ArgMissing, "no target path specified"); } _log.info("Trying to rename {} to {}", req.getSourcePath(), req.getTargetPath()); try { _door.moveFile(createFullPath(req.getSourcePath()), createFullPath(req.getTargetPath()), req.getSubject(), _authz); return withOk(req); } catch (TimeoutCacheException e) { throw new XrootdException(kXR_ServerError, "Internal timeout"); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (FileNotFoundCacheException e) { throw new XrootdException(kXR_NotFound, String.format("Source file does not exist (%s) ", e.getMessage())); } catch (FileExistsCacheException e) { throw new XrootdException(kXR_FSError, String.format("Will not overwrite existing file " + "(%s).", e.getMessage())); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, String.format("Failed to move file " + "(%s [%d]).", e.getMessage(), e.getRc())); } } @Override protected XrootdResponse<QueryRequest> doOnQuery(ChannelHandlerContext ctx, QueryRequest msg) throws XrootdException { switch (msg.getReqcode()) { case kXR_Qconfig: StringBuilder s = new StringBuilder(); for (String name : msg.getArgs().split(" ")) { switch (name) { case "bind_max": s.append(0); break; case "csname": /** * xrdcp expects lower case names for checksum algorithms * https://github.com/xrootd/xrootd/issues/459 * TODO: revert to upper case then above issue is addressed */ s.append("1:adler32,2:md5"); break; case "tpc": /** * Indicate support for third-party copy by responding * with the protocol version. */ s.append(XrootdProtocol.PROTOCOL_VERSION); break; case "tpcdlg": //TODO NOT YET SUPPORTED default: s.append(_queryConfig.getOrDefault(name, name)); break; } s.append('\n'); } return new QueryResponse(msg, s.toString()); case kXR_Qcksum: try { Set<Checksum> checksums = _door.getChecksums(createFullPath(msg.getArgs()), msg.getSubject(), _authz); if (!checksums.isEmpty()) { Checksum checksum = Checksums.preferrredOrder().min(checksums); /** * xrdcp expects lower case names for checksum algorithms * https://github.com/xrootd/xrootd/issues/459 * TODO: remove toLowerCase() call when above issue is addressed */ return new QueryResponse(msg, checksum.getType().getName().toLowerCase() + " " + checksum.getValue()); } } catch (FileNotFoundCacheException e) { throw new XrootdException(kXR_NotFound, e.getMessage()); } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } catch (CacheException e) { throw new XrootdException(kXR_ServerError, e.getMessage()); } throw new XrootdException(kXR_Unsupported, "No checksum available for this file."); default: return unsupported(ctx, msg); } } @Override protected XrootdResponse<DirListRequest> doOnDirList(ChannelHandlerContext ctx, DirListRequest request) throws XrootdException { try { String listPath = request.getPath(); if (listPath.isEmpty()) { throw new XrootdException(kXR_ArgMissing, "no source path specified"); } _log.info("Listing directory {}", listPath); FsPath fullListPath = createFullPath(listPath); if (request.isDirectoryStat()) { _door.listPath(fullListPath, request.getSubject(), _authz, new StatListCallback(request, fullListPath, ctx), _door.getRequiredAttributesForFileStatus()); } else { _door.listPath(fullListPath, request.getSubject(), _authz, new ListCallback(request, ctx), EnumSet.noneOf(FileAttribute.class)); } return null; } catch (PermissionDeniedCacheException e) { throw new XrootdException(kXR_NotAuthorized, e.getMessage()); } } @Override protected XrootdResponse<PrepareRequest> doOnPrepare(ChannelHandlerContext ctx, PrepareRequest msg) throws XrootdException { return withOk(msg); } private void logDebugOnOpen(OpenRequest req) { int options = req.getOptions(); String openFlags = "options to apply for open path (raw=" + options + " ):"; if ((options & kXR_async) == kXR_async) { openFlags += " kXR_async"; } if ((options & kXR_compress) == kXR_compress) { openFlags += " kXR_compress"; } if ((options & kXR_delete) == kXR_delete) { openFlags += " kXR_delete"; } if ((options & kXR_force) == kXR_force) { openFlags += " kXR_force"; } if ((options & kXR_new) == kXR_new) { openFlags += " kXR_new"; } if ((options & kXR_open_read) == kXR_open_read) { openFlags += " kXR_open_read"; } if ((options & kXR_open_updt) == kXR_open_updt) { openFlags += " kXR_open_updt"; } if ((options & kXR_refresh) == kXR_refresh) { openFlags += " kXR_refresh"; } if ((options & kXR_mkpath) == kXR_mkpath) { openFlags += " kXR_mkpath"; } if ((options & kXR_open_apnd) == kXR_open_apnd) { openFlags += " kXR_open_apnd"; } if ((options & kXR_retstat) == kXR_retstat) { openFlags += " kXR_retstat"; } if ((options & kXR_posc) == kXR_posc) { openFlags += " kXR_posc"; } _log.debug("open flags: {}", openFlags); int mode = req.getUMask(); String s = ""; if ((mode & kXR_ur) == kXR_ur) { s += "r"; } else { s += "-"; } if ((mode & kXR_uw) == kXR_uw) { s += "w"; } else { s += "-"; } if ((mode & kXR_ux) == kXR_ux) { s += "x"; } else { s += "-"; } s += " "; if ((mode & kXR_gr) == kXR_gr) { s += "r"; } else { s += "-"; } if ((mode & kXR_gw) == kXR_gw) { s += "w"; } else { s += "-"; } if ((mode & kXR_gx) == kXR_gx) { s += "x"; } else { s += "-"; } s += " "; if ((mode & kXR_or) == kXR_or) { s += "r"; } else { s += "-"; } if ((mode & kXR_ow) == kXR_ow) { s += "w"; } else { s += "-"; } if ((mode & kXR_ox) == kXR_ox) { s += "x"; } else { s += "-"; } _log.debug("mode to apply to open path: {}", s); } /** * Callback responding to client depending on the list directory messages * it receives from Pnfs via the door. * @author tzangerl * */ private class ListCallback extends AbstractMessageCallback<PnfsListDirectoryMessage> { protected final DirListRequest _request; protected final ChannelHandlerContext _context; protected final DirListResponse.Builder _response; public ListCallback(DirListRequest request, ChannelHandlerContext context) { _request = request; _response = DirListResponse.builder(request); _context = context; } /** * Respond to client if message contains errors. Try to use * meaningful status codes from the xrootd-protocol to map the errors * from PnfsManager. * * @param rc The error code of the message * @param error Object describing the actual error that occurred */ @Override public void failure(int rc, Object error) { switch (rc) { case CacheException.TIMEOUT: respond(_context, withError(_request, kXR_ServerError, "Timeout when trying to list directory: " + error.toString())); break; case CacheException.PERMISSION_DENIED: respond(_context, withError(_request, kXR_NotAuthorized, "Permission to list that directory denied: " + error.toString())); break; case CacheException.FILE_NOT_FOUND: respond(_context, withError(_request, kXR_NotFound, "Path not found")); break; default: respond(_context, withError(_request, kXR_ServerError, "Error when processing list response: " + error.toString())); break; } } /** * Reply to client if no route to PNFS manager was found. * */ @Override public void noroute(CellPath path) { respond(_context, withError(_request, kXR_ServerError, "Could not contact PNFS Manager.")); } /** * In case of a listing success, inspect the message. If the message * is the final listing message, reply with kXR_ok and the full * directory listing. If the message is not the final message, reply * with oksofar and the partial directory listing. * * @param message The PnfsListDirectoryMessage-reply as it was received * from the PNFSManager. */ @Override public void success(PnfsListDirectoryMessage message) { message.getEntries().stream().map(DirectoryEntry::getName).forEach(_response::add); if (message.isFinal()) { respond(_context, _response.buildFinal()); } else { respond(_context, _response.buildPartial()); } } /** * Respond to client in the case of a timeout. */ @Override public void timeout(String error) { respond(_context, withError(_request, kXR_ServerError, "Timeout when trying to list directory!")); } } private class StatListCallback extends ListCallback { private final String _client; protected final FsPath _dirPath; public StatListCallback(DirListRequest request, FsPath dirPath, ChannelHandlerContext context) { super(request, context); _client = getSourceAddress().getAddress().getHostAddress(); _dirPath = dirPath; } @Override public void success(PnfsListDirectoryMessage message) { message.getEntries().stream() .forEach(e -> _response.add(e.getName(), _door.getFileStatus(_request.getSubject(), _authz, _dirPath.child(e.getName()), _client, e.getFileAttributes()))); if (message.isFinal()) { respond(_context, _response.buildFinal()); } else { respond(_context, _response.buildPartial()); } } } /** * Execute login strategy to make an user authorization decision. */ private void loggedIn(LoginEvent event) { LoginReply reply = event.getLoginReply(); _authz = Restrictions.none(); if (reply != null) { _authz = reply.getRestriction(); _isLoggedIn = true; _userRootPath = reply.getLoginAttributes().stream().filter(RootDirectory.class::isInstance).findFirst() .map(RootDirectory.class::cast).map(RootDirectory::getRoot).map(FsPath::create) .orElse(FsPath.ROOT); _maximumUploadSize = LoginAttributes.maximumUploadSize(reply.getLoginAttributes()); } else { _isLoggedIn = false; } } /** * 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. */ private FsPath createFullPath(String path) throws PermissionDeniedCacheException { return _rootPath.chroot(path); } }