/*
* 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import javax.sql.DataSource;
import java.io.File;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import org.dcache.acl.enums.AceFlags;
import org.dcache.acl.enums.RsType;
import org.dcache.chimera.posix.Stat;
/**
* PostgreSQL specific
*
*
*/
public class PgSQLFsSqlDriver extends FsSqlDriver {
/**
* logger
*/
private static final Logger _log = LoggerFactory.getLogger(PgSQLFsSqlDriver.class);
/**
* this is a utility class which is issues SQL queries on database
*
*/
public PgSQLFsSqlDriver(DataSource dataSource) throws ChimeraFsException
{
super(dataSource);
_log.info("Running PostgreSQL specific Driver");
}
@Override
protected FsInode createInodeInParent(FsInode parent, String name, String id, int owner, int group, int mode, int type,
int nlink, long size) {
Timestamp now = new Timestamp(System.currentTimeMillis());
Long inumber =
_jdbc.query("SELECT f_create_inode(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
cs -> {
cs.setLong(1, parent.ino());
cs.setString(2, name);
cs.setString(3, id);
cs.setInt(4, type);
cs.setInt(5, mode & UnixPermission.S_PERMS);
cs.setInt(6, nlink);
cs.setInt(7, owner);
cs.setInt(8, group);
cs.setLong(9, size);
cs.setInt(10, _ioMode);
cs.setTimestamp(11, now);
},
rs -> rs.next() ? rs.getLong(1) : null);
if (inumber == null) {
throw new IncorrectUpdateSemanticsDataAccessException("f_create_inode failed to return an inumber.");
}
Stat stat = new Stat();
stat.setIno(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(owner);
stat.setGid(group);
stat.setMode(mode & UnixPermission.S_PERMS | type);
stat.setNlink(nlink);
stat.setDev(17);
stat.setRdev(13);
return new FsInode(parent.getFs(), inumber, FsInodeType.INODE, 0, stat);
}
@Override
boolean removeInodeIfUnlinked(FsInode inode)
{
return _jdbc.update("DELETE FROM t_inodes WHERE inumber=? AND inlink = 0", inode.ino()) > 0;
}
/**
*
* 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
* @return
*/
@Override
String inode2path(FsInode inode, FsInode startFrom) {
if (inode.equals(startFrom)) {
return "/";
}
return _jdbc.query("SELECT inumber2path(?)",
ps -> ps.setLong(1, inode.ino()),
rs -> rs.next() ? rs.getString(1) : null);
}
/**
* Returns a normalized path string for the given path. The
* normalized string uses the slash character as a path separator,
* it does not have a leading slash (i.e. it is relative), and it
* has no empty path elements.
*/
private String normalizePath(String path) {
File file = new File(path);
List<String> elements = new ArrayList<>();
do {
String fileName = file.getName();
if (!fileName.isEmpty()) {
/*
* skip multiple '/'
*/
elements.add(fileName);
}
file = file.getParentFile();
} while (file != null);
StringBuilder normalizedPath = new StringBuilder();
if (!elements.isEmpty()) {
normalizedPath.append(elements.get(elements.size() - 1));
for (int i = elements.size() - 2; i >= 0; i--) {
normalizedPath.append('/').append(elements.get(i));
}
}
return normalizedPath.toString();
}
/**
* 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.
*/
@Override
FsInode path2inode(FsInode root, String path) throws ChimeraFsException
{
/* Ideally we would use the SQL array type for the second
* parameter to inject the path elements, however there is no
* easy way to do that with prepared statements. Hence we use
* a slash delimited string instead. We cannot use
* <code>path</code> as that uses the platform specific path
* separator.
*/
String normalizedPath = normalizePath(path);
if (normalizedPath.isEmpty()) {
return root;
}
return _jdbc.query("SELECT path2inumber(?, ?)",
ps -> {
ps.setLong(1, root.ino());
ps.setString(2, normalizedPath);
},
rs -> {
if (rs.next()) {
long id = rs.getLong(1);
if (!rs.wasNull()) {
return new FsInode(root.getFs(), id);
}
}
return null;
});
}
@Override
List<FsInode> path2inodes(FsInode root, String path) throws ChimeraFsException
{
/* Ideally we would use the SQL array type for the second
* parameter to inject the path elements, however there is no
* easy way to do that with prepared statements. Hence we use
* a slash delimited string instead. We cannot use
* <code>path</code> as that uses the platform specific path
* separator.
*/
String normalizedPath = normalizePath(path);
if (normalizedPath.isEmpty()) {
return Collections.singletonList(root);
}
return _jdbc.query(
"SELECT inumber,ipnfsid,isize,inlink,itype,imode,iuid,igid,iatime,ictime,imtime from path2inodes(?, ?)",
ps -> {
ps.setLong(1, root.ino());
ps.setString(2, normalizedPath);
},
(rs, rowNum) -> {
FsInode inode = new FsInode(root.getFs(), rs.getLong("inumber"));
Stat stat = new Stat();
stat.setIno(rs.getLong("inumber"));
stat.setId(rs.getString("ipnfsid"));
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);
inode.setStatCache(stat);
return inode;
});
}
@Override
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);
_jdbc.update("INSERT INTO t_acl (inumber,rs_type,type,flags,access_msk,who,who_id,ace_order) " +
"SELECT ?, ?, type, (flags | ?) # ?, access_msk, who, who_id, ace_order " +
"FROM t_acl WHERE inumber = ? AND ((flags & ?) > 0)",
ps -> {
ps.setLong(1, inode.ino());
ps.setInt(2, type.getValue());
ps.setInt(3, msk);
ps.setInt(4, msk);
ps.setLong(5, source.ino());
ps.setInt(6, flgs);
});
}
@Override
void createEntryInParent(FsInode parent, String name, FsInode inode) {
int n = _jdbc.update("insert into t_dirs (iparent, iname, ichild) " +
"(select ? as iparent, ? as iname, ? as ichild " +
" where not exists (select 1 from t_dirs where iparent=? and iname=?))",
ps -> {
ps.setLong(1, parent.ino());
ps.setString(2, name);
ps.setLong(3, inode.ino());
ps.setLong(4, parent.ino());
ps.setString(5, name);
});
if (n == 0) {
/*
* no updates as such entry already exists.
* To be compatible with others, throw corresponding
* DataAccessException.
*/
throw new DuplicateKeyException("Entry already exists");
}
}
@Override
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 ?,?,?,?,?,?,? 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);
});
}
}