Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
f82fda5
Added the option to increase WebDAV size limit
d2dyno1 Jul 1, 2026
7c0d559
Update nwebdav
d2dyno1 Jul 1, 2026
cdc110c
Fixed CryptoFolder Id resolving in RenameAsync
d2dyno1 Jul 1, 2026
55b6fc2
Fixed ChunkWriter skipping farther chunks than written
d2dyno1 Jul 1, 2026
9d8ff10
Fixed UniversalCache cache eviction
d2dyno1 Jul 1, 2026
ca5e2e2
Use thread locks in ChunkAccess
d2dyno1 Jul 2, 2026
6f8ab0c
Refresh FileInfo state when computing in WinFspFileHandle
d2dyno1 Jul 2, 2026
3f9f6f2
Added atomic GetOrCreate to OpenCryptFileManager
d2dyno1 Jul 2, 2026
3b0ba37
Minor fix for NativePathHelpers.MakeRelative
d2dyno1 Jul 2, 2026
26b68c6
Drop MethodImplOptions.Synchronized from Dokany and use locks instead
d2dyno1 Jul 2, 2026
29da561
Use File.Replace instead of Delete + Move in Dokany
d2dyno1 Jul 2, 2026
65ee83e
Fixed writing empty gap edge-case in PlaintextStream
d2dyno1 Jul 2, 2026
a2b7529
Improved reliability of reading Directory ID files + formatting
d2dyno1 Jul 2, 2026
42338a4
Lock HeaderBuffer.SyncRoot on reading header
d2dyno1 Jul 2, 2026
722dc8b
Corrected Dokany file region locking
d2dyno1 Jul 2, 2026
44046cd
Improved WinFsp enumeration performance
d2dyno1 Jul 2, 2026
3ed1bb7
Moved GenerateVideoThumbnailAsync to ThumbnailHelpers
d2dyno1 Jul 2, 2026
3011661
Improved DocumentsProvider implementation
d2dyno1 Jul 2, 2026
a0ae647
Added optional name parameter to AndroidFile
d2dyno1 Jul 2, 2026
29bd13c
Improved thumbnail handling on Android
d2dyno1 Jul 2, 2026
aae395d
Improved the performance of AndroidFolder
d2dyno1 Jul 2, 2026
56e7a3b
Return a snapshot of handles and enforce ReadWrite in FUSE
d2dyno1 Jul 2, 2026
386bb22
Improved reliability of FUSE
d2dyno1 Jul 2, 2026
acbae4b
Cleaned up namespaces
d2dyno1 Jul 2, 2026
c0d3819
Fixed ChannelledStream buffer marshalling
d2dyno1 Jul 2, 2026
bac649b
Improved iOS thumbnail handling
d2dyno1 Jul 2, 2026
9600573
Fixed overwrite contract in IOSFolder and use FileShare.Read in IOSFile
d2dyno1 Jul 2, 2026
9c96062
Use ReadExactlyAsync in ThumbnailCacheModel
d2dyno1 Jul 2, 2026
c56419c
Preemptively dispose security scope in IOSSecurityScopedStream on fai…
d2dyno1 Jul 2, 2026
efe7fbd
Fixed thumbnail cache race condition
d2dyno1 Jul 2, 2026
05d76c9
Clear completed tcs in TransferViewModel
d2dyno1 Jul 2, 2026
2c067aa
Fixed thumbnail and carousel loading
d2dyno1 Jul 2, 2026
413c01d
Improved size calculation when recycling files in browser control
d2dyno1 Jul 2, 2026
158259f
Avoid redundant MIME type lookups in applying adaptive layout
d2dyno1 Jul 2, 2026
4af3db5
Dispose all views in BrowserViewModel
d2dyno1 Jul 2, 2026
17e6c46
Fixed BrowserPage navigation stack tracking
d2dyno1 Jul 2, 2026
bdb5d6b
Adjust dictionary cache eviction comment
d2dyno1 Jul 2, 2026
2bc33e4
Added feedback for when entered recovery key is incorrect
d2dyno1 Jul 3, 2026
b924a71
Surface errors in vault wizard summary pages
d2dyno1 Jul 3, 2026
108d58e
Improved error handling for customizing vault icons
d2dyno1 Jul 4, 2026
4b0e70c
Replaced background Thread with loop Task in WebDavWrapper
d2dyno1 Jul 4, 2026
ca625e7
Added safety restraints for loading types from disk
d2dyno1 Jul 4, 2026
a1bcf93
Avoid starting a dead WebDav server edge-case
d2dyno1 Jul 4, 2026
14dc847
Support error reporting in TransferControl
d2dyno1 Jul 4, 2026
a8b8e90
Inform about folder enumeration failure in FolderViewModel
d2dyno1 Jul 4, 2026
cc5de11
Update BrowserPage.xaml
d2dyno1 Jul 4, 2026
4d32030
Added error reporting for BrowserViewModel and FileViewModel
d2dyno1 Jul 4, 2026
e097c40
Minor fixes
d2dyno1 Jul 4, 2026
696ac46
Better error handling for LoginViewModel
d2dyno1 Jul 4, 2026
33f7732
Added better cancellation handling in BrowserItemViewModel
d2dyno1 Jul 4, 2026
b7d4a94
Added status InfoBar for login page on Uno
d2dyno1 Jul 4, 2026
5f8c909
Report login errors on MAUI
d2dyno1 Jul 4, 2026
75e56fe
Better reporting for Credentials dialog
d2dyno1 Jul 4, 2026
538ae56
Better error reporting for recycle bin
d2dyno1 Jul 4, 2026
9a769d8
Added missing strings
d2dyno1 Jul 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static DokanyOptions ToOptions(IDictionary<string, object> options)
FileSystemStatistics = (IFileSystemStatistics?)options.Get(nameof(FileSystemStatistics)) ?? new FileSystemStatistics(),
IsReadOnly = (bool?)options.Get(nameof(IsReadOnly)) ?? false,
IsCachingChunks = (bool?)options.Get(nameof(IsCachingChunks)) ?? true,
IsCachingFileNames = (bool?)options.Get(nameof(IsCachingFileNames)) ?? true,
IsCachingFileNames = (bool?)options.Get(nameof(IsCachingFileNames)) ?? false,
IsCachingDirectoryIds = (bool?)options.Get(nameof(IsCachingDirectoryIds)) ?? true,
RecycleBinSize = (long?)options.Get(nameof(RecycleBinSize)) ?? 0L,

Expand Down
102 changes: 64 additions & 38 deletions src/Core/SecureFolderFS.Core.Dokany/Callbacks/BaseDokanyCallbacks.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
using DokanNet;
using SecureFolderFS.Core.Dokany.Helpers;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.FileSystem.AppModels;
using SecureFolderFS.Core.FileSystem.Exceptions;
using SecureFolderFS.Core.FileSystem.OpenHandles;
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.AccessControl;
using System.Security.Cryptography;
using DokanNet;
using SecureFolderFS.Core.Dokany.Helpers;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.FileSystem.AppModels;
using SecureFolderFS.Core.FileSystem.Exceptions;
using SecureFolderFS.Core.FileSystem.Helpers;
using SecureFolderFS.Core.FileSystem.OpenHandles;
using FileAccess = DokanNet.FileAccess;

namespace SecureFolderFS.Core.Dokany.Callbacks
Expand Down Expand Up @@ -64,12 +65,20 @@

try
{
fileHandle.Stream.Flush();
lock (fileHandle.Stream)
fileHandle.Stream.Flush();

return Trace(DokanResult.Success, fileName, info);
}
catch (IOException)
catch (IOException ioEx)
{
return DokanResult.DiskFull;
if (ErrorHandlingHelpers.IsDiskFullException(ioEx))
return Trace(DokanResult.DiskFull, fileName, info);

if (DokanyErrorHelpers.NtStatusFromException(ioEx, out var ntStatus))
return Trace((NtStatus)ntStatus, fileName, info);

return Trace(DokanResult.Unsuccessful, fileName, info);
}
}

Expand All @@ -88,7 +97,9 @@
if (handlesManager.GetHandle<FileHandle>(GetContextValue(info)) is not { } fileHandle)
return Trace(DokanResult.InvalidHandle, fileName, info);

fileHandle.Stream.SetLength(length);
lock (fileHandle.Stream)
fileHandle.Stream.SetLength(length);

return Trace(DokanResult.Success, fileName, info);
}

Expand Down Expand Up @@ -131,7 +142,6 @@
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.Synchronized)]
public virtual unsafe NtStatus ReadFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesRead, long offset,
IDokanFileInfo info)
{
Expand Down Expand Up @@ -164,20 +174,25 @@

try
{
// Check EOF
if (offset >= fileHandle.Stream.Length)
// Lock on the handle's stream. The kernel can issue concurrent operations
// on the same handle, and setting the position and reading must be atomic
lock (fileHandle.Stream)
{
bytesRead = 0;
return NtStatus.EndOfFile;
// Check EOF
if (offset >= fileHandle.Stream.Length)
{
bytesRead = 0;
return NtStatus.EndOfFile;
}

// Align position
fileHandle.Stream.Position = offset;

// Read file
var bufferSpan = new Span<byte>(buffer.ToPointer(), (int)bufferLength);
bytesRead = fileHandle.Stream.Read(bufferSpan);
}

// Align position
fileHandle.Stream.Position = offset;

// Read file
var bufferSpan = new Span<byte>(buffer.ToPointer(), (int)bufferLength);
bytesRead = fileHandle.Stream.Read(bufferSpan);

return Trace(DokanResult.Success, fileName, info);
}
catch (PathTooLongException)
Expand All @@ -203,7 +218,6 @@
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.Synchronized)]
public virtual unsafe NtStatus WriteFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesWritten, long offset, IDokanFileInfo info)
{
if (specifics.Options.IsReadOnly)
Expand All @@ -228,7 +242,9 @@
if (handlesManager.GetHandle<FileHandle>(GetContextValue(info)) is not { } fileHandle)
{
// Invalid handle...
contextHandle = handlesManager.OpenFileHandle(ciphertextPath, appendToFile ? FileMode.Append : FileMode.Open, System.IO.FileAccess.ReadWrite, FileShare.Read, FileOptions.None);
// FileMode.Append cannot be combined with FileAccess.ReadWrite - open normally
// and seek to the end instead (the append position is aligned below)
contextHandle = handlesManager.OpenFileHandle(ciphertextPath, appendToFile ? FileMode.OpenOrCreate : FileMode.Open, System.IO.FileAccess.ReadWrite, FileShare.Read, FileOptions.None);
fileHandle = handlesManager.GetHandle<FileHandle>(contextHandle);
openedNewHandle = true;
}
Expand All @@ -242,19 +258,24 @@

try
{
// Align for Paging I/O
var alignedBytesToCopy = AlignSizeForPagingIo((int)bufferLength, offset, fileHandle.Stream.Length, info);
// Lock on the handle's stream as the kernel can issue concurrent operations
// on the same handle, and setting the position and writing must be atomic
lock (fileHandle.Stream)
{
// Align for Paging I/O
var alignedBytesToCopy = AlignSizeForPagingIo((int)bufferLength, offset, fileHandle.Stream.Length, info);

// Align position for offset
var alignedPosition = appendToFile ? fileHandle.Stream.Length : offset;
// Align position for offset
var alignedPosition = appendToFile ? fileHandle.Stream.Length : offset;

// Align position
fileHandle.Stream.Position = alignedPosition;
// Align position
fileHandle.Stream.Position = alignedPosition;

// Write file
var bufferSpan = new ReadOnlySpan<byte>(buffer.ToPointer(), alignedBytesToCopy);
fileHandle.Stream.Write(bufferSpan);
bytesWritten = alignedBytesToCopy;
// Write file
var bufferSpan = new ReadOnlySpan<byte>(buffer.ToPointer(), alignedBytesToCopy);
fileHandle.Stream.Write(bufferSpan);
bytesWritten = alignedBytesToCopy;
}

return Trace(DokanResult.Success, fileName, info);
}
Expand Down Expand Up @@ -376,6 +397,11 @@
return bufferLength;

var longDistanceToEnd = streamLength - offset;

// Paging I/O must not write beyond the end of the file
if (longDistanceToEnd <= 0L)
return 0;

if (longDistanceToEnd > int.MaxValue)
return bufferLength;

Expand All @@ -394,10 +420,10 @@
if (Debugger.IsAttached)
return result;

if (!Core.FileSystem.Constants.OPT_IN_FOR_OPTIONAL_DEBUG_TRACING)
if (!FileSystem.Constants.OPT_IN_FOR_OPTIONAL_DEBUG_TRACING)
return result;

if (DisallowedTraceMethods.Contains(methodName))

Check warning on line 426 in src/Core/SecureFolderFS.Core.Dokany/Callbacks/BaseDokanyCallbacks.cs

View workflow job for this annotation

GitHub Actions / core (Debug, Dokany, net10.0)

Unreachable code detected
return result;

var message = FormatProviders.DokanFormat($"{methodName}('{fileName}', {info}, [{access}], [{share}], [{mode}], [{options}], [{attributes}]) -> {result}");
Expand All @@ -413,10 +439,10 @@
return result;
#endif

if (!Core.FileSystem.Constants.OPT_IN_FOR_OPTIONAL_DEBUG_TRACING)
if (!FileSystem.Constants.OPT_IN_FOR_OPTIONAL_DEBUG_TRACING)

Check warning on line 442 in src/Core/SecureFolderFS.Core.Dokany/Callbacks/BaseDokanyCallbacks.cs

View workflow job for this annotation

GitHub Actions / core (Release, Dokany, net10.0)

Unreachable code detected
return result;

if (!Debugger.IsAttached)

Check warning on line 445 in src/Core/SecureFolderFS.Core.Dokany/Callbacks/BaseDokanyCallbacks.cs

View workflow job for this annotation

GitHub Actions / core (Debug, Dokany, net10.0)

Unreachable code detected
return result;

if (DisallowedTraceMethods.Contains(methodName))
Expand All @@ -434,7 +460,7 @@

private static string[] DisallowedTraceMethods { get; } =
{
"GetVolumeInformation"
nameof(GetVolumeInformation)
};
}
}
61 changes: 31 additions & 30 deletions src/Core/SecureFolderFS.Core.Dokany/Callbacks/OnDeviceDokany.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
using DokanNet;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Cryptography;
using DokanNet;
using OwlCore.Storage;
using SecureFolderFS.Core.Dokany.Helpers;
using SecureFolderFS.Core.Dokany.OpenHandles;
Expand All @@ -13,15 +20,6 @@
using SecureFolderFS.Core.FileSystem.Helpers.RecycleBin.Native;
using SecureFolderFS.Core.FileSystem.OpenHandles;
using SecureFolderFS.Storage.VirtualFileSystem;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Cryptography;
using FileAccess = DokanNet.FileAccess;

namespace SecureFolderFS.Core.Dokany.Callbacks
Expand Down Expand Up @@ -518,15 +516,26 @@ public override NtStatus DeleteDirectory(string fileName, IDokanFileInfo info)
if (ciphertextPath is null)
return Trace(NtStatus.ObjectPathInvalid, fileName, info);

using var directoryEnumerator = Directory.EnumerateFileSystemEntries(ciphertextPath).GetEnumerator();
while (directoryEnumerator.MoveNext())
try
{
// Check for any files except core files
canDelete &= PathHelpers.IsCoreName(Path.GetFileName(directoryEnumerator.Current));
using var directoryEnumerator = Directory.EnumerateFileSystemEntries(ciphertextPath).GetEnumerator();
while (directoryEnumerator.MoveNext())
{
// Check for any files except core files
canDelete &= PathHelpers.IsCoreName(Path.GetFileName(directoryEnumerator.Current));

// If the flag changed (directory is not empty), break the loop
if (!canDelete)
break;
// If the flag changed (directory is not empty), break the loop
if (!canDelete)
break;
}
}
catch (DirectoryNotFoundException)
{
return Trace(DokanResult.PathNotFound, fileName, info);
}
catch (UnauthorizedAccessException)
{
return Trace(DokanResult.AccessDenied, fileName, info);
}

var result = canDelete ? DokanResult.Success : DokanResult.DirectoryNotEmpty;
Expand Down Expand Up @@ -575,9 +584,9 @@ public override NtStatus MoveFile(string oldName, string newName, bool replace,
return Trace(DokanResult.AccessDenied, fileNameCombined, info);
}

// File
File.Delete(newCiphertextPath);
File.Move(oldCiphertextPath, newCiphertextPath);
// Replace the destination file atomically. A separate delete and move
// could lose the destination file if the operation is interrupted in between
File.Replace(oldCiphertextPath, newCiphertextPath, null, true);

return Trace(DokanResult.Success, fileNameCombined, info);
}
Expand Down Expand Up @@ -758,7 +767,6 @@ public override NtStatus SetFileSecurity(string fileName, FileSystemSecurity sec
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.Synchronized)]
public override NtStatus ReadFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesRead, long offset, IDokanFileInfo info)
{
try
Expand All @@ -775,18 +783,14 @@ public override NtStatus ReadFile(string fileName, IntPtr buffer, uint bufferLen

throw;
}
catch (Exception ex)
catch (Exception)
{
_ = ex;
bytesRead = 0;
Debugger.Break();

return Trace(DokanResult.InternalError, fileName, info);
}
}

/// <inheritdoc/>
[MethodImpl(MethodImplOptions.Synchronized)]
public override NtStatus WriteFile(string fileName, IntPtr buffer, uint bufferLength, out int bytesWritten, long offset, IDokanFileInfo info)
{
try
Expand All @@ -803,12 +807,9 @@ public override NtStatus WriteFile(string fileName, IntPtr buffer, uint bufferLe

throw;
}
catch (Exception ex)
catch (Exception)
{
_ = ex;
bytesWritten = 0;

Debugger.Break();
return Trace(DokanResult.InternalError, fileName, info);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Core/SecureFolderFS.Core.Dokany/DokanyFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public async Task<IVfsRoot> MountAsync(IFolder folder, IDisposable unlockContrac
if (dokanyOptions.MountPoint is null)
throw new DirectoryNotFoundException("No available free mount points for vault file system.");

var handlesManager = new DokanyHandlesManager(specifics.StreamsAccess, specifics.Options);
var handlesManager = new DokanyHandlesManager(wrapper.Inner, specifics.StreamsAccess, specifics.Options);
var volumeModel = new VolumeModel(specifics.Options.VolumeName, Constants.Dokan.FS_TYPE_ID);
var dokanyCallbacks = new OnDeviceDokany(specifics, handlesManager, volumeModel);
var dokanyWrapper = new DokanyWrapper(dokanyCallbacks);
Expand Down
Loading
Loading