package net.fusejna;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import net.fusejna.types.TypeSize;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
public final class FuseJna {
// private static final Logger logger =
// Logger.getLogger("org.panbox.desktop.common");
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 File mountPoint;
private MountThread(final String filesystemName, final LibFuse fuse,
final String[] args, final File mountPoint,
final StructFuseOperations operations) {
super(filesystemName + "-fuse");
this.fuse = fuse;
this.args = args;
this.mountPoint = mountPoint;
this.operations = operations;
start();
}
private final Integer getResult() {
return result;
}
@Override
public final void run() {
result = fuse.fuse_main_real(args.length, args, operations,
new TypeSize(operations), null);
unregisterFilesystemName(mountPoint);
}
}
private static LibFuse libFuse = null;
private static Lock initLock = new ReentrantLock();
private static Lock filesystemNameLock = new ReentrantLock();
private static final Random defaultFilesystemRandom = new Random();
private static final Map<File, String> filesystemNames = new HashMap<File, String>();
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 String getFilesystemName(final File mountPoint,
final String fuseName) {
filesystemNameLock.lock();
if (filesystemNames.put(mountPoint, fuseName) == null) {
filesystemNameLock.unlock();
return fuseName;
}
String suffix;
do {
suffix = Long.toString(defaultFilesystemRandom.nextLong());
} while (filesystemNames.put(mountPoint, fuseName + suffix) != null);
filesystemNameLock.unlock();
return fuseName + suffix;
}
static StructFuseContext getFuseContext() {
return init().fuse_get_context();
}
static final int getGid() {
return currentGid;
}
static final int getUid() {
return currentUid;
}
private static final boolean handleShutdownHooks() {
final SecurityManager security = System.getSecurityManager();
if (security == null) {
return true;
}
try {
security.checkPermission(new RuntimePermission("shutdownHooks"));
return true;
} catch (final SecurityException e) {
return false;
}
}
static final LibFuse init() throws UnsatisfiedLinkError {
if (libFuse != null) {
// No need to lock if everything is fine already
return libFuse;
}
initLock.lock();
if (libFuse == null) {
libFuse = Platform.fuse();
}
try {
currentUid = Integer.parseInt(new ProcessGobbler("id", "-u")
.getStdout());
currentGid = Integer.parseInt(new ProcessGobbler("id", "-g")
.getStdout());
} catch (final Exception e) {
// Oh well, keep default values
} finally {
initLock.unlock();
}
// initLock.unlock();
return libFuse;
}
static final void mount(FuseFilesystem filesystem, File mountPoint,
final boolean blocking) throws FuseException {
mountPoint = mountPoint.getAbsoluteFile();
try {
mountPoint = mountPoint.getCanonicalFile();
} catch (final IOException e) {
throw new NotADirectoryMountpointException(mountPoint);
}
if (!mountPoint.isDirectory()) {
throw new NotADirectoryMountpointException(mountPoint);
}
if (!mountPoint.canRead() || !mountPoint.canWrite()
|| !mountPoint.canExecute()) {
boolean successful = true;
try {
successful = mountPoint.setReadable(true) && successful;
successful = mountPoint.setWritable(true) && successful;
successful = mountPoint.setExecutable(true) && successful;
} catch (final Exception e) {
throw new InvalidPermissionsMountpointException(mountPoint);
}
if (!successful) {
throw new InvalidPermissionsMountpointException(mountPoint);
}
}
final Logger logger = filesystem.getLogger();
// initializes FuseFilesystem-instance with very detailed logging
if (logger != null && logger.getLevel() == Level.ALL) {
filesystem = new LoggedFuseFilesystem(filesystem, logger);
}
filesystem.setFinalMountPoint(mountPoint);
final String filesystemName = getFilesystemName(mountPoint,
filesystem.getFuseName());
final String[] options = filesystem.getOptions();
final String[] argv;
if (options == null) {
argv = new String[3];
} else {
argv = new String[3 + options.length];
for (int i = 0; i < options.length; i++) {
argv[i + 2] = options[i];
}
}
argv[0] = filesystemName;
argv[1] = "-f";
argv[argv.length - 1] = mountPoint.getAbsolutePath();
final LibFuse fuse = init();
final StructFuseOperations operations = new StructFuseOperations(
filesystem);
final Integer result;
if (handleShutdownHooks()) {
Runtime.getRuntime().addShutdownHook(filesystem.getUnmountHook());
}
if (blocking) {
result = fuse.fuse_main_real(argv.length, argv, operations,
new TypeSize(operations), null);
unregisterFilesystemName(mountPoint);
} else {
final MountThread mountThread = new MountThread(filesystemName,
fuse, argv, mountPoint, operations);
try {
Thread.sleep(errorSleepDuration);
} catch (final InterruptedException e) {
// Carry on
}
result = mountThread.getResult();
}
if (result != null && result != 0) {
throw new FuseException(result);
}
}
public static final void setFusermount(final String fusermount) {
FuseJna.fusermount = fusermount;
}
public static final void setUmount(final String umount) {
FuseJna.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.
* @return The exit code from running `fusermount` or `umount`, 0 indicates
* success. You can change the location of these utilities using
* `setFusermount` and `setUmount`.
* @throws IOException
* thrown if an error occurs while starting the external
* process.
*/
public static int unmount(final File mountPoint) throws IOException {
ProcessGobbler process;
try {
process = new ProcessGobbler(FuseJna.fusermount, "-z", "-u",
mountPoint.toString());
} catch (final IOException e) {
process = new ProcessGobbler(FuseJna.umount, mountPoint.toString());
}
return process.getReturnCode();
}
static void unmount(final FuseFilesystem fuseFilesystem)
throws IOException, FuseException {
if (handleShutdownHooks()) {
try {
Runtime.getRuntime().removeShutdownHook(
fuseFilesystem.getUnmountHook());
} catch (final IllegalStateException e) {
// Already shutting down; this is fine and expected, ignore the
// exception.
}
}
final File mountPoint = fuseFilesystem.getMountPoint();
final int result = unmount(mountPoint);
if (result != 0) {
throw new FuseException(result);
}
unregisterFilesystemName(fuseFilesystem.getMountPoint());
}
private static final void unregisterFilesystemName(final File mountPoint) {
filesystemNameLock.lock();
filesystemNames.remove(mountPoint);
filesystemNameLock.unlock();
}
}