/* * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This library 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 Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program (see the file COPYING.LIB for more * details); if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.dcache.chimera; import com.google.common.base.Throwables; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.dao.DuplicateKeyException; import org.springframework.dao.NonTransientDataAccessResourceException; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; import java.io.File; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.dcache.acl.ACE; import org.dcache.acl.enums.RsType; import org.dcache.chimera.posix.Stat; import org.dcache.chimera.store.InodeStorageInformation; import org.dcache.util.Checksum; import static com.google.common.base.Preconditions.checkArgument; import static org.dcache.acl.enums.AceFlags.*; import static org.dcache.chimera.FileSystemProvider.StatCacheOption.NO_STAT; import static org.dcache.chimera.FileSystemProvider.StatCacheOption.STAT; import static org.dcache.util.SqlHelper.tryToClose; /** * * JDBC-FS is THE building block of Chimera. It's an abstraction layer, which * allows to build filesystem on top of a RDBMS. * * * @Immutable * @Threadsafe */ public class JdbcFs implements FileSystemProvider { /** * common error message for unimplemented */ private static final String NOT_IMPL = "this operation is unsupported for this " + "file system; please install a dCache-aware " + "implementation of the file system interface"; /** * logger */ private static final Logger _log = LoggerFactory.getLogger(JdbcFs.class); /** * the number of pnfs levels. Level zero associated with file real * content, which is not our regular case. */ public static final int LEVELS_NUMBER = 7; /** * SQL query engine */ private final FsSqlDriver _sqlDriver; /** * Database connection pool */ private final DataSource _dbConnectionsPool; private final PlatformTransactionManager _tx; private final TransactionDefinition _txDefinition = new DefaultTransactionDefinition(); /* * A dummy constant key force bay cache interface. the value doesn't * matter - only that it's the same value every time */ private static final Integer DUMMY_KEY = 0; /** * Cache value of FsStat */ private final Executor _fsStatUpdateExecutor = Executors.newSingleThreadExecutor( new ThreadFactoryBuilder() .setNameFormat("fsstat-updater-thread-%d") .build() ); private final LoadingCache<Object, FsStat> _fsStatCache = CacheBuilder.newBuilder() .refreshAfterWrite(100, TimeUnit.MILLISECONDS) .build( CacheLoader.asyncReloading(new CacheLoader<Object, FsStat>() { @Override public FsStat load(Object k) throws Exception { return JdbcFs.this.getFsStat0(); } } , _fsStatUpdateExecutor)); /* The PNFS ID to inode number mapping will never change while dCache is running. */ protected final Cache<String, Long> _inoCache = CacheBuilder.newBuilder() .maximumSize(100000) .build(); /* The inode number to PNFS ID mapping will never change while dCache is running. */ protected final Cache<Long, String> _idCache = CacheBuilder.newBuilder() .maximumSize(100000) .build(); /** * current fs id */ private final int _fsId; /** * available space (1 Exabyte) */ static final long AVAILABLE_SPACE = 1152921504606846976L; /** * total files */ static final long TOTAL_FILES = 62914560L; /** * maximal length of an object name in a directory. */ private static final int MAX_NAME_LEN = 255; public JdbcFs(DataSource dataSource, PlatformTransactionManager txManager) throws SQLException, ChimeraFsException { this(dataSource, txManager, 0); } public JdbcFs(DataSource dataSource, PlatformTransactionManager txManager, int id) throws SQLException, ChimeraFsException { _dbConnectionsPool = dataSource; _fsId = id; _tx = txManager; // try to get database dialect specific query engine _sqlDriver = FsSqlDriver.getDriverInstance(dataSource); } private FsInode getWormID() throws ChimeraFsException { return this.path2inode("/admin/etc/config"); } private <T> T inTransaction(FallibleTransactionCallback<T> callback) throws ChimeraFsException { TransactionStatus status = _tx.getTransaction(_txDefinition); T result; try { result = callback.doInTransaction(status); _tx.commit(status); } catch (ChimeraFsException e) { rollbackOnException(status, e); throw e; } catch (NonTransientDataAccessResourceException e) { rollbackOnException(status, e); throw new BackEndErrorHimeraFsException(e.getMessage(), e); } catch (DataAccessException e) { rollbackOnException(status, e); throw new IOHimeraFsException(e.getMessage(), e); } catch (Exception e) { rollbackOnException(status, e); throw e; } return result; } /** * Perform a rollback, handling rollback exceptions properly. * @param status object representing the transaction * @param ex the thrown application exception or error * @throws TransactionException in case of a rollback error */ private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException { _log.debug("Initiating transaction rollback on application exception", ex); try { _tx.rollback(status); } catch (TransactionSystemException e) { _log.error("Application exception overridden by rollback exception", ex); e.initApplicationException(ex); throw e; } catch (RuntimeException e) { _log.error("Application exception overridden by rollback exception", ex); throw e; } catch (Error err) { _log.error("Application exception overridden by rollback error", ex); throw err; } } ////////////////////////////////////////////////////////// //// //// //// Fs operations //// ///////////////////////////////////////////////////////// @Override public FsInode createLink(String src, String dest) throws ChimeraFsException { File file = new File(src); return inTransaction(status -> createLink(path2inode(file.getParent()), file.getName(), dest)); } @Override public FsInode createLink(FsInode parent, String name, String dest) throws ChimeraFsException { return inTransaction(status -> createLink(parent, name, 0, 0, 0644, dest.getBytes())); } @Override public FsInode createLink(FsInode parent, String name, int uid, int gid, int mode, byte[] dest) throws ChimeraFsException { checkNameLength(name); return inTransaction(status -> { FsInode inode; try { Stat stat = parent.statCache(); int group = (stat.getMode() & UnixPermission.S_ISGID) != 0 ? stat.getGid() : gid; inode = _sqlDriver.createFile(parent, name, uid, group, mode, UnixPermission.S_IFLNK); // link is a regular file where content is a reference _sqlDriver.setInodeIo(inode, true); _sqlDriver.write(inode, 0, 0, dest, 0, dest.length); _sqlDriver.copyAcl(parent, inode, RsType.FILE, EnumSet.of(INHERIT_ONLY_ACE, DIRECTORY_INHERIT_ACE, FILE_INHERIT_ACE), EnumSet.of(FILE_INHERIT_ACE)); fillIdCaches(inode); } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(e); } return inode; }); } /** * * create a hard link * * @param parent inode of directory where to create * @param inode * @param name * @return * @throws ChimeraFsException */ @Override public FsInode createHLink(FsInode parent, FsInode inode, String name) throws ChimeraFsException { checkNameLength(name); return inTransaction(status -> { try { _sqlDriver.createEntryInParent(parent, name, inode); _sqlDriver.incNlink(inode); _sqlDriver.incNlink(parent); } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(e); } return inode; }); } @Override public FsInode createFile(String path) throws ChimeraFsException { File file = new File(path); return inTransaction(status -> createFile(path2inode(file.getParent()), file.getName())); } @Override public FsInode createFile(FsInode parent, String name) throws ChimeraFsException { return inTransaction(status -> createFile(parent, name, 0, 0, 0644)); } @Override public FsInode createFileLevel(FsInode inode, int level) throws ChimeraFsException { return inTransaction(status -> _sqlDriver.createLevel(inode, 0, 0, 0644 | UnixPermission.S_IFREG, level)); } @Override public FsInode createFile(FsInode parent, String name, int owner, int group, int mode) throws ChimeraFsException { return createFile(parent, name, owner, group, mode, UnixPermission.S_IFREG); } @Override public FsInode createFile(FsInode parent, String name, int owner, int group, int mode, int type) throws ChimeraFsException { if (name.startsWith(".(")) { // special files only String[] cmd = PnfsCommandProcessor.process(name); if (name.startsWith(".(tag)(") && (cmd.length == 2)) { this.createTag(parent, cmd[1], owner, group, 0644); return new FsInode_TAG(this, parent.ino(), cmd[1]); } if (name.startsWith(".(pset)(") || name.startsWith(".(fset)(")) { /** * This is not 100% correct, as we throw exist even if * someone tries to set attribute for a file which does not exist. */ throw new FileExistsChimeraFsException(name); } if (name.startsWith(".(use)(") && (cmd.length == 3)) { int level = Integer.parseInt(cmd[1]); return inTransaction(status -> { FsInode useInode = _sqlDriver.inodeOf(parent, cmd[2], STAT); if (useInode == null) { throw new FileNotFoundHimeraFsException(cmd[2]); } try { Stat stat = useInode.statCache(); return _sqlDriver.createLevel(useInode, stat.getUid(), stat.getGid(), stat.getMode(), level); } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(name, e); } }); } if (name.startsWith(".(access)(") && (cmd.length == 3)) { int accessLevel = Integer.parseInt(cmd[2]); if (accessLevel == 0) { return id2inode(cmd[1], NO_STAT); } return inTransaction(status -> { try { FsInode accessInode = id2inode(cmd[1], STAT); Stat stat = accessInode.statCache(); return _sqlDriver.createLevel(accessInode, stat.getUid(), stat.getGid(), stat.getMode(), accessLevel); } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(name, e); } }); } return null; } checkNameLength(name); checkArgument(UnixPermission.getType(type) != UnixPermission.S_IFDIR); return inTransaction(status -> { try { Stat parentStat = parent.statCache(); if (parentStat == null) { throw new FileNotFoundHimeraFsException("parent=" + parent.toString()); } if ((parentStat.getMode() & UnixPermission.F_TYPE) != UnixPermission.S_IFDIR) { throw new NotDirChimeraException(parent); } int gid = (parentStat.getMode() & UnixPermission.S_ISGID) != 0 ? parentStat.getGid() : group; FsInode inode = _sqlDriver.createFile(parent, name, owner, gid, mode, type); _sqlDriver.copyAcl(parent, inode, RsType.FILE, EnumSet.of(INHERIT_ONLY_ACE, DIRECTORY_INHERIT_ACE, FILE_INHERIT_ACE), EnumSet.of(FILE_INHERIT_ACE)); fillIdCaches(inode); return inode; } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(e); } }); } /** * Create a new entry with given inode id. * * @param parent * @param id * @param name * @param owner * @param group * @param mode * @param type * @throws ChimeraFsException */ @Override public void createFileWithId(FsInode parent, String id, String name, int owner, int group, int mode, int type) throws ChimeraFsException { checkNameLength(name); checkArgument((type & UnixPermission.S_IFDIR) == 0); inTransaction(status -> { try { if (!parent.exists()) { throw new FileNotFoundHimeraFsException("parent=" + parent.toString()); } if (!parent.isDirectory()) { throw new NotDirChimeraException(parent); } Stat stat = parent.statCache(); int gid = (stat.getMode() & UnixPermission.S_ISGID) != 0 ? stat.getGid() : group; FsInode inode = _sqlDriver.createFileWithId(parent, id, name, owner, gid, mode, type); _sqlDriver.copyAcl(parent, inode, RsType.FILE, EnumSet.of(INHERIT_ONLY_ACE, DIRECTORY_INHERIT_ACE, FILE_INHERIT_ACE), EnumSet.of(FILE_INHERIT_ACE)); fillIdCaches(inode); return null; } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(e); } }); } @Override public String[] listDir(String dir) { try { return listDir(path2inode(dir)); } catch (ChimeraFsException e) { return null; } } @Override public String[] listDir(FsInode dir) throws ChimeraFsException { return _sqlDriver.listDir(dir); } @Override public DirectoryStreamB<HimeraDirectoryEntry> newDirectoryStream(FsInode dir) throws IOHimeraFsException { return _sqlDriver.newDirectoryStream(dir); } @Override public void remove(String path) throws ChimeraFsException { File filePath = new File(path); String parentPath = filePath.getParent(); if (parentPath == null) { throw new InvalidArgumentChimeraException("Cannot delete file system root."); } inTransaction(status -> { FsInode parent = path2inode(parentPath); String name = filePath.getName(); FsInode inode = _sqlDriver.inodeOf(parent, name, STAT); if (inode == null || !_sqlDriver.remove(parent, name, inode)) { throw new FileNotFoundHimeraFsException(path); } return null; }); } @Override public void remove(FsInode directory, String name, FsInode inode) throws ChimeraFsException { inTransaction(status -> { if (!_sqlDriver.remove(directory, name, inode)) { throw new FileNotFoundHimeraFsException(name); } return null; }); } @Override public void remove(FsInode inode) throws ChimeraFsException { inTransaction(status -> { if (inode.type() != FsInodeType.INODE) { // now allowed throw new InvalidArgumentChimeraException("Not a file."); } if (inode.ino() == _sqlDriver.getRootInumber()) { throw new InvalidArgumentChimeraException("Cannot delete file system root."); } if (!inode.exists()) { throw new FileNotFoundHimeraFsException("No such file."); } if (inode.isDirectory() && inode.statCache().getNlink() > 2) { throw new DirNotEmptyHimeraFsException("Directory is not empty"); } _sqlDriver.remove(inode); return null; }); } @Override public Stat stat(String path) throws ChimeraFsException { return stat(path2inode(path)); } @Override public Stat stat(FsInode inode) throws ChimeraFsException { return stat(inode, 0); } @Override public Stat stat(FsInode inode, int level) throws ChimeraFsException { Stat stat = _sqlDriver.stat(inode, level); if (stat == null) { throw new FileNotFoundHimeraFsException(inode.toString()); } if (level == 0) { _inoCache.put(stat.getId(), stat.getIno()); _idCache.put(stat.getIno(), stat.getId()); } return stat; } @Override public FsInode mkdir(String path) throws ChimeraFsException { int li = path.lastIndexOf('/'); String file = path.substring(li + 1); String dir = (li > 1) ? path.substring(0, li) : "/"; return inTransaction(status -> mkdir(path2inode(dir), file)); } @Override public FsInode mkdir(FsInode parent, String name) throws ChimeraFsException { return mkdir(parent, name, 0, 0, 0755); } @Override public FsInode mkdir(FsInode parent, String name, int owner, int group, int mode) throws ChimeraFsException { checkNameLength(name); return inTransaction(status -> { try { if (!parent.isDirectory()) { throw new NotDirChimeraException(parent); } Stat stat = parent.statCache(); int gid, perm; if ((stat.getMode() & UnixPermission.S_ISGID) != 0) { gid = stat.getGid(); perm = mode | UnixPermission.S_ISGID; } else { gid = group; perm = mode; } FsInode inode = _sqlDriver.mkdir(parent, name, owner, gid, perm); _sqlDriver.copyTags(parent, inode); _sqlDriver.copyAcl(parent, inode, RsType.DIR, EnumSet.of(INHERIT_ONLY_ACE), EnumSet.of(FILE_INHERIT_ACE, DIRECTORY_INHERIT_ACE)); fillIdCaches(inode); return inode; } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(name, e); } }); } private void fillIdCaches(FsInode inode) { Stat stat = inode.getStatCache(); if (stat != null) { _inoCache.put(stat.getId(), stat.getIno()); _idCache.put(stat.getIno(), stat.getId()); } } @Override public FsInode mkdir(FsInode parent, String name, int owner, int group, int mode, List<ACE> acl, Map<String, byte[]> tags) throws ChimeraFsException { checkNameLength(name); return inTransaction(status -> { try { if (!parent.isDirectory()) { throw new NotDirChimeraException(parent); } Stat stat = parent.statCache(); int gid, perm; if ((stat.getMode() & UnixPermission.S_ISGID) != 0) { gid = stat.getGid(); perm = mode | UnixPermission.S_ISGID; } else { gid = group; perm = mode; } FsInode inode = _sqlDriver.mkdir(parent, name, owner, gid, perm); _sqlDriver.createTags(inode, owner, gid, perm & 0666, tags); _sqlDriver.writeAcl(inode, RsType.DIR, acl); fillIdCaches(inode); return inode; } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(name, e); } }); } @Override public FsInode path2inode(String path) throws ChimeraFsException { return path2inode(path, new RootInode(this, _sqlDriver.getRootInumber())); } @Override public FsInode path2inode(String path, FsInode startFrom) throws ChimeraFsException { FsInode inode = _sqlDriver.path2inode(startFrom, path); if (inode == null) { throw new FileNotFoundHimeraFsException(path); } fillIdCaches(inode); return inode; } @Override public String inode2id(FsInode inode) throws ChimeraFsException { try { return _idCache.get(inode.ino(), () -> { String id = _sqlDriver.getId(inode); if (id == null) { throw new FileNotFoundHimeraFsException(String.valueOf(inode.ino())); } return id; }); } catch (ExecutionException e) { Throwables.throwIfInstanceOf(e.getCause(), ChimeraFsException.class); Throwables.throwIfInstanceOf(e.getCause(), DataAccessException.class); Throwables.throwIfUnchecked(e.getCause()); throw new RuntimeException(e.getCause()); } } @Override public FsInode id2inode(String id, StatCacheOption option) throws ChimeraFsException { if (option == NO_STAT) { try { return new FsInode(this, _inoCache.get(id, () -> { Long ino = _sqlDriver.getInumber(id); if (ino == null) { throw new FileNotFoundHimeraFsException(id); } return ino; })); } catch (ExecutionException e) { Throwables.throwIfInstanceOf(e.getCause(), ChimeraFsException.class); Throwables.throwIfInstanceOf(e.getCause(), DataAccessException.class); Throwables.throwIfUnchecked(e.getCause()); throw new RuntimeException(e.getCause()); } } else { Stat stat = _sqlDriver.stat(id); if (stat == null) { throw new FileNotFoundHimeraFsException(id); } _inoCache.put(stat.getId(), stat.getIno()); _idCache.put(stat.getIno(), stat.getId()); return new FsInode(this, stat.getIno(), FsInodeType.INODE, 0, stat); } } @Override public List<FsInode> path2inodes(String path) throws ChimeraFsException { return path2inodes(path, new RootInode(this, _sqlDriver.getRootInumber())); } @Override public List<FsInode> path2inodes(String path, FsInode startFrom) throws ChimeraFsException { List<FsInode> inodes = _sqlDriver.path2inodes(startFrom, path); if (inodes.isEmpty()) { throw new FileNotFoundHimeraFsException(path); } fillIdCaches(inodes.get(inodes.size() - 1)); return inodes; } @Override public FsInode inodeOf(FsInode parent, String name, StatCacheOption cacheOption) throws ChimeraFsException { // only if it's PNFS command if (name.startsWith(".(")) { if (name.startsWith(".(id)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode inode = _sqlDriver.inodeOf(parent, cmd[1], NO_STAT); if (inode == null) { throw new FileNotFoundHimeraFsException(cmd[1]); } return new FsInode_ID(this, inode.ino()); } if (name.startsWith(".(use)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 3) { throw new FileNotFoundHimeraFsException(name); } try { int level = Integer.parseInt(cmd[1]); FsInode inode = _sqlDriver.inodeOf(parent, cmd[2], NO_STAT); if (inode == null) { throw new FileNotFoundHimeraFsException(cmd[2]); } if (level <= LEVELS_NUMBER) { stat(inode, level); return new FsInode(this, inode.ino(), level); } else { // is it error or a real file? } } catch (NumberFormatException nfe) { // possible wrong format...ignore } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundHimeraFsException(name); } } if (name.startsWith(".(access)(")) { String[] cmd = PnfsCommandProcessor.process(name); if ((cmd.length < 2) || (cmd.length > 3)) { throw new FileNotFoundHimeraFsException(name); } try { int level = cmd.length == 2 ? 0 : Integer.parseInt(cmd[2]); FsInode useInode = id2inode(cmd[1], STAT); if (level <= LEVELS_NUMBER) { return new FsInode(this, useInode.ino(), level); } else { // is it error or a real file? } } catch (NumberFormatException nfe) { // possible wrong format...ignore } catch (FileNotFoundHimeraFsException e) { throw new FileNotFoundHimeraFsException(name); } } if (name.startsWith(".(nameof)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode nameofInode = new FsInode_NAMEOF(this, id2inode(cmd[1], NO_STAT).ino()); if (!nameofInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return nameofInode; } if (name.startsWith(".(const)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode constInode = new FsInode_CONST(this, parent.ino()); if (!constInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return constInode; } if (name.startsWith(".(parent)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode parentInode = new FsInode_PARENT(this, id2inode(cmd[1], NO_STAT).ino()); if (!parentInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return parentInode; } if (name.startsWith(".(pathof)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode pathofInode = new FsInode_PATHOF(this, id2inode(cmd[1], NO_STAT).ino()); if (!pathofInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return pathofInode; } if (name.startsWith(".(tag)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode tagInode = new FsInode_TAG(this, parent.ino(), cmd[1]); if (!tagInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return tagInode; } if (name.equals(".(tags)()")) { return new FsInode_TAGS(this, parent.ino()); } if (name.startsWith(".(pset)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length < 3) { throw new FileNotFoundHimeraFsException(name); } String[] args = new String[cmd.length - 2]; System.arraycopy(cmd, 2, args, 0, args.length); FsInode psetInode = new FsInode_PSET(this, id2inode(cmd[1], NO_STAT).ino(), args); if (!psetInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return psetInode; } if (name.equals(".(get)(cursor)")) { FsInode pgetInode = new FsInode_PCUR(this, parent.ino()); if (!pgetInode.exists()) { throw new FileNotFoundHimeraFsException(name); } return pgetInode; } if (name.startsWith(".(get)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length < 3) { throw new FileNotFoundHimeraFsException(name); } FsInode inode = _sqlDriver.inodeOf(parent, cmd[1], NO_STAT); if (inode == null) { throw new FileNotFoundHimeraFsException(cmd[1]); } switch(cmd[2]) { case "locality": return new FsInode_PLOC(this, inode.ino()); case "checksum": case "checksums": return new FsInode_PCRC(this, inode.ino()); default: throw new FileNotFoundHimeraFsException(cmd[2]); } } if (name.equals(".(config)")) { return getWormID(); } if (name.startsWith(".(config)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length != 2) { throw new FileNotFoundHimeraFsException(name); } FsInode inode = _sqlDriver.inodeOf(getWormID(), cmd[1], NO_STAT); if (inode == null) { throw new FileNotFoundHimeraFsException(cmd[1]); } return inode; } if (name.startsWith(".(fset)(")) { String[] cmd = PnfsCommandProcessor.process(name); if (cmd.length < 3) { throw new FileNotFoundHimeraFsException(name); } String[] args = new String[cmd.length - 2]; System.arraycopy(cmd, 2, args, 0, args.length); FsInode fsetInode = _sqlDriver.inodeOf(parent, cmd[1], NO_STAT); if (fsetInode == null) { throw new FileNotFoundHimeraFsException(cmd[1]); } return new FsInode_PSET(this, fsetInode.ino(), args); } } FsInode inode = _sqlDriver.inodeOf(parent, name, cacheOption); if (inode == null) { throw new FileNotFoundHimeraFsException(name); } fillIdCaches(inode); inode.setParent(parent); return inode; } @Override public String inode2path(FsInode inode) throws ChimeraFsException { return inode2path(inode, new RootInode(this, _sqlDriver.getRootInumber())); } /** * * @param inode * @param startFrom * @return path of inode starting from startFrom * @throws ChimeraFsException */ @Override public String inode2path(FsInode inode, FsInode startFrom) throws ChimeraFsException { return _sqlDriver.inode2path(inode, startFrom); } @Override public boolean removeFileMetadata(String path, int level) throws ChimeraFsException { return inTransaction(status -> _sqlDriver.removeInodeLevel(path2inode(path), level)); } @Override public FsInode getParentOf(FsInode inode) throws ChimeraFsException { return _sqlDriver.getParentOf(inode); } @Override public void setInodeAttributes(FsInode inode, int level, Stat stat) throws ChimeraFsException { inTransaction(status -> { switch (inode.type()) { case INODE: case PSET: boolean applied = _sqlDriver.setInodeAttributes(inode, level, stat); if (!applied) { /** * there are two cases why update can fail: 1. inode * does not exists 2. we try to set a size on a non file * object */ Stat s = _sqlDriver.stat(inode); if (s == null) { throw new FileNotFoundHimeraFsException(); } if ((s.getMode() & UnixPermission.F_TYPE) == UnixPermission.S_IFDIR) { throw new IsDirChimeraException(inode); } throw new InvalidArgumentChimeraException(); } break; case TAG: if (stat.isDefined(Stat.StatAttributes.MODE)) { _sqlDriver.setTagMode((FsInode_TAG) inode, stat.getMode()); } if (stat.isDefined(Stat.StatAttributes.UID)) { _sqlDriver.setTagOwner((FsInode_TAG) inode, stat.getUid()); } if (stat.isDefined(Stat.StatAttributes.GID)) { _sqlDriver.setTagOwnerGroup((FsInode_TAG) inode, stat.getGid()); } break; } return null; }); } @Override public boolean isIoEnabled(FsInode inode) throws ChimeraFsException { return _sqlDriver.isIoEnabled(inode); } @Override public void setInodeIo(FsInode inode, boolean enable) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.setInodeIo(inode, enable); return null; }); } public int write(FsInode inode, long beginIndex, byte[] data, int offset, int len) throws ChimeraFsException { return this.write(inode, 0, beginIndex, data, offset, len); } @Override public int write(FsInode inode, int level, long beginIndex, byte[] data, int offset, int len) throws ChimeraFsException { return inTransaction(status -> { try { if (level == 0 && !inode.isIoEnabled()) { _log.debug("{}: IO (write) not allowed", inode); return -1; } return _sqlDriver.write(inode, level, beginIndex, data, offset, len); } catch (ForeignKeyViolationException e) { throw new FileNotFoundHimeraFsException(e); } }); } public int read(FsInode inode, long beginIndex, byte[] data, int offset, int len) throws ChimeraFsException { return this.read(inode, 0, beginIndex, data, offset, len); } @Override public int read(FsInode inode, int level, long beginIndex, byte[] data, int offset, int len) throws ChimeraFsException { if (level == 0 && !inode.isIoEnabled()) { _log.debug("{}: IO(read) not allowed", inode); return -1; } return _sqlDriver.read(inode, level, beginIndex, data, offset, len); } @Override public byte[] readLink(String path) throws ChimeraFsException { return readLink(path2inode(path)); } @Override public byte[] readLink(FsInode inode) throws ChimeraFsException { int len = (int) inode.statCache().getSize(); byte[] b = new byte[len]; int n = read(inode, 0, b, 0, b.length); return (n >= 0) ? b : new byte[0]; } @Override public boolean rename(FsInode inode, FsInode srcDir, String source, FsInode destDir, String dest) throws ChimeraFsException { checkNameLength(dest); return inTransaction(status -> { if (!destDir.isDirectory()) { throw new NotDirChimeraException(destDir); } FsInode destInode = _sqlDriver.inodeOf(destDir, dest, STAT); if (destInode != null) { if (destInode.equals(inode)) { // according to POSIX, we are done return false; } /* Renaming into existing is only allowed for the same type of entry. */ if (inode.isDirectory() != destInode.isDirectory()) { throw new FileExistsChimeraFsException(dest); } if (!_sqlDriver.remove(destDir, dest, destInode)) { // Concurrent modification - retry return rename(inode, srcDir, source, destDir, dest); } } if (!_sqlDriver.rename(inode, srcDir, source, destDir, dest)) { throw new FileNotFoundHimeraFsException(source); } return true; }); } ///////////////////////////////////////////////////////////////////// //// //// Location info //// //////////////////////////////////////////////////////////////////// @Override public List<StorageLocatable> getInodeLocations(FsInode inode, int type) throws ChimeraFsException { return _sqlDriver.getInodeLocations(inode, type); } @Override public List<StorageLocatable> getInodeLocations(FsInode inode) throws ChimeraFsException { return _sqlDriver.getInodeLocations(inode); } @Override public void addInodeLocation(FsInode inode, int type, String location) throws ChimeraFsException { inTransaction(status -> { try { _sqlDriver.addInodeLocation(inode, type, location); } catch (ForeignKeyViolationException e) { throw new FileNotFoundHimeraFsException(e); } return null; }); } @Override public void clearInodeLocation(FsInode inode, int type, String location) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.clearInodeLocation(inode, type, location); return null; }); } ///////////////////////////////////////////////////////////////////// //// //// Directory tags handling //// //////////////////////////////////////////////////////////////////// @Override public String[] tags(FsInode inode) throws ChimeraFsException { return _sqlDriver.tags(inode); } @Override public Map<String, byte[]> getAllTags(FsInode inode) throws ChimeraFsException { return _sqlDriver.getAllTags(inode); } @Override public void createTag(FsInode inode, String name) throws ChimeraFsException { this.createTag(inode, name, 0, 0, 0644); } @Override public void createTag(FsInode inode, String name, int uid, int gid, int mode) throws ChimeraFsException { inTransaction(status -> { try { _sqlDriver.createTag(inode, name, uid, gid, mode); return null; } catch (DuplicateKeyException e) { throw new FileExistsChimeraFsException(); } }); } @Override public int setTag(FsInode inode, String tagName, byte[] data, int offset, int len) throws ChimeraFsException { return inTransaction(status -> _sqlDriver.setTag(inode, tagName, data, offset, len)); } @Override public void removeTag(FsInode dir, String tagName) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.removeTag(dir, tagName); return null; }); } @Override public void removeTag(FsInode dir) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.removeTag(dir); return null; }); } @Override public int getTag(FsInode inode, String tagName, byte[] data, int offset, int len) throws ChimeraFsException { return _sqlDriver.getTag(inode, tagName, data, offset, len); } @Override public Stat statTag(FsInode dir, String name) throws ChimeraFsException { return _sqlDriver.statTag(dir, name); } @Override public void setTagOwner(FsInode_TAG tagInode, String name, int owner) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.setTagOwner(tagInode, owner); return null; }); } @Override public void setTagOwnerGroup(FsInode_TAG tagInode, String name, int owner) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.setTagOwnerGroup(tagInode, owner); return null; }); } @Override public void setTagMode(FsInode_TAG tagInode, String name, int mode) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.setTagMode(tagInode, mode); return null; }); } /////////////////////////////////////////////////////////////// // // Id and Co. // @Override public int getFsId() { return _fsId; } /* * Storage Information * * currently it's not allowed to modify it */ @Override public void setStorageInfo(FsInode inode, InodeStorageInformation storageInfo) throws ChimeraFsException { inTransaction(status -> { try { _sqlDriver.setStorageInfo(inode, storageInfo); } catch (ForeignKeyViolationException e) { throw new FileNotFoundHimeraFsException(e); } return null; }); } @Override public InodeStorageInformation getStorageInfo(FsInode inode) throws ChimeraFsException { return _sqlDriver.getStorageInfo(inode); } /* * inode checksum handling */ @Override public void setInodeChecksum(FsInode inode, int type, String checksum) throws ChimeraFsException { inTransaction(status -> { try { _sqlDriver.setInodeChecksum(inode, type, checksum); } catch (ForeignKeyViolationException e) { throw new FileNotFoundHimeraFsException(e); } return null; }); } @Override public void removeInodeChecksum(FsInode inode, int type) throws ChimeraFsException { inTransaction(status -> { _sqlDriver.removeInodeChecksum(inode, type); return null; }); } @Override public Set<Checksum> getInodeChecksums(FsInode inode) throws ChimeraFsException { return new HashSet<>(_sqlDriver.getInodeChecksums(inode)); } /** * Get inode's Access Control List. An empty list is returned if there are no ACL assigned * to the <code>inode</code>. * @param inode * @return acl */ @Override public List<ACE> getACL(FsInode inode) throws ChimeraFsException { return _sqlDriver.readAcl(inode); } /** * Set inode's Access Control List. The existing ACL will be replaced. * @param inode * @param acl */ @Override public void setACL(FsInode inode, List<ACE> acl) throws ChimeraFsException { inTransaction(status -> { boolean modified = _sqlDriver.deleteAcl(inode); if (!acl.isEmpty()) { _sqlDriver.writeAcl(inode, inode.isDirectory() ? RsType.DIR : RsType.FILE, acl); modified = true; } if (modified) { // empty stat will update ctime _sqlDriver.setInodeAttributes(inode, 0, new Stat()); } return null; }); } private static void checkNameLength(String name) throws InvalidNameChimeraException { if (name.length() > MAX_NAME_LEN) { throw new InvalidNameChimeraException("Name too long"); } } public FsStat getFsStat0() throws ChimeraFsException { return _sqlDriver.getFsStat(); } @Override public FsStat getFsStat() throws ChimeraFsException { try { return _fsStatCache.get(DUMMY_KEY); } catch(ExecutionException e) { Throwable t = e.getCause(); Throwables.propagateIfPossible(t, ChimeraFsException.class); throw new ChimeraFsException(t.getMessage(), t); } } /////////////////////////////////////////////////////////////// // // Some information // @Override public String getInfo() { String databaseProductName = "Unknown"; String databaseProductVersion = "Unknown"; Connection dbConnection = null; try { dbConnection = _dbConnectionsPool.getConnection(); if (dbConnection != null) { databaseProductName = dbConnection.getMetaData().getDatabaseProductName(); databaseProductVersion = dbConnection.getMetaData().getDatabaseProductVersion(); } } catch (SQLException se) { // ignored } finally { tryToClose(dbConnection); } StringBuilder sb = new StringBuilder(); sb.append("DB : ").append(_dbConnectionsPool).append('\n'); sb.append("DB Engine : ").append(databaseProductName).append(' ').append(databaseProductVersion).append('\n'); try { sb.append("rootID : ").append(inode2id(new FsInode(this, _sqlDriver.getRootInumber()))).append('\n'); } catch (ChimeraFsException e) { sb.append("rootID : ").append(e.getMessage()).append('\n'); } sb.append("FsId : ").append(_fsId).append('\n'); return sb.toString(); } /* * (non-Javadoc) * @see java.io.Closeable#close() */ @Override public void close() throws IOException { // enforced by the interface } @Override public String getFileLocality(FsInode_PLOC node) throws ChimeraFsException { throw new ChimeraFsException(NOT_IMPL); } /** * To maintain the abstraction level, we relegate the actual * callout to the subclass. */ @Override public void pin(FsInode pnfsid, long lifetime) throws ChimeraFsException { throw new ChimeraFsException(NOT_IMPL); } /** * To maintain the abstraction level, we relegate the actual * callout to the subclass. */ @Override public void unpin(FsInode pnfsid) throws ChimeraFsException { throw new ChimeraFsException(NOT_IMPL); } private interface FallibleTransactionCallback<T> { T doInTransaction(TransactionStatus status) throws ChimeraFsException; } private static class RootInode extends FsInode { public RootInode(FileSystemProvider fs, long ino) { super(fs, ino); } @Override public boolean exists() throws ChimeraFsException { return true; } @Override public boolean isDirectory() { return true; } @Override public boolean isLink() { return false; } @Override public FsInode getParent() { return null; } } }