diff --git a/README.md b/README.md index 6f8db272..ff83b8d1 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,15 @@ To publish to your local Maven repository, execute as the third step `./gradlew. ## Examples An example user filesystems using this library can be found in the examples package [dev.dokan.dokan_java.examples](https://github.com/dokan-dev/dokan-java/tree/develop/src/main/java/dev/dokan/dokan_java/examples). +## Versioning +Dokan-Java uses it's own versioning system and therefore doesn't match the versions of Dokany. +For Dokan-Java to work you need to have a suitable version of Dokany installed on your machine. The following table helps you choose the correct Dokany-version. + +| Dokan-Java Version | Minimum Version of Dokany | +|--------------------|---------------------------| +| 1.0.0 - 1.1.X | 1.2.0.1000 | +| 1.2.0+ | 1.3.0.1000 | + ## Contributing You're encouraged to contribute. Fork the code and then submit a pull request. diff --git a/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java b/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java index a2d4d65d..16c4e7f2 100644 --- a/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java +++ b/src/main/java/dev/dokan/dokan_java/AbstractDokanFileSystem.java @@ -157,7 +157,7 @@ private void init(DokanOperations dokanOperations) { } } - private boolean isImplemented(String funcName) { + public boolean isImplemented(String funcName) { return !notImplementedMethods.contains(funcName); } diff --git a/src/main/java/dev/dokan/dokan_java/DokanUtils.java b/src/main/java/dev/dokan/dokan_java/DokanUtils.java index 48f6eb36..3636dfbe 100644 --- a/src/main/java/dev/dokan/dokan_java/DokanUtils.java +++ b/src/main/java/dev/dokan/dokan_java/DokanUtils.java @@ -4,6 +4,7 @@ import com.sun.jna.platform.win32.WinNT; import dev.dokan.dokan_java.constants.microsoft.CreationDisposition; +import java.nio.file.attribute.FileTime; import java.util.Date; import static dev.dokan.dokan_java.constants.microsoft.CreateDispositions.*; @@ -38,6 +39,10 @@ public static String trimStrToSize(final String str, final int len) { return str.substring(0, Math.min(str.length(), len)); } + public static FILETIME toFILETIME(final FileTime time) { + return getTime(time.toMillis()); + } + public static FILETIME getTime(final Date date) { return new FILETIME(date); } diff --git a/src/main/java/dev/dokan/dokan_java/NativeName.java b/src/main/java/dev/dokan/dokan_java/NativeName.java new file mode 100644 index 00000000..64acc6e2 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/NativeName.java @@ -0,0 +1,15 @@ +package dev.dokan.dokan_java; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; + +@Retention(RetentionPolicy.RUNTIME) +@Target(value = METHOD) +public @interface NativeName { + + String value(); + +} diff --git a/src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java b/src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java new file mode 100644 index 00000000..6216784f --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/constants/dokan_java/DefaultFileTimePolicy.java @@ -0,0 +1,10 @@ +package dev.dokan.dokan_java.constants.dokan_java; + +public enum DefaultFileTimePolicy { + + STATIC_YEAR_1601, + STATIC_YEAR_1970, + INHERIT_ELSE_1601, + INHERIT_ELSE_1970 + +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/constants/dokany/DokanFileInfoFlag.java b/src/main/java/dev/dokan/dokan_java/constants/dokany/DokanFileInfoFlag.java new file mode 100644 index 00000000..ccca428e --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/constants/dokany/DokanFileInfoFlag.java @@ -0,0 +1,30 @@ +package dev.dokan.dokan_java.constants.dokany; + + +import dev.dokan.dokan_java.masking.MaskValueEnum; +import dev.dokan.dokan_java.masking.MaskValueSet; + +public enum DokanFileInfoFlag implements MaskValueEnum { + + DELETE_ON_CLOSE(1), + IS_DIRECTORY(2), + NO_CACHE(4), + PAGING_IO(8), + SYNCHRONOUS_IO(16), + WRITE_TO_END_OF_FILE(32); + + private final int maskingValue; + + DokanFileInfoFlag(int maskingValue) { + this.maskingValue = maskingValue; + } + + public static MaskValueSet maskValueSet(final int mask) { + return MaskValueSet.maskValueSet(mask, values()); + } + + @Override + public int intValue() { + return this.maskingValue; + } +} diff --git a/src/main/java/dev/dokan/dokan_java/constants/microsoft/FileShareAccess.java b/src/main/java/dev/dokan/dokan_java/constants/microsoft/FileShareAccess.java new file mode 100644 index 00000000..d9f4d487 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/constants/microsoft/FileShareAccess.java @@ -0,0 +1,27 @@ +package dev.dokan.dokan_java.constants.microsoft; + + +import dev.dokan.dokan_java.masking.MaskValueEnum; +import dev.dokan.dokan_java.masking.MaskValueSet; + +public enum FileShareAccess implements MaskValueEnum { + + FILE_SHARE_READ(1), + FILE_SHARE_WRITE(2), + FILE_SHARE_DELETE(4); + + private final int maskingValue; + + FileShareAccess(int maskingValue) { + this.maskingValue = maskingValue; + } + + public static MaskValueSet maskValueSet(final int mask) { + return MaskValueSet.maskValueSet(mask, values()); + } + + @Override + public int intValue() { + return this.maskingValue; + } +} diff --git a/src/main/java/dev/dokan/dokan_java/constants/microsoft/MicrosoftReparsePointTag.java b/src/main/java/dev/dokan/dokan_java/constants/microsoft/MicrosoftReparsePointTag.java new file mode 100644 index 00000000..53f827e2 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/constants/microsoft/MicrosoftReparsePointTag.java @@ -0,0 +1,49 @@ +package dev.dokan.dokan_java.constants.microsoft; + + +import dev.dokan.dokan_java.masking.EnumInteger; +import dev.dokan.dokan_java.structure.ReparsePointTag; + + +public enum MicrosoftReparsePointTag implements ReparsePointTag { + + IO_REPARSE_TAG_CSV(0x80000009), + IO_REPARSE_TAG_DEDUP(0x80000013), + IO_REPARSE_TAG_DFS(0x8000000A), + IO_REPARSE_TAG_DFSR(0x80000012), + IO_REPARSE_TAG_HSM(0xC0000004), + IO_REPARSE_TAG_HSM2(0x80000006), + IO_REPARSE_TAG_MOUNT_POINT(0xA0000003), + IO_REPARSE_TAG_NFS(0x80000014), + IO_REPARSE_TAG_SIS(0x80000007), + IO_REPARSE_TAG_SYMLINK(0xA000000C), + IO_REPARSE_TAG_WIM(0x80000008); + + private static final String SOURCE = "WinNT.h"; + private static final String DEFINED_BY = "Microsoft"; + + private final int intValue; + + MicrosoftReparsePointTag(int intValue) { + this.intValue = intValue; + } + + public static MicrosoftReparsePointTag fromInt(final int value) { + return EnumInteger.enumFromInt(value, values()); + } + + @Override + public int intValue() { + return this.intValue; + } + + @Override + public String getSource() { + return SOURCE; + } + + @Override + public String getDefinedBy() { + return DEFINED_BY; + } +} diff --git a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java index 3a163e6e..dcde566a 100644 --- a/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java +++ b/src/main/java/dev/dokan/dokan_java/structure/DokanOptions.java @@ -79,9 +79,13 @@ public DokanOptions() { } public DokanOptions(String mountPoint, @Unsigned short threadCount, MaskValueSet mountOptions, String uncName, @Unsigned int timeout, @Unsigned int allocationUnitSize, @Unsigned int sectorSize) { + this(mountPoint, threadCount, mountOptions.intValue(), uncName, timeout, allocationUnitSize, sectorSize); + } + + public DokanOptions(String mountPoint, @Unsigned short threadCount, @Unsigned int mountOptions, String uncName, @Unsigned int timeout, @Unsigned int allocationUnitSize, @Unsigned int sectorSize) { MountPoint = new WString(mountPoint); ThreadCount = threadCount; - Options = mountOptions.intValue(); + Options = mountOptions; if (uncName != null) { UNCName = new WString(uncName); } else { diff --git a/src/main/java/dev/dokan/dokan_java/structure/ReparsePointTag.java b/src/main/java/dev/dokan/dokan_java/structure/ReparsePointTag.java new file mode 100644 index 00000000..d3ea2cc5 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/structure/ReparsePointTag.java @@ -0,0 +1,13 @@ +package dev.dokan.dokan_java.structure; + + +import dev.dokan.dokan_java.masking.EnumInteger; + + +public interface ReparsePointTag extends EnumInteger { + + String getSource(); + + String getDefinedBy(); + +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/AbstractEasyDokanFileSystem.java b/src/main/java/dev/dokan/dokan_java/wrappers/AbstractEasyDokanFileSystem.java new file mode 100644 index 00000000..352f2173 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/AbstractEasyDokanFileSystem.java @@ -0,0 +1,69 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.NativeName; +import dev.dokan.dokan_java.FileSystemInformation; +import dev.dokan.dokan_java.NotImplemented; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +public abstract class AbstractEasyDokanFileSystem implements EasyDokanFileSystem { + + private final Set notImplementedMethods; + private final ForwardingFileSystem lowLevelFS; + + public AbstractEasyDokanFileSystem(FileSystemInformation fileSystemInformation) { + this.notImplementedMethods = Arrays.stream(getClass().getMethods()) + .filter(method -> method.getAnnotation(NotImplemented.class) != null) + .map((method) -> getNativeName(method)) + .collect(Collectors.toSet()); + + this.lowLevelFS = new ForwardingFileSystem(fileSystemInformation, this.notImplementedMethods, this); + } + + private String getNativeName(Method method) { + Method annotated = getAnnotatedMethod(NativeName.class, method); + + if(annotated == null) { + return method.getName(); + } + return annotated.getAnnotation(NativeName.class).value(); + } + + private Method getAnnotatedMethod(Class annotation, Method method) { + return getAnnotatedMethod(annotation, method.getDeclaringClass(), method.getName(), method.getParameterTypes()); + } + + private Method getAnnotatedMethod(Class annotation, + Class inClass, + String methodName, + Class... parameterTypes) { + + Method result; + try { + result = inClass.getMethod(methodName, parameterTypes); + } catch(NoSuchMethodException e) { + return null; + } + + if(result.isAnnotationPresent(annotation)) { + return result; + } + + Class superClass = inClass.getSuperclass(); + if(superClass != null) { + result = getAnnotatedMethod(annotation, superClass, methodName, parameterTypes); + } + + if(result != null) { + return result; + } + + return Arrays.stream(inClass.getInterfaces()) + .map((cInterface) -> getAnnotatedMethod(annotation, cInterface, methodName, parameterTypes)) + .findFirst().orElse(null); + } +} diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java new file mode 100644 index 00000000..3cb178d1 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/AbstractFileInfo.java @@ -0,0 +1,166 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.constants.dokan_java.DefaultFileTimePolicy; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.masking.MaskValueSet; + +import java.nio.file.attribute.FileTime; +import java.util.concurrent.atomic.AtomicInteger; + +public class AbstractFileInfo { + + //See field by same name in com.sun.jna.platform.win32.WinBase.FILETIME; + private final static long WINDOWS_EPOCH_0 = -11644473600000L; + private final static long UNIX_EPOCH_0 = 0; + + private final static FileTime YEAR_1601 = FileTime.fromMillis(WINDOWS_EPOCH_0); + private final static FileTime YEAR_1970 = FileTime.fromMillis(UNIX_EPOCH_0); + + private final AtomicInteger fileAttributes; + + private FileTime creationTime; + private FileTime lastAccessTime; + private FileTime lastWriteTime; + + private DefaultFileTimePolicy fileTimePolicy = DefaultFileTimePolicy.INHERIT_ELSE_1970; + private long fileSize; + + public AbstractFileInfo(MaskValueSet attributes) { + this(attributes.isEmpty() ? FileAttribute.NORMAL.maskingValue() : attributes.intValue()); + } + + public AbstractFileInfo(int attributes) { + this.fileAttributes = new AtomicInteger(attributes); + } + + public int getFlags() { + return this.fileAttributes.get(); + } + + public void setFlags(int flags) { + this.fileAttributes.set(flags); + } + + public MaskValueSet getFileAttributes() { + return MaskValueSet.maskValueSet(this.fileAttributes.get(), FileAttribute.values()); + } + + public boolean getFlag(FileAttribute flag) { + return (this.fileAttributes.get() & flag.maskingValue()) != 0; + } + + public boolean setFlag(FileAttribute flag) { + return updateFlag(flag, true); + } + + public boolean unsetFlag(FileAttribute flag) { + return updateFlag(flag, false); + } + + public boolean updateFlag(FileAttribute flag, boolean value) { + int prev = this.fileAttributes.getAndUpdate(current -> current & (value ? flag.maskingValue() : ~flag.maskingValue())); + return (prev & flag.maskingValue()) != 0; + } + + public void setTimes(long creationTime, long lastAccessTime, long lastWriteTime) { + setCreationTime(creationTime); + setLastAccessTime(lastAccessTime); + setLastWriteTime(lastWriteTime); + } + + public void setTimes(FileTime creationTime, FileTime lastAccessTime, FileTime lastWriteTime) { + setCreationTime(creationTime); + setLastAccessTime(lastAccessTime); + setLastWriteTime(lastWriteTime); + } + + public FileTime getStaticCreationTime() { + return this.creationTime; + } + + public FileTime getCreationTime() { + if(this.creationTime != null) { + return this.creationTime; + } + return getAlternateTime(); + } + + public void setCreationTime(FileTime creationTime) { + this.creationTime = creationTime; + } + + public void setCreationTime(long millis) { + setCreationTime(FileTime.fromMillis(millis)); + } + + public FileTime getStaticLastWriteTime() { + return this.lastWriteTime; + } + + public FileTime getLastWriteTime() { + if(this.lastWriteTime != null) { + return this.lastWriteTime; + } + return getAlternateTime(); + } + + public void setLastWriteTime(FileTime lastWriteTime) { + this.lastWriteTime = lastWriteTime; + } + + public void setLastWriteTime(long millis) { + setLastWriteTime(FileTime.fromMillis(millis)); + } + + public FileTime getStaticLastAccessTime() { + return this.lastAccessTime; + } + + public FileTime getLastAccessTime() { + if(this.lastAccessTime != null) { + return this.lastAccessTime; + } + return getAlternateTime(); + } + + public void setLastAccessTime(FileTime lastAccessTime) { + this.lastAccessTime = lastAccessTime; + } + + public void setLastAccessTime(long millis) { + setLastAccessTime(FileTime.fromMillis(millis)); + } + + public long getFileSize() { + return this.fileSize; + } + + public void setFileSize(long fileSize) { + this.fileSize = fileSize; + } + + private FileTime getAlternateTime() { + if(this.fileTimePolicy == DefaultFileTimePolicy.STATIC_YEAR_1601) { + return YEAR_1601; + } + if(this.fileTimePolicy == DefaultFileTimePolicy.STATIC_YEAR_1970) { + return YEAR_1970; + } + + //--> Inheritance + //#1 Priority: Last write Time + if(this.lastWriteTime != null) { + return this.lastWriteTime; + } + //#2 Priority: Creation Time + if(this.creationTime != null) { + return this.creationTime; + } + //#3 Priority: Access Time + if(this.lastAccessTime != null) { + return this.lastAccessTime; + } + //#4 Resort to default time by policy + return this.fileTimePolicy == DefaultFileTimePolicy.INHERIT_ELSE_1601 ? YEAR_1601 : YEAR_1970; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/BasicDesiredAccessMask.java b/src/main/java/dev/dokan/dokan_java/wrappers/BasicDesiredAccessMask.java new file mode 100644 index 00000000..7e06446e --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/BasicDesiredAccessMask.java @@ -0,0 +1,86 @@ +package dev.dokan.dokan_java.wrappers; + + +import dev.dokan.dokan_java.constants.microsoft.accessmaskflags.BasicAccessMaskFlag; +import dev.dokan.dokan_java.masking.MaskValueSet; + +import java.util.concurrent.atomic.AtomicInteger; + + +public class BasicDesiredAccessMask { + + private final static int ACCESS_MASK_VALUES = MaskValueSet.of(BasicAccessMaskFlag.values()).intValue(); + + protected final AtomicInteger accessMask; + + public BasicDesiredAccessMask(MaskValueSet accessMask) { + this(accessMask.intValue()); + } + + public BasicDesiredAccessMask(int accessMask) { + this.accessMask = new AtomicInteger(accessMask); + } + + public MaskValueSet getBasicRights() { + return BasicAccessMaskFlag.maskValueSet(this.accessMask.get()); + } + + public int getBasicAccessMask() { + return getAccessMask() & ACCESS_MASK_VALUES; + } + + public void setBasicAccessMask(int accessMask) { + setAccessMask(accessMask & ACCESS_MASK_VALUES); + } + + public int getAccessMask() { + return this.accessMask.get(); + } + + public void setAccessMask(int accessMask) { + this.accessMask.set(accessMask); + } + + public boolean getFlag(BasicAccessMaskFlag flag) { + return getFlag(flag.maskingValue()); + } + + public boolean getFlag(int flag) { + ensureSingleFlag(flag); + + return (this.accessMask.get() & flag) != 0; + } + + public boolean setFlag(BasicAccessMaskFlag flag) { + return updateFlag(flag, true); + } + + public boolean unsetFlag(BasicAccessMaskFlag flag) { + return updateFlag(flag, false); + } + + public boolean updateFlag(BasicAccessMaskFlag flag, boolean value) { + return updateFlag(flag.maskingValue(), value); + } + + public boolean updateFlag(int flag, boolean value) { + ensureSingleFlag(flag); + + int prev = this.accessMask.getAndUpdate(current -> current & (value ? flag : ~flag)); + return (prev & flag) != 0; + } + + protected void ensureSingleFlag(int flag) { + if (!isSingleFlag(flag)) { + throw new IllegalArgumentException("Result for more than one flag is undefined!"); + } + } + + protected boolean isSingleFlag(int flag) { + /* + * This may be more performant, but it doesn't really matter + * Integer.highestOneBit(flag) != Integer.lowestOneBit(flag) + */ + return Integer.bitCount(flag) == 1; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/DesiredAccessMask.java b/src/main/java/dev/dokan/dokan_java/wrappers/DesiredAccessMask.java new file mode 100644 index 00000000..c279b5fa --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/DesiredAccessMask.java @@ -0,0 +1,106 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.constants.microsoft.accessmaskflags.DirectoryAccessMaskFlag; +import dev.dokan.dokan_java.constants.microsoft.accessmaskflags.FileAccessMaskFlag; +import dev.dokan.dokan_java.masking.MaskValueSet; + + +public class DesiredAccessMask extends BasicDesiredAccessMask { + + private final boolean isDirectory; + + public DesiredAccessMask(int rawAccessMask, int rawFileAttributes) { + this(rawAccessMask, (rawFileAttributes & FileAttribute.DIRECTORY.maskingValue()) != 0); + } + + public DesiredAccessMask(int rawAccessMask, MaskValueSet fileAttributes) { + this(rawAccessMask, fileAttributes.contains(FileAttribute.DIRECTORY)); + } + + //TODO Check for illegal flags if isDirectory=true + //See https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwcreatefile Note below first table + public DesiredAccessMask(int rawAccessMask, boolean isDirectory) { + super(rawAccessMask); + this.isDirectory = isDirectory; + } + + public MaskValueSet getSpecificRights() { + return FileAccessMaskFlag.maskValueSet(this.accessMask.get()); + } + + public boolean isDirectory() { + return this.isDirectory; + } + + public boolean getFlag(FileAccessMaskFlag flag) { + return getFlag(flag.maskingValue()); + } + + public boolean getFlag(DirectoryAccessMaskFlag flag) { + if(!this.isDirectory) { + throw new IllegalArgumentException("Not a directory!"); + } + return silentGetFlag(flag); + } + + public boolean silentGetFlag(DirectoryAccessMaskFlag flag) { + return getFlag(flag.maskingValue()); + } + + public boolean setFlag(FileAccessMaskFlag flag) { + return updateFlag(flag, true); + } + + public boolean unsetFlag(FileAccessMaskFlag flag) { + return updateFlag(flag, false); + } + + public boolean updateFlag(FileAccessMaskFlag flag, boolean value) { + return updateFlag(flag.maskingValue(), value); + } + + public boolean setFlag(DirectoryAccessMaskFlag flag) { + if(!this.isDirectory) { + throw new IllegalArgumentException("Not a directory!"); + } + return silentSetFlag(flag); + } + + public boolean unsetFlag(DirectoryAccessMaskFlag flag) { + if(!this.isDirectory) { + throw new IllegalArgumentException("Not a directory!"); + } + return silentUnsetFlag(flag); + } + + public boolean updateFlag(DirectoryAccessMaskFlag flag, boolean value) { + if(!this.isDirectory) { + throw new IllegalArgumentException("Not a directory!"); + } + return silentUpdateFlag(flag, value); + } + + public boolean silentSetFlag(DirectoryAccessMaskFlag flag) { + return silentUpdateFlag(flag, true); + } + + public boolean silentUnsetFlag(DirectoryAccessMaskFlag flag) { + return silentUpdateFlag(flag, false); + } + + public boolean silentUpdateFlag(DirectoryAccessMaskFlag flag, boolean value) { + return updateFlag(flag.maskingValue(), value); + } + + private FileAccessMaskFlag getFileAccessMask(DirectoryAccessMaskFlag attribute) { + //This lookup is fine, but a different kind of mapping would be preferable + switch(attribute) { + case LIST_DIRECTORY: //1 + return FileAccessMaskFlag.READ_DATA; + case TRAVERSE: //32 + return FileAccessMaskFlag.EXECUTE; + } + throw new IllegalStateException(); + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/DiskSpaceInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/DiskSpaceInfo.java new file mode 100644 index 00000000..10196c25 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/DiskSpaceInfo.java @@ -0,0 +1,42 @@ +package dev.dokan.dokan_java.wrappers; + +public class DiskSpaceInfo { + + private long usableSpace; + private long totalSpace; + private long unallocatedSpace; + + public DiskSpaceInfo() { + this(0L, 0L, 0L); + } + + public DiskSpaceInfo(long usableSpace, long totalSpace, long unallocatedSpace) { + this.usableSpace = usableSpace; + this.totalSpace = totalSpace; + this.unallocatedSpace = unallocatedSpace; + } + + public long getUsableSpace() { + return this.usableSpace; + } + + public void setUsableSpace(long usableSpace) { + this.usableSpace = usableSpace; + } + + public long getTotalSpace() { + return this.totalSpace; + } + + public void setTotalSpace(long totalSpace) { + this.totalSpace = totalSpace; + } + + public long getUnallocatedSpace() { + return this.unallocatedSpace; + } + + public void setUnallocatedSpace(long unallocatedSpace) { + this.unallocatedSpace = unallocatedSpace; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/DokanFileHandle.java b/src/main/java/dev/dokan/dokan_java/wrappers/DokanFileHandle.java new file mode 100644 index 00000000..6350986d --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/DokanFileHandle.java @@ -0,0 +1,121 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.constants.dokany.DokanFileInfoFlag; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.DokanFileInfo; +import dev.dokan.dokan_java.wrappers.mountinfo.MountInfo; +import dev.dokan.dokan_java.wrappers.mountinfo.ROMountInfo; + +import java.util.concurrent.atomic.AtomicInteger; + +public class DokanFileHandle { + + private static final long INVALID_HANDLE = 0L; + + private final AtomicInteger flags; + private final long dokanContext; //DokanFileInfo tells us to "never modify". Happy to oblige + private final ROMountInfo mountInfo; + + private long context; + private int processId; + + public DokanFileHandle(DokanFileInfo nativeInfo) { + this.context = nativeInfo.Context; + this.dokanContext = nativeInfo.DokanContext; + this.processId = nativeInfo.ProcessId; + /* + * FIXME + * This copy is as expensive as it is useless. A reference to a stored MountInfo-Object would be far more useful. + * Due to bug/issue #46 this would be inherently unsafe, so it's a copy for now... + * + * See https://github.com/dokan-dev/dokan-java/issues/46 + */ + this.mountInfo = new MountInfo(nativeInfo.DokanOpts); + + MaskValueSet flagSet = MaskValueSet.emptySet(DokanFileInfoFlag.class); + if(nativeInfo.deleteOnClose()) { + flagSet.add(DokanFileInfoFlag.DELETE_ON_CLOSE); + } + if(nativeInfo.isDirectory()) { + flagSet.add(DokanFileInfoFlag.IS_DIRECTORY); + } + if(nativeInfo.noCache()) { + flagSet.add(DokanFileInfoFlag.NO_CACHE); + } + if(nativeInfo.pagingIo()) { + flagSet.add(DokanFileInfoFlag.PAGING_IO); + } + if(nativeInfo.synchronousIo()) { + flagSet.add(DokanFileInfoFlag.SYNCHRONOUS_IO); + } + if(nativeInfo.writeToEndOfFile()) { + flagSet.add(DokanFileInfoFlag.WRITE_TO_END_OF_FILE); + } + this.flags = new AtomicInteger(flagSet.intValue()); + } + + public MaskValueSet getFileInfo() { + return MaskValueSet.maskValueSet(this.flags.get(), DokanFileInfoFlag.values()); + } + + public void setFileInfo(MaskValueSet flagSet) { + this.flags.set(flagSet.intValue()); + } + + public int getFlags() { + return this.flags.get(); + } + + public void setFlags(int flags) { + this.flags.set(flags); + } + + public boolean getFlag(DokanFileInfoFlag flag) { + return (this.flags.get() & flag.maskingValue()) != 0; + } + + public boolean setFlag(DokanFileInfoFlag flag) { + return updateFlag(flag, true); + } + + public boolean unsetFlag(DokanFileInfoFlag flag) { + return updateFlag(flag, false); + } + + public boolean updateFlag(DokanFileInfoFlag flag, boolean value) { + int prev = this.flags.getAndUpdate(current -> current & (value ? flag.maskingValue() : ~flag.maskingValue())); + return (prev & flag.maskingValue()) != 0; + } + + public long getContext() { + return this.context; + } + + public void setContext(long context) { + this.context = context; + } + + public boolean isValid() { + return getContext() != INVALID_HANDLE; + } + + public void invalidate() { + setContext(INVALID_HANDLE); + } + + public long getDokanContext() { + return this.dokanContext; + } + + public long getProcessId() { + return this.processId; + } + + public void setProcessId(int processId) { + this.processId = processId; + } + + public ROMountInfo getMountInfo() { + return mountInfo; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanFileSystem.java b/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanFileSystem.java new file mode 100644 index 00000000..200e85a1 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanFileSystem.java @@ -0,0 +1,169 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.NativeName; +import dev.dokan.dokan_java.constants.microsoft.CreateOption; +import dev.dokan.dokan_java.constants.microsoft.CreationDisposition; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.constants.microsoft.FileShareAccess; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.filesecurity.SelfRelativeSecurityDescriptor; + +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.Collection; + +public interface EasyDokanFileSystem { + + //public int zwCreateFile(WString rawPath, + // DokanIOSecurityContext securityContext, + // int rawDesiredAccess, + // int rawFileAttributes, + // int rawShareAccess, + // int rawCreateDisposition, + // int rawCreateOptions, + // DokanFileInfo dokanFileInfo) { //TODO Return type //TODO Incorporate path into DokanFileHandle + @NativeName("zwCreateFile") + void createHandle(Path absolutePath, + String relativePath, + EasyDokanIOSecurityContext securityContext, + DesiredAccessMask desiredAccess, + MaskValueSet fileAttributes, + MaskValueSet shareAccess, + CreationDisposition creationDisposition, + MaskValueSet createOptions, + DokanFileHandle dokanFileHandle + ); //--> zwCreateFile + + @NativeName("cleanup") + void cleanup(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle); + + @NativeName("closeFile") + void closeHandle(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle); //--> CloseFile + + @NativeName("readFile") + byte[] readFile(Path absolutePath, + String relativePath, + long offset, + int readLength, + DokanFileHandle dokanFileHandle); //TODO return -> callback-array? + + @NativeName("writeFile") + int writeFile(Path absolutePath, String relativePath, byte[] buffer, long offset, DokanFileHandle dokanFileHandle); + + @NativeName("flushFileBuffers") + void flush(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle); + + @NativeName("getFileInformation") + EasyFileInfo getFileInformation(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle); + + @NativeName("findFiles") + Collection findFiles(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle); + //TODO --> Collection to callback?! --> default method? + + @NativeName("findFilesWithPattern") + Collection findFilesWithPattern(Path absolutePath, + String relativePath, + String pattern, //TODO Regex + DokanFileHandle dokanFileHandle); + //TODO --> Collection to callback?! + + @NativeName("setFileAttributes") + void setFileAttributes(Path absolutePath, + String relativePath, + MaskValueSet fileAttributes, + DokanFileHandle dokanFileHandle); + + @NativeName("setFileTime") + void setFileTime(Path absolutePath, + String relativePath, + FileTime creationTime, + FileTime lastAccessTime, + FileTime lastWriteTime, + DokanFileHandle dokanFileHandle); + + @NativeName("deleteFile") + void prepareDeleteFile(Path absolutePath, + String relativePath, + DokanFileHandle dokanFileHandle); //TODO Rename, return value? + + @NativeName("deleteDirectory") + void prepareDeleteDirectory(Path absolutePath, + String relativePath, + DokanFileHandle dokanFileHandle); //TODO Rename, return value? + + @NativeName("moveFile") + void moveFile(Path sourceAbsolutePath, + String sourceRelativePath, + Path destinationAbsolutePath, + String destinationRelativePath, + boolean replaceIfExisting, + DokanFileHandle dokanFileHandle); + + @NativeName("setEndOfFile") + void setEndOfFile(Path absolutePath, + String relativePath, + long fileSize, + DokanFileHandle dokanFileHandle); + + @NativeName("setAllocationSize") + void setAllocationSize(Path absolutePath, + String relativePath, + long allocationSize, + DokanFileHandle dokanFileHandle); + + @NativeName("lockFile") + void lockFile(Path absolutePath, + String relativePath, + long lockOffset, + long lockLength, + DokanFileHandle dokanFileHandle); + + @NativeName("unlockFile") + void unlockFile(Path absolutePath, + String relativePath, + long lockOffset, + long lockLength, + DokanFileHandle dokanFileHandle); + + @NativeName("getDiskFreeSpace") + DiskSpaceInfo getDiskSpaceInfo(DokanFileHandle dokanFileHandle); //TODO Path? + + @NativeName("getVolumeInformation") + VolumeInformation getVolumeInformation(DokanFileHandle dokanFileHandle); //TODO Path? + + @NativeName("mounted") + void mounted(DokanFileHandle dokanFileHandle); //TODO Path? + + @NativeName("unmounted") + void unmounted(DokanFileHandle dokanFileHandle); //TODO Path? + + // int getFileSecurity(WString rawPath, + // int /* SecurityInformation */ rawSecurityInformation, //Requested information //DesiredAccessMask OR Similar CLASS OR MaskValueSet + // Pointer rawSecurityDescriptor, //Pointer to Buffer. Buffer gets copy of Security Descriptor. SECURITY_DESCRIPTOR in Self relative secdesc format //SelfRelativeSecurityDescriptor + // int rawSecurityDescriptorLength, //available length for secdesc + // IntByReference rawSecurityDescriptorLengthNeeded, //callback needed length for secdesc //Should be computed by dokany + // DokanFileInfo dokanFileInfo); + @NativeName("getFileSecurity") + SelfRelativeSecurityDescriptor getFileSecurity(Path absolutePath, //TODO Return type + String relativePath, + DesiredAccessMask requestedDescriptorInfo, + int availableLength, + DokanFileHandle dokanFileHandle); + + // int setFileSecurity( + // WString rawPath, + // int rawSecurityInformation, + // Pointer rawSecurityDescriptor, + // int rawSecurityDescriptorLength, + // DokanFileInfo dokanFileInfo); + @NativeName("setFileSecurity") + void setFileSecurity(Path absolutePath, + String relativePath, + DesiredAccessMask suppliedDescriptorInfo, + SelfRelativeSecurityDescriptor securityDescriptor, + int availableLength, + DokanFileHandle dokanFileHandle); + + //TODO fillWin32FindData, findStreams + +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanFileSystemStub.java b/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanFileSystemStub.java new file mode 100644 index 00000000..c61c0bdb --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanFileSystemStub.java @@ -0,0 +1,217 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.FileSystemInformation; +import dev.dokan.dokan_java.NotImplemented; +import dev.dokan.dokan_java.constants.microsoft.CreateOption; +import dev.dokan.dokan_java.constants.microsoft.CreationDisposition; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.constants.microsoft.FileShareAccess; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.filesecurity.SelfRelativeSecurityDescriptor; + +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.Collection; + +public class EasyDokanFileSystemStub extends AbstractEasyDokanFileSystem { + + public EasyDokanFileSystemStub(FileSystemInformation fileSystemInformation) { + super(fileSystemInformation); + } + + @NotImplemented + @Override + public void createHandle(Path absolutePath, + String relativePath, + EasyDokanIOSecurityContext securityContext, + DesiredAccessMask desiredAccess, + MaskValueSet fileAttributes, + MaskValueSet shareAccess, + CreationDisposition creationDisposition, + MaskValueSet createOptions, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void cleanup(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void closeHandle(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public byte[] readFile(Path absolutePath, + String relativePath, + long offset, + int readLength, + DokanFileHandle dokanFileHandle) { + return new byte[0]; + } + + @NotImplemented + @Override + public int writeFile(Path absolutePath, + String relativePath, + byte[] buffer, + long offset, + DokanFileHandle dokanFileHandle) { + return 0; + } + + @NotImplemented + @Override + public void flush(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public EasyFileInfo getFileInformation(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + return null; + } + + @NotImplemented + @Override + public Collection findFiles(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + return null; + } + + @NotImplemented + @Override + public Collection findFilesWithPattern(Path absolutePath, + String relativePath, + String pattern, + DokanFileHandle dokanFileHandle) { + return null; + } + + @NotImplemented + @Override + public void setFileAttributes(Path absolutePath, + String relativePath, + MaskValueSet fileAttributes, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void setFileTime(Path absolutePath, + String relativePath, + FileTime creationTime, + FileTime lastAccessTime, + FileTime lastWriteTime, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void prepareDeleteFile(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void prepareDeleteDirectory(Path absolutePath, String relativePath, DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void moveFile(Path sourceAbsolutePath, + String sourceRelativePath, + Path destinationAbsolutePath, + String destinationRelativePath, + boolean replaceIfExisting, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void setEndOfFile(Path absolutePath, String relativePath, long fileSize, DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void setAllocationSize(Path absolutePath, + String relativePath, + long allocationSize, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void lockFile(Path absolutePath, + String relativePath, + long lockOffset, + long lockLength, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void unlockFile(Path absolutePath, + String relativePath, + long lockOffset, + long lockLength, + DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public DiskSpaceInfo getDiskSpaceInfo(DokanFileHandle dokanFileHandle) { + return null; + } + + @NotImplemented + @Override + public VolumeInformation getVolumeInformation(DokanFileHandle dokanFileHandle) { + return null; + } + + @NotImplemented + @Override + public void mounted(DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public void unmounted(DokanFileHandle dokanFileHandle) { + + } + + @NotImplemented + @Override + public SelfRelativeSecurityDescriptor getFileSecurity(Path absolutePath, + String relativePath, + DesiredAccessMask requestedDescriptorInfo, + int availableLength, + DokanFileHandle dokanFileHandle) { + return null; + } + + @NotImplemented + @Override + public void setFileSecurity(Path absolutePath, + String relativePath, + DesiredAccessMask suppliedDescriptorInfo, + SelfRelativeSecurityDescriptor securityDescriptor, + int availableLength, + DokanFileHandle dokanFileHandle) { + + } +} diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanIOSecurityContext.java b/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanIOSecurityContext.java new file mode 100644 index 00000000..b0fa32f3 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/EasyDokanIOSecurityContext.java @@ -0,0 +1,7 @@ +package dev.dokan.dokan_java.wrappers; + +public class EasyDokanIOSecurityContext { //Wrapper for WinBase.SECURITY_ATTRIBUTES + + //TODO Hey, I'm a stub! //FIXME + +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java new file mode 100644 index 00000000..e7b30d1c --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/EasyFileInfo.java @@ -0,0 +1,84 @@ +package dev.dokan.dokan_java.wrappers; + +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.ByHandleFileInformation; + +import java.nio.file.Path; + +public class EasyFileInfo extends AbstractFileInfo { + + private final Path path; + + private int volumeSerialNumber; + private long fileIndex; + private int numberOfLinks; + + public EasyFileInfo(Path path) { + this(path, FileAttribute.NORMAL.maskingValue()); + } + + public EasyFileInfo(Path path, MaskValueSet attributes) { + super(attributes); + this.path = path; + } + + public EasyFileInfo(Path path, int attributes) { + super(attributes); + this.path = path; + } + + public Path getPath() { + return this.path; + } + + public int getVolumeSerialNumber() { + return this.volumeSerialNumber; + } + + public void setVolumeSerialNumber(int volumeSerialNumber) { + this.volumeSerialNumber = volumeSerialNumber; + } + + public long getFileIndex() { + return this.fileIndex; + } + + public void setFileIndex(long fileIndex) { + this.fileIndex = fileIndex; + } + + public int getNumberOfLinks() { + return this.numberOfLinks; + } + + public void setNumberOfLinks(int numberOfLinks) { + this.numberOfLinks = numberOfLinks; + } + + public ByHandleFileInformation toByHandleFileInformation() { + return toByHandleFileInformation(this.path); + } + + public ByHandleFileInformation toByHandleFileInformation(Path pathOverride) { + ByHandleFileInformation info = new ByHandleFileInformation(pathOverride, + getFlags(), + getCreationTime(), + getLastAccessTime(), + getLastWriteTime(), + this.volumeSerialNumber, + getFileSize(), + this.fileIndex); + info.nNumberOfLinks = this.numberOfLinks; + + return info; + } + + public void copyTo(ByHandleFileInformation byHandleFileInformation) { + copyTo(byHandleFileInformation, this.path); + } + + public void copyTo(ByHandleFileInformation byHandleFileInformation, Path pathOverride) { + toByHandleFileInformation(pathOverride).copyTo(byHandleFileInformation); //That's not the most efficient way to do this, but it's less prone to human error + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java new file mode 100644 index 00000000..4a437a84 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/FindFileInfo.java @@ -0,0 +1,145 @@ +package dev.dokan.dokan_java.wrappers; + +import com.sun.jna.platform.win32.WinBase; +import dev.dokan.dokan_java.DokanUtils; +import dev.dokan.dokan_java.constants.microsoft.FileAttribute; +import dev.dokan.dokan_java.constants.microsoft.MicrosoftReparsePointTag; +import dev.dokan.dokan_java.masking.EnumInteger; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.ReparsePointTag; + +import java.util.Objects; +import java.util.function.Function; + +public class FindFileInfo extends AbstractFileInfo { + + private static final int MAX_ALTERNATIVE_NAME_LENGTH = 14; + private static final FileAttribute REPARSE_POINT_FLAG = FileAttribute.REPARSE_POINT; + private static final String REPARSE_POINT_NOT_SET_MESSAGE = + "Reparse point tags are disabled on this File(-Handle); Flag \"FileAttribute.REPARSE_POINT\" not set"; + private final int RESERVED_1_FIELD_DEFAULT = 0; + + private int reparsePointTag; + // private int reserved_1; //Not yet specified by Microsoft + private String fileName; + private String alternativeName; + + public FindFileInfo(MaskValueSet attributes) { + super(attributes); + } + + public FindFileInfo(int attributes) { + super(attributes); + } + + public int getReparsePointTagValueLeniently() { + return getReparsePointFlag() ? this.reparsePointTag : 0; + } + + public int getReparsePointTagValue() { + tryReparsePoint(); + return this.reparsePointTag; + } + + public void setReparsePointTagValue(int reparsePointTagValue) { + tryReparsePoint(); + this.reparsePointTag = reparsePointTagValue; + } + + public MicrosoftReparsePointTag getMSReparsePointTag() { + return getReparsePointTag(MicrosoftReparsePointTag.values()); + } + + public void setMSReparsePointTag(MicrosoftReparsePointTag tag) { + setReparsePointTag(tag); + } + + public & EnumInteger> T getReparsePointTag(T[] possibleValues) { + return EnumInteger.enumFromInt(getReparsePointTagValue(), possibleValues); + } + + public & ReparsePointTag> void setReparsePointTag(T tag) { + setReparsePointTagValue(tag.intValue()); + } + + public & ReparsePointTag> T getReparsePointTag(Function parser) { + return parser.apply(getReparsePointTagValue()); + } + + public & ReparsePointTag> void setReparsePointTag(T tag, Function parser) { + setReparsePointTagValue(parser.apply(tag)); + } + + public boolean getReparsePointFlag() { + return getFlag(REPARSE_POINT_FLAG); + } + + public boolean setReparsePointFlag() { + return updateReparsePointFlag(true); + } + + public boolean unsetReparsePointFlag() { + return updateReparsePointFlag(false); + } + + public boolean updateReparsePointFlag(boolean value) { + /* + * The previous tag value is not discarded when updating the flag to false. + * But: The tag value will only be present in the WIN32_FIND_DATA if the flag is set to true. + * The following code could change that behavior. + * + * if(value && (prevValueFromUpdate != value)) { setReparsePointTagValue(0); } + */ + return updateFlag(REPARSE_POINT_FLAG, value); + } + + public String getFileName() { + return this.fileName; + } + + public void setFileName(String fileName) { + this.fileName = Objects.requireNonNullElse(fileName, ""); + } + + public String getAlternativeName() { + return this.alternativeName; + } + + public void setAlternativeName(String alternativeName) { + alternativeName = Objects.requireNonNullElse(alternativeName, ""); + if(alternativeName.length() > 14) { + throw new IllegalArgumentException("Alternative name must not be longer than 14 chars"); + } + this.alternativeName = alternativeName; + } + + public String setAlternativeNameLeniently(String alternativeName) { + alternativeName = Objects.requireNonNullElse(alternativeName, ""); + this.alternativeName = alternativeName.substring(0, MAX_ALTERNATIVE_NAME_LENGTH); + + return this.alternativeName; + } + + public WinBase.WIN32_FIND_DATA toWIN32_FIND_DATA() { + long fileSize = getFileSize(); + int fileSizeHigh = (int) (fileSize >> 32 & 0xffffffffL); + int fileSizeLow = (int) (fileSize & 0xffffffffL); + + return new WinBase.WIN32_FIND_DATA(getFlags(), + DokanUtils.toFILETIME(getCreationTime()), + DokanUtils.toFILETIME(getLastAccessTime()), + DokanUtils.toFILETIME(getLastWriteTime()), + fileSizeHigh, + fileSizeLow, + getReparsePointTagValueLeniently(), + this.RESERVED_1_FIELD_DEFAULT, + this.fileName.toCharArray(), + this.alternativeName.toCharArray()); + } + + private void tryReparsePoint() { + if(!getReparsePointFlag()) { + throw new IllegalStateException(REPARSE_POINT_NOT_SET_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/ForwardingFileSystem.java b/src/main/java/dev/dokan/dokan_java/wrappers/ForwardingFileSystem.java new file mode 100644 index 00000000..2cbcb75b --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/ForwardingFileSystem.java @@ -0,0 +1,241 @@ +package dev.dokan.dokan_java.wrappers; + + +import com.sun.jna.Pointer; +import com.sun.jna.WString; +import com.sun.jna.platform.win32.WinBase; +import com.sun.jna.ptr.IntByReference; +import com.sun.jna.ptr.LongByReference; +import dev.dokan.dokan_java.AbstractDokanFileSystem; +import dev.dokan.dokan_java.DokanException; +import dev.dokan.dokan_java.DokanOperations; +import dev.dokan.dokan_java.FileSystemInformation; +import dev.dokan.dokan_java.constants.microsoft.Win32ErrorCodes; +import dev.dokan.dokan_java.structure.ByHandleFileInformation; +import dev.dokan.dokan_java.structure.DokanFileInfo; +import dev.dokan.dokan_java.structure.DokanIOSecurityContext; + +import java.nio.file.Path; +import java.util.Set; + + +public class ForwardingFileSystem extends AbstractDokanFileSystem { + + private final Set notImplementedMethods; + private final AbstractEasyDokanFileSystem creator; + + public ForwardingFileSystem(FileSystemInformation fileSystemInformation, Set notImplementedMethods, AbstractEasyDokanFileSystem target) { + super(fileSystemInformation); + this.notImplementedMethods = notImplementedMethods; + this.creator = target; + } + + @Override + public boolean isImplemented(String funcName) { + return !this.notImplementedMethods.contains(funcName); + } + + + @Override + public int zwCreateFile(WString rawPath, DokanIOSecurityContext securityContext, int rawDesiredAccess, int rawFileAttributes, int rawShareAccess, int rawCreateDisposition, int rawCreateOptions, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public void cleanup(WString rawPath, DokanFileInfo dokanFileInfo) { + + } + + @Override + public void closeFile(WString rawPath, DokanFileInfo dokanFileInfo) { + + } + + @Override + public int readFile(WString rawPath, + Pointer rawBuffer, + int rawBufferLength, + IntByReference rawReadLength, + long rawOffset, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int writeFile(WString rawPath, + Pointer rawBuffer, + int rawNumberOfBytesToWrite, + IntByReference rawNumberOfBytesWritten, + long rawOffset, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int flushFileBuffers(WString rawPath, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int getFileInformation(WString fileName, + ByHandleFileInformation handleFileInfo, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int findFiles(WString rawPath, + DokanOperations.FillWin32FindData rawFillFindData, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int findFilesWithPattern(WString fileName, + WString searchPattern, + DokanOperations.FillWin32FindData rawFillFindData, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int setFileAttributes(WString rawPath, int rawAttributes, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int setFileTime(WString rawPath, + WinBase.FILETIME rawCreationTime, + WinBase.FILETIME rawLastAccessTime, + WinBase.FILETIME rawLastWriteTime, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int deleteFile(WString rawPath, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int deleteDirectory(WString rawPath, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int moveFile(WString rawPath, + WString rawNewFileName, + boolean rawReplaceIfExisting, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int setEndOfFile(WString rawPath, long rawByteOffset, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int setAllocationSize(WString rawPath, long rawLength, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int lockFile(WString rawPath, long rawByteOffset, long rawLength, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int unlockFile(WString rawPath, long rawByteOffset, long rawLength, DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int getDiskFreeSpace(LongByReference freeBytesAvailable, + LongByReference totalNumberOfBytes, + LongByReference totalNumberOfFreeBytes, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int getVolumeInformation(Pointer rawVolumeNameBuffer, + int rawVolumeNameSize, + IntByReference rawVolumeSerialNumber, + IntByReference rawMaximumComponentLength, + IntByReference rawFileSystemFlags, + Pointer rawFileSystemNameBuffer, + int rawFileSystemNameSize, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int mounted(DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int unmounted(DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int getFileSecurity(WString rawPath, + int rawSecurityInformation, + Pointer rawSecurityDescriptor, + int rawSecurityDescriptorLength, + IntByReference rawSecurityDescriptorLengthNeeded, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public int setFileSecurity(WString rawPath, + int rawSecurityInformation, + Pointer rawSecurityDescriptor, + int rawSecurityDescriptorLength, + DokanFileInfo dokanFileInfo) { + return 0; + } + + @Override + public void fillWin32FindData(WinBase.WIN32_FIND_DATA rawFillFindData, DokanFileInfo dokanFileInfo) { + + } + + @Override + public int findStreams(WString rawPath, + DokanOperations.FillWin32FindStreamData rawFillFindData, + DokanFileInfo dokanFileInfo) { + return 0; + } + + private int handleException(DokanException exc) { //TODO Reformat to allow any kind of exception + if (exc == null) { + return Win32ErrorCodes.ERROR_GEN_FAILURE; + } + + if (exc.getErrorCode() == Integer.MIN_VALUE) { + //TODO + } + //TODO Log + return exc.getErrorCode(); + } + + private String resolveRelativePath(WString path) { + return resolveRelativePath(path.toString()); + } + + private String resolveRelativePath(String path) { + return path; + } + + private Path resolveAbsolutePath(WString path) { + return resolveAbsolutePath(path.toString()); + } + + private Path resolveAbsolutePath(String path) { + return null; //TODO Implement + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/VolumeInformation.java b/src/main/java/dev/dokan/dokan_java/wrappers/VolumeInformation.java new file mode 100644 index 00000000..c5af5094 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/VolumeInformation.java @@ -0,0 +1,114 @@ +package dev.dokan.dokan_java.wrappers; + + +import dev.dokan.dokan_java.DokanOperations; +import dev.dokan.dokan_java.constants.microsoft.FileSystemFlag; +import dev.dokan.dokan_java.masking.MaskValueSet; + +/** + * Supplementary class to bundle information of the mounted volume and its filesystem. + *

Mainly used for {@link DokanOperations#GetVolumeInformation} function to have all needed information at one place.

+ * TODO: Maybe this can be completely integrated in the DokanFileSystem class + */ +public final class VolumeInformation { + private final int maxComponentLength; + private final String name; + private final int serialNumber; + private final String fileSystemName; + private final MaskValueSet fileSystemFeatures; + + public static final int DEFAULT_MAX_COMPONENT_LENGTH = 256; + public static final int DEFAULT_SERIAL_NUMBER = 305419896; + public static final String DEFAULT_VOLUME_NAME = "VOLUME1"; + public static final String DEFAULT_FS_NAME = "DOKANY"; + public static final MaskValueSet DEFAULT_FS_FEATURES = MaskValueSet.emptySet(FileSystemFlag.class); + + static { + DEFAULT_FS_FEATURES.add(FileSystemFlag.CASE_PRESERVED_NAMES); + } + + /** + * Provides default values for maxComponentLength and fileSystemFeatures. + * + * @param volumeName + * @param serialNumber + * @param fileSystemName + */ + public VolumeInformation(final String volumeName, final int serialNumber, final String fileSystemName) { + this(DEFAULT_MAX_COMPONENT_LENGTH, volumeName, serialNumber, fileSystemName, DEFAULT_FS_FEATURES); + } + + /** + * Provides default values for all values. + */ + public VolumeInformation() { + this(DEFAULT_MAX_COMPONENT_LENGTH, DEFAULT_VOLUME_NAME, DEFAULT_SERIAL_NUMBER, DEFAULT_FS_NAME, DEFAULT_FS_FEATURES); + } + + public int getMaxComponentLength() { + return this.maxComponentLength; + } + + public String getName() { + return this.name; + } + + public int getSerialNumber() { + return this.serialNumber; + } + + public String getFileSystemName() { + return this.fileSystemName; + } + + public MaskValueSet getFileSystemFeatures() { + return this.fileSystemFeatures; + } + + @Override + public boolean equals(final Object o) { + if (o == this) return true; + if (!(o instanceof VolumeInformation)) return false; + final VolumeInformation other = (VolumeInformation) o; + if (this.getMaxComponentLength() != other.getMaxComponentLength()) return false; + final Object this$name = this.getName(); + final Object other$name = other.getName(); + if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; + if (this.getSerialNumber() != other.getSerialNumber()) return false; + final Object this$fileSystemName = this.getFileSystemName(); + final Object other$fileSystemName = other.getFileSystemName(); + if (this$fileSystemName == null ? other$fileSystemName != null : !this$fileSystemName.equals(other$fileSystemName)) return false; + final Object this$fileSystemFeatures = this.getFileSystemFeatures(); + final Object other$fileSystemFeatures = other.getFileSystemFeatures(); + if (this$fileSystemFeatures == null ? other$fileSystemFeatures != null : !this$fileSystemFeatures.equals(other$fileSystemFeatures)) return false; + return true; + } + + @Override + public int hashCode() { + final int PRIME = 59; + int result = 1; + result = result * PRIME + this.getMaxComponentLength(); + final Object $name = this.getName(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + result = result * PRIME + this.getSerialNumber(); + final Object $fileSystemName = this.getFileSystemName(); + result = result * PRIME + ($fileSystemName == null ? 43 : $fileSystemName.hashCode()); + final Object $fileSystemFeatures = this.getFileSystemFeatures(); + result = result * PRIME + ($fileSystemFeatures == null ? 43 : $fileSystemFeatures.hashCode()); + return result; + } + + @Override + public String toString() { + return "VolumeInformation(maxComponentLength=" + this.getMaxComponentLength() + ", name=" + this.getName() + ", serialNumber=" + this.getSerialNumber() + ", fileSystemName=" + this.getFileSystemName() + ", fileSystemFeatures=" + this.getFileSystemFeatures() + ")"; + } + + public VolumeInformation(final int maxComponentLength, final String name, final int serialNumber, final String fileSystemName, final MaskValueSet fileSystemFeatures) { + this.maxComponentLength = maxComponentLength; + this.name = name; + this.serialNumber = serialNumber; + this.fileSystemName = fileSystemName; + this.fileSystemFeatures = fileSystemFeatures; + } +} diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/ImmutableMountInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/ImmutableMountInfo.java new file mode 100644 index 00000000..674d17be --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/ImmutableMountInfo.java @@ -0,0 +1,145 @@ +package dev.dokan.dokan_java.wrappers.mountinfo; + + +import com.sun.jna.WString; +import dev.dokan.dokan_java.DokanNativeMethods; +import dev.dokan.dokan_java.constants.dokany.MountOption; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.DokanOptions; + + +public class ImmutableMountInfo implements ROMountInfo { + +// private final long globalContext; + + private final short threadCount; + private final int mountOptions; + private final String mountPoint; + private final String uncName; + private final long timeout; + private final long allocationUnitSize; + private final long sectorSize; + + public ImmutableMountInfo(DokanOptions dokanOptions) { + this.threadCount = dokanOptions.ThreadCount; + this.mountOptions = dokanOptions.Options; +// this.globalContext = dokanOptions.GlobalContext; + this.mountPoint = dokanOptions.MountPoint.toString(); + this.uncName = dokanOptions.UNCName.toString(); + this.timeout = dokanOptions.Timeout; + this.allocationUnitSize = dokanOptions.AllocationUnitSize; + this.sectorSize = dokanOptions.SectorSize; + } + + ImmutableMountInfo(ImmutableMountInfo info) { + this.threadCount = info.threadCount; + this.mountOptions = info.mountOptions; +// this.globalContext = info.globalContext; + this.mountPoint = info.mountPoint; + this.uncName = info.uncName; + this.timeout = info.timeout; + this.allocationUnitSize = info.allocationUnitSize; + this.sectorSize = info.sectorSize; + } + + ImmutableMountInfo(MountInfo info) { + this.threadCount = info.getThreadCount(); + this.mountOptions = info.getFlags(); +// this.globalContext = info.getGlobalContext(); + this.mountPoint = info.getMountPoint(); + this.uncName = info.getUNCName(); + this.timeout = info.getTimeout(); + this.allocationUnitSize = info.getAllocationUnitSize(); + this.sectorSize = info.getSectorSize(); + } + + @Override + public short getDokanVersion() { + return DokanNativeMethods.getMinimumRequiredDokanVersion(); + } + + @Override + public short getThreadCount() { + return this.threadCount; + } + + @Override + public MaskValueSet getMountOptions() { + return MountOption.maskValueSet(this.mountOptions); + } + + @Override + public int getFlags() { + return this.mountOptions; + } + + @Override + public boolean getFlag(MountOption flag) { + return (this.mountOptions & flag.maskingValue()) != 0; + } + +// @Override +// public long getGlobalContext() { +// return this.globalContext; +// } + + @Override + public String getMountPoint() { + return this.mountPoint; + } + + @Override + public String getUNCName() { + return this.uncName; + } + + @Override + public long getTimeout() { + return this.timeout; + } + + @Override + public long getAllocationUnitSize() { + return this.allocationUnitSize; + } + + @Override + public long getSectorSize() { + return this.sectorSize; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public ROMountInfo immutableCopy() { + return new ImmutableMountInfo(this); + } + + @Override + public RWMountInfo mutableCopy() { + return new MountInfo(this); + } + + @Override + public DokanOptions nativeCopy() { + DokanOptions options = new DokanOptions(this.mountPoint, this.threadCount, this.mountOptions, this.uncName, this.timeout, this.allocationUnitSize, this.sectorSize); +// options.GlobalContext = this.globalContext; + + return options; + } + + @Override + public void nativeCopy(DokanOptions options) { + options.ThreadCount = this.threadCount; + options.Options = this.mountOptions; +// options.GlobalContext = this.globalContext; + options.MountPoint = new WString(this.mountPoint); + options.UNCName = new WString(this.uncName); + options.Timeout = this.timeout; + options.AllocationUnitSize = this.allocationUnitSize; + options.SectorSize = this.sectorSize; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/MountInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/MountInfo.java new file mode 100644 index 00000000..134aad3f --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/MountInfo.java @@ -0,0 +1,203 @@ +package dev.dokan.dokan_java.wrappers.mountinfo; + + +import com.sun.jna.WString; +import dev.dokan.dokan_java.DokanNativeMethods; +import dev.dokan.dokan_java.constants.dokany.MountOption; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.DokanOptions; + + +public class MountInfo implements RWMountInfo { + +// private long globalContext; + + private short threadCount; + private int mountOptions; + private String mountPoint; + private String uncName; + private long timeout; + private long allocationUnitSize; + private long sectorSize; + + public MountInfo(DokanOptions dokanOptions) { + this.threadCount = dokanOptions.ThreadCount; + this.mountOptions = dokanOptions.Options; +// this.globalContext = dokanOptions.GlobalContext; + this.mountPoint = dokanOptions.MountPoint.toString(); + this.uncName = dokanOptions.UNCName.toString(); + this.timeout = dokanOptions.Timeout; + this.allocationUnitSize = dokanOptions.AllocationUnitSize; + this.sectorSize = dokanOptions.SectorSize; + } + + MountInfo(MountInfo info) { + this.threadCount = info.threadCount; + this.mountOptions = info.mountOptions; +// this.globalContext = info.globalContext; + this.mountPoint = info.mountPoint; + this.uncName = info.uncName; + this.timeout = info.timeout; + this.allocationUnitSize = info.allocationUnitSize; + this.sectorSize = info.sectorSize; + } + + MountInfo(ImmutableMountInfo info) { + this.threadCount = info.getThreadCount(); + this.mountOptions = info.getFlags(); +// this.globalContext = info.getGlobalContext(); + this.mountPoint = info.getMountPoint(); + this.uncName = info.getUNCName(); + this.timeout = info.getTimeout(); + this.allocationUnitSize = info.getAllocationUnitSize(); + this.sectorSize = info.getSectorSize(); + } + + @Override + public short getDokanVersion() { + return DokanNativeMethods.getMinimumRequiredDokanVersion(); + } + + @Override + public short getThreadCount() { + return this.threadCount; + } + + @Override + public void setThreadCount(short threadCount) { + this.threadCount = threadCount; + } + + @Override + public MaskValueSet getMountOptions() { + return MountOption.maskValueSet(this.mountOptions); + } + + @Override + public void setMountOptions(MaskValueSet mountOptions) { + this.mountOptions = mountOptions.intValue(); + } + + @Override + public int getFlags() { + return this.mountOptions; + } + + @Override + public void setFlags(int flags) { + this.mountOptions = flags; + } + + @Override + public boolean getFlag(MountOption flag) { + return (this.mountOptions & flag.maskingValue()) != 0; + } + + @Override + public boolean setFlag(MountOption flag) { + return updateFlag(flag, true); + } + + @Override + public boolean unsetFlag(MountOption flag) { + return updateFlag(flag, false); + } + + @Override + public boolean updateFlag(MountOption flag, boolean value) { + boolean prev = getFlag(flag); + this.mountOptions &= value ? flag.maskingValue() : ~flag.maskingValue(); + + return prev; + } + +// @Override +// public long getGlobalContext() { +// return this.globalContext; +// } + + @Override + public String getMountPoint() { + return this.mountPoint; + } + + @Override + public void setMountPoint(String mountPoint) { + this.mountPoint = mountPoint; + } + + @Override + public String getUNCName() { + return this.uncName; + } + + @Override + public void setUNCName(String uncName) { + this.uncName = uncName; + } + + @Override + public long getTimeout() { + return this.timeout; + } + + @Override + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + @Override + public long getAllocationUnitSize() { + return this.allocationUnitSize; + } + + @Override + public void setAllocationUnitSize(long allocationUnitSize) { + this.allocationUnitSize = allocationUnitSize; + } + + @Override + public long getSectorSize() { + return this.sectorSize; + } + + @Override + public void setSectorSize(long sectorSize) { + this.sectorSize = sectorSize; + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public ROMountInfo immutableCopy() { + return new ImmutableMountInfo(this); + } + + @Override + public RWMountInfo mutableCopy() { + return new MountInfo(this); + } + + @Override + public DokanOptions nativeCopy() { + DokanOptions options = new DokanOptions(this.mountPoint, this.threadCount, this.mountOptions, this.uncName, this.timeout, this.allocationUnitSize, this.sectorSize); +// options.GlobalContext = this.globalContext; + + return options; + } + + @Override + public void nativeCopy(DokanOptions options) { + options.ThreadCount = this.threadCount; + options.Options = this.mountOptions; +// options.GlobalContext = this.globalContext; + options.MountPoint = new WString(this.mountPoint); + options.UNCName = new WString(this.uncName); + options.Timeout = this.timeout; + options.AllocationUnitSize = this.allocationUnitSize; + options.SectorSize = this.sectorSize; + } +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/ROMountInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/ROMountInfo.java new file mode 100644 index 00000000..9fb2b9a8 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/ROMountInfo.java @@ -0,0 +1,42 @@ +package dev.dokan.dokan_java.wrappers.mountinfo; + + +import dev.dokan.dokan_java.constants.dokany.MountOption; +import dev.dokan.dokan_java.masking.MaskValueSet; +import dev.dokan.dokan_java.structure.DokanOptions; + + +public interface ROMountInfo { + + short getDokanVersion(); + + short getThreadCount(); + + MaskValueSet getMountOptions(); + + int getFlags(); + + boolean getFlag(MountOption flag); + +// long getGlobalContext(); + + String getMountPoint(); + + String getUNCName(); + + long getTimeout(); + + long getAllocationUnitSize(); + + long getSectorSize(); + + boolean isMutable(); + + ROMountInfo immutableCopy(); + + RWMountInfo mutableCopy(); + + DokanOptions nativeCopy(); + + void nativeCopy(DokanOptions options); +} \ No newline at end of file diff --git a/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/RWMountInfo.java b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/RWMountInfo.java new file mode 100644 index 00000000..5d599e29 --- /dev/null +++ b/src/main/java/dev/dokan/dokan_java/wrappers/mountinfo/RWMountInfo.java @@ -0,0 +1,31 @@ +package dev.dokan.dokan_java.wrappers.mountinfo; + + +import dev.dokan.dokan_java.constants.dokany.MountOption; +import dev.dokan.dokan_java.masking.MaskValueSet; + + +public interface RWMountInfo extends ROMountInfo { + + void setThreadCount(short threadCount); + + void setMountOptions(MaskValueSet mountOptions); + + void setFlags(int flags); + + boolean setFlag(MountOption flag); + + boolean unsetFlag(MountOption flag); + + boolean updateFlag(MountOption flag, boolean value); + + void setMountPoint(String mountPoint); + + void setUNCName(String uncName); + + void setTimeout(long timeout); + + void setAllocationUnitSize(long allocationUnitSize); + + void setSectorSize(long sectorSize); +} \ No newline at end of file