/*
* 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.primitives.Longs;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import org.dcache.acl.ACE;
import org.dcache.chimera.posix.Stat;
import static org.dcache.chimera.FileSystemProvider.StatCacheOption.STAT;
import static com.google.common.base.Preconditions.checkArgument;
/**
* inode representation
*/
public class FsInode {
protected final long _ino;
/**
* type of inode
*/
private final FsInodeType _type;
protected final FileSystemProvider _fs;
/**
* inode level. Level zero associated with real file
*/
private final int _level;
/**
* inode posix stat Object
*/
private Stat _stat;
/**
* parent inode. In case of hard links, one of the
* possible parents.
*/
private FsInode _parent;
/**
* Copy constructor.
*/
public FsInode(FileSystemProvider fs, FsInode inode) {
this(fs, inode.ino(), inode.type(), inode.getLevel(), inode.getStatCache());
}
/**
* create a new inode in filesystem fs with given id and type
* @param fs file system
* @param ino the new id
* @param type inode type
*/
public FsInode(FileSystemProvider fs, long ino, FsInodeType type) {
this(fs, ino, type, 0);
}
/**
* create a new inode of type 'inode' in the filesystem fs with given id
* @param fs file system
* @param ino inode id
*/
public FsInode(FileSystemProvider fs, long ino) {
this(fs, ino, 0);
}
/**
* Create a new FsInode with given id and level, type == INODE
* @param fs
* @param ino
* @param level
*/
public FsInode(FileSystemProvider fs, long ino, int level) {
this(fs, ino, FsInodeType.INODE, level);
}
/**
* Create a new FsInode with given id, type and level
*
* @param fs
* @param ino
* @param type
* @param level
*/
public FsInode(FileSystemProvider fs, long ino, FsInodeType type, int level) {
this(fs, ino, type, level, null);
}
public FsInode(FileSystemProvider fs, long ino, FsInodeType type, int level, Stat stat)
{
checkArgument(level >= 0 && level <= JdbcFs.LEVELS_NUMBER, "invalid level number: " + level);
_ino = ino;
_fs = fs;
_level = level;
_type = type;
_stat = stat;
}
public long ino() {
return _ino;
}
/**
* Generates new inode id assigned to filesystem with fsId == 0
*
* @return new id
*/
public static final String generateNewID() {
return InodeId.newID(0);
}
/**
* Generates new inode id assigned to filesystem referenced by <i>fsId</>
*
* @param fsId
* @return new id
*/
public static final String generateNewID(int fsId) {
return InodeId.newID(fsId);
}
/**
*
* @return inode's type, e.q.: inode, tag, id
*/
public FsInodeType type() {
return _type;
}
/**
* A helper method to generate the base part of identifier.
* @param opaque inode specific data
* @return byte array identifying the inode.
*/
protected final byte[] byteBase(byte[] opaque) {
/**
* allocate array with a correct number of bytes:
* 1 - fs is
* 1 - inode type
* 1 - number of bytes in ino, for compatibility
* 8 - ino,
* 1 - size of type specific data
* opaque.len - opaque data
*/
byte[] bytes = new byte[1 + 1 + 1 + Long.BYTES + 1 + opaque.length];
ByteBuffer b = ByteBuffer.wrap(bytes);
b.put((byte) _fs.getFsId())
.put((byte) _type.getType())
.put((byte)Long.BYTES) // set the file id size to be compatible with old format
.putLong(_ino);
b.put((byte) opaque.length);
b.put(opaque);
return bytes;
}
/**
* @return a byte[] representation of inode, including type and fsid
*/
public byte[] getIdentifier() {
return byteBase(new byte[] { (byte)(0x30 + _level)}); // 0x30 is ascii code for '0'
}
/**
* @return a String representation of inode id only
*/
@Override
public String toString() {
return String.valueOf(_ino);
}
/**
*
* gets the actual stat information of the inode and updated the cached value
* See also statCache()
* @return Stat
* @throws FileNotFoundHimeraFsException
*/
public Stat stat() throws ChimeraFsException {
_stat = _fs.stat(this, _level);
return _stat;
}
/**
*
* gets the cached value of stat information of the inode
* See also stat()
* @return Stat
* @throws FileNotFoundHimeraFsException
*/
public Stat statCache() throws ChimeraFsException {
return (_stat == null) ? stat() : _stat;
}
public int write(long pos, byte[] data, int offset, int len) throws ChimeraFsException
{
int ret = _fs.write(this, _level, pos, data, offset, len);
_stat = null;
return ret;
}
public int read(long pos, byte[] data, int offset, int len) throws ChimeraFsException
{
return _fs.read(this, _level, pos, data, offset, len);
}
/**
* crate a directory with name 'newDir' in current inode
*/
public FsInode mkdir(String newDir) throws ChimeraFsException {
FsInode dir = _fs.mkdir(this, newDir);
_stat = null;
return dir;
}
/**
* crate a directory with name 'newDir' in current inode with different access rights
*/
public FsInode mkdir(String name, int owner, int group, int mode) throws ChimeraFsException {
FsInode dir = _fs.mkdir(this, name, owner, group, mode);
_stat = null;
return dir;
}
/**
* crate a directory with name 'newDir' in current inode with different access rights
*/
public FsInode mkdir(String name, int owner, int group, int mode, List<ACE> acl, Map<String, byte[]> tags)
throws ChimeraFsException {
FsInode dir = _fs.mkdir(this, name, owner, group, mode, acl, tags);
_stat = null;
return dir;
}
/**
* get inode of file in the current directory with name 'name'
*/
public FsInode inodeOf(String name, FileSystemProvider.StatCacheOption stat) throws ChimeraFsException {
return _fs.inodeOf(this, name, stat);
}
/**
* create new file in the current directory with name 'name'
*/
public FsInode create(String name, int uid, int gid, int mode) throws ChimeraFsException {
FsInode file = _fs.createFile(this, name, uid, gid, mode);
_stat = null;
return file;
}
/**
* create new link to a specified file in the current directory with name 'name'
* ( FsInode parent, String name , int uid, int gid, int mode, byte[] dest)
*/
public FsInode createLink(String name, int uid, int gid, int mode, byte[] dest) throws ChimeraFsException {
if (!this.isDirectory()) {
throw new NotDirChimeraException(this);
}
FsInode link = _fs.createLink(this, name, uid, gid, mode, dest);
_stat = null;
return link;
}
/**
* get inode of root element of the file system
*/
public static FsInode getRoot(FileSystemProvider fs) throws ChimeraFsException {
return fs.path2inode("/");
}
public boolean exists() throws ChimeraFsException {
boolean rc = false;
try {
statCache();
rc = true;
} catch (FileNotFoundHimeraFsException hfe) {
}
return rc;
}
public boolean isDirectory() {
boolean rc = false;
try {
if (exists() && ((_stat.getMode() & UnixPermission.F_TYPE) == UnixPermission.S_IFDIR)) {
rc = true;
}
} catch(ChimeraFsException ignore) {
}
return rc;
}
public boolean isLink() {
boolean rc = false;
try {
if (exists() && new UnixPermission(_stat.getMode()).isSymLink()) {
rc = true;
}
} catch(ChimeraFsException ignore) {
}
return rc;
}
public byte[] readlink() throws ChimeraFsException {
if (!isLink()) {
throw new ChimeraFsException("not a link");
}
return _fs.readLink(this);
}
public void remove(String name) throws ChimeraFsException {
if (!isDirectory()) {
throw new IOHimeraFsException("Not a directory");
}
_fs.remove(this, name, inodeOf(name, STAT));
_stat = null;
}
public int fsId() {
return _fs.getFsId();
}
public FileSystemProvider getFs() {
return _fs;
}
public FsInode getParent() {
if (_parent == null) {
try {
_parent = _fs.getParentOf(this);
} catch (ChimeraFsException e) {
}
}
return _parent;
}
public void setParent(FsInode parent) {
_parent = parent;
}
public void setStat(Stat predefinedStat) throws ChimeraFsException {
_fs.setInodeAttributes(this, _level, predefinedStat);
if (_stat != null) {
_stat.update(predefinedStat);
}
}
protected Stat getStatCache()
{
return _stat;
}
protected void setStatCache(Stat predefinedStat) {
_stat = predefinedStat;
}
// for use in Collections
// Override from Object
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof FsInode)) {
return false;
}
FsInode otherInode = (FsInode) o;
// FIXME: _jdbcFs it the part of the test
return _level == otherInode._level && _ino == otherInode._ino && _type == otherInode._type;
// return this.toFullString().equals( otherInode.toFullString() ) ;
}
@Override
public int hashCode() {
return Longs.hashCode(_ino);
}
// only package classes allowed to use this
private boolean _ioEnabled;
private boolean _ioFlagUpToDate;
boolean isIoEnabled() {
if (!_ioFlagUpToDate) {
try {
_ioEnabled = _fs.isIoEnabled(this);
} catch (ChimeraFsException e) {
}
_ioFlagUpToDate = true;
}
return _ioEnabled;
}
public int getLevel() {
return _level;
}
public DirectoryStreamB<HimeraDirectoryEntry> newDirectoryStream() throws ChimeraFsException {
return _fs.newDirectoryStream(this);
}
public String getId() throws ChimeraFsException
{
Stat stat = _stat;
return (stat != null) ? stat.getId() : _fs.inode2id(this);
}
}