reinitate workaround for MOUNT_WITHIN_PARENT

This commit is contained in:
Armin Schrenk 2023-01-13 15:59:44 +01:00
parent 47a32893f0
commit 23060a8497
No known key found for this signature in database
GPG Key ID: 8F2992163CBBA7FC
4 changed files with 133 additions and 30 deletions

View File

@ -0,0 +1,8 @@
package org.cryptomator.common.mount;
public class MountPointPreparationException extends RuntimeException {
public MountPointPreparationException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,110 @@
package org.cryptomator.common.mount;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
public final class MountWithinParentUtil {
private static final Logger LOG = LoggerFactory.getLogger(Mounter.class);
private static final String HIDEAWAY_PREFIX = ".~$";
private static final String HIDEAWAY_SUFFIX = ".tmp";
private static final String WIN_HIDDEN_ATTR = "dos:hidden";
private MountWithinParentUtil() {}
static void prepareParentNoMountPoint(Path mountPoint) throws MountPointPreparationException {
Path hideaway = getHideaway(mountPoint);
var mpExists = Files.exists(mountPoint, LinkOption.NOFOLLOW_LINKS);
var hideExists = Files.exists(hideaway, LinkOption.NOFOLLOW_LINKS);
//TODO: possible improvement by just deleting an _empty_ hideaway
if (mpExists && hideExists) { //both resources exist (whatever type)
throw new MountPointPreparationException(new FileAlreadyExistsException(hideaway.toString()));
} else if (!mpExists && !hideExists) { //neither mountpoint nor hideaway exist
throw new MountPointPreparationException(new NoSuchFileException(mountPoint.toString()));
} else if (!mpExists) { //only hideaway exists
checkIsDirectory(hideaway);
LOG.info("Mountpoint {} seems to be not properly cleaned up. Will be fixed on unmount.", mountPoint);
try {
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
}
} catch (IOException e) {
throw new MountPointPreparationException(e);
}
} else { //only mountpoint exists
try {
checkIsDirectory(mountPoint);
checkIsEmpty(mountPoint);
Files.move(mountPoint, hideaway);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(hideaway, WIN_HIDDEN_ATTR, true, LinkOption.NOFOLLOW_LINKS);
}
} catch (IOException e) {
throw new MountPointPreparationException(e);
}
}
}
static void cleanup(Path mountPoint) {
Path hideaway = getHideaway(mountPoint);
try {
waitForMountpointRestoration(mountPoint);
Files.move(hideaway, mountPoint);
if (SystemUtils.IS_OS_WINDOWS) {
Files.setAttribute(mountPoint, WIN_HIDDEN_ATTR, false);
}
} catch (IOException e) {
LOG.error("Unable to restore hidden directory to mountpoint {}.", mountPoint, e);
}
}
//on Windows removing the mountpoint takes some time, so we poll for at most 3 seconds
private static void waitForMountpointRestoration(Path mountPoint) throws FileAlreadyExistsException {
int attempts = 0;
while (!Files.notExists(mountPoint, LinkOption.NOFOLLOW_LINKS)) {
attempts++;
if (attempts >= 5) {
throw new FileAlreadyExistsException("Timeout waiting for mountpoint cleanup for " + mountPoint + " .");
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new FileAlreadyExistsException("Interrupted before mountpoint " + mountPoint + " was cleared");
}
}
}
private static void checkIsDirectory(Path toCheck) throws MountPointPreparationException {
if (!Files.isDirectory(toCheck, LinkOption.NOFOLLOW_LINKS)) {
throw new MountPointPreparationException(new NotDirectoryException(toCheck.toString()));
}
}
private static void checkIsEmpty(Path toCheck) throws MountPointPreparationException, IOException {
try (var dirStream = Files.list(toCheck)) {
if (dirStream.findFirst().isPresent()) {
throw new MountPointPreparationException(new DirectoryNotEmptyException(toCheck.toString()));
}
}
}
//visible for testing
static Path getHideaway(Path mountPoint) {
return mountPoint.resolveSibling(HIDEAWAY_PREFIX + mountPoint.getFileName().toString() + HIDEAWAY_SUFFIX);
}
}

View File

@ -50,7 +50,7 @@ public class Mounter {
this.vaultSettings = vaultSettings;
}
MadePreparations prepare() throws IOException {
Runnable prepare() throws IOException {
for (var capability : service.capabilities()) {
switch (capability) {
case FILE_SYSTEM_NAME -> builder.setFileSystemName("cryptoFs");
@ -67,13 +67,13 @@ public class Mounter {
return prepareMountPoint();
}
private MadePreparations prepareMountPoint() throws IOException {
private Runnable prepareMountPoint() throws IOException {
Runnable cleanup = () -> {};
var userChosenMountPoint = vaultSettings.getMountPoint();
var defaultMountPointBase = env.getMountPointsDir().orElseThrow();
var canMountToDriveLetter = service.hasCapability(MOUNT_AS_DRIVE_LETTER);
var canMountToParent = service.hasCapability(MOUNT_WITHIN_EXISTING_PARENT);
var canMountToDir = service.hasCapability(MOUNT_TO_EXISTING_DIR);
boolean mountWithinCustomParent = false;
if (userChosenMountPoint == null) {
if (service.hasCapability(MOUNT_TO_SYSTEM_CHOSEN_PATH)) {
@ -89,9 +89,12 @@ public class Mounter {
builder.setMountpoint(mountPoint);
}
} else {
mountWithinCustomParent = canMountToParent && !canMountToDir;
if (mountWithinCustomParent) {
// TODO: move the mount point away in case of MOUNT_WITHIN_EXISTING_PARENT
if (canMountToParent && !canMountToDir) {
MountWithinParentUtil.prepareParentNoMountPoint(userChosenMountPoint);
cleanup = () -> {
System.out.println("CLEANUP");
MountWithinParentUtil.cleanup(userChosenMountPoint);
};
}
try {
builder.setMountpoint(userChosenMountPoint);
@ -104,17 +107,12 @@ public class Mounter {
//mountpoint must exist
throw new MountPointNotExistsException(e.getMessage());
} else {
//TODO: if (!canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)) {
throw new IllegalMountPointException(e.getMessage());
}
/*
//TODO:
if (!canMountToDir && canMountToParent && !Files.notExists(userChosenMountPoint)) {
//parent must exist, mountpoint must not exist
}
*/
}
}
return new MadePreparations(mountWithinCustomParent);
return cleanup;
}
}
@ -123,22 +121,11 @@ public class Mounter {
var mountService = this.mountServiceObservable.getValue().service();
var builder = mountService.forFileSystem(cryptoFsRoot);
var internal = new SettledMounter(mountService, builder, vaultSettings);
var preps = internal.prepare();
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), preps.mountWithinCustomParent);
var cleanup = internal.prepare();
return new MountHandle(builder.mount(), mountService.hasCapability(UNMOUNT_FORCED), cleanup);
}
public void cleanup(MountHandle handle) {
if(handle.mountWithinCustomParent) {
//TODO
}
}
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, boolean mountWithinCustomParent) {
}
private record MadePreparations(boolean mountWithinCustomParent) {
public record MountHandle(Mount mountObj, boolean supportsUnmountForced, Runnable specialCleanup) {
}

View File

@ -173,9 +173,7 @@ public class Vault {
try {
mountHandle.mountObj().close();
if(mountHandle.mountWithinCustomParent()) {
mounter.cleanup(mountHandle);
}
mountHandle.specialCleanup().run();
} finally {
destroyCryptoFileSystem();
}