Skip to content

Commit

Permalink
Merge branch 'release/1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Jul 3, 2019
2 parents 1fe2afa + 60f5016 commit e8b885a
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 114 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>fuse-nio-adapter</artifactId>
<version>1.1.3</version>
<version>1.2.0</version>
<name>FUSE-NIO-Adapter</name>
<description>Access resources at a given NIO path via FUSE.</description>
<url>https://github.com/cryptomator/fuse-nio-adapter</url>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
package org.cryptomator.frontend.fuse.mount;

import com.google.common.collect.ObjectArrays;
import com.google.common.base.Preconditions;
import org.cryptomator.frontend.fuse.AdapterFactory;
import org.cryptomator.frontend.fuse.FuseNioAdapter;

import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;

abstract class AbstractMount implements Mount {

protected final EnvironmentVariables envVars;
protected final FuseNioAdapter fuseAdapter;
protected final EnvironmentVariables envVars;

public AbstractMount(Path directory, EnvironmentVariables envVars) {
public AbstractMount(FuseNioAdapter fuseAdapter, EnvironmentVariables envVars) {
Preconditions.checkArgument(fuseAdapter.isMounted());
this.fuseAdapter = fuseAdapter;
this.envVars = envVars;
this.fuseAdapter = AdapterFactory.createReadWriteAdapter(directory);

}

protected void mount(String... additionalFuseOptions) throws CommandFailedException {
protected void mount() throws CommandFailedException {
try {
fuseAdapter.mount(envVars.getMountPath(), false, false, ObjectArrays.concat(getFuseOptions(), additionalFuseOptions, String.class));
fuseAdapter.mount(envVars.getMountPoint(), false, false, envVars.getFuseFlags());
} catch (RuntimeException e) {
throw new CommandFailedException(e);
}
}

protected abstract String[] getFuseOptions();

protected abstract ProcessBuilder getRevealCommand();

protected abstract ProcessBuilder getUnmountCommand();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@

public class EnvironmentVariables {

private final Path mountPath;
private final Optional<String> mountName;
private final Path mountPoint;
private final String[] fuseFlags;
private final Optional<String> revealCommand;

private EnvironmentVariables(Path mountPath, Optional<String> mountName, Optional<String> revealCommand) {
this.mountPath = mountPath;
this.mountName = mountName;
private EnvironmentVariables(Path mountPoint, String[] fuseFlags, Optional<String> revealCommand) {
this.mountPoint = mountPoint;
this.fuseFlags = fuseFlags;
this.revealCommand = revealCommand;
}

public static EnvironmentVariablesBuilder create() {
return new EnvironmentVariablesBuilder();
}

public Path getMountPath() {
return mountPath;
public Path getMountPoint() {
return mountPoint;
}

public Optional<String> getMountName() {
return mountName;
public String[] getFuseFlags() {
return fuseFlags;
}

public Optional<String> getRevealCommand() {
Expand All @@ -33,17 +33,17 @@ public Optional<String> getRevealCommand() {

public static class EnvironmentVariablesBuilder {

private Path mountPath = null;
private Optional<String> mountName = Optional.empty();
private Path mountPoint = null;
private String[] fuseFlags;
private Optional<String> revealCommand = Optional.empty();

public EnvironmentVariablesBuilder withMountPath(Path mountPath) {
this.mountPath = mountPath;
public EnvironmentVariablesBuilder withMountPoint(Path mountPoint) {
this.mountPoint = mountPoint;
return this;
}

public EnvironmentVariablesBuilder withMountName(String mountName) {
this.mountName = Optional.ofNullable(mountName);
public EnvironmentVariablesBuilder withFlags(String[] fuseFlags) {
this.fuseFlags = fuseFlags;
return this;
}

Expand All @@ -53,7 +53,7 @@ public EnvironmentVariablesBuilder withRevealCommand(String revealCommand) {
}

public EnvironmentVariables build() {
return new EnvironmentVariables(mountPath, mountName, revealCommand);
return new EnvironmentVariables(mountPoint, fuseFlags, revealCommand);
}

}
Expand Down
51 changes: 27 additions & 24 deletions src/main/java/org/cryptomator/frontend/fuse/mount/LinuxMounter.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
package org.cryptomator.frontend.fuse.mount;

import com.google.common.collect.ObjectArrays;
import org.cryptomator.frontend.fuse.AdapterFactory;
import org.cryptomator.frontend.fuse.FuseNioAdapter;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;

class LinuxMounter implements Mounter {

private static final boolean IS_LINUX = System.getProperty("os.name").toLowerCase().contains("linux");
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));

@Override
public Mount mount(Path directory, EnvironmentVariables envVars, String... additionalMountParams) throws CommandFailedException {
LinuxMount mount = new LinuxMount(directory, envVars);
mount.mount(additionalMountParams);
return mount;
public synchronized Mount mount(Path directory, EnvironmentVariables envVars) throws CommandFailedException {
FuseNioAdapter fuseAdapter = AdapterFactory.createReadWriteAdapter(directory);
try {
fuseAdapter.mount(envVars.getMountPoint(), false, false, envVars.getFuseFlags());
} catch (RuntimeException e) {
throw new CommandFailedException(e);
}
return new LinuxMount(fuseAdapter, envVars);
}

@Override
public String[] defaultMountFlags() {
try {
return new String[]{
"-ouid=" + Files.getAttribute(USER_HOME, "unix:uid"),
"-ogid=" + Files.getAttribute(USER_HOME, "unix:gid"),
"-oauto_unmount"
};
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
Expand All @@ -27,16 +46,15 @@ public boolean isApplicable() {

private static class LinuxMount extends AbstractMount {

private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
private static final String DEFAULT_REVEALCOMMAND_LINUX = "xdg-open";

private final ProcessBuilder revealCommand;
private final ProcessBuilder unmountCommand;
private final ProcessBuilder unmountForcedCommand;

private LinuxMount(Path directory, EnvironmentVariables envVars) {
super(directory, envVars);
Path mountPoint = envVars.getMountPath();
private LinuxMount(FuseNioAdapter fuseAdapter, EnvironmentVariables envVars) {
super(fuseAdapter, envVars);
Path mountPoint = envVars.getMountPoint();
String[] command = envVars.getRevealCommand().orElse(DEFAULT_REVEALCOMMAND_LINUX).split("\\s+");
this.revealCommand = new ProcessBuilder(ObjectArrays.concat(command, mountPoint.toString()));
this.unmountCommand = new ProcessBuilder("fusermount", "-u", "--", mountPoint.getFileName().toString());
Expand All @@ -45,21 +63,6 @@ private LinuxMount(Path directory, EnvironmentVariables envVars) {
this.unmountForcedCommand.directory(mountPoint.getParent().toFile());
}

@Override
protected String[] getFuseOptions() {
ArrayList<String> mountOptions = new ArrayList<>(8);
mountOptions.add(("-oatomic_o_trunc"));
try {
mountOptions.add("-ouid=" + Files.getAttribute(USER_HOME, "unix:uid"));
mountOptions.add("-ogid=" + Files.getAttribute(USER_HOME, "unix:gid"));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
mountOptions.add("-oauto_unmount");
mountOptions.add("-ofsname=CryptoFs");
return mountOptions.toArray(new String[mountOptions.size()]);
}

@Override
public ProcessBuilder getRevealCommand() {
return revealCommand;
Expand Down
117 changes: 65 additions & 52 deletions src/main/java/org/cryptomator/frontend/fuse/mount/MacMounter.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,66 @@
package org.cryptomator.frontend.fuse.mount;

import org.cryptomator.frontend.fuse.AdapterFactory;
import org.cryptomator.frontend.fuse.FuseNioAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;

class MacMounter implements Mounter {

private static final Logger LOG = LoggerFactory.getLogger(MacMounter.class);
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));
private static final int[] OSXFUSE_MINIMUM_SUPPORTED_VERSION = new int[]{3, 8, 2};
private static final Path OSXFUSE_VERSIONFILE_LOCATION = Paths.get("/Library/Filesystems/osxfuse.fs/Contents/version.plist");
private static final String OSXFUSE_XML_VERSION_TEXT = "CFBundleShortVersionString";
private static final String OSXFUSE_VERSIONFILE_LOCATION = "/Library/Filesystems/osxfuse.fs/Contents/version.plist";
private static final String OSXFUSE_VERSIONFILE_XPATH = "/plist/dict/key[.='CFBundleShortVersionString']/following-sibling::string[1]";

@Override
public Mount mount(Path directory, EnvironmentVariables envVars, String... additionalMountParams) throws CommandFailedException {
MacMount mount = new MacMount(directory, envVars);
mount.mount(additionalMountParams);
return mount;
public synchronized Mount mount(Path directory, EnvironmentVariables envVars) throws CommandFailedException {
FuseNioAdapter fuseAdapter = AdapterFactory.createReadWriteAdapter(directory);
try {
fuseAdapter.mount(envVars.getMountPoint(), false, false, envVars.getFuseFlags());
} catch (RuntimeException e) {
throw new CommandFailedException(e);
}
return new MacMount(fuseAdapter, envVars);
}

@Override
public String[] defaultMountFlags() {
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
try {
return new String[]{
"-ouid=" + Files.getAttribute(USER_HOME, "unix:uid"),
"-ogid=" + Files.getAttribute(USER_HOME, "unix:gid"),
"-oatomic_o_trunc",
"-oauto_xattr",
"-oauto_cache",
"-omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC", // show files names in Unicode NFD encoding
"-onoappledouble", // vastly impacts performance for some reason...
"-odefault_permissions" // let the kernel assume permissions based on file attributes etc
};
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

/**
Expand All @@ -43,7 +74,7 @@ public boolean isApplicable() {
public boolean installedVersionSupported() {
String versionString = getVersionString();
if (versionString == null) {
LOG.error("Did not find {} in document {}.", OSXFUSE_XML_VERSION_TEXT, OSXFUSE_VERSIONFILE_LOCATION);
LOG.error("Did not find {} in document {}.", OSXFUSE_VERSIONFILE_XPATH, OSXFUSE_VERSIONFILE_LOCATION);
return false;
}

Expand All @@ -64,38 +95,40 @@ public boolean installedVersionSupported() {
}


/**
* @return Value for {@value OSXFUSE_VERSIONFILE_XPATH} in {@value OSXFUSE_VERSIONFILE_LOCATION} or <code>null</code> if this value is not present.
*/
private String getVersionString() {
String version = null;
try (InputStream in = Files.newInputStream(OSXFUSE_VERSIONFILE_LOCATION, StandardOpenOption.READ)) {
XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(in);
while (reader.hasNext()) {
reader.next();
if (reader.getEventType() == XMLStreamReader.CHARACTERS && OSXFUSE_XML_VERSION_TEXT.equalsIgnoreCase(reader.getText())) {
reader.next();
reader.next();
reader.next();
version = reader.getElementText();
}
Path plistFile = Paths.get(OSXFUSE_VERSIONFILE_LOCATION);
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
XPath xPath = XPathFactory.newInstance().newXPath();
try (InputStream in = Files.newInputStream(plistFile, StandardOpenOption.READ)) {
Document doc = domFactory.newDocumentBuilder().parse(in);
NodeList nodeList = (NodeList) xPath.compile(OSXFUSE_VERSIONFILE_XPATH).evaluate(doc, XPathConstants.NODESET);
Node node = nodeList.item(0);
if (node == null) {
return null; // not found
} else {
return node.getTextContent();
}
} catch (XMLStreamException | FactoryConfigurationError e) {
} catch (ParserConfigurationException | SAXException | XPathException e) {
LOG.error("Could not parse file {} to detect version of OSXFUSE.", OSXFUSE_VERSIONFILE_LOCATION);
} catch (IOException e1) {
return null;
} catch (IOException e) {
LOG.error("Could not read file {} to detect version of OSXFUSE.", OSXFUSE_VERSIONFILE_LOCATION);
return null;
}
return version;
}

private static class MacMount extends AbstractMount {

private static final Path USER_HOME = Paths.get(System.getProperty("user.home"));

private final ProcessBuilder revealCommand;
private final ProcessBuilder unmountCommand;
private final ProcessBuilder unmountForcedCommand;

private MacMount(Path directory, EnvironmentVariables envVars) {
super(directory, envVars);
Path mountPoint = envVars.getMountPath();
private MacMount(FuseNioAdapter fuseAdapter, EnvironmentVariables envVars) {
super(fuseAdapter, envVars);
Path mountPoint = envVars.getMountPoint();
this.revealCommand = new ProcessBuilder("open", ".");
this.revealCommand.directory(mountPoint.toFile());
this.unmountCommand = new ProcessBuilder("umount", "--", mountPoint.getFileName().toString());
Expand All @@ -104,26 +137,6 @@ private MacMount(Path directory, EnvironmentVariables envVars) {
this.unmountForcedCommand.directory(mountPoint.getParent().toFile());
}

@Override
protected String[] getFuseOptions() {
// see: https://github.com/osxfuse/osxfuse/wiki/Mount-options
ArrayList<String> mountOptions = new ArrayList<>();
try {
mountOptions.add("-ouid=" + Files.getAttribute(USER_HOME, "unix:uid"));
mountOptions.add("-ogid=" + Files.getAttribute(USER_HOME, "unix:gid"));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
mountOptions.add("-oatomic_o_trunc");
mountOptions.add("-ovolname=" + envVars.getMountName().orElse("vault"));
mountOptions.add("-oauto_xattr");
mountOptions.add("-oauto_cache");
mountOptions.add("-omodules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"); // show files names in Unicode NFD encoding
mountOptions.add("-onoappledouble"); // vastly impacts performance for some reason...
mountOptions.add("-odefault_permissions"); // let the kernel assume permissions based on file attributes etc
return mountOptions.toArray(new String[mountOptions.size()]);
}

@Override
public ProcessBuilder getRevealCommand() {
return revealCommand;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

public interface Mounter {

Mount mount(Path directory, EnvironmentVariables envVars, String... additionalMountParams) throws CommandFailedException;
Mount mount(Path directory, EnvironmentVariables envVars) throws CommandFailedException;

String[] defaultMountFlags();

boolean isApplicable();

Expand Down
Loading

0 comments on commit e8b885a

Please sign in to comment.