Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Commit

Permalink
Merge branch 'release/1.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Apr 15, 2021
2 parents aa68bfa + 04011a8 commit 640cba4
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 157 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/${{ github.repository }}/actions?query=workflow%3A%22Publish+to+Maven+Central%22|deploy to Maven Central>."
SLACK_MESSAGE: "Ready to <https://github.com/${{ github.repository }}/actions/workflows/publish-central.yml|deploy to Maven Central>."
SLACK_FOOTER:
MSG_MINIMAL: true
MSG_MINIMAL: true
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>dokany-nio-adapter</artifactId>
<version>1.2.4</version>
<version>1.3.0</version>
<description>Access resources at a given NIO path via Dokany.</description>
<name>Dokany-NIO Adapter</name>
<url>https://github.com/cryptomator/dokany-nio-adapter</url>
Expand Down Expand Up @@ -194,7 +194,7 @@
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>6.1.0</version>
<version>6.1.1</version>
<configuration>
<cveValidForHours>24</cveValidForHours>
<failBuildOnCVSS>0</failBuildOnCVSS>
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/dokany/java/DokanyException.java
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -9,4 +9,8 @@ public DokanyException(String msg) {
public DokanyException(Throwable e) {
super(e);
}

DokanyException(String msg, Throwable cause) {
super(msg, cause);
}
}
128 changes: 67 additions & 61 deletions src/main/java/com/dokany/java/DokanyMount.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@
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.
*/
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);
Expand All @@ -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;
}

Expand All @@ -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.
* <p>
* 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.
* <p>
* 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<Throwable> 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<Throwable> 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);
}
}
}
Expand Down Expand Up @@ -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.");
}
}

}
41 changes: 27 additions & 14 deletions src/main/java/com/dokany/java/DokanyOperationsProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
Expand All @@ -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<Callback> 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);

Expand Down Expand Up @@ -103,28 +103,23 @@ 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);

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 {

@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()) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}
}

}
9 changes: 0 additions & 9 deletions src/main/java/com/dokany/java/LibraryNotFoundException.java

This file was deleted.

2 changes: 1 addition & 1 deletion src/main/java/com/dokany/java/constants/MountError.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.");

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 640cba4

Please sign in to comment.