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 - - - - - - -