diff --git a/pom.xml b/pom.xml
index bf9980f..d0f25be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -159,6 +159,9 @@
META-INF/services/org.cryptomator.jfuse.api.FuseBuilder
+
+ META-INF/services/ch.qos.logback.classic.spi.Configurator
+
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 5f4f545..0231ed6 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,8 +1,16 @@
+import ch.qos.logback.classic.spi.Configurator;
+import org.cryptomator.cli.LogbackConfigurator;
+
open module org.cryptomator.cli {
+ uses Configurator;
requires org.cryptomator.cryptofs;
requires org.cryptomator.frontend.fuse;
requires info.picocli;
requires org.slf4j;
requires org.cryptomator.integrations.api;
requires org.fusesource.jansi;
+ requires ch.qos.logback.core;
+ requires ch.qos.logback.classic;
+
+ provides Configurator with LogbackConfigurator;
}
\ No newline at end of file
diff --git a/src/main/java/org/cryptomator/cli/CryptomatorCli.java b/src/main/java/org/cryptomator/cli/CryptomatorCli.java
index cd9887e..60bbbda 100644
--- a/src/main/java/org/cryptomator/cli/CryptomatorCli.java
+++ b/src/main/java/org/cryptomator/cli/CryptomatorCli.java
@@ -37,6 +37,9 @@ public class CryptomatorCli implements Callable {
@Parameters(index = "0", paramLabel = "/path/to/vaultDirectory", description = "Path to the vault directory")
Path pathToVault;
+ @CommandLine.Option(names = {"--verbose"}, description = "Use verbose logging.")
+ boolean verbose = false;
+
@CommandLine.ArgGroup(multiplicity = "1")
PasswordSource passwordSource;
@@ -59,6 +62,15 @@ void setMaxCleartextNameLength(int input) {
@Override
public Integer call() throws Exception {
+ if (verbose) {
+ var logConfigurator = LogbackConfigurator.INSTANCE.get();
+ if (logConfigurator != null) {
+ logConfigurator.setLogLevels(LogbackConfigurator.DEBUG_LOG_LEVELS);
+ LOG.debug("User verbose logging");
+ } else {
+ throw new IllegalStateException("Logging is not configured.");
+ }
+ }
csrpg = SecureRandom.getInstanceStrong();
var unverifiedConfig = readConfigFromStorage(pathToVault);
@@ -103,6 +115,7 @@ private Masterkey loadMasterkey(URI keyId) {
*/
static VaultConfig.UnverifiedVaultConfig readConfigFromStorage(Path vaultPath) throws IOException {
Path configPath = vaultPath.resolve(CONFIG_FILE_NAME);
+ LOG.debug("Reading vault config from file {}.", configPath);
String token = Files.readString(configPath, StandardCharsets.US_ASCII);
return VaultConfig.decode(token);
}
diff --git a/src/main/java/org/cryptomator/cli/LogbackConfigurator.java b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java
new file mode 100644
index 0000000..7358df6
--- /dev/null
+++ b/src/main/java/org/cryptomator/cli/LogbackConfigurator.java
@@ -0,0 +1,77 @@
+package org.cryptomator.cli;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.Configurator;
+import ch.qos.logback.classic.spi.ConfiguratorRank;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.spi.ContextAwareBase;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+@ConfiguratorRank(ConfiguratorRank.CUSTOM_HIGH_PRIORITY)
+public class LogbackConfigurator extends ContextAwareBase implements Configurator {
+
+ public static final AtomicReference INSTANCE = new AtomicReference<>();
+
+ static final Map DEFAULT_LOG_LEVELS = Map.of( //
+ Logger.ROOT_LOGGER_NAME, Level.INFO, //
+ "org.cryptomator", Level.INFO //
+ );
+
+ public static final Map DEBUG_LOG_LEVELS = Map.of( //
+ Logger.ROOT_LOGGER_NAME, Level.DEBUG, //
+ "org.cryptomator", Level.TRACE //
+ );
+
+ @Override
+ public ExecutionStatus configure(LoggerContext context) {
+ var encoder = new PatternLayoutEncoder();
+ encoder.setContext(context);
+ encoder.setPattern("[%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n");
+ encoder.start();
+
+ var stdout = new ConsoleAppender();
+ stdout.setWithJansi(true);
+ stdout.setContext(context);
+ stdout.setName("STDOUT");
+ stdout.setEncoder(encoder);
+ stdout.start();
+
+ // configure loggers:
+ for (var loglevel : DEFAULT_LOG_LEVELS.entrySet()) {
+ Logger logger = context.getLogger(loglevel.getKey());
+ logger.setLevel(loglevel.getValue());
+ logger.setAdditive(false);
+ logger.addAppender(stdout);
+ }
+
+ //disable fuselocking messages
+ Logger fuseLocking = context.getLogger("org.cryptomator.frontend.fuse.locks");
+ fuseLocking.setLevel(Level.OFF);
+
+ //make instance accessible
+ INSTANCE.compareAndSet(null, this);
+ return ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY;
+ }
+
+ /**
+ * Adjust the log levels
+ *
+ * @param logLevels new log levels to use
+ */
+ public void setLogLevels(Map logLevels) {
+ if (context instanceof LoggerContext lc) {
+ for (var loglevel : logLevels.entrySet()) {
+ Logger logger = lc.getLogger(loglevel.getKey());
+ System.out.println(logger.getName());
+ logger.setLevel(loglevel.getValue());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/cryptomator/cli/MountOptions.java b/src/main/java/org/cryptomator/cli/MountOptions.java
index 6cb4857..7e3825f 100644
--- a/src/main/java/org/cryptomator/cli/MountOptions.java
+++ b/src/main/java/org/cryptomator/cli/MountOptions.java
@@ -2,18 +2,19 @@
import org.cryptomator.integrations.common.IntegrationsLoader;
import org.cryptomator.integrations.mount.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import java.nio.file.FileSystem;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
+import java.util.*;
import java.util.stream.Collectors;
public class MountOptions {
+ private static final Logger LOG = LoggerFactory.getLogger(MountOptions.class);
+
@CommandLine.Spec
CommandLine.Model.CommandSpec spec;
@@ -48,34 +49,65 @@ void setMountService(String value) {
@CommandLine.Option(names = {"--loopbackPort"}, description = "Port used at the loopback address.")
Optional loopbackPort;
+
MountBuilder prepareMountBuilder(FileSystem fs) {
+ var specifiedOptions = filterNotSpecifiedOptions();
var builder = mountService.forFileSystem(fs.getPath("/"));
for (var capability : mountService.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
- case LOOPBACK_PORT -> loopbackPort.ifPresent(builder::setLoopbackPort);
- case LOOPBACK_HOST_NAME -> loopbackHostName.ifPresent(builder::setLoopbackHostName);
+ case LOOPBACK_PORT -> {
+ loopbackPort.ifPresent(builder::setLoopbackPort);
+ specifiedOptions.put("loopbackPort", false);
+ }
+ case LOOPBACK_HOST_NAME -> {
+ loopbackHostName.ifPresent(builder::setLoopbackHostName);
+ specifiedOptions.put("loopbackHostname", false);
+ }
//TODO: case READ_ONLY -> builder.setReadOnly(vaultSettings.usesReadOnlyMode.get());
case MOUNT_FLAGS -> {
+ specifiedOptions.put("mountOptions", false);
if (mountOptions.isEmpty()) {
- builder.setMountFlags(mountService.getDefaultMountFlags());
+ var defaultFlags = mountService.getDefaultMountFlags();
+ LOG.debug("Using default mount options {}", defaultFlags);
+ builder.setMountFlags(defaultFlags);
} else {
builder.setMountFlags(String.join(" ", mountOptions));
}
}
- case VOLUME_ID -> builder.setVolumeId(volumeId);
- case VOLUME_NAME -> volumeName.ifPresent(builder::setVolumeName);
+ case VOLUME_ID -> {
+ builder.setVolumeId(volumeId);
+ }
+ case VOLUME_NAME -> {
+ volumeName.ifPresent(builder::setVolumeName);
+ specifiedOptions.put("volumeName", false);
+ }
}
}
+
+ var ignoredOptions = specifiedOptions.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.joining(","));
+ LOG.info("Ignoring unsupported options: {}", ignoredOptions);
return builder;
}
+ private Map filterNotSpecifiedOptions() {
+ var map = new HashMap();
+ loopbackPort.ifPresent(_ -> map.put("loopbackPort", true));
+ loopbackHostName.ifPresent(_ -> map.put("loopbackHostname", true));
+ volumeName.ifPresent(_ -> map.put("volumeName", true));
+ if (!mountOptions.isEmpty()) {
+ map.put("mountOption", true);
+ }
+ return map;
+ }
+
Mount mount(FileSystem fs) throws MountFailedException {
if (!mountService.hasCapability(MountCapability.MOUNT_TO_SYSTEM_CHOSEN_PATH) && mountPoint.isEmpty()) {
throw new CommandLine.ParameterException(spec.commandLine(), "The selected mounter %s requires a mount point. Use --mountPoint /path/to/mount/point to specify it.".formatted(mountService.displayName()));
}
var builder = prepareMountBuilder(fs);
mountPoint.ifPresent(builder::setMountpoint);
+ LOG.debug("Mounting vault using {} to {}.", mountService.displayName(), mountPoint.isPresent() ? mountPoint.get() : "system chosen location");
return builder.mount();
}
}
diff --git a/src/main/java/org/cryptomator/cli/PasswordSource.java b/src/main/java/org/cryptomator/cli/PasswordSource.java
index 4e8b96a..5a8b601 100644
--- a/src/main/java/org/cryptomator/cli/PasswordSource.java
+++ b/src/main/java/org/cryptomator/cli/PasswordSource.java
@@ -1,5 +1,7 @@
package org.cryptomator.cli;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import java.io.IOException;
@@ -11,6 +13,8 @@
public class PasswordSource {
+ public static final Logger LOG = LoggerFactory.getLogger(PasswordSource.class);
+
@CommandLine.Option(names = {"--password:stdin"}, paramLabel = "Passphrase", description = "Passphrase, read from STDIN")
boolean passphraseStdin;
@@ -38,6 +42,7 @@ Passphrase readPassphrase() throws IOException {
}
private Passphrase readPassphraseFromStdin() {
+ LOG.debug("Reading passphrase from STDIN");
System.out.println("Enter the password:");
var console = System.console();
if (console == null) {
@@ -47,6 +52,7 @@ private Passphrase readPassphraseFromStdin() {
}
private Passphrase readPassphraseFromEnvironment() {
+ LOG.debug("Reading passphrase from env variable '{}'", passphraseEnvironmentVariable);
var tmp = System.getenv(passphraseEnvironmentVariable);
if (tmp == null) {
throw new ReadingEnvironmentVariableFailedException("Environment variable " + passphraseEnvironmentVariable + " is not defined.");
@@ -57,6 +63,7 @@ private Passphrase readPassphraseFromEnvironment() {
}
private Passphrase readPassphraseFromFile() throws ReadingFileFailedException {
+ LOG.debug("Reading passphrase from file '{}'", passphraseFile);
try {
var bytes = Files.readAllBytes(passphraseFile);
var byteBuffer = ByteBuffer.wrap(bytes);
@@ -90,7 +97,7 @@ static class ReadingEnvironmentVariableFailedException extends PasswordSourceExc
}
}
- record Passphrase(char [] content) implements AutoCloseable {
+ record Passphrase(char[] content) implements AutoCloseable {
@Override
public void close() {
diff --git a/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator
new file mode 100644
index 0000000..e7a2757
--- /dev/null
+++ b/src/main/resources/META-INF/services/ch.qos.logback.classic.spi.Configurator
@@ -0,0 +1 @@
+org.cryptomator.cli.LogbackConfigurator
\ No newline at end of file
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
deleted file mode 100644
index 20e1b8f..0000000
--- a/src/main/resources/logback.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- true
-
- [%thread] %highlight(%-5level) %cyan(%logger{15}) - %msg %n
-
-
-
-
-
-
-