package org.dcache.chimera.namespace; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Range; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.security.auth.Subject; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.regex.Pattern; import diskCacheV111.namespace.NameSpaceProvider; import diskCacheV111.util.AccessLatency; import diskCacheV111.util.CacheException; import diskCacheV111.util.FileCorruptedCacheException; import diskCacheV111.util.FileExistsCacheException; import diskCacheV111.util.FileIsNewCacheException; import diskCacheV111.util.FileNotFoundCacheException; import diskCacheV111.util.FsPath; import diskCacheV111.util.InvalidMessageCacheException; import diskCacheV111.util.LockedCacheException; import diskCacheV111.util.NotDirCacheException; import diskCacheV111.util.NotFileCacheException; import diskCacheV111.util.PermissionDeniedCacheException; import diskCacheV111.util.PnfsId; import diskCacheV111.util.RetentionPolicy; import diskCacheV111.vehicles.StorageInfo; import dmg.cells.nucleus.CellInfo; import dmg.cells.nucleus.CellInfoProvider; import org.dcache.acl.ACE; import org.dcache.acl.ACL; import org.dcache.auth.Subjects; import org.dcache.chimera.ChimeraFsException; import org.dcache.chimera.DirNotEmptyHimeraFsException; import org.dcache.chimera.DirectoryStreamB; import org.dcache.chimera.FileExistsChimeraFsException; import org.dcache.chimera.FileNotFoundHimeraFsException; import org.dcache.chimera.FileSystemProvider; import org.dcache.chimera.FsInode; import org.dcache.chimera.HimeraDirectoryEntry; import org.dcache.chimera.NotDirChimeraException; import org.dcache.chimera.StorageGenericLocation; import org.dcache.chimera.StorageLocatable; import org.dcache.chimera.UnixPermission; import org.dcache.chimera.posix.Stat; import org.dcache.commons.stats.MonitoringProxy; import org.dcache.commons.stats.RequestCounters; import org.dcache.commons.stats.RequestExecutionTimeGauges; import org.dcache.namespace.CreateOption; import static org.dcache.namespace.FileAttribute.*; import org.dcache.namespace.FileAttribute; import org.dcache.namespace.FileType; import org.dcache.namespace.ListHandler; import org.dcache.namespace.PermissionHandler; import org.dcache.util.Checksum; import org.dcache.util.ChecksumType; import org.dcache.util.Glob; import org.dcache.vehicles.FileAttributes; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.nullToEmpty; import static org.dcache.acl.enums.AccessType.ACCESS_ALLOWED; import static org.dcache.chimera.FileSystemProvider.StatCacheOption.NO_STAT; import static org.dcache.chimera.FileSystemProvider.StatCacheOption.STAT; public class ChimeraNameSpaceProvider implements NameSpaceProvider, CellInfoProvider { private static final int SYMLINK_MODE = 0777; private static final int INHERIT_MODE = -1; public static final String TAG_EXPECTED_SIZE = "ExpectedSize"; public static final String TAG_PATH = "Path"; public static final String TAG_WRITE_TOKEN = "WriteToken"; public static final String TAG_RETENTION_POLICY = "RetentionPolicy"; public static final String TAG_ACCESS_LATENCY = "AccessLatency"; // Similar to (but not the same as) similarly named constants in // PnfsCreateEntryMessage. public static final EnumSet INVALID_CREATE_DIRECTORY_ATTRIBUTES = EnumSet.of(CACHECLASS, CHECKSUM, CREATION_TIME, FLAGS, HSM, LOCATIONS, NLINK, PNFSID, RETENTION_POLICY, SIMPLE_TYPE, SIZE, STORAGECLASS, STORAGEINFO, TYPE); public static final EnumSet INVALID_CREATE_FILE_ATTRIBUTES = EnumSet.of(CACHECLASS, CREATION_TIME, NLINK, PNFSID, STORAGECLASS, STORAGEINFO, SIMPLE_TYPE, TYPE); public static final EnumSet INVALID_CREATE_SYM_LINK_ATTRIBUTES = EnumSet.of(ACCESS_LATENCY, CACHECLASS, CHECKSUM, CREATION_TIME, FLAGS, HSM, LOCATIONS, NLINK, PNFSID, RETENTION_POLICY, SIZE, STORAGECLASS, STORAGEINFO, SIMPLE_TYPE, TYPE); private FileSystemProvider _fs; private ChimeraStorageInfoExtractable _extractor; private static final Logger _log = LoggerFactory.getLogger(ChimeraNameSpaceProvider.class); private boolean _inheritFileOwnership; private boolean _verifyAllLookups; private boolean _aclEnabled; private boolean _allowMoveToDirectoryWithDifferentStorageClass; private PermissionHandler _permissionHandler; private String _uploadDirectory; private String _uploadSubDirectory; private final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { private final AtomicInteger counter = new AtomicInteger(); @Override protected Integer initialValue() { return counter.getAndIncrement(); } }; private final RequestCounters<Method> _counters = new RequestCounters<>(ChimeraNameSpaceProvider.class.getSimpleName()); private final RequestExecutionTimeGauges<Method> _gauges = new RequestExecutionTimeGauges<>(ChimeraNameSpaceProvider.class.getSimpleName()); @Required public void setExtractor(ChimeraStorageInfoExtractable extractor) { _extractor = extractor; } @Required public void setInheritFileOwnership(boolean inherit) { _inheritFileOwnership = inherit; } @Required public void setVerifyAllLookups(boolean verify) { _verifyAllLookups = verify; } @Required public void setAllowMoveToDirectoryWithDifferentStorageClass(boolean allow) { _allowMoveToDirectoryWithDifferentStorageClass = allow; } @Required public void setPermissionHandler(PermissionHandler handler) { _permissionHandler = handler; } @Required public void setFileSystem(FileSystemProvider fs) { _fs = MonitoringProxy.decorateWithMonitoringProxy(new Class[] { FileSystemProvider.class }, fs, _counters, _gauges); } @Required public void setAclEnabled(boolean isEnabled) { _aclEnabled = isEnabled; } /** * Base directory for temporary upload directories. If not an absolute path, the directory * is relative to the user's root directory. */ public void setUploadDirectory(String path) { _uploadDirectory = path; } /** * Sub directory in the upload directory in which to create temporary upload directories. * * May be parametrised by a thread id by inserting %d into the string. This allows Chimera * lock contention on the base directory to be reduced. If used it is important that the * same set threads call into the provider repeatedly as otherwise a large number of * base directories will be created. */ public void setUploadSubDirectory(String path) { _uploadSubDirectory = path; } private ExtendedInode pathToInode(Subject subject, String path) throws ChimeraFsException, CacheException { if (Subjects.isRoot(subject)) { return new ExtendedInode(_fs, _fs.path2inode(path)); } List<FsInode> inodes = _fs.path2inodes(path); if (_verifyAllLookups) { for (FsInode inode: inodes.subList(0, inodes.size() - 1)) { if (inode.isDirectory()) { FileAttributes attributes = getFileAttributesForPermissionHandler(inode); if (_permissionHandler.canLookup(subject, attributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } } } } else { for (FsInode inode: Iterables.skip(Lists.reverse(inodes), 1)) { if (inode.isDirectory()) { FileAttributes attributes = getFileAttributesForPermissionHandler(inode); if (_permissionHandler.canLookup(subject, attributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } /* dCache only checks the lookup permissions of * the last directory of a path. */ break; } } } return new ExtendedInode(_fs, inodes.get(inodes.size() - 1)); } private int defaultUid(Subject subject, ExtendedInode parent) throws UncheckedIOException { try { if (Subjects.isNobody(subject) || _inheritFileOwnership) { return parent.statCache().getUid(); } else { return (int) Subjects.getUid(subject); } } catch (ChimeraFsException e) { throw new UncheckedIOException(e); } } private int defaultGid(Subject subject, ExtendedInode parent) throws UncheckedIOException { try { if (Subjects.isNobody(subject) || _inheritFileOwnership) { return parent.statCache().getGid(); } else { return (int) Subjects.getPrimaryGid(subject); } } catch (ChimeraFsException e) { throw new UncheckedIOException(e); } } @Override public FileAttributes createFile(Subject subject, String path, FileAttributes assignAttributes, Set<FileAttribute> requestedAttributes) throws CacheException { checkArgument(assignAttributes.isUndefined(INVALID_CREATE_FILE_ATTRIBUTES), "Illegal assign attributes: %s", assignAttributes.getDefinedAttributes()); try { File newEntryFile = new File(path); String parentPath = newEntryFile.getParent(); if (parentPath == null) { throw new FileExistsCacheException("File exists: " + path); } ExtendedInode parent = pathToInode(subject, parentPath); if (!Subjects.isRoot(subject)) { FileAttributes attributes = getFileAttributesForPermissionHandler(parent); if (_permissionHandler.canCreateFile(subject, attributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } } ExtendedInode inode; try { int uid = assignAttributes.getOwnerIfPresent().or(() -> defaultUid(subject, parent)); int gid = assignAttributes.getGroupIfPresent().or(() -> defaultGid(subject, parent)); int mode = assignAttributes.getModeIfPresent().or(parent.statCache().getMode() & UMASK_FILE); assignAttributes.undefine(OWNER, OWNER_GROUP, MODE); inode = parent.create(newEntryFile.getName(), uid, gid, mode); } catch (UncheckedIOException e) { throw e.getCause(); } FileAttributes fileAttributes; if (assignAttributes.getDefinedAttributes().isEmpty()) { fileAttributes = getFileAttributes(inode, requestedAttributes); } else { fileAttributes = setFileAttributes(subject, inode.getPnfsId(), assignAttributes, requestedAttributes); } if (parent.getTags().containsKey(TAG_EXPECTED_SIZE)) { ImmutableList<String> size = parent.getTag(TAG_EXPECTED_SIZE); if (!size.isEmpty()) { fileAttributes.setSize(Long.parseLong(size.get(0))); } } return fileAttributes; } catch (NotDirChimeraException e) { throw new NotDirCacheException("Not a directory: " + path); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + path); } catch (FileExistsChimeraFsException e) { throw new FileExistsCacheException("File exists: " + path); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public PnfsId createDirectory(Subject subject, String path, FileAttributes attributes) throws CacheException { checkArgument(attributes.isUndefined(INVALID_CREATE_DIRECTORY_ATTRIBUTES), "Illegal assign attributes: %s", attributes.getDefinedAttributes()); try { File newEntryFile = new File(path); String parentPath = newEntryFile.getParent(); if (parentPath == null) { throw new FileExistsCacheException("File exists: " + path); } ExtendedInode parent = pathToInode(subject, parentPath); ExtendedInode inode; try { int uid = attributes.getOwnerIfPresent().or(() -> defaultUid(subject, parent)); int gid = attributes.getGroupIfPresent().or(() -> defaultGid(subject, parent)); int mode = attributes.getModeIfPresent().or(parent.statCache().getMode() & UMASK_DIR); attributes.undefine(OWNER, OWNER_GROUP, MODE); inode = mkdir(subject, parent, newEntryFile.getName(), uid, gid, mode); } catch (UncheckedIOException e) { throw e.getCause(); } if (!attributes.getDefinedAttributes().isEmpty()) { setFileAttributes(subject, inode.getPnfsId(), attributes, EnumSet.noneOf(FileAttribute.class)); } return inode.getPnfsId(); } catch (NotDirChimeraException e) { throw new NotDirCacheException("Not a directory: " + path); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + path); } catch (FileExistsChimeraFsException e) { throw new FileExistsCacheException("File exists: " + path); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public PnfsId createSymLink(Subject subject, String path, String dest, FileAttributes assignAttributes) throws CacheException { checkArgument(assignAttributes.isUndefined(INVALID_CREATE_SYM_LINK_ATTRIBUTES), "Illegal assign attributes: %s", assignAttributes.getDefinedAttributes()); try { File newEntryFile = new File(path); String parentPath = newEntryFile.getParent(); if (parentPath == null) { throw new FileExistsCacheException("File exists: " + path); } ExtendedInode parent = pathToInode(subject, parentPath); if (!Subjects.isRoot(subject)) { FileAttributes attributes = getFileAttributesForPermissionHandler(parent); if (_permissionHandler.canCreateFile(subject, attributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } } FsInode inode; try { int uid = assignAttributes.getOwnerIfPresent().or(() -> defaultUid(subject, parent)); int gid = assignAttributes.getGroupIfPresent().or(() -> defaultGid(subject, parent)); int mode = assignAttributes.getModeIfPresent().or(SYMLINK_MODE); assignAttributes.undefine(OWNER, OWNER_GROUP, MODE); inode = _fs.createLink(parent, newEntryFile.getName(), uid, gid, mode, dest.getBytes(Charsets.UTF_8)); } catch (UncheckedIOException e) { throw e.getCause(); } PnfsId pnfsid = new PnfsId(inode.getId()); if (!assignAttributes.getDefinedAttributes().isEmpty()) { setFileAttributes(subject, pnfsid, assignAttributes, EnumSet.noneOf(FileAttribute.class)); } return pnfsid; } catch (NotDirChimeraException e) { throw new NotDirCacheException("Not a directory: " + path); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + path); } catch (FileExistsChimeraFsException e) { throw new FileExistsCacheException("File exists: " + path); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } private void checkAllowed(Set<FileType> allowed, ExtendedInode inode) throws ChimeraFsException, NotDirCacheException, NotFileCacheException { if (!allowed.contains(inode.getFileType())) { if (allowed.contains(FileType.DIR)) { throw new NotDirCacheException("Path exists but is not of the expected type"); } else { throw new NotFileCacheException("Path exists but is not of the expected type"); } } } private boolean canDelete(Subject subject, ExtendedInode parent, ExtendedInode inode) throws ChimeraFsException, CacheException { FileAttributes parentAttributes = getFileAttributesForPermissionHandler(parent); FileAttributes fileAttributes = getFileAttributesForPermissionHandler(inode); if (inode.isDirectory()) { if (_permissionHandler.canDeleteDir(subject, parentAttributes, fileAttributes) != ACCESS_ALLOWED) { return false; } } else { if (_permissionHandler.canDeleteFile(subject, parentAttributes, fileAttributes) != ACCESS_ALLOWED) { return false; } } return true; } @Override public FileAttributes deleteEntry(Subject subject, Set<FileType> allowed, PnfsId pnfsId, Set<FileAttribute> attr) throws CacheException { try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, STAT); checkAllowed(allowed, inode); if (!Subjects.isRoot(subject) && !canDelete(subject, inode.getParent(), inode)) { throw new PermissionDeniedCacheException("Access denied: " + pnfsId); } _fs.remove(inode); return getFileAttributes(inode, attr); }catch(FileNotFoundHimeraFsException fnf) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); }catch(DirNotEmptyHimeraFsException e) { throw new CacheException("Directory is not empty: " + pnfsId); }catch(IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public FileAttributes deleteEntry(Subject subject, Set<FileType> allowed, String path, Set<FileAttribute> attr) throws CacheException { try { File filePath = new File(path); String parentPath = filePath.getParent(); if (parentPath == null) { throw new CacheException("Cannot delete file system root."); } ExtendedInode parent = pathToInode(subject, parentPath); String name = filePath.getName(); ExtendedInode inode = parent.inodeOf(name, STAT); checkAllowed(allowed, inode); if (!Subjects.isRoot(subject) && !canDelete(subject, parent, inode)) { throw new PermissionDeniedCacheException("Access denied: " + path); } _fs.remove(parent, name, inode); return getFileAttributes(inode, attr); }catch(FileNotFoundHimeraFsException fnf) { throw new FileNotFoundCacheException("No such file or directory: " + path); }catch(DirNotEmptyHimeraFsException e) { throw new CacheException("Directory is not empty: " + path); }catch(IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public FileAttributes deleteEntry(Subject subject, Set<FileType> allowed, PnfsId pnfsId, String path, Set<FileAttribute> attr) throws CacheException { try { File filePath = new File(path); String parentPath = filePath.getParent(); if (parentPath == null) { throw new CacheException("Cannot delete file system root."); } ExtendedInode parent = pathToInode(subject, parentPath); String name = filePath.getName(); ExtendedInode inode = parent.inodeOf(name, STAT); if (!inode.getPnfsId().equals(pnfsId)) { throw new FileNotFoundCacheException("PNFSID does not correspond to provided file."); } checkAllowed(allowed, inode); if (!Subjects.isRoot(subject) && !canDelete(subject, parent, inode)) { throw new PermissionDeniedCacheException("Access denied: " + path); } _fs.remove(parent, name, inode); return getFileAttributes(inode, attr); } catch (FileNotFoundHimeraFsException fnf) { throw new FileNotFoundCacheException("No such file or directory: " + path); } catch (DirNotEmptyHimeraFsException e) { throw new CacheException("Directory is not empty: " + path); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public void rename(Subject subject, @Nullable PnfsId pnfsId, String sourcePath, String destinationPath, boolean overwrite) throws CacheException { try { /* Resolve the source directory. */ File source = new File(sourcePath); ExtendedInode sourceDir = pathToInode(subject, source.getParent()); Set<FileAttribute> attributes = EnumSet.noneOf(FileAttribute.class); attributes.addAll(_permissionHandler.getRequiredAttributes()); if (!_allowMoveToDirectoryWithDifferentStorageClass) { attributes.add(FileAttribute.STORAGECLASS); attributes.add(FileAttribute.CACHECLASS); } FileAttributes sourceDirAttributes = getFileAttributes(sourceDir,attributes); ExtendedInode inode; if (pnfsId != null) { inode = new ExtendedInode(_fs, pnfsId, STAT); } else { if (!Subjects.isRoot(subject) && _permissionHandler.canLookup(subject, sourceDirAttributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + sourcePath); } inode = sourceDir.inodeOf(source.getName(), STAT); } /* Resolve the target directory. */ File dest = new File(destinationPath); ExtendedInode destDir; FileAttributes destDirAttributes; try { if (dest.getParent().equals(source.getParent())) { destDir = sourceDir; destDirAttributes = sourceDirAttributes; } else { destDir = pathToInode(subject, dest.getParent()); destDirAttributes = getFileAttributes(destDir,attributes); if (!_allowMoveToDirectoryWithDifferentStorageClass) { FileAttributes sourceAttributes = sourceDirAttributes; if (inode.isDirectory()) { sourceAttributes = getFileAttributes(new ExtendedInode(_fs, inode),attributes); } if (!(nullToEmpty(destDirAttributes.getStorageClass()). equals(nullToEmpty(sourceAttributes.getStorageClass())) && nullToEmpty(destDirAttributes.getCacheClass()). equals(nullToEmpty(sourceAttributes.getCacheClass())))) { throw new PermissionDeniedCacheException("Mv denied: " + dest.getParent() + " has different storage tags; use cp."); } } } } catch (FileNotFoundHimeraFsException e) { throw new NotDirCacheException("No such directory: " + dest.getParent()); } /* Permission checks. */ if (!Subjects.isRoot(subject) || !overwrite) { if (!Subjects.isRoot(subject) && _permissionHandler.canRename(subject, sourceDirAttributes, destDirAttributes, inode.isDirectory()) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + pnfsId); } try { ExtendedInode destInode = destDir.inodeOf(dest.getName(), STAT); if (!overwrite) { throw new FileExistsCacheException("File exists:" + destinationPath); } /* Destination name exists and we were requested to * overwrite it. Thus the subject must have delete * permission for the destination name. */ FileAttributes destAttributes = getFileAttributesForPermissionHandler(destInode); if (destInode.isDirectory()) { if (_permissionHandler.canDeleteDir(subject, destDirAttributes, destAttributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + destinationPath); } } else { if (_permissionHandler.canDeleteFile(subject, destDirAttributes, destAttributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + destinationPath); } } } catch (FileNotFoundHimeraFsException e) { /* Destination doesn't exist and we can move the file; * unfortunately there is no way to test this with * Chimera without throwing an exception. */ } } _fs.rename(inode, sourceDir, source.getName(), destDir, dest.getName()); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); } catch (FileExistsChimeraFsException e) { /* With the current implementation of Chimera, I don't * expect this to be thrown. Instead Chimera insists on * overwriting the destination file. */ throw new FileExistsCacheException("File exists:" + destinationPath); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public void addCacheLocation(Subject subject, PnfsId pnfsId, String cacheLocation) throws CacheException { _log.debug ("add cache location {} for {}", cacheLocation, pnfsId); try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, NO_STAT); _fs.addInodeLocation(inode, StorageGenericLocation.DISK, cacheLocation); }catch(FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file: " + pnfsId); } catch (ChimeraFsException e){ _log.error("Exception in addCacheLocation {}", e); throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public List<String> getCacheLocation(Subject subject, PnfsId pnfsId) throws CacheException { try { List<String> locations = new ArrayList<>(); ExtendedInode inode = new ExtendedInode(_fs, pnfsId, NO_STAT); List<StorageLocatable> localyManagerLocations = _fs.getInodeLocations(inode, StorageGenericLocation.DISK); for (StorageLocatable location: localyManagerLocations) { locations.add( location.location() ); } return locations; } catch (ChimeraFsException e){ throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public void clearCacheLocation(Subject subject, PnfsId pnfsId, String cacheLocation, boolean removeIfLast) throws CacheException { _log.debug("clearCacheLocation : {} for {}", cacheLocation, pnfsId) ; try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, NO_STAT); _fs.clearInodeLocation(inode, StorageGenericLocation.DISK, cacheLocation); if (removeIfLast) { List<StorageLocatable> locations = _fs.getInodeLocations(inode, StorageGenericLocation.DISK); if (locations.isEmpty()) { _log.debug("last location cleaned. removing file {}", inode); _fs.remove(inode); } } } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId, e); } catch (ChimeraFsException e){ _log.error("Exception in clearCacheLocation for {} : {}", pnfsId, e); throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public String pnfsidToPath(Subject subject, PnfsId pnfsId) throws CacheException { try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, STAT); if (!inode.exists()) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); } return _fs.inode2path(inode); } catch (ChimeraFsException e){ throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public PnfsId pathToPnfsid(Subject subject, String path, boolean followLink) throws CacheException { try { return pathToInode(subject, path).getPnfsId(); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory " + path); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public void removeFileAttribute(Subject subject, PnfsId pnfsId, String attribute) throws CacheException { try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, NO_STAT).getLevel(2); ChimeraCacheInfo info = new ChimeraCacheInfo(inode); ChimeraCacheInfo.CacheFlags flags = info.getFlags(); flags.remove(attribute); info.writeCacheInfo(inode); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory " + pnfsId); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public void removeChecksum(Subject subject, PnfsId pnfsId, ChecksumType type) throws CacheException { try { _fs.removeInodeChecksum(new ExtendedInode(_fs, pnfsId, NO_STAT), type.getType()); }catch(FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); }catch(ChimeraFsException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public CellInfo getCellInfo(CellInfo info) { return info; } @Override public void getInfo(PrintWriter pw) { pw.append("Acl Enabled: ").println(_aclEnabled); pw.append(_fs.getInfo()); pw.println("Statistics:"); pw.println(_gauges); pw.println(_counters); } @Override public PnfsId getParentOf(Subject subject, PnfsId pnfsId) throws CacheException { try { ExtendedInode inodeOfResource = new ExtendedInode(_fs, pnfsId, NO_STAT); ExtendedInode inodeParent = inodeOfResource.getParent(); if (inodeParent == null) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); } return inodeParent.getPnfsId(); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } private FileAttributes getFileAttributesForPermissionHandler(@Nullable FsInode inode) throws ChimeraFsException, CacheException { return (inode == null) ? null : getFileAttributesForPermissionHandler(new ExtendedInode(_fs, inode)); } private FileAttributes getFileAttributesForPermissionHandler(@Nonnull ExtendedInode inode) throws ChimeraFsException, CacheException { return getFileAttributes(inode, _permissionHandler.getRequiredAttributes()); } private FileAttributes getFileAttributes(ExtendedInode inode, Set<FileAttribute> attr) throws ChimeraFsException, CacheException { if (!inode.exists()) { throw new FileNotFoundHimeraFsException(); } FileAttributes attributes = new FileAttributes(); Stat stat; for (FileAttribute attribute: attr) { switch (attribute) { case ACL: if(_aclEnabled) { attributes.setAcl(inode.getAcl()); } else { attributes.setAcl(null); } break; case ACCESS_LATENCY: AccessLatency accessLatency = _extractor.getAccessLatency(inode); if (accessLatency != null) { attributes.setAccessLatency(accessLatency); } break; case ACCESS_TIME: stat = inode.statCache(); attributes.setAccessTime(stat.getATime()); break; case RETENTION_POLICY: RetentionPolicy retentionPolicy = _extractor.getRetentionPolicy(inode); if (retentionPolicy != null) { attributes.setRetentionPolicy(retentionPolicy); } break; case SIZE: stat = inode.statCache(); // REVISIT when we have another way to detect new files ExtendedInode level2 = inode.getLevel(2); boolean isNew = (stat.getSize() == 0) && !level2.exists() && inode.getLocations().isEmpty(); if (!isNew) { attributes.setSize(stat.getSize()); } break; case CHANGE_TIME: stat = inode.statCache(); attributes.setChangeTime(stat.getCTime()); break; case CREATION_TIME: stat = inode.statCache(); attributes.setCreationTime(stat.getCrTime()); break; case MODIFICATION_TIME: stat = inode.statCache(); attributes.setModificationTime(stat.getMTime()); break; case OWNER: stat = inode.statCache(); attributes.setOwner(stat.getUid()); break; case OWNER_GROUP: stat = inode.statCache(); attributes.setGroup(stat.getGid()); break; case CHECKSUM: attributes.setChecksums(Sets.newHashSet(inode.getChecksums())); break; case LOCATIONS: attributes.setLocations(Lists.newArrayList(inode.getLocations(StorageGenericLocation.DISK))); break; case FLAGS: attributes.setFlags(Maps.newHashMap(inode.getFlags())); break; case SIMPLE_TYPE: case TYPE: attributes.setFileType(inode.getFileType()); break; case MODE: stat = inode.statCache(); attributes.setMode(stat.getMode()); break; case PNFSID: attributes.setPnfsId(inode.getPnfsId()); break; case STORAGEINFO: case STORAGECLASS: case CACHECLASS: case HSM: if (!attributes.isDefined(FileAttribute.STORAGEINFO)) { StorageInfo storageInfo = _extractor.getStorageInfo(inode); attributes.setStorageInfo(storageInfo); attributes.setStorageClass(storageInfo.getStorageClass()); attributes.setCacheClass(storageInfo.getCacheClass()); attributes.setHsm(storageInfo.getHsm()); } break; case NLINK: stat = inode.statCache(); attributes.setNlink(stat.getNlink()); break; default: throw new UnsupportedOperationException("Attribute " + attribute + " not supported yet."); } } return attributes; } @Override public FileAttributes getFileAttributes(Subject subject, PnfsId pnfsId, Set<FileAttribute> attr) throws CacheException { try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, STAT); if (Subjects.isRoot(subject)) { return getFileAttributes(inode, attr); } /* If we have to authorize the check then we fetch * permission handler attributes in addition to the * attributes requested by the caller. */ Set<FileAttribute> required = EnumSet.noneOf(FileAttribute.class); required.addAll(_permissionHandler.getRequiredAttributes()); required.addAll(attr); FileAttributes fileAttributes = getFileAttributes(inode, required); /* The permission check is performed after we fetched the * attributes to avoid fetching the attributes twice. */ if (_permissionHandler.canGetAttributes(subject, fileAttributes, attr) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + pnfsId); } return fileAttributes; } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); } catch (IOException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public FileAttributes setFileAttributes(Subject subject, PnfsId pnfsId, FileAttributes attr, Set<FileAttribute> acquire) throws CacheException { _log.debug("File attributes update: {}", attr.getDefinedAttributes()); try { ExtendedInode inode = new ExtendedInode(_fs, pnfsId, Subjects.isRoot(subject) ? NO_STAT : STAT); if (!Subjects.isRoot(subject)) { FileAttributes attributes = getFileAttributesForPermissionHandler(inode); if (_permissionHandler.canSetAttributes(subject, attributes, attr.getDefinedAttributes()) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + pnfsId); } } /* Update the t_inodes row first (the Stat object) to acquire a FOR UPDATE / FOR NO KEY UPDATE * first. If the inserts into secondary table referring t_inodes would be done first, the * referential integrity check would obtain a FOR SHARE / FOR KEY SHARE on the t_inodes row which * latter would have to be upgraded (potentially leading to deadlocks if that's not possible). */ Stat stat = new Stat(); for (FileAttribute attribute : attr.getDefinedAttributes()) { switch (attribute) { case LOCATIONS: break; case SIZE: stat.setSize(attr.getSize()); break; case MODE: stat.setMode(attr.getMode()); break; case CREATION_TIME: stat.setCrTime(attr.getCreationTime()); break; case CHANGE_TIME: stat.setCTime(attr.getChangeTime()); break; case MODIFICATION_TIME: stat.setMTime(attr.getModificationTime()); break; case ACCESS_TIME: stat.setATime(attr.getAccessTime()); break; case OWNER: stat.setUid(attr.getOwner()); break; case OWNER_GROUP: stat.setGid(attr.getGroup()); break; case CHECKSUM: break; case ACCESS_LATENCY: stat.setAccessLatency(attr.getAccessLatency()); break; case RETENTION_POLICY: stat.setRetentionPolicy(attr.getRetentionPolicy()); break; case FLAGS: break; case ACL: break; case STORAGEINFO: _extractor.setStorageInfo(inode, attr.getStorageInfo()); break; default: throw new UnsupportedOperationException("Attribute " + attribute + " not supported yet."); } } if (stat.isDefinedAny()) { inode.setStat(stat); } if (attr.isDefined(FileAttribute.LOCATIONS)) { for (String location : attr.getLocations()) { _fs.addInodeLocation(inode, StorageGenericLocation.DISK, location); } } if (attr.isDefined(FileAttribute.CHECKSUM)) { for (Checksum sum: attr.getChecksums()) { ChecksumType type = sum.getType(); String value = sum.getValue(); Optional<Checksum> existingSum = Checksum.forType(_fs.getInodeChecksums(inode),type); if (!existingSum.isPresent()) { _fs.setInodeChecksum(inode, type.getType(), value); } else if (!existingSum.get().equals(sum)) { throw new FileCorruptedCacheException(existingSum.asSet(), Collections.singleton(new Checksum(type, value))); } } } if (attr.isDefined(FileAttribute.FLAGS)) { FsInode level2 = new ExtendedInode(_fs, pnfsId, NO_STAT).getLevel(2); ChimeraCacheInfo cacheInfo = new ChimeraCacheInfo(level2); for (Map.Entry<String,String> flag: attr.getFlags().entrySet()) { cacheInfo.getFlags().put(flag.getKey(), flag.getValue()); } cacheInfo.writeCacheInfo(level2); } if (attr.isDefined(FileAttribute.ACL)) { ACL acl = attr.getAcl(); _fs.setACL(inode, acl.getList()); } return getFileAttributes(inode, acquire); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + pnfsId); } catch (IOException e) { _log.error("Exception in setFileAttributes: {}", e); throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } @Override public void list(Subject subject, String path, Glob glob, Range<Integer> range, Set<FileAttribute> attrs, ListHandler handler) throws CacheException { try { Pattern pattern = (glob == null) ? null : glob.toPattern(); ExtendedInode dir = pathToInode(subject, path); if (!dir.isDirectory()) { throw new NotDirCacheException("Not a directory: " + path); } if (!Subjects.isRoot(subject)) { FileAttributes attributes = getFileAttributesForPermissionHandler(dir); if (!dir.isDirectory()) { throw new NotDirCacheException("Not a directory"); } else if (_permissionHandler.canListDir(subject, attributes) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } } int counter = 0; try (DirectoryStreamB<HimeraDirectoryEntry> dirStream = dir .newDirectoryStream()) { for (HimeraDirectoryEntry entry : dirStream) { try { String name = entry.getName(); if (!name.equals(".") && !name.equals("..") && (pattern == null || pattern.matcher(name) .matches()) && range.contains(counter++)) { // FIXME: actually, HimeraDirectoryEntry // already contains most of attributes FileAttributes fa = attrs.isEmpty() ? null : getFileAttributes(new ExtendedInode(_fs, entry.getInode()), attrs); handler.addEntry(name, fa); } } catch (FileNotFoundHimeraFsException e) { /* Not an error; files may be deleted during the * list operation. */ } } } } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + path); } catch (IOException e) { _log.error("Exception in list: {}", e); throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } private ExtendedInode mkdir(Subject subject, ExtendedInode parent, String name, int uid, int gid, int mode) throws ChimeraFsException, CacheException { if (!Subjects.isRoot(subject)) { FileAttributes attributesOfParent = getFileAttributesForPermissionHandler(parent); if (_permissionHandler.canCreateSubDir(subject, attributesOfParent) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + parent.getPath().child(name)); } } return parent.mkdir(name, uid, gid, mode); } private ExtendedInode installSystemDirectory(FsPath path, int mode, List<ACE> acl, Map<String, byte[]> tags) throws ChimeraFsException, CacheException { ExtendedInode inode; try { inode = lookupDirectory(Subjects.ROOT, path); } catch (FileNotFoundCacheException e) { ExtendedInode parentOfPath = installDirectory(Subjects.ROOT, path.parent(), mode); try { inode = parentOfPath.mkdir(path.name(), 0, 0, mode, acl, tags); } catch (FileExistsChimeraFsException e1) { /* Concurrent directory creation. Current transaction is invalid. */ throw new LockedCacheException("Concurrent access prevented this operation from completing. Please retry."); } } return inode; } private ExtendedInode installDirectory(Subject subject, FsPath path, int mode) throws ChimeraFsException, CacheException { ExtendedInode inode; try { inode = lookupDirectory(subject, path); } catch (FileNotFoundCacheException e) { ExtendedInode parentOfPath = installDirectory(subject, path.parent(), mode); try { int uid = defaultUid(subject, parentOfPath); int gid = defaultGid(subject, parentOfPath); inode = mkdir(subject, parentOfPath, path.name(), uid, gid, mode == INHERIT_MODE ? (parentOfPath.statCache().getMode() & UMASK_DIR) : mode); } catch (FileExistsChimeraFsException e1) { /* Concurrent directory creation. Current transaction is invalid. */ throw new LockedCacheException("Concurrent access prevented this operation from completing. Please retry."); } } return inode; } private ExtendedInode lookupDirectory(Subject subject, FsPath path) throws ChimeraFsException, CacheException { try { ExtendedInode inode = pathToInode(subject, path.toString()); if (!inode.isDirectory()) { throw new NotDirCacheException("Not a directory: " + path); } return inode; } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + path, e); } } @Override public FsPath createUploadPath(Subject subject, FsPath path, FsPath rootPath, Long size, AccessLatency al, RetentionPolicy rp, String spaceToken, Set<CreateOption> options) throws CacheException { checkState(_uploadDirectory != null, "Upload directory is not configured."); try { /* Parent directory must exist. */ ExtendedInode parentOfPath = options.contains(CreateOption.CREATE_PARENTS) ? installDirectory(subject, path.parent(), INHERIT_MODE) : lookupDirectory(subject, path.parent()); FileAttributes attributesOfParent = !Subjects.isRoot(subject) ? getFileAttributesForPermissionHandler(parentOfPath) : null; /* File must not exist unless overwrite is enabled. */ try { ExtendedInode inodeOfPath = parentOfPath.inodeOf(path.name(), STAT); if (!options.contains(CreateOption.OVERWRITE_EXISTING) || (inodeOfPath.statCache().getMode() & UnixPermission.S_TYPE) != UnixPermission.S_IFREG) { throw new FileExistsCacheException("File exists: " + path); } /* User must be authorized to delete existing file. */ if (!Subjects.isRoot(subject)) { FileAttributes attributesOfPath = getFileAttributesForPermissionHandler(inodeOfPath); if (_permissionHandler.canDeleteFile(subject, attributesOfParent, attributesOfPath) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } } } catch (FileNotFoundHimeraFsException ignored) { } /* User must be authorized to create file. */ if (!Subjects.isRoot(subject)) { if (_permissionHandler.canCreateFile(subject, attributesOfParent) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Access denied: " + path); } } /* Attributes are inherited from real parent directory. */ int mode = parentOfPath.statCache().getMode() & UnixPermission.S_PERMS; int gid; if ((mode & UnixPermission.S_ISGID) != 0) { gid = parentOfPath.statCache().getGid(); } else if (Subjects.isNobody(subject) || _inheritFileOwnership) { gid = parentOfPath.statCache().getGid(); } else { gid = Ints.checkedCast(Subjects.getPrimaryGid(subject)); } int uid; if (Subjects.isNobody(subject) || _inheritFileOwnership) { uid = parentOfPath.statCache().getUid(); } else { uid = Ints.checkedCast(Subjects.getUid(subject)); } /* ACLs are copied from real parent to the temporary upload directory * such that the upload is allowed (in case write permissions rely * on ACLs) and such that the file will inherit the correct ACLs. */ List<ACE> acl = _fs.getACL(parentOfPath); /* The temporary upload directory has the same tags as the real parent, * except target file specific properties are stored as tags local to * the upload directory. */ Map<String, byte[]> tags = Maps.newHashMap(parentOfPath.getTags()); if (spaceToken != null) { tags.put(TAG_WRITE_TOKEN, spaceToken.getBytes(Charsets.UTF_8)); /* If client provides space token to upload to, the access latency and * retention policy tags of the upload directory must be disregarded. */ tags.remove(TAG_ACCESS_LATENCY); tags.remove(TAG_RETENTION_POLICY); } if (al != null) { tags.put(TAG_ACCESS_LATENCY, al.toString().getBytes(Charsets.UTF_8)); } if (rp != null) { tags.put(TAG_RETENTION_POLICY, rp.toString().getBytes(Charsets.UTF_8)); } if (size != null) { tags.put(TAG_EXPECTED_SIZE, size.toString().getBytes(Charsets.UTF_8)); } tags.put(TAG_PATH, path.toString().getBytes(Charsets.UTF_8)); /* Upload directory may optionally be relative to the user's root path. Whether * that's the case depends on if the configured upload directory is an absolute * or relative path. */ FsPath uploadDirectory = rootPath.resolve(_uploadDirectory); if (_uploadSubDirectory != null) { uploadDirectory = uploadDirectory.chroot(String.format(_uploadSubDirectory, threadId.get())); } /* Upload directory must exist and have the right permissions. */ ExtendedInode inodeOfUploadDir = installSystemDirectory(uploadDirectory, 0711, Collections.emptyList(), Collections.emptyMap()); if (inodeOfUploadDir.statCache().getUid() != 0) { _log.error("Owner must be root: {}", uploadDirectory); throw new CacheException("Owner must be root: " + uploadDirectory); } if ((inodeOfUploadDir.statCache().getMode() & UnixPermission.S_PERMS) != 0711) { _log.error("File mode must be 0711: {}", uploadDirectory); throw new CacheException("File mode must be 0711: " + uploadDirectory); } /* Use cryptographically strong pseudo random UUID to create temporary upload directory. */ UUID uuid = UUID.randomUUID(); _fs.mkdir(inodeOfUploadDir, uuid.toString(), uid, gid, mode, acl, tags); return uploadDirectory.child(uuid.toString()).child(path.name()); } catch (ChimeraFsException e) { _log.error("Problem with database: {}", e.getMessage()); throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } } protected void checkIsTemporaryDirectory(FsPath temporaryPath, FsPath temporaryDir) throws NotFileCacheException, InvalidMessageCacheException { checkState(_uploadDirectory != null, "Upload directory is not configured."); FsPath temporaryDirContainer = getParentOfFile(temporaryDir); if (_uploadDirectory.startsWith("/")) { if (!temporaryDirContainer.hasPrefix(FsPath.create(_uploadDirectory))) { throw new InvalidMessageCacheException( temporaryPath + " is not part of the " + _uploadDirectory + " tree."); } } else { if (!temporaryDirContainer.contains(_uploadDirectory)) { throw new InvalidMessageCacheException( temporaryPath + " is not part of the " + _uploadDirectory + " tree."); } } if (temporaryDir.isRoot()) { throw new InvalidMessageCacheException("A temporary upload path cannot be in the root directory."); } } @Override public FileAttributes commitUpload(Subject subject, FsPath temporaryPath, FsPath finalPath, Set<CreateOption> options, Set<FileAttribute> attributesToFetch) throws CacheException { try { FsPath temporaryDir = getParentOfFile(temporaryPath); FsPath finalDir = getParentOfFile(finalPath); checkIsTemporaryDirectory(temporaryPath, temporaryDir); /* File must have been created... */ ExtendedInode uploadDirInode; ExtendedInode temporaryDirInode; ExtendedInode inodeOfFile; try { uploadDirInode = new ExtendedInode(_fs, _fs.path2inode(temporaryDir.parent().toString())); temporaryDirInode = uploadDirInode.inodeOf(temporaryDir.name(), STAT); inodeOfFile = temporaryDirInode.inodeOf(temporaryPath.name(), STAT); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + temporaryPath, e); } /* ...and upload must have completed... */ ImmutableList<StorageLocatable> locations = inodeOfFile.getLocations(); if (locations.isEmpty()) { throw new FileIsNewCacheException("Upload has not completed."); } /* ...and it must have the correct size. */ ImmutableList<String> size = inodeOfFile.getTag(TAG_EXPECTED_SIZE); if (!size.isEmpty()) { long expectedSize = Long.parseLong(size.get(0)); long actualSize = inodeOfFile.statCache().getSize(); if (expectedSize != actualSize) { throw new FileCorruptedCacheException(expectedSize, actualSize); } } /* Target directory must exist. */ ExtendedInode finalDirInode; try { finalDirInode = new ExtendedInode(_fs, _fs.path2inode(finalDir.toString())); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + finalDir, e); } /* File must not exist unless overwrite is enabled. */ try { ExtendedInode inodeOfExistingFile = finalDirInode.inodeOf(finalPath.name(), STAT); if (!options.contains(CreateOption.OVERWRITE_EXISTING)) { throw new FileExistsCacheException("File exists: " + finalPath); } /* User must be authorized to delete existing file. */ if (!Subjects.isRoot(subject)) { FileAttributes attributesOfParent = getFileAttributesForPermissionHandler(finalDirInode); FileAttributes attributesOfFile = getFileAttributesForPermissionHandler(inodeOfExistingFile); if (_permissionHandler.canDeleteFile(subject, attributesOfParent, attributesOfFile) != ACCESS_ALLOWED) { throw new PermissionDeniedCacheException("Overwrite denied: " + finalPath); } } } catch (FileNotFoundHimeraFsException ignored) { } /* Read file attributes before moving the file. Otherwise the cached parent will * be gone. */ FileAttributes attributes = getFileAttributes(inodeOfFile, attributesToFetch); /* File is moved to correct directory. */ _fs.rename(inodeOfFile, temporaryDirInode, temporaryPath.name(), finalDirInode, finalPath.name()); /* Delete temporary upload directory and any files in it. */ removeRecursively(uploadDirInode, temporaryDir.name(), temporaryDirInode, i -> {}); return attributes; } catch (ChimeraFsException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } catch (NumberFormatException e) { throw new FileCorruptedCacheException("Failed to commit file: " + e.getMessage()); } } @Override public Collection<FileAttributes> cancelUpload(Subject subject, FsPath temporaryPath, FsPath finalPath, Set<FileAttribute> requested, String explanation) throws CacheException { List<FileAttributes> deleted = new ArrayList(); try { FsPath temporaryDir = getParentOfFile(temporaryPath); checkIsTemporaryDirectory(temporaryPath, temporaryDir); /* Temporary upload directory must exist. */ ExtendedInode uploadDirInode; try { uploadDirInode = new ExtendedInode(_fs, _fs.path2inode(temporaryDir.parent().toString())); } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundCacheException("No such file or directory: " + temporaryDir, e); } /* Delete temporary upload directory and any files in it. */ String name = temporaryPath.parent().name(); removeRecursively(uploadDirInode, name, uploadDirInode.inodeOf(name, STAT), i -> { try { if (i.getFileType() == FileType.REGULAR) { deleted.add(getFileAttributes(i, requested)); } } catch (CacheException|ChimeraFsException e) { _log.info("Unable to identify deleted file for upload cancellation: {}", e.toString()); }}); } catch (ChimeraFsException e) { throw new CacheException(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, e.getMessage()); } return deleted; } private void removeRecursively(ExtendedInode parent, String name, ExtendedInode inode, Consumer<ExtendedInode> deleted) throws ChimeraFsException, CacheException { try { if (inode.isDirectory() && inode.stat().getNlink() > 2) { try (DirectoryStreamB<HimeraDirectoryEntry> list = _fs.newDirectoryStream(inode)) { for (HimeraDirectoryEntry entry : list) { String child = entry.getName(); if (!child.equals(".") && !child.equals("..")) { ExtendedInode childInode = new ExtendedInode(_fs, entry.getInode()); removeRecursively(inode, child, childInode, deleted); } } } } deleted.accept(inode); _fs.remove(parent, name, inode); } catch (ChimeraFsException e) { throw e; } catch (IOException e) { throw new ChimeraFsException("Failed to delete directory recursively: " + e); } } private FsPath getParentOfFile(FsPath path) throws NotFileCacheException { try { return path.parent(); } catch (IllegalStateException e) { throw new NotFileCacheException("Not a file: " + path); } } }