package co.paralleluniverse.fuse; import java.io.IOException; import java.nio.file.AccessDeniedException; import java.nio.file.Files; import java.nio.file.NotDirectoryException; import java.nio.file.Path; import java.util.Map; import java.util.Random; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; import jnr.ffi.Struct; public final class Fuse { private static final class MountThread extends Thread { private Integer result = null; private final String[] args; private final StructFuseOperations operations; private final LibFuse fuse; private final Path mountPoint; private MountThread(String filesystemName, LibFuse fuse, String[] args, Path mountPoint, StructFuseOperations operations) { super(filesystemName + "-fuse"); this.fuse = fuse; this.args = args; this.mountPoint = mountPoint; this.operations = operations; } private Integer getResult() { return result; } @Override public void run() { result = fuse.fuse_main_real(args.length, args, operations, Struct.size(operations), null); } } private static LibFuse libFuse = null; private static long initTime; private static final Lock initLock = new ReentrantLock(); private static final Random defaultFilesystemRandom = new Random(); private static final long errorSleepDuration = 750; private static String fusermount = "fusermount"; private static String umount = "umount"; private static int currentUid = 0; private static int currentGid = 0; private static final ConcurrentMap<Path, FuseFilesystem> mountedFs = new ConcurrentHashMap<>(); static void destroyed(FuseFilesystem fuseFilesystem) { if (handleShutdownHooks()) { try { Runtime.getRuntime().removeShutdownHook(fuseFilesystem.getUnmountHook()); } catch (IllegalStateException e) { // Already shutting down; this is fine and expected, ignore the exception. } } } static StructFuseContext getFuseContext() { return init().fuse_get_context(); } static int getGid() { return currentGid; } static long getInitTime() { init(); return initTime; } static int getUid() { return currentUid; } private static boolean handleShutdownHooks() { final SecurityManager security = System.getSecurityManager(); if (security == null) { return true; } try { security.checkPermission(new RuntimePermission("shutdownHooks")); return true; } catch (SecurityException e) { return false; } } static LibFuse init() throws UnsatisfiedLinkError { if (libFuse != null) return libFuse; // No need to lock if everything is fine already initLock.lock(); try { if (libFuse == null) { libFuse = Platform.fuse(); initTime = System.currentTimeMillis(); } try { currentUid = Integer.parseInt(new ProcessGobbler("id", "-u").getStdout()); currentGid = Integer.parseInt(new ProcessGobbler("id", "-g").getStdout()); } catch (Exception e) { // Oh well, keep default values } return libFuse; } finally { initLock.unlock(); } } public static void mount(FuseFilesystem filesystem, Path mountPoint, boolean blocking, boolean debug, Map<String, String> mountOptions) throws IOException { mountPoint = mountPoint.toAbsolutePath().normalize().toRealPath(); if (!Files.isDirectory(mountPoint)) throw new NotDirectoryException(mountPoint.toString()); if (!Files.isReadable(mountPoint) || !Files.isWritable(mountPoint) || !Files.isExecutable(mountPoint)) throw new AccessDeniedException(mountPoint.toString()); final Logger logger = filesystem.getLogger(); if (logger != null) filesystem = new LoggedFuseFilesystem(filesystem, logger); filesystem.mount(mountPoint, blocking); final String filesystemName = filesystem.getFuseName(); final String[] options = toOptionsArray(mountOptions); final String[] argv; if (options == null) argv = new String[debug ? 4 : 3]; else { argv = new String[(debug ? 4 : 3) + options.length]; System.arraycopy(options, 0, argv, (debug ? 3 : 2), options.length); } argv[0] = filesystemName; argv[1] = "-f"; if (debug) argv[2] = "-d"; argv[argv.length - 1] = mountPoint.toString(); final LibFuse fuse = init(); final StructFuseOperations operations = new StructFuseOperations(jnr.ffi.Runtime.getRuntime(fuse), filesystem); if (handleShutdownHooks()) Runtime.getRuntime().addShutdownHook(filesystem.getUnmountHook()); mountedFs.put(mountPoint, filesystem); final Integer result; if (blocking) result = fuse.fuse_main_real(argv.length, argv, operations, Struct.size(operations), null); else { final MountThread mountThread = new MountThread(filesystemName, fuse, argv, mountPoint, operations); mountThread.start(); try { mountThread.join(errorSleepDuration); } catch (final InterruptedException e) { } result = mountThread.getResult(); } if (result != null && result != 0) throw new FuseException(result); } static void unmount(FuseFilesystem fuseFilesystem) throws IOException { fuseFilesystem.unmount(); final Path mountPoint = fuseFilesystem.getMountPoint(); final FuseFilesystem fs = mountedFs.remove(mountPoint); assert fs == null || fs == fuseFilesystem; unmount(mountPoint); } public static void setFusermount(String fusermount) { Fuse.fusermount = fusermount; } public static void setUmount(String umount) { Fuse.umount = umount; } /** * Try to unmount an existing FUSE mountpoint. NOTE: You should use {@link FuseFilesystem#unmount FuseFilesystem.unmount()} * for unmounting the FuseFilesystem (or let the shutdown hook take care unmounting during shutdown of the application). * This method is available for special cases, e.g. where mountpoints were left over from previous invocations and need to * be unmounted before the filesystem can be mounted again. * * @param mountPoint The location where the filesystem is mounted. * @throws IOException thrown if an error occurs while starting the external process. */ public static void unmount(Path mountPoint) throws IOException { final FuseFilesystem fs = mountedFs.get(mountPoint); if (fs != null) { unmount(fs); return; } ProcessGobbler process; try { process = new ProcessGobbler(Fuse.fusermount, "-z", "-u", mountPoint.toString()); } catch (IOException e) { process = new ProcessGobbler(Fuse.umount, mountPoint.toString()); } final int res = process.getReturnCode(); if (res != 0) throw new FuseException(res); } private static String[] toOptionsArray(Map<String, String> options) { if (options == null) { return null; } int i = 0; String[] values = new String[options.size() * 2]; for (Entry<String, String> entry : options.entrySet()) { values[i++] = "-o"; values[i++] = entry.getKey() + (entry.getValue() == null ? "" : "=" + entry.getValue()); } return values; } }