Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<webdav-nio.version>1.0.11</webdav-nio.version>
<commons.cli.version>1.4</commons.cli.version>
<logback.version>1.2.3</logback.version>
<fuse-nio.version>1.2.4</fuse-nio.version>

<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down Expand Up @@ -46,6 +47,11 @@
<artifactId>webdav-nio-adapter</artifactId>
<version>${webdav-nio.version}</version>
</dependency>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>fuse-nio-adapter</artifactId>
<version>${fuse-nio.version}</version>
</dependency>

<!-- Commons -->
<dependency>
Expand Down
46 changes: 36 additions & 10 deletions src/main/java/org/cryptomator/cli/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*******************************************************************************/
package org.cryptomator.cli;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -71,22 +72,43 @@ public class Args {
.valueSeparator() //
.hasArgs() //
.build());
OPTIONS.addOption(Option.builder() //
.longOpt("fusemount") //
.argName("mount point") //
.desc("Format must be vaultName=mountpoint") //
.valueSeparator() //
.hasArgs() //
.build());
}

private final String bindAddr;
private final int port;
private final boolean hasValidWebDavConfig;
private final Properties vaultPaths;
private final Properties vaultPasswords;
private final Properties vaultPasswordFiles;
private final Map<String, PasswordStrategy> passwordStrategies;
private final Properties fuseMountPoints;

public Args(CommandLine commandLine) throws ParseException {
this.bindAddr = commandLine.getOptionValue("bind", "localhost");
this.port = Integer.parseInt(commandLine.getOptionValue("port", "0"));
if (commandLine.hasOption("bind") && commandLine.hasOption("port")) {
hasValidWebDavConfig = true;
this.bindAddr = commandLine.getOptionValue("bind", "localhost");
this.port = Integer.parseInt(commandLine.getOptionValue("port", "0"));
} else {
hasValidWebDavConfig = false;
this.bindAddr = "";
this.port = -1;
}
this.vaultPaths = commandLine.getOptionProperties("vault");
this.vaultPasswords = commandLine.getOptionProperties("password");
this.vaultPasswordFiles = commandLine.getOptionProperties("passwordfile");
this.passwordStrategies = new HashMap<>();
this.fuseMountPoints = commandLine.getOptionProperties("fusemount");
}

public boolean hasValidWebDavConf() {
return hasValidWebDavConfig;
}

public String getBindAddr() {
Expand Down Expand Up @@ -118,15 +140,10 @@ public PasswordStrategy addPasswortStrategy(final String vaultName) {
PasswordStrategy passwordStrategy = new PasswordFromStdInputStrategy(vaultName);

if (vaultPasswords.getProperty(vaultName) != null) {
passwordStrategy = new PasswordFromPropertyStrategy(
vaultName,
vaultPasswords.getProperty(vaultName)
);
passwordStrategy = new PasswordFromPropertyStrategy(vaultName, vaultPasswords.getProperty(vaultName));
} else if (vaultPasswordFiles.getProperty(vaultName) != null) {
passwordStrategy = new PasswordFromFileStrategy(
vaultName,
Paths.get(vaultPasswordFiles.getProperty(vaultName))
);
passwordStrategy = new PasswordFromFileStrategy(vaultName,
Paths.get(vaultPasswordFiles.getProperty(vaultName)));
}

this.passwordStrategies.put(vaultName, passwordStrategy);
Expand All @@ -136,4 +153,13 @@ public PasswordStrategy addPasswortStrategy(final String vaultName) {
public PasswordStrategy getPasswordStrategy(final String vaultName) {
return passwordStrategies.get(vaultName);
}

public Path getFuseMountPoint(String vaultName) {
String mountPoint = fuseMountPoints.getProperty(vaultName);
if (mountPoint == null) {
return null;
}
Path mountPointPath = Paths.get(mountPoint);
return mountPointPath;
}
}
58 changes: 46 additions & 12 deletions src/main/java/org/cryptomator/cli/CryptomatorCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@
*******************************************************************************/
package org.cryptomator.cli;

import org.cryptomator.cli.frontend.FuseMount;
import org.cryptomator.cli.frontend.WebDav;

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

import org.apache.commons.cli.ParseException;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -42,7 +45,7 @@ public static void main(String[] rawArgs) throws IOException {

private static void validate(Args args) throws IllegalArgumentException {
Set<String> vaultNames = args.getVaultNames();
if (args.getPort() < 0 || args.getPort() > 65536) {
if (args.hasValidWebDavConf() && (args.getPort() < 0 || args.getPort() > 65536)) {
throw new IllegalArgumentException("Invalid WebDAV Port.");
}

Expand All @@ -56,38 +59,69 @@ private static void validate(Args args) throws IllegalArgumentException {
throw new IllegalArgumentException("Not a directory: " + vaultPath);
}
args.addPasswortStrategy(vaultName).validate();

Path mountPoint = args.getFuseMountPoint(vaultName);
if (mountPoint != null && !Files.isDirectory(mountPoint)) {
throw new IllegalArgumentException("Fuse mount point does not exist: " + mountPoint);
}
}
}

private static void startup(Args args) throws IOException {
WebDavServer server = WebDavServer.create();
server.bind(args.getBindAddr(), args.getPort());
server.start();
Optional<WebDav> server = initWebDavServer(args);
ArrayList<FuseMount> mounts = new ArrayList<>();

for (String vaultName : args.getVaultNames()) {
Path vaultPath = Paths.get(args.getVaultPath(vaultName));
LOG.info("Unlocking vault \"{}\" located at {}", vaultName, vaultPath);
String vaultPassword = args.getPasswordStrategy(vaultName).password();
CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties().withPassphrase(vaultPassword).build();
CryptoFileSystemProperties properties = CryptoFileSystemProperties.cryptoFileSystemProperties()
.withPassphrase(vaultPassword).build();
Path vaultRoot = CryptoFileSystemProvider.newFileSystem(vaultPath, properties).getPath("/");
WebDavServletController servlet = server.createWebDavServlet(vaultRoot, vaultName);
servlet.start();

Path fuseMountPoint = args.getFuseMountPoint(vaultName);
if (fuseMountPoint != null) {
FuseMount newMount = new FuseMount(vaultRoot, fuseMountPoint);
if (newMount.mount()) {
mounts.add(newMount);
}
}

server.ifPresent(serv -> serv.addServlet(vaultRoot, vaultName));
}

waitForShutdown(() -> {
LOG.info("Shutting down...");
try {
server.stop();
server.ifPresent(serv -> serv.stop());

for (FuseMount mount : mounts) {
mount.unmount();
}
LOG.info("Shutdown successful.");
} catch (Throwable e) {
LOG.error("Error during shutdown", e);
}
});
}

private static Optional<WebDav> initWebDavServer(Args args) {
Optional<WebDav> server = Optional.empty();
if (args.hasValidWebDavConf()) {
server = Optional.of(new WebDav(args.getBindAddr(), args.getPort()));
}
return server;
}

private static void waitForShutdown(Runnable runnable) {
Runtime.getRuntime().addShutdownHook(new Thread(runnable));
LOG.info("Server started. Press Ctrl+C to terminate.");
LOG.info("Press Ctrl+C to terminate.");
try {
while (true) {
System.in.read();
}
} catch (IOException e) {
e.printStackTrace();
}
Comment on lines +119 to +125
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to retain the previous functionality for FUSE mounts (if no WebDAV server is not running): Starting the app mounts the vault and ctrl-c quits the app + umounts. If there is no loop for stdin it just quits the app immediately.

Please let me know if you have suggestions how to achieve this more cleanly!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please let me know if you have suggestions how to achieve this more cleanly!

You can run FUSE in "blocking mode" (second argument of the mount method), but you'd need to also overload the Mounter's mount method, as blocking is currently always false:

https://github.com/cryptomator/fuse-nio-adapter/blob/develop/src/main/java/org/cryptomator/frontend/fuse/mount/LinuxMounter.java#L22

I'd prefer this approach but leave it open to you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I have a couple of concerns, though. Please let me know if they are valid:

  • If I mount in blocking mode it implies that I can only have a single FUSE mount when running the CLI-app, right? Or surely I could add some threading, but that would IMO complicate this app unnecessarily. With the current approach there can be multiple simultaneous FUSE and/or WebDAV mounts running with a single CLI-app instance and the logic is fairly straight forward.
  • What is the release cadence of fuse-nio-adapter? I mean if I make a pull request, when will it be available in Maven?
  • I probably should also make similar changes to MacMounter.java. I currently do not have means to test that it works.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I mount in blocking mode it implies that I can only have a single FUSE mount when running the CLI-app, right? Or surely I could add some threading, but that would IMO complicate this app unnecessarily. With the current approach there can be multiple simultaneous FUSE and/or WebDAV mounts running with a single CLI-app instance and the logic is fairly straight forward.

You're right. You can only have a single mount in this case. Ctrl+C will unmount it and end the process.

What is the release cadence of fuse-nio-adapter? I mean if I make a pull request, when will it be available in Maven?

It can be released any time, there is no schedule we need to follow. It is just a matter of tagging and letting CI do its job.

I probably should also make similar changes to MacMounter.java. I currently do not have means to test that it works.

MacMounter has the same boolean flag, which works the same way. I have already tested it locally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I think I would then keep this functionality as it is currently implemented as the previous WebDAV implementation also supports multiple mounts. Now you can basically add any number of WebDAV and/or FUSE mounts with a single instance.

From my side this PR is ready to be merged now in case it's fine for you too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now you can basically add any number of WebDAV and/or FUSE mounts with a single instance.

I still think this is a debatable feature, since you can as well spawn multiple processes and thus have better control over each mounted vault. However, this is out of scope of this PR and should be discussed in a separate issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might this cause #35?

}

}
63 changes: 63 additions & 0 deletions src/main/java/org/cryptomator/cli/frontend/FuseMount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.cryptomator.cli.frontend;

import java.nio.file.Path;

import org.cryptomator.frontend.fuse.mount.CommandFailedException;
import org.cryptomator.frontend.fuse.mount.EnvironmentVariables;
import org.cryptomator.frontend.fuse.mount.FuseMountFactory;
import org.cryptomator.frontend.fuse.mount.Mount;
import org.cryptomator.frontend.fuse.mount.Mounter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FuseMount {
private static final Logger LOG = LoggerFactory.getLogger(FuseMount.class);

private Path vaultRoot;
private Path mountPoint;
private Mount mnt;

public FuseMount(Path vaultRoot, Path mountPoint) {
this.vaultRoot = vaultRoot;
this.mountPoint = mountPoint;
this.mnt = null;
}

public boolean mount() {
if (mnt != null) {
LOG.info("Already mounted to {}", mountPoint);
return false;
}

try {
Mounter mounter = FuseMountFactory.getMounter();
EnvironmentVariables envVars = EnvironmentVariables.create().withFlags(mounter.defaultMountFlags())
.withMountPoint(mountPoint).build();
mnt = mounter.mount(vaultRoot, envVars);
LOG.info("Mounted to {}", mountPoint);
} catch (CommandFailedException e) {
LOG.error("Can't mount: {}, error: {}", mountPoint, e.getMessage());
return false;
}
return true;
}

public void unmount() {
try {
mnt.unmount();
LOG.info("Unmounted {}", mountPoint);
} catch (CommandFailedException e) {
LOG.error("Can't unmount gracefully: {}. Force unmount.", e.getMessage());
forceUnmount();
}
}

private void forceUnmount() {
try {
mnt.unmountForced();
LOG.info("Unmounted {}", mountPoint);
} catch (CommandFailedException e) {
LOG.error("Force unmount failed: {}", e.getMessage());
}
}
}
38 changes: 38 additions & 0 deletions src/main/java/org/cryptomator/cli/frontend/WebDav.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.cryptomator.cli.frontend;

import java.nio.file.Path;
import java.util.ArrayList;

import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WebDav {
private static final Logger LOG = LoggerFactory.getLogger(WebDav.class);

private final WebDavServer server;
private ArrayList<WebDavServletController> servlets;

public WebDav(String bindAddr, int port) {
servlets = new ArrayList<>();
server = WebDavServer.create();
server.bind(bindAddr, port);
server.start();
LOG.info("WebDAV server started: {}:{}", bindAddr, port);
}

public void stop() {
for (WebDavServletController controller : servlets) {
controller.stop();
}
server.stop();
}

public void addServlet(Path vaultRoot, String vaultName) {
WebDavServletController servlet = server.createWebDavServlet(vaultRoot, vaultName);
servlets.add(servlet);
servlet.start();
}
}