diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 32a3041..8be609b 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -35,6 +35,6 @@ jobs: SLACK_ICON_EMOJI: ':bot:' SLACK_CHANNEL: 'cryptomator-desktop' SLACK_TITLE: "Published ${{ github.event.repository.name }} ${{ github.event.release.tag_name }}" - SLACK_MESSAGE: "Ready to ." + SLACK_MESSAGE: "Ready to ." SLACK_FOOTER: - MSG_MINIMAL: true \ No newline at end of file + MSG_MINIMAL: true diff --git a/README.md b/README.md index 23a6d47..1144c2e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Build Status](https://github.com/cryptomator/dokany-nio-adapter/workflows/Build/badge.svg)](https://github.com/cryptomator/dokany-nio-adapter/actions?query=workflow%3ABuild) -[![Codacy Code Quality](https://app.codacy.com/project/badge/Grade/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter) -[![Codacy Coverage](https://app.codacy.com/project/badge/Coverage/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter) +[![Codacy Code Quality](https://app.codacy.com/project/badge/Grade/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter/dashboard) +[![Codacy Coverage](https://app.codacy.com/project/badge/Coverage/f3f46474adb14bc59d4fc489903950f5)](https://www.codacy.com/gh/cryptomator/dokany-nio-adapter/dashboard) [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/dokany-nio-adapter/badge.svg)](https://snyk.io/test/github/cryptomator/dokany-nio-adapter) # dokany-nio-adapter diff --git a/pom.xml b/pom.xml index 504bffc..5df084e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator dokany-nio-adapter - 1.2.4 + 1.3.0 Access resources at a given NIO path via Dokany. Dokany-NIO Adapter https://github.com/cryptomator/dokany-nio-adapter @@ -194,7 +194,7 @@ org.owasp dependency-check-maven - 6.1.0 + 6.1.1 24 0 diff --git a/src/main/java/com/dokany/java/DokanyException.java b/src/main/java/com/dokany/java/DokanyException.java index 2ab3cc6..1fca9a9 100644 --- a/src/main/java/com/dokany/java/DokanyException.java +++ b/src/main/java/com/dokany/java/DokanyException.java @@ -1,6 +1,6 @@ package com.dokany.java; -public final class DokanyException extends RuntimeException { +public final class DokanyException extends Exception { public DokanyException(String msg) { super(msg); @@ -9,4 +9,8 @@ public DokanyException(String msg) { public DokanyException(Throwable e) { super(e); } + + DokanyException(String msg, Throwable cause) { + super(msg, cause); + } } \ No newline at end of file diff --git a/src/main/java/com/dokany/java/DokanyMount.java b/src/main/java/com/dokany/java/DokanyMount.java index 83abf45..88b40af 100644 --- a/src/main/java/com/dokany/java/DokanyMount.java +++ b/src/main/java/com/dokany/java/DokanyMount.java @@ -9,12 +9,12 @@ import org.slf4j.LoggerFactory; import java.nio.file.Path; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; /** * Main class to start and stop Dokany file system. @@ -22,13 +22,14 @@ public final class DokanyMount implements Mount { private static final Logger LOG = LoggerFactory.getLogger(DokanyMount.class); + private static final int MOUNT_TIMEOUT_MILLIS = 3000; + private static final AtomicInteger MOUNT_COUNTER = new AtomicInteger(1); private final DeviceOptions deviceOptions; private final DokanyFileSystem fileSystem; private final SafeUnmountCheck unmountCheck; - private volatile boolean isMounted; - private volatile CompletableFuture mountFuture; + private final AtomicBoolean isMounted; public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem) { this(deviceOptions, fileSystem, () -> true); @@ -37,8 +38,7 @@ public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fil public DokanyMount(final DeviceOptions deviceOptions, final DokanyFileSystem fileSystem, SafeUnmountCheck unmountCheck) { this.deviceOptions = deviceOptions; this.fileSystem = fileSystem; - this.mountFuture = CompletableFuture.failedFuture(new IllegalStateException("Not mounted.")); - this.isMounted = false; + this.isMounted = new AtomicBoolean(false); this.unmountCheck = unmountCheck; } @@ -63,83 +63,85 @@ public long getVersion() { } /** - * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a thread of the Common ForkJoin Pool. - * Adds to the JVM a {@link java.lang.Runtime#addShutdownHook(Thread)} which calls {@link #close()} + * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a new thread. + * No-op if this object is already mounted. + *

+ * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. */ - public synchronized void mount() throws DokanyException { - this.mount(ForkJoinPool.commonPool()); + public void mount() throws DokanyException { + this.mount(ignored -> {}); } /** - * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} on the given executor. - * Adds to the JVM a {@link java.lang.Runtime#addShutdownHook(Thread)} which calls {@link #close()} + * Calls {@link NativeMethods#DokanMain(DeviceOptions, DokanyOperations)} in a new thread and after DokanMain exit runs the specified action with an throwable as parameter if DokanMain terminiated irregularly. + * No-op if this object is already mounted. + *

+ * Additionally a shutdown hook invoking {@link #close()} is registered to the JVM. * - * @Param Executor executor + * @param onDokanExit object with a run() method which is executed after the Dokan process exited */ - public synchronized void mount(Executor executor) throws DokanyException { - if (!isMounted) { + public synchronized void mount(Consumer onDokanExit) throws DokanyException { + if (!isMounted.getAndSet(true)) { + int mountId = MOUNT_COUNTER.getAndIncrement(); try { Runtime.getRuntime().addShutdownHook(new Thread(this::close)); LOG.info("Dokany API/driver version: {} / {}", getVersion(), getDriverVersion()); + //real mount op - mountFuture = CompletableFuture - .supplyAsync(() -> NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(fileSystem)), executor) - .handle((returnVal, exception) -> { - setIsMounted(false); - if (returnVal != null && returnVal.equals(0)) { - return 0; - } - - if (returnVal == null) { - throw new DokanyException(exception); - } else { - throw new DokanyException("Return Code " + returnVal + ": " + MountError.fromInt(returnVal).getDescription()); - } - }); - - //check return value - try { - mountFuture.get(1000, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - // up and running - } catch (ExecutionException e) { - LOG.error("Error while mounting", e); - if (e.getCause() instanceof DokanyException) { - throw (DokanyException) e.getCause(); - } else { - throw new DokanyException(e); + CountDownLatch mountSuccessSignal = new CountDownLatch(1); + AtomicReference exception = new AtomicReference<>(); + var mountThread = new Thread(() -> { + try { + int r = NativeMethods.DokanMain(deviceOptions, new DokanyOperationsProxy(mountSuccessSignal, fileSystem, "dokanMount-" + mountId + "-callback-")); + if (r != 0) { + throw new DokanyException("DokanMain returned error code" + r + ": " + MountError.fromInt(r).getDescription()); + } + } catch (Exception e) { + exception.set(e); + } finally { + isMounted.set(false); + onDokanExit.accept(exception.get()); + } + }); + mountThread.setName("dokanMount-" + mountId + "-main"); + mountThread.setDaemon(true); + mountThread.start(); + + // wait for mounted() is called, unlocking the barrier + if (!mountSuccessSignal.await(MOUNT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { + if (exception.get() != null) { + if( exception.get() instanceof DokanyException) { + throw (DokanyException) exception.get(); + } else { + throw new DokanyException(exception.get()); + } } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + throw new DokanyException("Mount timed out"); } - setIsMounted(true); } catch (UnsatisfiedLinkError err) { - LOG.error("Unable to load dokan driver.", err); - throw new LibraryNotFoundException(err.getMessage()); + throw new DokanyException(err); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new DokanyException("Mount interrupted."); } } else { LOG.debug("Dokan Device already mounted on {}.", deviceOptions.MountPoint); } } - public synchronized void setIsMounted(boolean newValue) { - isMounted = newValue; - } - - /** - * Unmounts the Dokan Device from the mount point given in the mount options. + * Unmounts the Dokan device from its mount point. No-op if the device is not mounted. */ public synchronized void close() { - if (isMounted) { - LOG.info("Unmounting Dokan device at {}", deviceOptions.MountPoint); - boolean unmounted = NativeMethods.DokanRemoveMountPoint(deviceOptions.MountPoint); + if (isMounted.get()) { + LOG.info("Unmounting dokan device at {}", deviceOptions.MountPoint); + var unmounted = NativeMethods.DokanRemoveMountPoint(deviceOptions.MountPoint); if (unmounted) { - setIsMounted(false); + isMounted.set(false); } else { - LOG.error("Unable to unmount Dokan device at {}. Use dokanctl.exe to unmount", deviceOptions.MountPoint); + LOG.error("Unable to unmount dokan device at {}.", deviceOptions.MountPoint); } } } @@ -170,7 +172,11 @@ public void unmountForced() { @Override public void reveal(Revealer revealer) throws Exception { - revealer.reveal(Path.of(deviceOptions.MountPoint.toString())); + if (isMounted.get()) { + revealer.reveal(Path.of(deviceOptions.MountPoint.toString())); + } else { + throw new IllegalStateException("Filesystem not mounted."); + } } } diff --git a/src/main/java/com/dokany/java/DokanyOperationsProxy.java b/src/main/java/com/dokany/java/DokanyOperationsProxy.java index 6f413c8..1a2eee7 100644 --- a/src/main/java/com/dokany/java/DokanyOperationsProxy.java +++ b/src/main/java/com/dokany/java/DokanyOperationsProxy.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; /** @@ -26,17 +27,16 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { private final static Logger LOG = LoggerFactory.getLogger(DokanyOperationsProxy.class); - private final static String DEFAULT_THREAD_NAME = "DOKAN"; - + private final CountDownLatch mountSuccessSignaler; private final DokanyFileSystem fileSystem; - private final ThreadGroup nativeDokanThreads = new ThreadGroup("AttachedDokanThreads"); - private final CallbackThreadInitializer DEFAULT_CALLBACK_THREAD_INITIALIZER = new CallbackThreadInitializer(true, false, DEFAULT_THREAD_NAME, nativeDokanThreads); - private final AtomicInteger threadCounter = new AtomicInteger(0); + private final CallbackThreadInitializer callbackThreadInitializer; private final List usedCallbacks = new ArrayList<>(); - DokanyOperationsProxy(final DokanyFileSystem fileSystem) { + DokanyOperationsProxy(CountDownLatch mountSuccessSignaler, final DokanyFileSystem fileSystem, String nativeThreadNamePrefix) { + this.mountSuccessSignaler = mountSuccessSignaler; this.fileSystem = fileSystem; + this.callbackThreadInitializer = new DokanCallbackThreadInitializer(nativeThreadNamePrefix); super.ZwCreateFile = new ZwCreateFileProxy(); usedCallbacks.add(super.ZwCreateFile); @@ -103,8 +103,8 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { super.Unmounted = new UnmountedProxy(); usedCallbacks.add(super.Unmounted); - super.GetFileSecurity = null; - //callbacks.add(super.GetFileSecurity); + super.GetFileSecurity = ((rawPath, rawSecurityInformation, rawSecurityDescriptor, rawSecurityDescriptorLength, rawSecurityDescriptorLengthNeeded, dokanyFileInfo) -> NtStatus.NOT_IMPLEMENTED.getMask()); + usedCallbacks.add(super.GetFileSecurity); super.SetFileSecurity = null; //callbacks.add(super.SetFileSecurity); @@ -112,7 +112,7 @@ final class DokanyOperationsProxy extends com.dokany.java.DokanyOperations { super.FindStreams = null; //callbacks.add(super.FindStreams); - usedCallbacks.forEach(callback -> Native.setCallbackThreadInitializer(callback, DEFAULT_CALLBACK_THREAD_INITIALIZER)); + usedCallbacks.forEach(callback -> Native.setCallbackThreadInitializer(callback, callbackThreadInitializer)); } class ZwCreateFileProxy implements ZwCreateFile { @@ -120,11 +120,6 @@ class ZwCreateFileProxy implements ZwCreateFile { @Override public long callback(WString rawPath, WinBase.SECURITY_ATTRIBUTES securityContext, int rawDesiredAccess, int rawFileAttributes, int rawShareAccess, int rawCreateDisposition, int rawCreateOptions, DokanyFileInfo dokanyFileInfo) { try { - //hack to uniquely identify the dokan threads - if (Thread.currentThread().getName().equals(DEFAULT_THREAD_NAME)) { - Thread.currentThread().setName("dokan-" + threadCounter.getAndIncrement()); - } - int win32ErrorCode = fileSystem.zwCreateFile(rawPath, securityContext, rawDesiredAccess, rawFileAttributes, rawShareAccess, rawCreateDisposition, rawCreateOptions, dokanyFileInfo); //little cheat for issue #24 if (win32ErrorCode == Win32ErrorCode.ERROR_INVALID_STATE.getMask()) { @@ -372,6 +367,7 @@ class MountedProxy implements Mounted { @Override public long mounted(DokanyFileInfo dokanyFileInfo) { + mountSuccessSignaler.countDown(); try { return NativeMethods.DokanNtStatusFromWin32(fileSystem.mounted(dokanyFileInfo)); } catch (Exception e) { @@ -433,4 +429,21 @@ public long callback(WString rawPath, FillWin32FindStreamData rawFillFindData, D } } + private static class DokanCallbackThreadInitializer extends CallbackThreadInitializer { + + private String prefix; + private AtomicInteger counter; + + DokanCallbackThreadInitializer(String prefix) { + super(true, false, prefix, new ThreadGroup(prefix)); + this.prefix = prefix; + this.counter = new AtomicInteger(0); + } + + @Override + public String getName(Callback cb) { + return prefix + counter.getAndIncrement(); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/dokany/java/LibraryNotFoundException.java b/src/main/java/com/dokany/java/LibraryNotFoundException.java deleted file mode 100644 index 636da1d..0000000 --- a/src/main/java/com/dokany/java/LibraryNotFoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.dokany.java; - -public class LibraryNotFoundException extends RuntimeException{ - - public LibraryNotFoundException(String message) { - super(message); - } - -} diff --git a/src/main/java/com/dokany/java/constants/MountError.java b/src/main/java/com/dokany/java/constants/MountError.java index ad1da28..4a14ddc 100644 --- a/src/main/java/com/dokany/java/constants/MountError.java +++ b/src/main/java/com/dokany/java/constants/MountError.java @@ -9,7 +9,7 @@ public enum MountError implements EnumInteger { DRIVE_LETTER_ERROR(-2, "Dokan mount failed - Bad drive letter."), DRIVER_INSTALL_ERROR(-3, "Dokan mount failed - Cannot install driver."), START_ERROR(-4, "Dokan mount failed - Driver answer that something is wrong."), - MOUNT_ERROR(-5, "Dokan mount failed -Cannot assign a drive letter or mount point. Probably already used by another volume."), + MOUNT_ERROR(-5, "Dokan mount failed - Cannot assign a drive letter or mount point. Probably already used by another volume."), MOUNT_POINT_ERROR(-6, "Dokan mount failed - Mount point is invalid."), VERSION_ERROR(-7, "Dokan mount failed - Requested an incompatible version."); diff --git a/src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java b/src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java new file mode 100644 index 0000000..9bc8b72 --- /dev/null +++ b/src/main/java/org/cryptomator/frontend/dokany/DokanyMountFailedException.java @@ -0,0 +1,16 @@ +package org.cryptomator.frontend.dokany; + +public class DokanyMountFailedException extends Exception { + + public DokanyMountFailedException(String message) { + super(message); + } + + public DokanyMountFailedException(String msg, Throwable cause) { + super(msg, cause); + } + + public DokanyMountFailedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java index 6f3d15f..ff4858a 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java +++ b/src/main/java/org/cryptomator/frontend/dokany/MountFactory.java @@ -1,5 +1,6 @@ package org.cryptomator.frontend.dokany; +import com.dokany.java.DokanyException; import com.dokany.java.DokanyFileSystem; import com.dokany.java.DokanyMount; import com.dokany.java.constants.DokanOption; @@ -20,11 +21,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; public class MountFactory { private static final Logger LOG = LoggerFactory.getLogger(MountFactory.class); - private static final int MOUNT_TIMEOUT_MS = 5000; private static final short THREAD_COUNT = 5; private static final String UNC_NAME = ""; private static final int TIMEOUT = 10000; @@ -37,14 +38,10 @@ public class MountFactory { // FileSystemFeature.SUPPORTS_REMOTE_STORAGE, // FileSystemFeature.UNICODE_ON_DISK); - private final ExecutorService executorService; - - public MountFactory(ExecutorService executorService) { - this.executorService = executorService; - } + private MountFactory() {} /** - * Mounts a virtual drive at the given mount point containing contents of the given path. + * Mounts the root of a filesystem at the given mount point. * This method blocks until the mount succeeds or times out. * * @param fileSystemRoot Path to the directory which will be the content root of the mounted drive. @@ -52,9 +49,9 @@ public MountFactory(ExecutorService executorService) { * @param volumeName The name of the drive as shown to the user. * @param fileSystemName The technical file system name shown in the drive properties window. * @return The mount object. - * @throws MountFailedException if the mount process is aborted due to errors + * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName) throws MountFailedException { + public static Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), THREAD_COUNT, @@ -63,11 +60,37 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri TIMEOUT, ALLOC_UNIT_SIZE, SECTOR_SIZE); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, ignored -> {}); + } + + /** + * Mounts the root of a filesystem at the given mount point with the specified additional mount options. + * If an additional mount option is not specified the default value is used. + * This method blocks until the mount succeeds or times out. + * + * @param fileSystemRoot Path to the directory which will be the content root of the mounted drive. + * @param mountPoint The mount point of the mounted drive. Can be an empty directory or a drive letter. + * @param volumeName The name of the drive as shown to the user. + * @param fileSystemName The technical file system name shown in the drive properties window. + * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. + * @return The mount object. + * @throws DokanyMountFailedException if the mount process is aborted due to errors + */ + public static Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions) throws DokanyMountFailedException { + var absMountPoint = mountPoint.toAbsolutePath(); + var mountOptions = parseMountOptions(additionalOptions); + DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), + mountOptions.getThreadCount().orElse(THREAD_COUNT), + mountOptions.getDokanOptions(), + UNC_NAME, + mountOptions.getTimeout().orElse(TIMEOUT), + mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), + mountOptions.getSectorSize().orElse(SECTOR_SIZE)); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, ignored -> {}); } /** - * Mounts a virtual drive at the given mount point containing contents of the given path with the specified additional mount options. + * Mounts the root of a filesystem at the given mount point with the specified additional mount options and an action which is executed after unmount. * If an additional mount option is not specified the default value is used. * This method blocks until the mount succeeds or times out. * @@ -76,10 +99,11 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri * @param volumeName The name of the drive as shown to the user. * @param fileSystemName The technical file system name shown in the drive properties window. * @param additionalOptions Additional options for the mount. For any unset option a default value is used. See {@link MountUtil} for details. + * @param onDokanExit consumer which runs after the dokan mount exited. If the exit was irregularly, the Throwable parameter is not null. * @return The mount object. - * @throws MountFailedException if the mount process is aborted due to errors + * @throws DokanyMountFailedException if the mount process is aborted due to errors */ - public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions) throws MountFailedException { + public static Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, String fileSystemName, String additionalOptions, Consumer onDokanExit) throws DokanyMountFailedException { var absMountPoint = mountPoint.toAbsolutePath(); var mountOptions = parseMountOptions(additionalOptions); DeviceOptions deviceOptions = new DeviceOptions(absMountPoint.toString(), @@ -89,37 +113,31 @@ public Mount mount(Path fileSystemRoot, Path mountPoint, String volumeName, Stri mountOptions.getTimeout().orElse(TIMEOUT), mountOptions.getAllocationUnitSize().orElse(ALLOC_UNIT_SIZE), mountOptions.getSectorSize().orElse(SECTOR_SIZE)); - return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions); + return mount(fileSystemRoot, volumeName, fileSystemName, deviceOptions, onDokanExit); } - private Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions) throws MountFailedException { + private static Mount mount(Path fileSystemRoot, String volumeName, String fileSystemName, DeviceOptions deviceOptions, Consumer onDokanExit) throws DokanyMountFailedException { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, volumeName, 0x98765432, fileSystemName, FILE_SYSTEM_FEATURES); - CompletableFuture mountDidSucceed = new CompletableFuture<>(); OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder = OpenHandleCheck.getBuilder(); - DokanyFileSystem dokanyFs = new ReadWriteAdapter(fileSystemRoot, new LockManager(), volumeInfo, mountDidSucceed, handleCheckBuilder); + DokanyFileSystem dokanyFs = new ReadWriteAdapter(fileSystemRoot, new LockManager(), volumeInfo, handleCheckBuilder); DokanyMount mount = new DokanyMount(deviceOptions, dokanyFs, handleCheckBuilder.build()); LOG.debug("Mounting on {}: ...", deviceOptions.MountPoint); try { - mount.mount(executorService); - mountDidSucceed.get(MOUNT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + mount.mount(onDokanExit); LOG.debug("Mounted directory at {} successfully.", deviceOptions.MountPoint); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { + } catch (DokanyException e) { LOG.error("Mounting failed.", e); - throw new MountFailedException(e.getCause()); - } catch (TimeoutException e) { - LOG.warn("Mounting timed out."); + throw new DokanyMountFailedException("Error while mounting.", e); } return mount; } - private MountUtil.MountOptions parseMountOptions(String options) throws MountFailedException { + private static MountUtil.MountOptions parseMountOptions(String options) throws DokanyMountFailedException { try { return MountUtil.parse(options); } catch (IllegalArgumentException | ParseException e) { - throw new MountFailedException(e); + throw new DokanyMountFailedException("Unable to parse mount options", e); } } diff --git a/src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java b/src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java deleted file mode 100644 index cb45bf0..0000000 --- a/src/main/java/org/cryptomator/frontend/dokany/MountFailedException.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.cryptomator.frontend.dokany; - -public class MountFailedException extends Exception{ - - public MountFailedException(String message) { - super(message); - } - - public MountFailedException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java b/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java index 93e47f7..84fe843 100644 --- a/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java +++ b/src/main/java/org/cryptomator/frontend/dokany/ReadWriteAdapter.java @@ -68,15 +68,13 @@ public class ReadWriteAdapter implements DokanyFileSystem { private final Path root; private final LockManager lockManager; private final VolumeInformation volumeInformation; - private final CompletableFuture didMount; private final OpenHandleFactory fac; private final FileStore fileStore; - public ReadWriteAdapter(Path root, LockManager lockManager, VolumeInformation volumeInformation, CompletableFuture didMount) { + public ReadWriteAdapter(Path root, LockManager lockManager, VolumeInformation volumeInformation) { this.root = root; this.lockManager = lockManager; this.volumeInformation = volumeInformation; - this.didMount = didMount; this.fac = new OpenHandleFactory(); try { this.fileStore = Files.getFileStore(root); @@ -91,14 +89,12 @@ public ReadWriteAdapter(Path root, LockManager lockManager, VolumeInformation vo * @param fileSystemRoot * @param lockManager * @param volumeInfo - * @param mountDidSucceed * @param handleCheckBuilder */ - public ReadWriteAdapter(Path fileSystemRoot, LockManager lockManager, VolumeInformation volumeInfo, CompletableFuture mountDidSucceed, OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder) { + public ReadWriteAdapter(Path fileSystemRoot, LockManager lockManager, VolumeInformation volumeInfo, OpenHandleCheck.OpenHandleCheckBuilder handleCheckBuilder) { this.root = fileSystemRoot; this.lockManager = lockManager; this.volumeInformation = volumeInfo; - this.didMount = mountDidSucceed; this.fac = new OpenHandleFactory(); try { this.fileStore = Files.getFileStore(root); @@ -835,7 +831,6 @@ public int getVolumeInformation(Pointer rawVolumeNameBuffer, int rawVolumeNameSi @Override public int mounted(DokanyFileInfo dokanyFileInfo) { LOG.trace("mounted() is called."); - didMount.complete(null); return 0; } @@ -850,6 +845,16 @@ public int unmounted(DokanyFileInfo dokanyFileInfo) { return 0; } + /** + * Not implemented, handled in proxy. + * @param rawPath + * @param rawSecurityInformation + * @param rawSecurityDescriptor + * @param rawSecurityDescriptorLength + * @param rawSecurityDescriptorLengthNeeded + * @param dokanyFileInfo {@link DokanyFileInfo} with information about the file or directory. + * @return + */ @Override public int getFileSecurity(WString rawPath, int rawSecurityInformation, Pointer rawSecurityDescriptor, int rawSecurityDescriptorLength, IntByReference rawSecurityDescriptorLengthNeeded, DokanyFileInfo dokanyFileInfo) { // Path path = getRootedPath(rawPath); diff --git a/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java b/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java index 7b3e4cc..e474c0d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java +++ b/src/test/java/org/cryptomator/frontend/dokany/MirrorReadOnlyThread.java @@ -1,5 +1,6 @@ package org.cryptomator.frontend.dokany; +import com.dokany.java.DokanyException; import com.dokany.java.DokanyMount; import com.dokany.java.DokanyFileSystem; import com.dokany.java.constants.FileSystemFeature; @@ -42,14 +43,18 @@ public MirrorReadOnlyThread(Path dirToMirror, Path mountPoint) { VolumeInformation volumeInfo = new VolumeInformation(VolumeInformation.DEFAULT_MAX_COMPONENT_LENGTH, "Mirror", 0x98765432, "Dokany MirrorFS", fsFeatures); - DokanyFileSystem myFs = new ReadWriteAdapter(dirToMirror, new LockManager(), volumeInfo, new CompletableFuture()); + DokanyFileSystem myFs = new ReadWriteAdapter(dirToMirror, new LockManager(), volumeInfo); dokany = new DokanyMount(devOps, myFs); } @Override public void run() { - dokany.mount(); System.out.println("Starting new dokany thread with mount point " + mountPoint.toString()); + try { + dokany.mount(); + } catch (DokanyException e) { + e.printStackTrace(); + } } public DokanyMount getDokanyDriver() { diff --git a/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java b/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java index f389794..638b84d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/MultipleMirrorsTest.java @@ -29,8 +29,6 @@ public class MultipleMirrorsTest { private static String testDirPrefix = "testDir"; private ConcurrentLinkedQueue mounts; - private MountFactory mountFactory; - private ExecutorService mountThreads; @BeforeAll public static void beforeAll() { @@ -53,8 +51,6 @@ public static void beforeAll() { @BeforeEach public void init() throws IOException { this.mounts = new ConcurrentLinkedQueue<>(); - this.mountThreads = Executors.newCachedThreadPool(); - this.mountFactory = new MountFactory(mountThreads); } @@ -66,10 +62,9 @@ public void testMultipleConcurrentMirrorsFolderMounts() { var testDir = SANDBOX.resolve(testDirPrefix + i); var mountPoint = SANDBOX.resolve("mnt" + i); try { - MountFactory mountFactory = new MountFactory(mountThreads); - var mount = mountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); + var mount = MountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); mounts.add(mount); - } catch (MountFailedException e) { + } catch (DokanyMountFailedException e) { e.printStackTrace(); } } @@ -85,10 +80,9 @@ public void testMultipleConcurrentMirrorsDriveLetterMounts() { char driveLetter = Character.toChars(0x4a + i)[0]; var mountPoint = Path.of(driveLetter + ":\\"); try { - MountFactory mountFactory = new MountFactory(mountThreads); - var mount = mountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); + var mount = MountFactory.mount(testDir, mountPoint, "mnt" + i, "Mirror FS"); mounts.add(mount); - } catch (MountFailedException e) { + } catch (DokanyMountFailedException e) { e.printStackTrace(); } } diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java index 6584a19..da6394d 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteCryptoFsTest.java @@ -20,7 +20,7 @@ public class ReadWriteCryptoFsTest { System.setProperty(SimpleLogger.DATE_TIME_FORMAT_KEY, "HH:mm:ss:SSS"); } - public static void main(String[] args) throws IOException, MountFailedException { + public static void main(String[] args) throws IOException, DokanyMountFailedException { if (!MountFactory.isApplicable()) { System.err.println("Dokany not installed."); return; @@ -53,8 +53,7 @@ public static void main(String[] args) throws IOException, MountFailedException .build(); CryptoFileSystem cryptofs = CryptoFileSystemProvider.newFileSystem(vaultPath, props); Path path = cryptofs.getPath("/"); - MountFactory mountFactory = new MountFactory(Executors.newCachedThreadPool()); - try (Mount mount = mountFactory.mount(path, mountPoint, "MyVault", "CryptoFS")) { + try (Mount mount = MountFactory.mount(path, mountPoint, "MyVault", "CryptoFS")) { try { mount.reveal(new WindowsExplorerRevealer()); } catch (Exception e) { diff --git a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java index 575d1ec..932072a 100644 --- a/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java +++ b/src/test/java/org/cryptomator/frontend/dokany/ReadWriteMirrorTest.java @@ -6,7 +6,9 @@ import java.nio.file.Path; import java.util.Optional; import java.util.Scanner; -import java.util.concurrent.Executors; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; public class ReadWriteMirrorTest { @@ -17,7 +19,7 @@ public class ReadWriteMirrorTest { System.setProperty(SimpleLogger.DATE_TIME_FORMAT_KEY, "HH:mm:ss:SSS"); } - public static void main(String[] args) throws IOException, MountFailedException { + public static void main(String[] args) throws IOException, DokanyMountFailedException, InterruptedException { if (!MountFactory.isApplicable()) { System.err.println("Dokany not installed."); return; @@ -39,8 +41,9 @@ public static void main(String[] args) throws IOException, MountFailedException } } - MountFactory mountFactory = new MountFactory(Executors.newCachedThreadPool()); - try (Mount mount = mountFactory.mount(dirPath, mountPoint, "Test", "DokanyNioFS")) { + CountDownLatch exitSignal = new CountDownLatch(1); + Consumer onDokanExitAction = exception -> exitSignal.countDown(); + try (Mount mount = MountFactory.mount(dirPath, mountPoint, "Test", "DokanyNioFS", "--thread-count 5", onDokanExitAction)) { try { mount.reveal(new WindowsExplorerRevealer()); } catch (Exception e) { @@ -49,7 +52,15 @@ public static void main(String[] args) throws IOException, MountFailedException } System.in.read(); mount.unmountForced(); + + } finally { + if (exitSignal.await(3000, TimeUnit.MILLISECONDS)) { + System.out.println("onExit action executed."); + } else { + System.out.println("onExit action NOT executed after 3s. Exiting..."); + } } + } }