package co.paralleluniverse.javafs; import java.io.FileNotFoundException; import java.io.IOError; import java.io.IOException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.channels.Channel; import java.nio.channels.FileChannel; import java.nio.channels.SeekableByteChannel; import java.nio.channels.FileLock; import java.nio.file.AccessDeniedException; import java.nio.file.AccessMode; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.attribute.FileTime; import java.nio.file.NotDirectoryException; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.ReadOnlyFileSystemException; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.nio.file.attribute.GroupPrincipal; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import jnr.constants.platform.Errno; import jnr.ffi.Pointer; import co.paralleluniverse.fuse.AccessConstants; import co.paralleluniverse.fuse.DirectoryFiller; import co.paralleluniverse.fuse.StructFuseFileInfo; import co.paralleluniverse.fuse.FuseFilesystem; import co.paralleluniverse.fuse.StructFlock; import co.paralleluniverse.fuse.StructFuseBufvec; import co.paralleluniverse.fuse.StructFusePollHandle; import co.paralleluniverse.fuse.StructStat; import co.paralleluniverse.fuse.StructStatvfs; import co.paralleluniverse.fuse.StructTimeBuffer; import co.paralleluniverse.fuse.TypeMode; import co.paralleluniverse.fuse.XattrFiller; import co.paralleluniverse.fuse.XattrListFiller; import java.util.Arrays; import java.util.logging.Level; class FuseFileSystemProvider extends FuseFilesystem { private final FileSystemProvider fsp; private final FileSystem fs; private final ConcurrentMap<Long, Object> openFiles = new ConcurrentHashMap<>(); private final AtomicLong fileHandle = new AtomicLong(0); private final boolean debug; private static final long BLOCK_SIZE = 4096; public FuseFileSystemProvider(FileSystemProvider fsp, URI uri, boolean debug) { this.fsp = fsp; this.fs = fsp.getFileSystem(uri); this.debug = debug; } public FuseFileSystemProvider(FileSystem fs, boolean debug) { this.fsp = fs.provider(); this.fs = fs; this.debug = debug; } private Path path(String p) { return fs.getPath(p); } @Override protected void afterUnmount(Path mountPoint) { } @Override protected void beforeMount(Path mountPoint) { } @Override protected String getName() { return fs.toString(); } @Override protected String[] getOptions() { return null; } @Override protected int getattr(String path, StructStat stat) { try { Path p = path(path); BasicFileAttributes attributes = fsp.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); // time stat.atime(sec(attributes.lastAccessTime()), nsec(attributes.lastAccessTime())); stat.mtime(sec(attributes.lastModifiedTime()), nsec(attributes.lastModifiedTime())); stat.ctime(sec(attributes.creationTime()), nsec(attributes.creationTime())); stat.size(attributes.size()); // mode long mode = 0L; if (attributes.isRegularFile()) mode = TypeMode.S_IFREG; else if (attributes.isDirectory()) mode = TypeMode.S_IFDIR; else if (attributes.isSymbolicLink()) mode = TypeMode.S_IFLNK; PosixFileAttributes pas = attributes instanceof PosixFileAttributes ? (PosixFileAttributes) attributes : null; if (pas == null) { try { pas = fsp.readAttributes(p, PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); } catch (UnsupportedOperationException e) { } } if (pas != null) mode |= permissionsToMode(pas.permissions()); stat.mode(mode); try { final Map<String, Object> uattrs = fsp.readAttributes(p, "unix:*", LinkOption.NOFOLLOW_LINKS); int uid = (int) uattrs.get("uid"); stat.uid(uid); int gid = (int) uattrs.get("gid"); stat.gid(gid); } catch (Exception e) { } return 0; } catch (Exception e) { return -errno(e); } } @Override protected int readlink(String path, ByteBuffer buffer, long size) { try { fillBufferWithString(fsp.readSymbolicLink(path(path)).toString(), buffer, size); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int mknod(String path, long mode, long dev) { return create(path, mode, null); } @Override protected int mkdir(String path, long mode) { try { fsp.createDirectory(path(path), PosixFilePermissions.asFileAttribute(modeToPermissions(mode))); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int unlink(String path) { try { Path p = path(path); if (Files.isDirectory(p)) throw new IOException(p + " is a directory"); fsp.delete(path(path)); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int rmdir(String path) { try { Path p = path(path); if (!Files.isDirectory(p)) throw new IOException(p + " is not a directory"); fsp.delete(p); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int symlink(String path, String target) { try { fsp.createSymbolicLink(path(path), path(target)); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int rename(String path, String newName) { try { fsp.move(path(path), path(newName)); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int link(String path, String target) { try { fsp.createLink(path(target), path(path)); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int chmod(String path, long mode) { try { final PosixFileAttributeView attrs = fsp.getFileAttributeView(path(path), PosixFileAttributeView.class); attrs.setPermissions(modeToPermissions(mode)); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int chown(String path, long uid, long gid) { try { final PosixFileAttributeView attrs = fsp.getFileAttributeView(path(path), PosixFileAttributeView.class); attrs.setOwner(fs.getUserPrincipalLookupService().lookupPrincipalByName(Long.toString(uid))); attrs.setGroup(fs.getUserPrincipalLookupService().lookupPrincipalByGroupName(Long.toString(gid))); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int truncate(String path, long offset) { try { final SeekableByteChannel ch = fsp.newByteChannel(path(path), EnumSet.of(StandardOpenOption.WRITE)); ch.truncate(offset); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int open(String path, StructFuseFileInfo info) { try { final SeekableByteChannel channel = fsp.newByteChannel(path(path), fileInfoToOpenOptions(info)); final long fh = fileHandle.incrementAndGet(); openFiles.put(fh, channel); info.fh(fh); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int read(String path, ByteBuffer buffer, long size, long offset, StructFuseFileInfo info) { try { final Channel channel = toChannel(info); if (channel instanceof SeekableByteChannel) { final SeekableByteChannel ch = ((SeekableByteChannel) channel); if (info.nonseekable()) assert offset == ch.position(); else ch.position(offset); int n = ch.read(buffer); if (n > 0) { if (!info.noblock()) assert n <= 0 || n == size; else { int c; while (n < size) { if ((c = ch.read(buffer)) <= 0) break; n += c; } } } return n; } else if (channel instanceof AsynchronousFileChannel) { final AsynchronousFileChannel ch = ((AsynchronousFileChannel) channel); int n = ch.read(buffer, offset).get(); assert n == size; return n; } else throw new UnsupportedOperationException(); } catch (Exception e) { return -errno(e); } } @Override protected int write(String path, ByteBuffer buffer, long size, long offset, StructFuseFileInfo info) { try { final Channel channel = toChannel(info); if (channel instanceof SeekableByteChannel) { final SeekableByteChannel ch = ((SeekableByteChannel) channel); if (!info.append() && !info.nonseekable()) ch.position(offset); int n = ch.write(buffer); if (n > 0) { if (!info.noblock()) assert n <= 0 || n == size; else { int c; while (n < size) { if ((c = ch.write(buffer)) <= 0) break; n += c; } } } return n; } else if (channel instanceof AsynchronousFileChannel) { final AsynchronousFileChannel ch = ((AsynchronousFileChannel) channel); int n = ch.write(buffer, offset).get(); return n; } else throw new UnsupportedOperationException(); } catch (Exception e) { return -errno(e); } } @Override protected int statfs(String path, StructStatvfs statvfs) { try { boolean hasStore = false; // only one store allowed for (FileStore store : fs.getFileStores()) { if (hasStore) throw new IOException("Multiple FileStores not supported"); hasStore = true; final long blockSize = BLOCK_SIZE; statvfs.bsize(blockSize); statvfs.blocks(store.getTotalSpace() / blockSize); statvfs.bfree(store.getUnallocatedSpace() / blockSize); statvfs.bavail(store.getUsableSpace() / blockSize); long mountFlags = 0; if (fs.isReadOnly()) mountFlags |= StructStatvfs.ST_RDONLY; statvfs.flags(mountFlags); } return 0; } catch (Exception e) { return -errno(e); } } @Override protected int flush(String path, StructFuseFileInfo info) { return 0; } @Override public int release(String path, StructFuseFileInfo info) { try { final Channel ch = toChannel(info); ch.close(); openFiles.remove(info.fh()); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int fsync(String path, int datasync, StructFuseFileInfo info) { try { final Channel channel = toChannel(info); if (channel instanceof FileChannel) { final FileChannel ch = ((FileChannel) channel); ch.force(datasync == 0); } else if (channel instanceof AsynchronousFileChannel) { final AsynchronousFileChannel ch = ((AsynchronousFileChannel) channel); ch.force(true); ch.force(datasync == 0); } else throw new UnsupportedOperationException(); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int setxattr(String path, String xattr, ByteBuffer value, long size, int flags, int position) { return -Errno.ENOSYS.intValue(); } @Override protected int getxattr(String path, String xattr, XattrFiller filler, long size, long position) { return -Errno.ENOSYS.intValue(); } @Override protected int listxattr(String path, XattrListFiller filler) { return -Errno.ENOSYS.intValue(); } @Override protected int removexattr(String path, String xattr) { return -Errno.ENOSYS.intValue(); } @Override protected int opendir(String path, StructFuseFileInfo info) { try { final DirectoryStream<Path> ds = fsp.newDirectoryStream(path(path), new DirectoryStream.Filter<Path>() { @Override public boolean accept(Path entry) throws IOException { return true; } }); final long fh = fileHandle.incrementAndGet(); openFiles.put(fh, ds); info.fh(fh); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int readdir(String path, StructFuseFileInfo info, DirectoryFiller filler) { final DirectoryStream<Path> ds = (DirectoryStream<Path>) openFiles.get(info.fh()); filler.add(toStringIterable(ds)); return 0; } @Override protected int releasedir(String path, StructFuseFileInfo info) { try { final DirectoryStream<Path> ds = (DirectoryStream<Path>) openFiles.get(info.fh()); ds.close(); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int fsyncdir(String path, int datasync, StructFuseFileInfo info) { return 0; } @Override protected void init() { } @Override protected void destroy() { try { fs.close(); } catch (IOException ex) { throw new RuntimeException(ex); } } @Override protected int access(String path, int access) { try { final Path p = path(path); fsp.checkAccess(p, toAccessMode(access, Files.isDirectory(p))); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int create(String path, long mode, StructFuseFileInfo info) { try { final Set<OpenOption> options = fileInfoToOpenOptions(info); options.add(StandardOpenOption.CREATE); final SeekableByteChannel channel = fsp.newByteChannel(path(path), options); final long fh = fileHandle.incrementAndGet(); openFiles.put(fh, channel); info.fh(fh); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int ftruncate(String path, long offset, StructFuseFileInfo info) { try { final Channel channel = toChannel(info); if (channel instanceof SeekableByteChannel) ((SeekableByteChannel) channel).truncate(offset); else if (channel instanceof AsynchronousFileChannel) ((AsynchronousFileChannel) channel).truncate(offset); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int fgetattr(String path, StructStat stat, StructFuseFileInfo info) { return getattr(path, stat); } @Override protected int lock(String path, StructFuseFileInfo info, int command, StructFlock flock) { try { throw new UnsupportedOperationException(); // if (command == StructFlock.CMD_GETLK) // throw new UnsupportedOperationException(); // // final Channel channel = toChannel(info); // if (channel instanceof FileChannel) { // FileChannel ch = (FileChannel) channel; // switch (command) { // case StructFlock.CMD_SETLK: // FileLock lock = ch.lock(flock.start(), flock.len(), false); // lock. // } // } else if (channel instanceof AsynchronousFileChannel) { // AsynchronousFileChannel ch = (AsynchronousFileChannel) channel; // } // return 0; } catch (Exception e) { return -errno(e); } } @Override protected int utimens(String path, StructTimeBuffer timeBuffer) { try { fsp.getFileAttributeView(path(path), BasicFileAttributeView.class).setTimes( FileTime.from(toNanos(timeBuffer.mod_sec(), timeBuffer.mod_nsec()), TimeUnit.NANOSECONDS), FileTime.from(toNanos(timeBuffer.ac_sec(), timeBuffer.ac_nsec()), TimeUnit.NANOSECONDS), null); return 0; } catch (Exception e) { return -errno(e); } } @Override protected int bmap(String path, StructFuseFileInfo info) { try { throw new UnsupportedOperationException(); } catch (Exception e) { return -errno(e); } } @Override public int ioctl(String path, int cmd, Pointer arg, StructFuseFileInfo fi, long flags, Pointer data) { throw new UnsupportedOperationException("Not supported yet."); } @Override public int poll(String path, StructFuseFileInfo fi, StructFusePollHandle ph, Pointer reventsp) { throw new UnsupportedOperationException("Not supported yet."); } @Override protected int write_buf(String path, StructFuseBufvec buf, long off, StructFuseFileInfo fi) { throw new UnsupportedOperationException("Not supported yet."); // TODO: implement } @Override protected int read_buf(String path, Pointer bufp, long size, long off, StructFuseFileInfo fi) { throw new UnsupportedOperationException("Not supported yet."); // TODO: implement } @Override public int flock(String path, StructFuseFileInfo fi, int op) { throw new UnsupportedOperationException("Not supported yet."); } @Override public int fallocate(String path, int mode, long off, long length, StructFuseFileInfo fi) { throw new UnsupportedOperationException("Not supported yet."); } //////////// private Channel toChannel(StructFuseFileInfo info) { return (Channel) openFiles.get(info.fh()); } private static Set<OpenOption> fileInfoToOpenOptions(StructFuseFileInfo info) { final Set<OpenOption> options = new HashSet<>(); if (info != null) { if (info.create()) options.add(StandardOpenOption.CREATE); if (info.append()) options.add(StandardOpenOption.APPEND); if (info.truncate()) options.add(StandardOpenOption.TRUNCATE_EXISTING); switch (info.openMode()) { case StructFuseFileInfo.O_RDONLY: options.add(StandardOpenOption.READ); break; case StructFuseFileInfo.O_WRONLY: options.add(StandardOpenOption.WRITE); break; case StructFuseFileInfo.O_RDWR: options.add(StandardOpenOption.READ); options.add(StandardOpenOption.WRITE); break; } } return options; } private static Set<PosixFilePermission> modeToPermissions(long mode) { final EnumSet<PosixFilePermission> permissions = EnumSet.noneOf(PosixFilePermission.class); if ((mode & TypeMode.S_IRUSR) != 0) permissions.add(PosixFilePermission.OWNER_READ); if ((mode & TypeMode.S_IWUSR) != 0) permissions.add(PosixFilePermission.OWNER_WRITE); if ((mode & TypeMode.S_IXUSR) != 0) permissions.add(PosixFilePermission.OWNER_EXECUTE); if ((mode & TypeMode.S_IRGRP) != 0) permissions.add(PosixFilePermission.GROUP_READ); if ((mode & TypeMode.S_IWGRP) != 0) permissions.add(PosixFilePermission.GROUP_WRITE); if ((mode & TypeMode.S_IXGRP) != 0) permissions.add(PosixFilePermission.GROUP_EXECUTE); if ((mode & TypeMode.S_IROTH) != 0) permissions.add(PosixFilePermission.OTHERS_READ); if ((mode & TypeMode.S_IWOTH) != 0) permissions.add(PosixFilePermission.OTHERS_WRITE); if ((mode & TypeMode.S_IXOTH) != 0) permissions.add(PosixFilePermission.OTHERS_EXECUTE); return permissions; } private static long permissionsToMode(Set<PosixFilePermission> permissions) { long mode = 0; for (PosixFilePermission px : permissions) { switch (px) { case OWNER_READ: mode |= TypeMode.S_IRUSR; break; case OWNER_WRITE: mode |= TypeMode.S_IWUSR; break; case OWNER_EXECUTE: mode |= TypeMode.S_IXUSR; break; case GROUP_READ: mode |= TypeMode.S_IRGRP; break; case GROUP_WRITE: mode |= TypeMode.S_IWGRP; break; case GROUP_EXECUTE: mode |= TypeMode.S_IXGRP; break; case OTHERS_READ: mode |= TypeMode.S_IROTH; break; case OTHERS_WRITE: mode |= TypeMode.S_IWOTH; break; case OTHERS_EXECUTE: mode |= TypeMode.S_IXOTH; break; } } return mode; } private static AccessMode[] toAccessMode(int access, boolean dir) { List<AccessMode> modes = new ArrayList<>(3); if ((access & AccessConstants.R_OK) != 0 || (dir && (access & AccessConstants.X_OK) != 0)) modes.add(AccessMode.READ); if ((access & AccessConstants.W_OK) != 0) modes.add(AccessMode.WRITE); if (!dir && (access & AccessConstants.X_OK) != 0) modes.add(AccessMode.EXECUTE); return modes.toArray(new AccessMode[modes.size()]); } private static Iterable<String> toStringIterable(final Iterable<Path> iterable) { return new Iterable<String>() { @Override public Iterator<String> iterator() { final Iterator<Path> it = iterable.iterator(); return new Iterator<String>() { @Override public boolean hasNext() { return it.hasNext(); } @Override public String next() { return it.next().toString(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } private int errno(Throwable e) { final Errno en = errno0(e); if (en == null) return 0; if (debug) getLogger().log(Level.WARNING, "Exception", e); return en.intValue(); } private static Errno errno0(Throwable t) { if (t == null) return null; if (t instanceof IllegalArgumentException) return Errno.EINVAL; if (t instanceof UnsupportedOperationException) return Errno.ENOSYS; // Errno.EOPNOTSUPP if (t instanceof InterruptedException) return Errno.EINTR; if (t instanceof NullPointerException || t instanceof ClassCastException) return Errno.EFAULT; if (t instanceof SecurityException) return Errno.EPERM; if (t instanceof TimeoutException) return Errno.ETIMEDOUT; if (t instanceof AccessDeniedException) return Errno.EACCES; if (t instanceof FileAlreadyExistsException) return Errno.EEXIST; if (t instanceof FileNotFoundException || t instanceof NoSuchFileException) return Errno.ENOENT; if (t instanceof NotDirectoryException) return Errno.ENOTDIR; if (t instanceof DirectoryNotEmptyException) return Errno.ENOTEMPTY; if (t instanceof ReadOnlyFileSystemException) return Errno.EROFS; // Errno.EACCES if (t instanceof IOException || t instanceof IOError) return Errno.EIO; return Errno.EFAULT; } private static void fillBufferWithString(String str, ByteBuffer buffer, long size) { final byte[] bytes = str.getBytes(); final int s = (int) Math.min((long) Integer.MAX_VALUE, size - 1); buffer.put(bytes, 0, Math.min(bytes.length, s)); buffer.put((byte) 0); buffer.flip(); } private static long toNanos(long sec, long nanos) { return sec * 1_000_000_000 + nanos; } private static long sec(FileTime ft) { return ft != null ? sec(ft.to(TimeUnit.NANOSECONDS)) : 0; } private static long nsec(FileTime ft) { return ft != null ? nsec(ft.to(TimeUnit.NANOSECONDS)) : 0; } private static long sec(long nanos) { return nanos / 1_000_000_000; } private static long nsec(long nanos) { return nanos % 1_000_000_000; } }