From f6f50bcd8f7064cd425e5c86b708976e3bbb727e Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 17 Jun 2019 13:54:16 +0200 Subject: [PATCH 1/2] refactored mount options --- .../frontend/fuse/mount/AbstractMount.java | 17 ++- .../fuse/mount/EnvironmentVariables.java | 32 ++--- .../frontend/fuse/mount/LinuxMounter.java | 51 ++++---- .../frontend/fuse/mount/MacMounter.java | 117 ++++++++++-------- .../frontend/fuse/mount/Mounter.java | 4 +- .../fuse/mount/EnvironmentVariablesTest.java | 14 ++- .../fuse/mount/LinuxEnvironmentTest.java | 7 +- .../fuse/mount/MirroringFuseMountTest.java | 4 +- 8 files changed, 133 insertions(+), 113 deletions(-) diff --git a/src/main/java/org/cryptomator/frontend/fuse/mount/AbstractMount.java b/src/main/java/org/cryptomator/frontend/fuse/mount/AbstractMount.java index bc3b133..9674fe2 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/mount/AbstractMount.java +++ b/src/main/java/org/cryptomator/frontend/fuse/mount/AbstractMount.java @@ -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(); diff --git a/src/main/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariables.java b/src/main/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariables.java index 0ef2879..92cf061 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariables.java +++ b/src/main/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariables.java @@ -5,13 +5,13 @@ public class EnvironmentVariables { - private final Path mountPath; - private final Optional mountName; + private final Path mountPoint; + private final String[] fuseFlags; private final Optional revealCommand; - private EnvironmentVariables(Path mountPath, Optional mountName, Optional revealCommand) { - this.mountPath = mountPath; - this.mountName = mountName; + private EnvironmentVariables(Path mountPoint, String[] fuseFlags, Optional revealCommand) { + this.mountPoint = mountPoint; + this.fuseFlags = fuseFlags; this.revealCommand = revealCommand; } @@ -19,12 +19,12 @@ public static EnvironmentVariablesBuilder create() { return new EnvironmentVariablesBuilder(); } - public Path getMountPath() { - return mountPath; + public Path getMountPoint() { + return mountPoint; } - public Optional getMountName() { - return mountName; + public String[] getFuseFlags() { + return fuseFlags; } public Optional getRevealCommand() { @@ -33,17 +33,17 @@ public Optional getRevealCommand() { public static class EnvironmentVariablesBuilder { - private Path mountPath = null; - private Optional mountName = Optional.empty(); + private Path mountPoint = null; + private String[] fuseFlags; private Optional 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; } @@ -53,7 +53,7 @@ public EnvironmentVariablesBuilder withRevealCommand(String revealCommand) { } public EnvironmentVariables build() { - return new EnvironmentVariables(mountPath, mountName, revealCommand); + return new EnvironmentVariables(mountPoint, fuseFlags, revealCommand); } } diff --git a/src/main/java/org/cryptomator/frontend/fuse/mount/LinuxMounter.java b/src/main/java/org/cryptomator/frontend/fuse/mount/LinuxMounter.java index 0f178b7..57215ba 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/mount/LinuxMounter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/mount/LinuxMounter.java @@ -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 @@ -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()); @@ -45,21 +63,6 @@ private LinuxMount(Path directory, EnvironmentVariables envVars) { this.unmountForcedCommand.directory(mountPoint.getParent().toFile()); } - @Override - protected String[] getFuseOptions() { - ArrayList 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; diff --git a/src/main/java/org/cryptomator/frontend/fuse/mount/MacMounter.java b/src/main/java/org/cryptomator/frontend/fuse/mount/MacMounter.java index ab9d16b..06d84c3 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/mount/MacMounter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/mount/MacMounter.java @@ -1,12 +1,20 @@ 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; @@ -14,22 +22,45 @@ 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); + } } /** @@ -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; } @@ -64,38 +95,40 @@ public boolean installedVersionSupported() { } + /** + * @return Value for {@value OSXFUSE_VERSIONFILE_XPATH} in {@value OSXFUSE_VERSIONFILE_LOCATION} or null 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()); @@ -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 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; diff --git a/src/main/java/org/cryptomator/frontend/fuse/mount/Mounter.java b/src/main/java/org/cryptomator/frontend/fuse/mount/Mounter.java index 125a594..2db5d6c 100644 --- a/src/main/java/org/cryptomator/frontend/fuse/mount/Mounter.java +++ b/src/main/java/org/cryptomator/frontend/fuse/mount/Mounter.java @@ -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(); diff --git a/src/test/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariablesTest.java b/src/test/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariablesTest.java index 2e1e871..6a8a102 100644 --- a/src/test/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariablesTest.java +++ b/src/test/java/org/cryptomator/frontend/fuse/mount/EnvironmentVariablesTest.java @@ -9,11 +9,13 @@ public class EnvironmentVariablesTest { @Test - public void testEnvironmentVariablesBuilder(){ - Path mountPath = Paths.get("/home/testuser/mnt"); - String mountName = "coolStuff"; - EnvironmentVariables envVars = EnvironmentVariables.create().withMountName(mountName).withMountPath(mountPath).build(); - Assertions.assertEquals(mountName, envVars.getMountName().get()); - Assertions.assertEquals(mountPath, envVars.getMountPath()); + public void testEnvironmentVariablesBuilder() { + String[] flags = new String[]{"--test", "--debug"}; + Path mountPoint = Paths.get("/home/testuser/mnt"); + + EnvironmentVariables envVars = EnvironmentVariables.create().withFlags(flags).withMountPoint(mountPoint).build(); + + Assertions.assertEquals(flags, envVars.getFuseFlags()); + Assertions.assertEquals(mountPoint, envVars.getMountPoint()); } } diff --git a/src/test/java/org/cryptomator/frontend/fuse/mount/LinuxEnvironmentTest.java b/src/test/java/org/cryptomator/frontend/fuse/mount/LinuxEnvironmentTest.java index 5afbb6e..8b64b1b 100644 --- a/src/test/java/org/cryptomator/frontend/fuse/mount/LinuxEnvironmentTest.java +++ b/src/test/java/org/cryptomator/frontend/fuse/mount/LinuxEnvironmentTest.java @@ -13,13 +13,14 @@ public class LinuxEnvironmentTest { public static void main(String[] args) throws IOException { if (IS_LINUX) { Path mountPoint = Files.createTempDirectory("fuse-mount"); + Mounter mounter = FuseMountFactory.getMounter(); EnvironmentVariables envVars = EnvironmentVariables.create() - .withMountName("yolo") - .withMountPath(mountPoint) + .withFlags(mounter.defaultMountFlags()) + .withMountPoint(mountPoint) .withRevealCommand("nautilus") .build(); Path tmp = Paths.get("/tmp"); - try (Mount mnt = FuseMountFactory.getMounter().mount(tmp, envVars)) { + try (Mount mnt = mounter.mount(tmp, envVars)) { mnt.revealInFileManager(); System.out.println("Wait for it..."); System.in.read(); diff --git a/src/test/java/org/cryptomator/frontend/fuse/mount/MirroringFuseMountTest.java b/src/test/java/org/cryptomator/frontend/fuse/mount/MirroringFuseMountTest.java index adda3d9..7e46457 100644 --- a/src/test/java/org/cryptomator/frontend/fuse/mount/MirroringFuseMountTest.java +++ b/src/test/java/org/cryptomator/frontend/fuse/mount/MirroringFuseMountTest.java @@ -127,8 +127,8 @@ public static void main(String args[]) throws IOException { private static void mount(Path pathToMirror, Path mountPoint) { Mounter mounter = FuseMountFactory.getMounter(); EnvironmentVariables envVars = EnvironmentVariables.create() - .withMountName("FuseMirror") - .withMountPath(mountPoint) + .withFlags(mounter.defaultMountFlags()) + .withMountPoint(mountPoint) .build(); try (Mount mnt = mounter.mount(pathToMirror, envVars)) { LOG.info("Mounted successfully. Enter anything to stop the server..."); From 60f50169684a6f2498839734ff34d9e578fc4aa7 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 3 Jul 2019 13:16:30 +0200 Subject: [PATCH 2/2] Preparing 1.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 60f0981..b11d2a0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator fuse-nio-adapter - 1.2.0-SNAPSHOT + 1.2.0 FUSE-NIO-Adapter Access resources at a given NIO path via FUSE. https://github.com/cryptomator/fuse-nio-adapter