/* * 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.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException; import org.springframework.jdbc.LobRetrievalFailureException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; import javax.sql.DataSource; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.ServiceLoader; import diskCacheV111.util.AccessLatency; import diskCacheV111.util.RetentionPolicy; import org.dcache.acl.ACE; import org.dcache.acl.enums.AceFlags; import org.dcache.acl.enums.AceType; import org.dcache.acl.enums.RsType; import org.dcache.acl.enums.Who; import org.dcache.chimera.posix.Stat; import org.dcache.chimera.store.InodeStorageInformation; import org.dcache.chimera.spi.DBDriverProvider; import org.dcache.util.Checksum; import org.dcache.util.ChecksumType; import static java.util.stream.Collectors.toList; import static org.dcache.chimera.FileSystemProvider.StatCacheOption; import static org.dcache.chimera.FileSystemProvider.StatCacheOption.STAT; /** * SQL driver * * */ public class FsSqlDriver { /** * logger */ private static final Logger _log = LoggerFactory.getLogger(FsSqlDriver.class); private static final ServiceLoader<DBDriverProvider> ALL_PROVIDERS = ServiceLoader.load(DBDriverProvider.class); /** * default file IO mode */ private static final int IOMODE_ENABLE = 1; private static final int IOMODE_DISABLE = 0; protected final int _ioMode; final JdbcTemplate _jdbc; private final long _root; /** * this is a utility class which is issues SQL queries on database * */ protected FsSqlDriver(DataSource dataSource) throws ChimeraFsException { _ioMode = Boolean.valueOf(System.getProperty("chimera.inodeIoMode")) ? IOMODE_ENABLE : IOMODE_DISABLE; _jdbc = new JdbcTemplate(dataSource); _jdbc.setExceptionTranslator(new SQLErrorCodeSQLExceptionTranslator(dataSource) { @Override protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) { if (isForeignKeyError(sqlEx)) { return new ForeignKeyViolationException(buildMessage(task, sql, sqlEx), sqlEx); } return super.customTranslate(task, sql, sqlEx); } }); Long root = getInumber("000000000000000000000000000000000000"); if (root == null) { throw new FileNotFoundHimeraFsException("Root inode does not exist."); } _root = root; } long getRootInumber() { return _root; } /** * Get FsStat for a given filesystem. * @return fsStat */ FsStat getFsStat() { return _jdbc.queryForObject( "SELECT count(*) AS usedFiles, SUM(isize) AS usedSpace FROM t_inodes WHERE itype=32768", (rs, rowNum) -> { long usedFiles = rs.getLong("usedFiles"); long usedSpace = rs.getLong("usedSpace"); return new FsStat(JdbcFs.AVAILABLE_SPACE, JdbcFs.TOTAL_FILES, usedSpace, usedFiles); }); } /** * * creates a new inode and an entry name in parent directory. * Parent reference count and modification time is updated. * * @param parent * @param name * @param owner * @param group * @param mode * @param type * @return */ FsInode createFile(FsInode parent, String name, int owner, int group, int mode, int type) { return createFileWithId(parent, FsInode.generateNewID(), name, owner, group, mode, type); } /** * * Creates a new entry with given inode is in parent directory. * Parent reference count and modification time is updated. * * @param parent * @param id * @param name * @param owner * @param group * @param mode * @param type * @return */ FsInode createFileWithId(FsInode parent, String id, String name, int owner, int group, int mode, int type) { return createInodeInParent(parent, name, id, owner, group, mode, type, 1, 0); } Long getInumber(String id) { return _jdbc.query( "SELECT inumber FROM t_inodes WHERE ipnfsid = ?", ps -> ps.setString(1, id), rs -> rs.next() ? rs.getLong("inumber") : null); } String getId(FsInode inode) { return _jdbc.query( "SELECT ipnfsid FROM t_inodes WHERE inumber=?", ps -> ps.setLong(1, inode.ino()), rs -> rs.next() ? rs.getString("ipnfsid") : null); } /** * returns list of files in the directory. If there is no entries, * empty list is returned. inode is not tested to be a directory * * @param dir * @return */ String[] listDir(FsInode dir) { List<String> directoryList = _jdbc.queryForList("SELECT iname FROM t_dirs WHERE iparent=?", String.class, dir.ino()); return Stream.concat(Stream.of(".", ".."), directoryList.stream()).toArray(String[]::new); } /** * the same as listDir, but array of {@HimeraDirectoryEntry} is returned, which contains * file attributes as well. * * @param dir * @return */ DirectoryStreamB<HimeraDirectoryEntry> newDirectoryStream(FsInode dir) { return new DirectoryStreamB<HimeraDirectoryEntry>() { final DirectoryStreamImpl stream = new DirectoryStreamImpl(dir, _jdbc); @Override public Iterator<HimeraDirectoryEntry> iterator() { return new Iterator<HimeraDirectoryEntry>() { private HimeraDirectoryEntry current = innerNext(); @Override public boolean hasNext() { return current != null; } @Override public HimeraDirectoryEntry next() { if (current == null) { throw new NoSuchElementException("No more entries"); } HimeraDirectoryEntry entry = current; current = innerNext(); return entry; } protected HimeraDirectoryEntry innerNext() { try { ResultSet rs = stream.next(); if (rs == null) { return null; } Stat stat = toStat(rs); FsInode inode = new FsInode(dir.getFs(), rs.getLong("inumber"), FsInodeType.INODE, 0, stat); inode.setParent(dir); return new HimeraDirectoryEntry(rs.getString("iname"), inode, stat); } catch (SQLException e) { _log.error("failed to fetch next entry: {}", e.getMessage()); return null; } } }; } @Override public void close() throws IOException { stream.close(); } }; } /** * Removes the hard link {@code name} in {@code parent} to {@code inode}. If the * last link is removed the object is deleted. * * @return true if removed, false if the link did not exist. */ boolean remove(FsInode parent, String name, FsInode inode) throws ChimeraFsException { if (inode.type() != FsInodeType.INODE) { throw new InvalidArgumentChimeraException("Not a file."); } if (name.equals("..") || name.equals(".")) { throw new InvalidNameChimeraException("bad name: '" + name + '\''); } return inode.isDirectory() ? removeDir(parent, inode, name) : removeFile(parent, inode, name); } private boolean removeDir(FsInode parent, FsInode inode, String name) throws ChimeraFsException { if (!removeEntryInParent(parent, name, inode)) { return false; } // A directory contains two pseudo entries for '.' and '..' decNlink(inode, 2); removeTag(inode); if (!removeInodeIfUnlinked(inode)) { throw new DirNotEmptyHimeraFsException("directory is not empty"); } /* During bulk deletion of files in the same directory, * updating the parent inode is often a contention point. The * link count on the parent is updated last to reduce the time * in which the directory inode is locked by the database. */ decNlink(parent); return true; } private boolean removeFile(FsInode parent, FsInode inode, String name) throws ChimeraFsException { if (!removeEntryInParent(parent, name, inode)) { return false; } decNlink(inode); removeInodeIfUnlinked(inode); /* During bulk deletion of files in the same directory, * updating the parent inode is often a contention point. The * link count on the parent is updated last to reduce the time * in which the directory inode is locked by the database. */ decNlink(parent); return true; } void remove(FsInode inode) { if (inode.isDirectory()) { removeTag(inode); } /* Updating the inode effectively blocks anybody else from changing it and thus also from * adding more links. */ _jdbc.update("UPDATE t_inodes SET inlink=0 WHERE inumber=?", inode.ino()); /* Remove all hard-links. */ List<Long> parents = _jdbc.queryForList( "SELECT iparent FROM t_dirs WHERE ichild=?", Long.class, inode.ino()); for (Long parent : parents) { decNlink(new FsInode(inode.getFs(), parent)); } int n = _jdbc.update("DELETE FROM t_dirs WHERE ichild=?", inode.ino()); if (n != parents.size()) { throw new JdbcUpdateAffectedIncorrectNumberOfRowsException("DELETE FROM t_dirs WHERE ichild=?", parents.size(), n); } removeInodeIfUnlinked(inode); } public Stat stat(String id) { return _jdbc.query( "SELECT * FROM t_inodes WHERE ipnfsid=?", ps -> ps.setString(1, id), rs -> rs.next() ? toStat(rs) : null); } public Stat stat(FsInode inode) { return stat(inode, 0); } public Stat stat(FsInode inode, int level) { if (level == 0) { return _jdbc.query( "SELECT * FROM t_inodes WHERE inumber=?", ps -> ps.setLong(1, inode.ino()), rs -> rs.next() ? toStat(rs) : null); } else { return _jdbc.query( "SELECT * FROM t_level_" + level + " WHERE inumber=?", ps -> ps.setLong(1, inode.ino()), rs -> rs.next() ? toStatLevel(rs) : null); } } private Stat toStat(ResultSet rs) throws SQLException { Stat stat = new Stat(); stat.setIno(rs.getLong("inumber")); stat.setId(rs.getString("ipnfsid")); stat.setCrTime(rs.getTimestamp("icrtime").getTime()); stat.setGeneration(rs.getLong("igeneration")); int rp = rs.getInt("iretention_policy"); if (!rs.wasNull()) { stat.setRetentionPolicy(RetentionPolicy.getRetentionPolicy(rp)); } int al = rs.getInt("iaccess_latency"); if (!rs.wasNull()) { stat.setAccessLatency(AccessLatency.getAccessLatency(al)); } stat.setSize(rs.getLong("isize")); stat.setATime(rs.getTimestamp("iatime").getTime()); stat.setCTime(rs.getTimestamp("ictime").getTime()); stat.setMTime(rs.getTimestamp("imtime").getTime()); stat.setUid(rs.getInt("iuid")); stat.setGid(rs.getInt("igid")); stat.setMode(rs.getInt("imode") | rs.getInt("itype")); stat.setNlink(rs.getInt("inlink")); stat.setDev(17); stat.setRdev(13); return stat; } private Stat toStatLevel(ResultSet rs) throws SQLException { Stat stat = new Stat(); stat.setIno(rs.getLong("inumber")); stat.setCrTime(rs.getTimestamp("imtime").getTime()); stat.setGeneration(0); stat.setSize(rs.getLong("isize")); stat.setATime(rs.getTimestamp("iatime").getTime()); stat.setCTime(rs.getTimestamp("ictime").getTime()); stat.setMTime(rs.getTimestamp("imtime").getTime()); stat.setUid(rs.getInt("iuid")); stat.setGid(rs.getInt("igid")); stat.setMode(rs.getInt("imode") | UnixPermission.S_IFREG); stat.setNlink(rs.getInt("inlink")); stat.setDev(17); stat.setRdev(13); return stat; } /** * create a new directory in parent with name. The reference count if parent directory * as well modification time and reference count of newly created directory are updated. * * * @param parent * @param name * @param owner * @param group * @param mode * @throws ChimeraFsException * @return */ FsInode mkdir(FsInode parent, String name, int owner, int group, int mode) { return createInodeInParent(parent, name, FsInode.generateNewID(), owner, group, mode, UnixPermission.S_IFDIR, 2, 512); } /** * Move/rename inode from source in srcDir to dest in destDir. The reference counts * of srcDir and destDir are updated. * * @param srcDir * @param source * @param destDir * @param dest * @param inode * @return true if moved, false if source did not exist */ boolean rename(FsInode inode, FsInode srcDir, String source, FsInode destDir, String dest) { String moveLink = "UPDATE t_dirs SET iparent=?, iname=? WHERE iparent=? AND iname=? AND ichild=?"; int n = _jdbc.update(moveLink, ps -> { ps.setLong(1, destDir.ino()); ps.setString(2, dest); ps.setLong(3, srcDir.ino()); ps.setString(4, source); ps.setLong(5, inode.ino()); }); if (n == 0) { return false; } if (n > 1) { throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(moveLink, 1, n); } if (!srcDir.equals(destDir)) { incNlink(destDir); decNlink(srcDir); } else { incNlink(srcDir, 0); } return true; } /** * return the inode of path in directory. In case of pnfs magic commands ( '.(' ) * command specific inode is returned. * * @param parent * @param name * @return null if path is not found */ FsInode inodeOf(FsInode parent, String name, StatCacheOption stat) { switch (name) { case ".": return parent.isDirectory() ? parent : null; case "..": if (!parent.isDirectory()) { return null; } FsInode dir = parent.getParent(); return (dir == null) ? parent : dir; default: if (stat == STAT) { return _jdbc.query( "SELECT c.* FROM t_dirs d JOIN t_inodes c ON d.ichild = c.inumber " + "WHERE d.iparent = ? AND d.iname = ?", ps -> { ps.setLong(1, parent.ino()); ps.setString(2, name); }, rs -> rs.next() ? new FsInode(parent.getFs(), rs.getLong("inumber"), FsInodeType.INODE, 0, toStat(rs)) : null); } else { return _jdbc.query("SELECT ichild FROM t_dirs WHERE iparent=? AND iname=?", ps -> { ps.setLong(1, parent.ino()); ps.setString(2, name); }, rs -> rs.next() ? new FsInode(parent.getFs(), rs.getLong("ichild")) : null); } } } /** * * return the path associated with inode, starting from root of the tree. * in case of hard link, one of the possible paths is returned * * @param inode * @param startFrom defined the "root" * @return */ String inode2path(FsInode inode, FsInode startFrom) { if (inode.equals(startFrom)) { return "/"; } try { List<String> pList = new ArrayList<>(); long root = startFrom.ino(); long elementId = inode.ino(); do { Map<String, Object> map = _jdbc.queryForMap( "SELECT iparent, iname FROM t_dirs WHERE ichild=?", elementId); pList.add((String) map.get("iname")); elementId = (long) map.get("iparent"); } while (elementId != root); return Lists.reverse(pList).stream().collect(Collectors.joining("/", "/", "")); } catch (IncorrectResultSizeDataAccessException e) { return ""; } } FsInode createInodeInParent(FsInode parent, String name, String id, int owner, int group, int mode, int type, int nlink, long size) { Stat stat = createInode(id, type, owner, group, mode, nlink, size); FsInode inode = new FsInode(parent.getFs(), stat.getIno(), FsInodeType.INODE, 0, stat); createEntryInParent(parent, name, inode); incNlink(parent); return inode; } /** * * creates an entry in t_inodes table with initial values. * for optimization, initial value of reference count may be defined. * for newly created files , file size is zero. For directories 512. * * @param id * @param uid * @param gid * @param mode * @param nlink */ Stat createInode(String id, int type, int uid, int gid, int mode, int nlink, long size) { Timestamp now = new Timestamp(System.currentTimeMillis()); KeyHolder keyHolder = new GeneratedKeyHolder(); _jdbc.update( con -> { PreparedStatement ps = con.prepareStatement( "INSERT INTO t_inodes (ipnfsid,itype,imode,inlink,iuid,igid,isize,iio," + "ictime,iatime,imtime,icrtime,igeneration) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setString(1, id); ps.setInt(2, type); ps.setInt(3, mode & UnixPermission.S_PERMS); ps.setInt(4, nlink); ps.setInt(5, uid); ps.setInt(6, gid); ps.setLong(7, size); ps.setInt(8, _ioMode); ps.setTimestamp(9, now); ps.setTimestamp(10, now); ps.setTimestamp(11, now); ps.setTimestamp(12, now); ps.setLong(13, 0); return ps; }, keyHolder); Stat stat = new Stat(); stat.setIno((Long) keyHolder.getKeys().get("inumber")); stat.setId(id); stat.setCrTime(now.getTime()); stat.setGeneration(0); stat.setSize(size); stat.setATime(now.getTime()); stat.setCTime(now.getTime()); stat.setMTime(now.getTime()); stat.setUid(uid); stat.setGid(gid); stat.setMode(mode & UnixPermission.S_PERMS | type); stat.setNlink(nlink); stat.setDev(17); stat.setRdev(13); return stat; } /** * * creates an entry in t_level_x table * * @param inode * @param uid * @param gid * @param mode * @param level * @return */ FsInode createLevel(FsInode inode, int uid, int gid, int mode, int level) { Timestamp now = new Timestamp(System.currentTimeMillis()); _jdbc.update("INSERT INTO t_level_" + level + "(inumber,imode,inlink,iuid,igid,isize,ictime,iatime,imtime,ifiledata) VALUES(?,?,1,?,?,0,?,?,?, NULL)", ps -> { ps.setLong(1, inode.ino()); ps.setInt(2, mode); ps.setInt(3, uid); ps.setInt(4, gid); ps.setTimestamp(5, now); ps.setTimestamp(6, now); ps.setTimestamp(7, now); }); Stat stat = new Stat(); stat.setCrTime(now.getTime()); stat.setGeneration(0); stat.setSize(0); stat.setATime(now.getTime()); stat.setCTime(now.getTime()); stat.setMTime(now.getTime()); stat.setUid(uid); stat.setGid(gid); stat.setMode(mode | UnixPermission.S_IFREG); stat.setNlink(1); stat.setIno(inode.ino()); stat.setDev(17); stat.setRdev(13); return new FsInode(inode.getFs(), inode.ino(), FsInodeType.INODE, level, stat); } boolean removeInodeIfUnlinked(FsInode inode) { List<String> ids = _jdbc.queryForList("SELECT ipnfsid FROM t_inodes WHERE inumber=? AND inlink=0 FOR UPDATE", String.class, inode.ino()); if (ids.isEmpty()) { return false; } if (ids.size() > 1) { throw new IncorrectResultSizeDataAccessException(1, ids.size()); } String id = ids.get(0); _jdbc.update("INSERT INTO t_locationinfo_trash (ipnfsid,itype,ilocation,ipriority,ictime,iatime,istate) " + "(SELECT ?,l.itype,l.ilocation,l.ipriority,l.ictime,l.iatime,l.istate " + "FROM t_locationinfo l WHERE l.inumber=?)", ps -> { ps.setString(1, id); ps.setLong(2, inode.ino()); }); Timestamp now = new Timestamp(System.currentTimeMillis()); _jdbc.update( "INSERT INTO t_locationinfo_trash (ipnfsid,itype,ilocation,ipriority,ictime,iatime,istate) VALUES (?,2,'',0,?,?,1)", ps -> { ps.setString(1, id); ps.setTimestamp(2, now); ps.setTimestamp(3, now); }); _jdbc.update("DELETE FROM t_inodes WHERE inumber=?", inode.ino()); return true; } boolean removeInodeLevel(FsInode inode, int level) { return _jdbc.update("DELETE FROM t_level_" + level + " WHERE inumber=?", inode.ino()) > 0; } /** * increase inode reference count by 1; * the same as incNlink(dbConnection, inode, 1) * * @param inode */ void incNlink(FsInode inode) { incNlink(inode, 1); } /** * increases the reference count of the inode by delta * * @param inode * @param delta */ void incNlink(FsInode inode, int delta) { Timestamp now = new Timestamp(System.currentTimeMillis()); _jdbc.update("UPDATE t_inodes SET inlink=inlink +?,imtime=?,ictime=?,igeneration=igeneration+1 WHERE inumber=?", ps -> { ps.setInt(1, delta); ps.setTimestamp(2, now); ps.setTimestamp(3, now); ps.setLong(4, inode.ino()); }); } /** * decreases inode reverence count by 1. * the same as decNlink(dbConnection, inode, 1) * * @param inode */ void decNlink(FsInode inode) { decNlink(inode, 1); } /** * decreases inode reference count by delta * * @param inode * @param delta */ void decNlink(FsInode inode, int delta) { Timestamp now = new Timestamp(System.currentTimeMillis()); _jdbc.update("UPDATE t_inodes SET inlink=inlink -?,imtime=?,ictime=?,igeneration=igeneration+1 WHERE inumber=?", ps -> { ps.setInt(1, delta); ps.setTimestamp(2, now); ps.setTimestamp(3, now); ps.setLong(4, inode.ino()); }); } /** * * creates an entry name for the inode in the directory parent. * parent's reference count is not increased * * @param parent * @param name * @param inode */ void createEntryInParent(FsInode parent, String name, FsInode inode) { _jdbc.update("INSERT INTO t_dirs (iparent,ichild,iname) VALUES(?,?,?)", ps -> { ps.setLong(1, parent.ino()); ps.setLong(2, inode.ino()); ps.setString(3, name); }); } private boolean removeEntryInParent(FsInode parent, String name, FsInode child) { return _jdbc.update("DELETE FROM t_dirs WHERE iname=? AND iparent=? AND ichild=?", name, parent.ino(), child.ino()) > 0; } /** * * return a parent of inode. In case of hard links, one of the parents is returned * * @param inode * @return */ FsInode getParentOf(FsInode inode) { return _jdbc.query( "SELECT iparent FROM t_dirs WHERE ichild=?", ps -> ps.setLong(1, inode.ino()), rs -> rs.next() ? new FsInode(inode.getFs(), rs.getLong("iparent")) : null); } boolean setInodeAttributes(FsInode inode, int level, Stat stat) { return _jdbc.update(con -> generateAttributeUpdateStatement(con, inode, stat, level)) > 0; } /** * checks for IO flag of the inode. if IO enabled, regular read and write operations are allowed * * @param inode * @return */ boolean isIoEnabled(FsInode inode) { /* Since we access t_inodes anyway and the cost of transferring the entire row * is negligible, we fill the stat cache as a side effect. */ return _jdbc.query("SELECT * FROM t_inodes WHERE inumber=?", ps -> ps.setLong(1, inode.ino()), rs -> { if (rs.next()) { inode.setStatCache(toStat(rs)); return rs.getInt("iio") == 1; } else { return false; } }); } void setInodeIo(FsInode inode, boolean enable) { _jdbc.update("UPDATE t_inodes SET iio=? WHERE inumber=?", ps -> { ps.setInt(1, enable ? 1 : 0); ps.setLong(2, inode.ino()); }); } int write(FsInode inode, int level, long beginIndex, byte[] data, int offset, int len) { if (level == 0) { int n = _jdbc.queryForObject("SELECT count(*) FROM t_inodes_data WHERE inumber=?", Integer.class, inode.ino()); if (n > 0) { // entry exist, update only _jdbc.update("UPDATE t_inodes_data SET ifiledata=? WHERE inumber=?", ps -> { ps.setBinaryStream(1, new ByteArrayInputStream(data, offset, len), len); ps.setLong(2, inode.ino()); }); } else { // new entry _jdbc.update("INSERT INTO t_inodes_data (inumber,ifiledata) VALUES (?,?)", ps -> { ps.setLong(1, inode.ino()); ps.setBinaryStream(2, new ByteArrayInputStream(data, offset, len), len); }); } // correct file size _jdbc.update("UPDATE t_inodes SET isize=? WHERE inumber=?", ps -> { ps.setLong(1, len); ps.setLong(2, inode.ino()); }); } else { int n = _jdbc.queryForObject( "SELECT count(*) FROM t_level_" + level + " WHERE inumber=?", Integer.class, inode.ino()); if (n == 0) { // if level does not exist, create it Timestamp now = new Timestamp(System.currentTimeMillis()); _jdbc.update("INSERT INTO t_level_" + level + "(inumber,imode,inlink,iuid,igid,isize,ictime,iatime,imtime,ifiledata) VALUES(?,?,1,?,?,?,?,?,?,?)", ps -> { ps.setLong(1, inode.ino()); ps.setInt(2, 644); ps.setInt(3, 0); ps.setInt(4, 0); ps.setLong(5, len); ps.setTimestamp(6, now); ps.setTimestamp(7, now); ps.setTimestamp(8, now); ps.setBinaryStream(9, new ByteArrayInputStream(data, offset, len), len); }); } else { _jdbc.update("UPDATE t_level_" + level + " SET ifiledata=?,isize=? WHERE inumber=?", ps -> { ps.setBinaryStream(1, new ByteArrayInputStream(data, offset, len), len); ps.setLong(2, len); ps.setLong(3, inode.ino()); }); } } return len; } int read(FsInode inode, int level, long beginIndex, byte[] data, int offset, int len) { ResultSetExtractor<Integer> extractor = rs -> { try { int count = 0; if (rs.next()) { InputStream in = rs.getBinaryStream(1); if (in != null) { in.skip(beginIndex); int c; while (((c = in.read()) != -1) && (count < len)) { data[offset + count] = (byte) c; ++count; } } } return count; } catch (IOException e) { throw new LobRetrievalFailureException(e.getMessage(), e); } }; if (level == 0) { return _jdbc.query("SELECT ifiledata FROM t_inodes_data WHERE inumber=?", extractor, inode.ino()); } else { return _jdbc.query("SELECT ifiledata FROM t_level_" + level + " WHERE inumber=?", extractor, inode.ino()); } } /** * * returns a list of locations of defined type for the inode. * only 'online' locations is returned * * @param inode * @param type * @return */ List<StorageLocatable> getInodeLocations(FsInode inode, int type) { return _jdbc.query("SELECT ilocation,ipriority,ictime,iatime FROM t_locationinfo " + "WHERE itype=? AND inumber=? AND istate=1 ORDER BY ipriority DESC", ps -> { ps.setInt(1, type); ps.setLong(2, inode.ino()); }, (rs, rowNum) -> { long ctime = rs.getTimestamp("ictime").getTime(); long atime = rs.getTimestamp("iatime").getTime(); int priority = rs.getInt("ipriority"); String location = rs.getString("ilocation"); return new StorageGenericLocation(type, priority, location, ctime, atime, true); }); } /** * * returns a list of locations for the inode. * only 'online' locations is returned * * @param inode * @return */ List<StorageLocatable> getInodeLocations(FsInode inode) { return _jdbc.query("SELECT itype,ilocation,ipriority,ictime,iatime FROM t_locationinfo " + "WHERE inumber=? AND istate=1 ORDER BY ipriority DESC", ps -> { ps.setLong(1, inode.ino()); }, (rs, rowNum) -> { int type = rs.getInt("itype"); long ctime = rs.getTimestamp("ictime").getTime(); long atime = rs.getTimestamp("iatime").getTime(); int priority = rs.getInt("ipriority"); String location = rs.getString("ilocation"); return new StorageGenericLocation(type, priority, location, ctime, atime, true); }); } /** * * adds a new location for the inode * * @param inode * @param type * @param location */ void addInodeLocation(FsInode inode, int type, String location) { Timestamp now = new Timestamp(System.currentTimeMillis()); _jdbc.update("INSERT INTO t_locationinfo (inumber,itype,ilocation,ipriority,ictime,iatime,istate) " + "(SELECT * FROM (VALUES (?,?,?,?,?,?,?)) v WHERE NOT EXISTS " + "(SELECT 1 FROM t_locationinfo WHERE inumber=? AND itype=? AND ilocation=?))", ps -> { ps.setLong(1, inode.ino()); ps.setInt(2, type); ps.setString(3, location); ps.setInt(4, 10); // default priority ps.setTimestamp(5, now); ps.setTimestamp(6, now); ps.setInt(7, 1); // online ps.setLong(8, inode.ino()); ps.setInt(9, type); ps.setString(10, location); }); } /** * * remove the location for a inode * * @param inode * @param type * @param location */ void clearInodeLocation(FsInode inode, int type, String location) { _jdbc.update("DELETE FROM t_locationinfo WHERE inumber=? AND itype=? AND ilocation=?", ps -> { ps.setLong(1, inode.ino()); ps.setInt(2, type); ps.setString(3, location); }); } String[] tags(FsInode inode) { List<String> tags = _jdbc.queryForList("SELECT itagname FROM t_tags where inumber=?", String.class, inode.ino()); return tags.toArray(new String[tags.size()]); } Map<String,byte[]> getAllTags(FsInode inode) { Map<String,byte[]> tags = new HashMap<>(); _jdbc.query("SELECT t.itagname, i.ivalue, i.isize " + "FROM t_tags t JOIN t_tags_inodes i ON t.itagid = i.itagid WHERE t.inumber=?", ps -> { ps.setLong(1, inode.ino()); }, rs -> { try (InputStream in = rs.getBinaryStream("ivalue")) { byte[] data = new byte[Ints.saturatedCast(rs.getLong("isize"))]; // we get null if filed id NULL, e.g not set if (in != null) { ByteStreams.readFully(in, data); tags.put(rs.getString("itagname"), data); } } catch (IOException e) { throw new LobRetrievalFailureException(e.getMessage(), e); } }); return tags; } /** * creates a new tag for the inode. * the inode becomes the tag origin. * * @param inode * @param name * @param uid * @param gid * @param mode */ void createTag(FsInode inode, String name, int uid, int gid, int mode) { long id = createTagInode(uid, gid, mode); assignTagToDir(id, name, inode, false, true); } /** * returns tag id of a tag associated with inode * * @param dir * @param tag * @return */ Long getTagId(FsInode dir, String tag) { return _jdbc.query("SELECT itagid FROM t_tags WHERE inumber=? AND itagname=?", ps -> { ps.setLong(1, dir.ino()); ps.setString(2, tag); }, rs -> rs.next() ? rs.getLong("itagid") : null); } /** * * creates a new id for a tag and stores it into t_tags_inodes table. * * @param uid * @param gid * @param mode * @return */ long createTagInode(int uid, int gid, int mode) { final String CREATE_TAG_INODE_WITHOUT_VALUE = "INSERT INTO t_tags_inodes (imode, inlink, iuid, igid, isize, " + "ictime, iatime, imtime, ivalue) VALUES (?,1,?,?,0,?,?,?,NULL)"; Timestamp now = new Timestamp(System.currentTimeMillis()); KeyHolder keyHolder = new GeneratedKeyHolder(); int rc = _jdbc.update( con -> { PreparedStatement ps = con.prepareStatement( CREATE_TAG_INODE_WITHOUT_VALUE, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, mode | UnixPermission.S_IFREG); ps.setInt(2, uid); ps.setInt(3, gid); ps.setTimestamp(4, now); ps.setTimestamp(5, now); ps.setTimestamp(6, now); return ps; }, keyHolder); if (rc != 1) { throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(CREATE_TAG_INODE_WITHOUT_VALUE, 1, rc); } return (Long) keyHolder.getKeys().get("itagid"); } /** * * creates a new id for a tag and stores it into t_tags_inodes table. * * @param uid * @param gid * @param mode * @param value * @return */ long createTagInode(int uid, int gid, int mode, byte[] value) { final String CREATE_TAG_INODE_WITH_VALUE = "INSERT INTO t_tags_inodes (imode, inlink, iuid, igid, isize, " + "ictime, iatime, imtime, ivalue) VALUES (?,1,?,?,?,?,?,?,?)"; Timestamp now = new Timestamp(System.currentTimeMillis()); KeyHolder keyHolder = new GeneratedKeyHolder(); int rc = _jdbc.update( con -> { PreparedStatement ps = con.prepareStatement( CREATE_TAG_INODE_WITH_VALUE, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, mode | UnixPermission.S_IFREG); ps.setInt(2, uid); ps.setInt(3, gid); ps.setLong(4, value.length); ps.setTimestamp(5, now); ps.setTimestamp(6, now); ps.setTimestamp(7, now); ps.setBinaryStream(8, new ByteArrayInputStream(value), value.length); return ps; }, keyHolder); if (rc != 1) { throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(CREATE_TAG_INODE_WITH_VALUE, 1, rc); } return (Long) keyHolder.getKeys().get("itagid"); } /** * * creates a new or update existing tag for a directory * * @param tagId * @param tagName * @param dir * @param isUpdate * @param isOrign */ void assignTagToDir(long tagId, String tagName, FsInode dir, boolean isUpdate, boolean isOrign) { if (isUpdate) { _jdbc.update("UPDATE t_tags SET itagid=?,isorign=? WHERE inumber=? AND itagname=?", ps -> { ps.setLong(1, tagId); ps.setInt(2, isOrign ? 1 : 0); ps.setLong(3, dir.ino()); ps.setString(4, tagName); }); } else { _jdbc.update("INSERT INTO t_tags (inumber, itagid, isorign, itagname) VALUES(?,?,1,?)", ps -> { ps.setLong(1, dir.ino()); ps.setLong(2, tagId); ps.setString(3, tagName); }); } } int setTag(FsInode inode, String tagName, byte[] data, int offset, int len) throws ChimeraFsException { long tagId; if (!isTagOwner(inode, tagName)) { // tag bunching Stat tagStat = statTag(inode, tagName); tagId = createTagInode(tagStat.getUid(), tagStat.getGid(), tagStat.getMode()); assignTagToDir(tagId, tagName, inode, true, true); } else { tagId = getTagId(inode, tagName); } _jdbc.update("UPDATE t_tags_inodes SET ivalue=?, isize=?, imtime=? WHERE itagid=?", ps -> { ps.setBinaryStream(1, new ByteArrayInputStream(data, offset, len), len); ps.setLong(2, len); ps.setTimestamp(3, new Timestamp(System.currentTimeMillis())); ps.setLong(4, tagId); }); return len; } void removeTag(FsInode dir, String tag) { _jdbc.update("DELETE FROM t_tags WHERE inumber=? AND itagname=?", dir.ino(), tag); } void removeTag(FsInode dir) { /* Get the tag IDs of the tag links to be removed. */ List<Long> ids = _jdbc.queryForList("SELECT itagid FROM t_tags WHERE inumber=?", Long.class, dir.ino()); if (!ids.isEmpty()) { /* Remove the links. */ _jdbc.update("DELETE FROM t_tags WHERE inumber=?", dir.ino()); /* Remove any tag inode of of the tag links removed above, which are * not referenced by any other links either. * * We ought to maintain the link count in the inode, but Chimera has * not done so in the past. In the interest of avoiding costly schema * corrections in patch level releases, the current solution queries * for the existence of other links instead. * * The statement below relies on concurrent transactions not deleting * other links to affected tag inodes. Otherwise we could come into a * situation in which two concurrent transactions remove two links to * the same inode, yet none of them realize that the inode is left * without links (as there is another link). * * One way to ensure this would be to use repeatable read transaction * isolation, but PostgreSQL doesn't support changing the isolation level * in the middle of a transaction. Always running any operation that * might call this method with repeatable read was deemed unacceptable. * Another solution would be to lock the tag inode at the beginning of * this method using SELECT FOR UPDATE. This would be fairly expensive * way of solving this race. * * For now we decide to ignore the race: It seems unlikely to run into * and even if one does, the consequence is merely an orphaned inode. */ _jdbc.batchUpdate("DELETE FROM t_tags_inodes i WHERE itagid=? " + "AND NOT EXISTS (SELECT 1 FROM t_tags WHERE itagid=?)", ids, ids.size(), (ps, tagid) -> { ps.setLong(1, tagid); ps.setLong(2, tagid); }); } } /** * get content of the tag associated with name for inode * * @param inode * @param tagName * @param data * @param offset * @param len * @return */ int getTag(FsInode inode, String tagName, byte[] data, int offset, int len) { return _jdbc.query("SELECT i.ivalue,i.isize FROM t_tags t JOIN t_tags_inodes i ON t.itagid = i.itagid " + "WHERE t.inumber=? AND t.itagname=?", ps -> { ps.setLong(1, inode.ino()); ps.setString(2, tagName); }, rs -> { if (rs.next()) { try (InputStream in = rs.getBinaryStream("ivalue")) { /* some databases (hsqldb in particular) fill a full record for * BLOBs and on read reads a full record, which is not what we expect. */ return ByteStreams.read(in, data, offset, Math.min(len, (int) rs.getLong("isize"))); } catch (IOException e) { throw new LobRetrievalFailureException(e.getMessage(), e); } } return 0; }); } Stat statTag(FsInode dir, String name) throws ChimeraFsException { Long tagId = getTagId(dir, name); if (tagId == null) { throw new FileNotFoundHimeraFsException("tag does not exist"); } try { return _jdbc.queryForObject("SELECT isize,inlink,imode,iuid,igid,iatime,ictime,imtime " + "FROM t_tags_inodes WHERE itagid=?", (rs, rowNum) -> { Stat ret = new Stat(); ret.setSize(rs.getLong("isize")); ret.setATime(rs.getTimestamp("iatime").getTime()); ret.setCTime(rs.getTimestamp("ictime").getTime()); ret.setMTime(rs.getTimestamp("imtime").getTime()); ret.setUid(rs.getInt("iuid")); ret.setGid(rs.getInt("igid")); ret.setMode(rs.getInt("imode")); ret.setNlink(rs.getInt("inlink")); ret.setIno(dir.ino()); ret.setGeneration(rs.getTimestamp("imtime").getTime()); ret.setDev(17); ret.setRdev(13); return ret; }, tagId); } catch (IncorrectResultSizeDataAccessException e) { throw new FileNotFoundHimeraFsException(name); } } /** * checks for tag ownership * * @param dir * @param tagName * @return true, if inode is the origin of the tag */ boolean isTagOwner(FsInode dir, String tagName) { return _jdbc.query("SELECT isorign FROM t_tags WHERE inumber=? AND itagname=?", ps -> { ps.setLong(1, dir.ino()); ps.setString(2, tagName); }, rs -> rs.next() && rs.getInt("isorign") == 1); } void createTags(FsInode inode, int uid, int gid, int mode, Map<String, byte[]> tags) { if (!tags.isEmpty()) { Map<String, Long> ids = new HashMap<>(); tags.forEach((key, value) -> ids.put(key, createTagInode(uid, gid, mode, value))); _jdbc.batchUpdate("INSERT INTO t_tags (inumber,itagid,isorign,itagname) VALUES(?,?,1,?)", ids.entrySet(), ids.size(), (ps, tag) -> { ps.setLong(1, inode.ino()); ps.setLong(2, tag.getValue()); ps.setString(3, tag.getKey()); }); } } /** * copy all directory tags from origin directory to destination. New copy marked as inherited. * * @param orign * @param destination */ void copyTags(FsInode orign, FsInode destination) { _jdbc.update("INSERT INTO t_tags (inumber,itagid,isorign,itagname) (SELECT ?,itagid,0,itagname from t_tags WHERE inumber=?)", destination.ino(), orign.ino()); } void setTagOwner(FsInode_TAG tagInode, int newOwner) throws FileNotFoundHimeraFsException { Long tagId = getTagId(tagInode, tagInode.tagName()); if (tagId == null) { throw new FileNotFoundHimeraFsException("tag does not exist"); } _jdbc.update("UPDATE t_tags_inodes SET iuid=?, ictime=? WHERE itagid=?", ps -> { ps.setInt(1, newOwner); ps.setTimestamp(2, new Timestamp(System.currentTimeMillis())); ps.setLong(3, tagId); }); } void setTagOwnerGroup(FsInode_TAG tagInode, int newOwner) throws FileNotFoundHimeraFsException { Long tagId = getTagId(tagInode, tagInode.tagName()); if (tagId == null) { throw new FileNotFoundHimeraFsException("tag does not exist"); } _jdbc.update("UPDATE t_tags_inodes SET igid=?, ictime=? WHERE itagid=?", ps -> { ps.setInt(1, newOwner); ps.setTimestamp(2, new Timestamp(System.currentTimeMillis())); ps.setLong(3, tagId); }); } void setTagMode(FsInode_TAG tagInode, int mode) throws FileNotFoundHimeraFsException { Long tagId = getTagId(tagInode, tagInode.tagName()); if (tagId == null) { throw new FileNotFoundHimeraFsException("tag does not exist"); } _jdbc.update("UPDATE t_tags_inodes SET imode=?, ictime=? WHERE itagid=?", ps -> { ps.setInt(1, mode & UnixPermission.S_PERMS); ps.setTimestamp(2, new Timestamp(System.currentTimeMillis())); ps.setLong(3, tagId); }); } /** * set storage info of inode in t_storageinfo table. * once storage info is stores, it's not allowed to modify it * * @param inode * @param storageInfo */ void setStorageInfo(FsInode inode, InodeStorageInformation storageInfo) { _jdbc.update("INSERT INTO t_storageinfo (SELECT * FROM (VALUES (?,?,?,?)) v WHERE NOT EXISTS " + "(SELECT 1 FROM t_storageinfo WHERE inumber=?))", ps -> { ps.setLong(1, inode.ino()); ps.setString(2, storageInfo.hsmName()); ps.setString(3, storageInfo.storageGroup()); ps.setString(4, storageInfo.storageSubGroup()); ps.setLong(5, inode.ino()); }); } /** * * returns storage information like storage group, storage sub group, hsm, * retention policy and access latency associated with the inode. * * @param inode * @throws ChimeraFsException * @return */ InodeStorageInformation getStorageInfo(FsInode inode) throws ChimeraFsException { try { return _jdbc.queryForObject( "SELECT ihsmName, istorageGroup, istorageSubGroup FROM t_storageinfo WHERE inumber=?", (rs, rowNum) -> { String hsmName = rs.getString("ihsmName"); String storageGroup = rs.getString("istoragegroup"); String storageSubGroup = rs.getString("istoragesubgroup"); return new InodeStorageInformation(inode, hsmName, storageGroup, storageSubGroup); }, inode.ino()); } catch (IncorrectResultSizeDataAccessException e) { throw new FileNotFoundHimeraFsException(inode.toString()); } } /** * add a checksum value of <i>type</i> to an inode * * @param inode * @param type * @param value */ void setInodeChecksum(FsInode inode, int type, String value) { _jdbc.update("INSERT INTO t_inodes_checksum (inumber,itype,isum) (SELECT * FROM (VALUES (?,?,?)) v WHERE NOT EXISTS " + "(SELECT 1 FROM t_inodes_checksum WHERE inumber=?))", ps -> { ps.setLong(1, inode.ino()); ps.setInt(2, type); ps.setString(3, value); ps.setLong(4, inode.ino()); }); } /** * * @param inode */ List<Checksum> getInodeChecksums(FsInode inode) { return _jdbc.query("SELECT isum, itype FROM t_inodes_checksum WHERE inumber=?", ps -> ps.setLong(1, inode.ino()), (rs, rowNum) -> { String checksum = rs.getString("isum"); int type = rs.getInt("itype"); return new Checksum(ChecksumType.getChecksumType(type), checksum); }); } /** * * @param inode * @param type */ void removeInodeChecksum(FsInode inode, int type) { if (type >= 0) { _jdbc.update("DELETE FROM t_inodes_checksum WHERE inumber=? AND itype=?", ps -> { ps.setLong(1, inode.ino()); ps.setInt(2, type); }); } else { _jdbc.update("DELETE FROM t_inodes_checksum WHERE inumber=?", inode); } } /** * get inode of given path starting <i>root</i> inode. * @param root staring point * @param path * @return inode or null if path does not exist. */ FsInode path2inode(FsInode root, String path) throws ChimeraFsException { File pathFile = new File(path); List<String> pathElemts = new ArrayList<>(); do { String fileName = pathFile.getName(); if (!fileName.isEmpty()) { /* * skip multiple '/' */ pathElemts.add(pathFile.getName()); } pathFile = pathFile.getParentFile(); } while (pathFile != null); FsInode parentInode = root; FsInode inode = root; /* * while list in reverse order, we have too go backward */ for (int i = pathElemts.size(); i > 0; i--) { String f = pathElemts.get(i - 1); inode = inodeOf(parentInode, f, STAT); if (inode == null) { /* * element not found stop walking */ break; } /* * if is a link, then resolve it */ Stat s = inode.statCache(); if (UnixPermission.getType(s.getMode()) == UnixPermission.S_IFLNK) { byte[] b = new byte[(int) s.getSize()]; int n = read(inode, 0, 0, b, 0, b.length); String link = new String(b, 0, n); if (link.charAt(0) == File.separatorChar) { parentInode = new FsInode(parentInode.getFs(), _root); } inode = path2inode(parentInode, link); } parentInode = inode; } return inode; } /** * Get the inodes of given the path starting at <i>root</i>. * * @param root staring point * @param path * @return inode or null if path does not exist. */ List<FsInode> path2inodes(FsInode root, String path) throws ChimeraFsException { File pathFile = new File(path); List<String> pathElements = new ArrayList<>(); do { String fileName = pathFile.getName(); if (!fileName.isEmpty()) { /* Skip multiple file separators. */ pathElements.add(pathFile.getName()); } pathFile = pathFile.getParentFile(); } while (pathFile != null); FsInode parentInode = root; FsInode inode; List<FsInode> inodes = new ArrayList<>(pathElements.size() + 1); inodes.add(root); /* Path elements are in reverse order. */ for (String f: Lists.reverse(pathElements)) { inode = inodeOf(parentInode, f, STAT); if (inode == null) { return Collections.emptyList(); } inodes.add(inode); /* If inode is a link then resolve it. */ Stat s = inode.statCache(); if (UnixPermission.getType(s.getMode()) == UnixPermission.S_IFLNK) { byte[] b = new byte[(int) s.getSize()]; int n = read(inode, 0, 0, b, 0, b.length); String link = new String(b, 0, n); if (link.charAt(0) == '/') { parentInode = new FsInode(parentInode.getFs(), _root); inodes.add(parentInode); } List<FsInode> linkInodes = path2inodes(parentInode, link); if (linkInodes.isEmpty()) { return Collections.emptyList(); } inodes.addAll(linkInodes.subList(1, linkInodes.size())); inode = linkInodes.get(linkInodes.size() - 1); } parentInode = inode; } return inodes; } /** * 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 */ List<ACE> readAcl(FsInode inode) { return _jdbc.query("SELECT * FROM t_acl WHERE inumber = ? ORDER BY ace_order", ps -> ps.setLong(1, inode.ino()), (rs, rowNum) -> { AceType type = (rs.getInt("type") == 0) ? AceType.ACCESS_ALLOWED_ACE_TYPE : AceType.ACCESS_DENIED_ACE_TYPE; return new ACE(type, rs.getInt("flags"), rs.getInt("access_msk"), Who.valueOf(rs.getInt("who")), rs.getInt("who_id")); }); } /** * Set inode's Access Control List. The inode must not have any ACLs prior to this call. * @param inode * @param acl */ void writeAcl(FsInode inode, RsType rsType, List<ACE> acl) { _jdbc.batchUpdate("INSERT INTO t_acl (inumber,rs_type,type,flags,access_msk,who,who_id,ace_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", acl, acl.size(), new ParameterizedPreparedStatementSetter<ACE>() { int order = 0; @Override public void setValues(PreparedStatement ps, ACE ace) throws SQLException { ps.setLong(1, inode.ino()); ps.setInt(2, rsType.getValue()); ps.setInt(3, ace.getType().getValue()); ps.setInt(4, ace.getFlags()); ps.setInt(5, ace.getAccessMsk()); ps.setInt(6, ace.getWho().getValue()); ps.setInt(7, ace.getWhoID()); ps.setInt(8, order); order++; } }); } boolean deleteAcl(FsInode inode) { return _jdbc.update("DELETE FROM t_acl WHERE inumber = ?", inode.ino()) > 0; } /** * Copies ACL entries from source to inode. The inode must not have any ACLs prior to this call. * * @param source inode whose ACLs to copy * @param inode inode to add the ACLs to * @param type ACE object type * @param mask Flags to remove from the copied ACEs * @param flags Only copy ACEs that have one or more of these flags set */ void copyAcl(FsInode source, FsInode inode, RsType type, EnumSet<AceFlags> mask, EnumSet<AceFlags> flags) { int msk = mask.stream().mapToInt(AceFlags::getValue).reduce(0, (a, b) -> a | b); int flgs = flags.stream().mapToInt(AceFlags::getValue).reduce(0, (a, b) -> a | b); List<ACE> acl = readAcl(source).stream() .filter(ace -> (ace.getFlags() & flgs) > 0) .map(ace -> new ACE(ace.getType(), (ace.getFlags() | msk) ^ msk, ace.getAccessMsk(), ace.getWho(), ace.getWhoID())) .collect(toList()); writeAcl(inode, type, acl); } /** * Check <i>SQLException</i> for foreign key violation. * @return true is sqlState is a foreign key violation and false other wise */ public boolean isForeignKeyError(SQLException e) { return e.getSQLState().equals("23503"); } /** * creates an instance of org.dcache.chimera.<dialect>FsSqlDriver or * default driver, if specific driver not available * * @param dataSource database data source * @return FsSqlDriver */ static FsSqlDriver getDriverInstance(DataSource dataSource) throws ChimeraFsException, SQLException { for (DBDriverProvider driverProvider: ALL_PROVIDERS) { if (driverProvider.isSupportDB(dataSource)) { FsSqlDriver driver = driverProvider.getDriver(dataSource); _log.info("Using DBDriverProvider: {}", driver.getClass().getName()); return driver; } } // fall back to generic implementation _log.warn("No sutable DBDriverProvider found. Falling back to generic."); return new FsSqlDriver(dataSource); } private PreparedStatement generateAttributeUpdateStatement(Connection dbConnection, FsInode inode, Stat stat, int level) throws SQLException { if (stat.isDefined(Stat.StatAttributes.ATIME) && stat.getDefinedAttributeses().size() == 1) { /* * ATIME only update. The CTIME must stay unchanged. */ PreparedStatement preparedStatement = dbConnection.prepareStatement( "UPDATE t_inodes SET iatime=?,igeneration=igeneration+1 WHERE inumber=?"); preparedStatement.setTimestamp(1, new Timestamp(stat.getATime())); preparedStatement.setLong(2, inode.ino()); return preparedStatement; } final String attrUpdatePrefix = (level == 0) ? "UPDATE t_inodes SET ictime=?,igeneration=igeneration+1" : ("UPDATE t_level_" + level + " SET ictime=?"); final String attrUpdateSuffix = (level == 0 && stat.isDefined(Stat.StatAttributes.SIZE)) ? " WHERE inumber=? AND itype = " + UnixPermission.S_IFREG : " WHERE inumber=?"; StringBuilder sb = new StringBuilder(128); long ctime = stat.isDefined(Stat.StatAttributes.CTIME) ? stat.getCTime() : System.currentTimeMillis(); // set size always must trigger mtime update if (stat.isDefined(Stat.StatAttributes.SIZE) && !stat.isDefined(Stat.StatAttributes.MTIME)) { stat.setMTime(ctime); } sb.append(attrUpdatePrefix); if (stat.isDefined(Stat.StatAttributes.UID)) { sb.append(",iuid=?"); } if (stat.isDefined(Stat.StatAttributes.GID)) { sb.append(",igid=?"); } if (stat.isDefined(Stat.StatAttributes.SIZE)) { sb.append(",isize=?"); } if (stat.isDefined(Stat.StatAttributes.MODE)) { sb.append(",imode=?"); } if (stat.isDefined(Stat.StatAttributes.MTIME)) { sb.append(",imtime=?"); } if (stat.isDefined(Stat.StatAttributes.ATIME)) { sb.append(",iatime=?"); } if (stat.isDefined(Stat.StatAttributes.CRTIME)) { sb.append(",icrtime=?"); } if (stat.isDefined(Stat.StatAttributes.ACCESS_LATENCY)) { sb.append(",iaccess_latency=?"); } if (stat.isDefined(Stat.StatAttributes.RETENTION_POLICY)) { sb.append(",iretention_policy=?"); } sb.append(attrUpdateSuffix); String statement = sb.toString(); PreparedStatement preparedStatement = dbConnection.prepareStatement(statement); int idx = 1; preparedStatement.setTimestamp(idx++, new Timestamp(ctime)); // NOTICE: order here MUST match the order of processing attributes above. if (stat.isDefined(Stat.StatAttributes.UID)) { preparedStatement.setInt(idx++, stat.getUid()); } if (stat.isDefined(Stat.StatAttributes.GID)) { preparedStatement.setInt(idx++, stat.getGid()); } if (stat.isDefined(Stat.StatAttributes.SIZE)) { preparedStatement.setLong(idx++, stat.getSize()); } if (stat.isDefined(Stat.StatAttributes.MODE)) { preparedStatement.setInt(idx++, stat.getMode() & UnixPermission.S_PERMS); } if (stat.isDefined(Stat.StatAttributes.MTIME)) { preparedStatement.setTimestamp(idx++, new Timestamp(stat.getMTime())); } if (stat.isDefined(Stat.StatAttributes.ATIME)) { preparedStatement.setTimestamp(idx++, new Timestamp(stat.getATime())); } if (stat.isDefined(Stat.StatAttributes.CRTIME)) { preparedStatement.setTimestamp(idx++, new Timestamp(stat.getCrTime())); } if (stat.isDefined(Stat.StatAttributes.ACCESS_LATENCY)) { preparedStatement.setInt(idx++, stat.getAccessLatency().getId()); } if (stat.isDefined(Stat.StatAttributes.RETENTION_POLICY)) { preparedStatement.setInt(idx++, stat.getRetentionPolicy().getId()); } preparedStatement.setLong(idx++, inode.ino()); return preparedStatement; } }