package ru.serce.jnrfuse; import jnr.ffi.LibraryLoader; import jnr.ffi.Pointer; import jnr.ffi.Runtime; import jnr.ffi.Struct; import jnr.ffi.mapper.FromNativeConverter; import jnr.ffi.provider.jffi.ClosureHelper; import ru.serce.jnrfuse.struct.*; import ru.serce.jnrfuse.utils.MountUtils; import ru.serce.jnrfuse.utils.SecurityUtils; import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; public abstract class AbstractFuseFS implements FuseFS { private static final int TIMEOUT = 2000; // ms private static final String[] osxFuseLibraries = {"fuse4x", "osxfuse", "macfuse", "fuse"}; private Set<String> notImplementedMethods; protected final LibFuse libFuse; protected final FuseOperations fuseOperations; protected final AtomicBoolean mounted = new AtomicBoolean(); protected Path mountPoint; public AbstractFuseFS() { LibraryLoader<LibFuse> loader = LibraryLoader.create(LibFuse.class).failImmediately(); jnr.ffi.Platform p = jnr.ffi.Platform.getNativePlatform(); LibFuse libFuse = null; switch (p.getOS()) { case LINUX: libFuse = loader.load("libfuse.so.2"); break; case DARWIN: for (String library : osxFuseLibraries) { try { // Regular FUSE-compatible fuse library libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load(library); break; } catch (Throwable e) { // Carry on } } if (libFuse == null) { // Everything failed. Do a last-ditch attempt. // Worst-case scenario, this causes an exception // which will be more meaningful to the user than a NullPointerException on libFuse. libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("fuse"); } break; default: // try fuse try { // assume linux libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("libfuse.so.2"); } catch (Throwable e) { libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("fuse"); } } this.libFuse = libFuse; Runtime runtime = Runtime.getSystemRuntime(); fuseOperations = new FuseOperations(runtime); init(fuseOperations); } public FuseContext getContext() { return libFuse.fuse_get_context(); } private void init(FuseOperations fuseOperations) { notImplementedMethods = Arrays.stream(getClass().getMethods()) .filter(method -> method.getAnnotation(NotImplemented.class) != null) .map(Method::getName) .collect(Collectors.toSet()); AbstractFuseFS fuse = this; if (isImplemented("getattr")) { fuseOperations.getattr.set((path, stbuf) -> fuse.getattr(path, FileStat.of(stbuf))); } if (isImplemented("readlink")) { fuseOperations.readlink.set(fuse::readlink); } if (isImplemented("mknod")) { fuseOperations.mknod.set(fuse::mknod); } if (isImplemented("mkdir")) { fuseOperations.mkdir.set(fuse::mkdir); } if (isImplemented("unlink")) { fuseOperations.unlink.set(fuse::unlink); } if (isImplemented("rmdir")) { fuseOperations.rmdir.set(fuse::rmdir); } if (isImplemented("symlink")) { fuseOperations.symlink.set(fuse::symlink); } if (isImplemented("rename")) { fuseOperations.rename.set(fuse::rename); } if (isImplemented("link")) { fuseOperations.link.set(fuse::link); } if (isImplemented("chmod")) { fuseOperations.chmod.set(fuse::chmod); } if (isImplemented("chown")) { fuseOperations.chown.set(fuse::chown); } if (isImplemented("truncate")) { fuseOperations.truncate.set(fuse::truncate); } if (isImplemented("open")) { fuseOperations.open.set((path, fi) -> fuse.open(path, FuseFileInfo.of(fi))); } if (isImplemented("read")) { fuseOperations.read.set((path, buf, size, offset, fi) -> fuse.read(path, buf, size, offset, FuseFileInfo.of(fi))); } if (isImplemented("write")) { fuseOperations.write.set((path, buf, size, offset, fi) -> fuse.write(path, buf, size, offset, FuseFileInfo.of(fi))); } if (isImplemented("statfs")) { fuseOperations.statfs.set((path, stbuf) -> fuse.statfs(path, Statvfs.of(stbuf))); } if (isImplemented("flush")) { fuseOperations.flush.set((path, fi) -> fuse.flush(path, FuseFileInfo.of(fi))); } if (isImplemented("release")) { fuseOperations.release.set((path, fi) -> fuse.release(path, FuseFileInfo.of(fi))); } if (isImplemented("fsync")) { fuseOperations.fsync.set((path, isdatasync, fi) -> fuse.fsync(path, isdatasync, FuseFileInfo.of(fi))); } if (isImplemented("setxattr")) { fuseOperations.setxattr.set(fuse::setxattr); } if (isImplemented("getxattr")) { fuseOperations.getxattr.set(fuse::getxattr); } if (isImplemented("listxattr")) { fuseOperations.listxattr.set(fuse::listxattr); } if (isImplemented("removexattr")) { fuseOperations.removexattr.set(fuse::removexattr); } if (isImplemented("opendir")) { fuseOperations.opendir.set((path, fi) -> fuse.opendir(path, FuseFileInfo.of(fi))); } if (isImplemented("readdir")) { fuseOperations.readdir.set((path, buf, filter, offset, fi) -> { ClosureHelper helper = ClosureHelper.getInstance(); FromNativeConverter<FuseFillDir, Pointer> conveter = helper.getNativeConveter(FuseFillDir.class); FuseFillDir filterFunc = conveter.fromNative(filter, helper.getFromNativeContext()); return fuse.readdir(path, buf, filterFunc, offset, FuseFileInfo.of(fi)); }); } if (isImplemented("releasedir")) { fuseOperations.releasedir.set((path, fi) -> fuse.releasedir(path, FuseFileInfo.of(fi))); } if (isImplemented("fsyncdir")) { fuseOperations.fsyncdir.set((path, fi) -> fuse.fsyncdir(path, FuseFileInfo.of(fi))); } if (isImplemented("init")) { fuseOperations.init.set(fuse::init); } if (isImplemented("destroy")) { fuseOperations.destroy.set(fuse::destroy); } if (isImplemented("access")) { fuseOperations.access.set(fuse::access); } if (isImplemented("create")) { fuseOperations.create.set((path, mode, fi) -> fuse.create(path, mode, FuseFileInfo.of(fi))); } if (isImplemented("ftruncate")) { fuseOperations.ftruncate.set((path, size, fi) -> fuse.ftruncate(path, size, FuseFileInfo.of(fi))); } if (isImplemented("fgetattr")) { fuseOperations.fgetattr.set((path, stbuf, fi) -> fuse.fgetattr(path, FileStat.of(stbuf), FuseFileInfo.of(fi))); } if (isImplemented("lock")) { fuseOperations.lock.set((path, fi, cmd, flock) -> fuse.lock(path, FuseFileInfo.of(fi), cmd, Flock.of(flock))); } if (isImplemented("utimens")) { fuseOperations.utimens.set((path, timespec) -> { Timespec timespec1 = Timespec.of(timespec); Timespec timespec2 = Timespec.of(timespec.slice(Struct.size(timespec1))); return fuse.utimens(path, new Timespec[]{timespec1, timespec2}); }); } if (isImplemented("bmap")) { fuseOperations.bmap.set((path, blocksize, idx) -> fuse.bmap(path, blocksize, idx.getLong(0))); } if (isImplemented("ioctl")) { fuseOperations.ioctl.set((path, cmd, arg, fi, flags, data) -> fuse.ioctl(path, cmd, arg, FuseFileInfo.of(fi), flags, data)); } if (isImplemented("poll")) { fuseOperations.poll.set((path, fi, ph, reventsp) -> fuse.poll(path, FuseFileInfo.of(fi), FusePollhandle.of(ph), reventsp)); } if (isImplemented("write_buf")) { fuseOperations.write_buf.set((path, buf, off, fi) -> fuse.write_buf(path, FuseBufvec.of(buf), off, FuseFileInfo.of(fi))); } if (isImplemented("read_buf")) { fuseOperations.read_buf.set((path, bufp, size, off, fi) -> fuse.read_buf(path, bufp, size, off, FuseFileInfo.of(fi))); } if (isImplemented("flock")) { fuseOperations.flock.set((path, fi, op) -> fuse.flock(path, FuseFileInfo.of(fi), op)); } if (isImplemented("fallocate")) { fuseOperations.fallocate.set((path, mode, off, length, fi) -> fuse.fallocate(path, mode, off, length, FuseFileInfo.of(fi))); } } @Override public void mount(Path mountPoint, boolean blocking, boolean debug, String[] fuseOpts) { if (!mounted.compareAndSet(false, true)) { throw new FuseException("Fuse fs already mounted!"); } this.mountPoint = mountPoint; String[] arg; if (!debug) { arg = new String[]{getFSName(), "-f", mountPoint.toAbsolutePath().toString()}; } else { arg = new String[]{getFSName(), "-f", "-d", mountPoint.toAbsolutePath().toString()}; } if (fuseOpts.length != 0) { int argLen = arg.length; arg = Arrays.copyOf(arg, argLen + fuseOpts.length); System.arraycopy(fuseOpts, 0, arg, argLen, fuseOpts.length); } final String[] args = arg; try { if (!Files.isDirectory(mountPoint)) { throw new FuseException("Mount point should be directory"); } if (SecurityUtils.canHandleShutdownHooks()) { java.lang.Runtime.getRuntime().addShutdownHook(new Thread(this::umount)); } int res; if (blocking) { res = execMount(args); } else { try { res = CompletableFuture .supplyAsync(() -> execMount(args)) .get(TIMEOUT, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { // ok res = 0; } } if (res != 0) { throw new FuseException("Unable to mount FS, return code = " + res); } } catch (Exception e) { mounted.set(false); throw new FuseException("Unable to mount FS", e); } } private boolean isImplemented(String funcName) { return !notImplementedMethods.contains(funcName); } private int execMount(String[] arg) { return libFuse.fuse_main_real(arg.length, arg, fuseOperations, Struct.size(fuseOperations), null); } @Override public void umount() { if (!mounted.get()) { return; } MountUtils.umount(mountPoint); mounted.set(false); } protected String getFSName() { return "fusefs" + ThreadLocalRandom.current().nextInt(); } }