blob: e6991ecd0cff60e4cd2fc5c5916981961bb58fe6 [file] [log] [blame]
package jnr.posix;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.DosFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
public class JavaFileStat extends AbstractJavaFileStat {
short st_mode;
BasicFileAttributes attrs;
PosixFileAttributes posixAttrs;
DosFileAttributes dosAttrs;
public JavaFileStat(POSIX posix, POSIXHandler handler) {
super(posix, handler);
}
public void setup(String filePath) {
File file = new JavaSecuredFile(filePath);
Path path = file.toPath();
try {
try {
// try POSIX
posixAttrs = Files.readAttributes(path, PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
attrs = posixAttrs;
} catch (UnsupportedOperationException uoe) {
try {
// try DOS
dosAttrs = Files.readAttributes(path, DosFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
attrs = dosAttrs;
} catch (UnsupportedOperationException uoe2) {
attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
}
}
} catch (IOException ioe) {
// fall back on pre-NIO2 logic
attrs = new PreNIO2FileAttributes(file);
}
// Simulated mode value
st_mode = calculateMode(file, (short) 0);
}
private class PreNIO2FileAttributes implements BasicFileAttributes {
final long st_size;
final int st_ctime;
final int st_mtime;
final boolean regularFile;
final boolean directory;
PreNIO2FileAttributes(File file) {
st_size = file.length();
st_mtime = (int) (file.lastModified() / 1000);
// Parent file last modified will only represent when something was added or removed.
// This is not correct, but it is better than nothing and does work in one common use
// case.
if (file.getParentFile() != null) {
st_ctime = (int) (file.getParentFile().lastModified() / 1000);
} else {
st_ctime = st_mtime;
}
regularFile = file.isFile();
directory = file.isDirectory();
}
@Override
public FileTime lastModifiedTime() {
return FileTime.fromMillis(st_mtime);
}
@Override
public FileTime lastAccessTime() {
return lastModifiedTime();
}
@Override
public FileTime creationTime() {
return FileTime.fromMillis(st_mtime);
}
@Override
public boolean isRegularFile() {
return (st_mode & S_IFREG) != 0;
}
@Override
public boolean isDirectory() {
return (st_mode & S_IFDIR) != 0;
}
@Override
public boolean isSymbolicLink() {
return (st_mode & S_IFLNK) != 0;
}
@Override
public boolean isOther() {
return !(isRegularFile() || isDirectory() || isSymbolicLink());
}
@Override
public long size() {
return st_size;
}
@Override
public Object fileKey() {
return null;
}
}
private short calculateMode(File file, short st_mode) {
// implementation to lowest common denominator...
// Windows has no file mode, but C ruby returns either 0100444 or 0100644
if (file.canRead()) {
st_mode |= ALL_READ;
}
if (file.canWrite()) {
st_mode |= ALL_WRITE;
st_mode &= ~(S_IWGRP | S_IWOTH);
}
if (file.isDirectory()) {
st_mode |= S_IFDIR;
} else if (file.isFile()) {
st_mode |= S_IFREG;
}
if (posixAttrs != null && posixAttrs.isSymbolicLink()) {
st_mode |= S_IFLNK;
} else {
try {
st_mode = calculateSymlink(file, st_mode);
} catch (IOException e) {
// Not sure we can do much in this case...
}
}
return st_mode;
}
private static short calculateSymlink(File file, short st_mode) throws IOException {
if (file.getAbsoluteFile().getParentFile() == null) {
return st_mode;
}
File absoluteParent = file.getAbsoluteFile().getParentFile();
File canonicalParent = absoluteParent.getCanonicalFile();
if (canonicalParent.getAbsolutePath().equals(absoluteParent.getAbsolutePath())) {
// parent doesn't change when canonicalized, compare absolute and canonical file directly
if (!file.getAbsolutePath().equalsIgnoreCase(file.getCanonicalPath())) {
st_mode |= S_IFLNK;
return st_mode;
}
}
// directory itself has symlinks (canonical != absolute), so build new path with canonical parent and compare
file = new JavaSecuredFile(canonicalParent.getAbsolutePath() + "/" + file.getName());
if (!file.getAbsolutePath().equalsIgnoreCase(file.getCanonicalPath())) {
st_mode |= S_IFLNK;
}
return st_mode;
}
/**
* Limitation: Java has no access time support, so we return mtime as the next best thing.
*/
public long atime() {
return (int) (attrs.lastAccessTime().toMillis() / 1000);
}
public long ctime() {
return (int) (attrs.creationTime().toMillis() / 1000);
}
public boolean isDirectory() {
return attrs.isDirectory();
}
public boolean isEmpty() {
return attrs.size() == 0;
}
public boolean isExecutable() {
if (posixAttrs != null) {
Set<PosixFilePermission> permissions = posixAttrs.permissions();
return permissions.contains(PosixFilePermission.OWNER_EXECUTE) ||
permissions.contains(PosixFilePermission.GROUP_EXECUTE) ||
permissions.contains(PosixFilePermission.OTHERS_EXECUTE);
}
// silently return false, since it's likely an unusual filesystem
return false;
}
public boolean isExecutableReal() {
return isExecutable();
}
public boolean isFile() {
return attrs.isRegularFile();
}
public boolean isGroupOwned() {
return groupMember(gid());
}
public boolean isIdentical(FileStat other) {
// if attrs supports file keys, we can compare them
Object key = attrs.fileKey();
if (key != null && other instanceof JavaFileStat) {
JavaFileStat otherStat = (JavaFileStat) other;
return key.equals(otherStat.attrs.fileKey());
}
handler.unimplementedError("identical file detection");
return false;
}
public boolean isOwned() {
return posix.geteuid() == uid();
}
public boolean isROwned() {
return posix.getuid() == uid();
}
public boolean isReadable() {
if (posixAttrs != null) {
Set<PosixFilePermission> permissions = posixAttrs.permissions();
return permissions.contains(PosixFilePermission.OWNER_READ) ||
permissions.contains(PosixFilePermission.GROUP_READ) ||
permissions.contains(PosixFilePermission.OTHERS_READ);
}
int mode = mode();
if ((mode & S_IRUSR) != 0) return true;
if ((mode & S_IRGRP) != 0) return true;
if ((mode & S_IROTH) != 0) return true;
return false;
}
// We do both readable and readable_real through the same method because
public boolean isReadableReal() {
return isReadable();
}
public boolean isSymlink() {
if (posixAttrs != null) {
return posixAttrs.isSymbolicLink();
}
return (mode() & S_IFLNK) == S_IFLNK;
}
public boolean isWritable() {
if (posixAttrs != null) {
Set<PosixFilePermission> permissions = posixAttrs.permissions();
return permissions.contains(PosixFilePermission.OWNER_WRITE) ||
permissions.contains(PosixFilePermission.GROUP_WRITE) ||
permissions.contains(PosixFilePermission.OTHERS_WRITE);
} else if (dosAttrs != null) {
return !dosAttrs.isReadOnly();
}
int mode = mode();
if ((mode & S_IWUSR) != 0) return true;
if ((mode & S_IWGRP) != 0) return true;
if ((mode & S_IWOTH) != 0) return true;
return false;
}
// We do both readable and readable_real through the same method because
// in our java process effective and real userid will always be the same.
public boolean isWritableReal() {
return isWritable();
}
public int mode() {
return st_mode & 0xffff;
}
public long mtime() {
return (int) (attrs.lastModifiedTime().toMillis() / 1000);
}
public long st_size() {
return attrs.size();
}
}