Skip to content

Commit 08991c1

Browse files
committed
fix: Use Win32 API for network path disk space reporting
- Network paths (UNC paths like \\server\share) showed incorrect disk space (fallback values: 1TB/512GB) - DriveInfo.GetDrives().Single() failed for network paths causing InvalidOperationException - Users couldn't see actual disk space for mounted network shares - Added Win32 API `GetDiskFreeSpaceEx` to NativeMethods.cs - Implemented 3-tier fallback strategy in Mirror.GetDiskFreeSpace(): 1. Try Win32 API first (supports network paths and all path types) 2. Fallback to DriveInfo for local drives (changed Single() to FirstOrDefault() for safety) 3. Last resort: return fallback values (512GB/1TB) - Win32 API properly handles UNC paths with trailing backslash - Removed verbose logging to avoid unnecessary I/O overhead - Simple try-catch fallthrough pattern for clean code flow - Tested with network paths: correct disk space now displayed - Tested with local paths: no regression, works as before - Tested with special folders (System Volume Information, Recycle Bin): handles gracefully - `dokan-mirror-manager/NativeMethods.cs`: Added GetDiskFreeSpaceEx P/Invoke declaration - `dokan-mirror-manager/Mirror.cs`: Refactored GetDiskFreeSpace() with Win32 API priority
1 parent 70a29a3 commit 08991c1

File tree

2 files changed

+64
-19
lines changed

2 files changed

+64
-19
lines changed

dokan-mirror-manager/Mirror.cs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -662,35 +662,56 @@ public NtStatus UnlockFile(ReadOnlyNativeMemory<char> fileName, long offset, lon
662662

663663
public NtStatus GetDiskFreeSpace(out long freeBytesAvailable, out long totalNumberOfBytes, out long totalNumberOfFreeBytes, ref DokanFileInfo info)
664664
{
665+
// Try Win32 API first (supports network paths and all path types)
665666
try
666667
{
667-
var dinfo = DriveInfo.GetDrives().Single(di => string.Equals(di.RootDirectory.Name, Path.GetPathRoot(path + "\\"), StringComparison.OrdinalIgnoreCase));
668+
// Ensure path has trailing backslash for Win32 API
669+
var pathForApi = path.TrimEnd('\\') + "\\";
668670

669-
freeBytesAvailable = dinfo.TotalFreeSpace;
670-
totalNumberOfBytes = dinfo.TotalSize;
671-
totalNumberOfFreeBytes = dinfo.AvailableFreeSpace;
671+
if (NativeMethods.GetDiskFreeSpaceEx(pathForApi, out ulong free, out ulong total, out ulong totalFree))
672+
{
673+
freeBytesAvailable = (long)free;
674+
totalNumberOfBytes = (long)total;
675+
totalNumberOfFreeBytes = (long)totalFree;
672676

673-
return Trace(nameof(GetDiskFreeSpace), default, info, DokanResult.Success, $"out {freeBytesAvailable}",
674-
$"out {totalNumberOfBytes}", $"out {totalNumberOfFreeBytes}");
677+
return Trace(nameof(GetDiskFreeSpace), default, info, DokanResult.Success, $"out {freeBytesAvailable}",
678+
$"out {totalNumberOfBytes}", $"out {totalNumberOfFreeBytes}");
679+
}
675680
}
676-
catch (Exception ex)
681+
catch
677682
{
678-
// Log the actual exception for debugging
679-
var logPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "diskspace_error.log");
680-
try
683+
// Win32 API failed, fall through to DriveInfo
684+
}
685+
686+
// Fallback to DriveInfo for local drives
687+
try
688+
{
689+
var rootPath = Path.GetPathRoot(path + "\\");
690+
var dinfo = DriveInfo.GetDrives()
691+
.FirstOrDefault(di => string.Equals(di.RootDirectory.Name, rootPath, StringComparison.OrdinalIgnoreCase));
692+
693+
if (dinfo != null && dinfo.IsReady)
681694
{
682-
System.IO.File.AppendAllText(logPath, $"[{DateTime.Now}] GetDiskFreeSpace error for path '{path}':\n{ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}\n\n");
695+
freeBytesAvailable = dinfo.TotalFreeSpace;
696+
totalNumberOfBytes = dinfo.TotalSize;
697+
totalNumberOfFreeBytes = dinfo.AvailableFreeSpace;
698+
699+
return Trace(nameof(GetDiskFreeSpace), default, info, DokanResult.Success, $"out {freeBytesAvailable}",
700+
$"out {totalNumberOfBytes}", $"out {totalNumberOfFreeBytes}");
683701
}
684-
catch { }
702+
}
703+
catch
704+
{
705+
// DriveInfo also failed, fall through to fallback
706+
}
685707

686-
// Return fallback values
687-
freeBytesAvailable = 512L * 1024 * 1024 * 1024; // 512 GB
688-
totalNumberOfBytes = 1024L * 1024 * 1024 * 1024; // 1 TB
689-
totalNumberOfFreeBytes = 512L * 1024 * 1024 * 1024; // 512 GB
708+
// Last resort: return fallback values
709+
freeBytesAvailable = 512L * 1024 * 1024 * 1024; // 512 GB
710+
totalNumberOfBytes = 1024L * 1024 * 1024 * 1024; // 1 TB
711+
totalNumberOfFreeBytes = 512L * 1024 * 1024 * 1024; // 512 GB
690712

691-
return Trace(nameof(GetDiskFreeSpace), default, info, DokanResult.Success, $"out {freeBytesAvailable}",
692-
$"out {totalNumberOfBytes}", $"out {totalNumberOfFreeBytes}");
693-
}
713+
return Trace(nameof(GetDiskFreeSpace), default, info, DokanResult.Success, $"out {freeBytesAvailable}",
714+
$"out {totalNumberOfBytes}", $"out {totalNumberOfFreeBytes}");
694715
}
695716

696717
public NtStatus GetVolumeInformation(NativeMemory<char> volumeLabel, out FileSystemFeatures features,

dokan-mirror-manager/NativeMethods.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,28 @@ public static extern bool DeviceIoControl(
7171

7272
// IOCTL for getting volume info
7373
public const uint FSCTL_GET_RETRIEVAL_POINTERS = 0x00090073;
74+
75+
/// <summary>
76+
/// Retrieves information about the amount of space that is available on a disk volume,
77+
/// which is the total amount of space, the total amount of free space, and the total
78+
/// amount of free space available to the user that is associated with the calling thread.
79+
/// </summary>
80+
/// <param name="lpDirectoryName">A directory on the disk.
81+
/// If this parameter is NULL, the function uses the root of the current disk.
82+
/// If this parameter is a UNC name, it must include a trailing backslash (for example, "\\MyServer\MyShare\").
83+
/// Furthermore, a drive specification must have a trailing backslash (for example, "C:\").</param>
84+
/// <param name="lpFreeBytesAvailable">A pointer to a variable that receives the total number of free bytes on a disk
85+
/// that are available to the user who is associated with the calling thread.</param>
86+
/// <param name="lpTotalNumberOfBytes">A pointer to a variable that receives the total number of bytes on a disk
87+
/// that are available to the user who is associated with the calling thread.</param>
88+
/// <param name="lpTotalNumberOfFreeBytes">A pointer to a variable that receives the total number of free bytes on a disk.</param>
89+
/// <returns>If the function succeeds, the return value is nonzero. If the function fails, the return value is zero (0).</returns>
90+
/// <remarks>See <a href="https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespaceexw">GetDiskFreeSpaceEx function (MSDN)</a></remarks>
91+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
92+
[return: MarshalAs(UnmanagedType.Bool)]
93+
public static extern bool GetDiskFreeSpaceEx(
94+
string lpDirectoryName,
95+
out ulong lpFreeBytesAvailable,
96+
out ulong lpTotalNumberOfBytes,
97+
out ulong lpTotalNumberOfFreeBytes);
7498
}

0 commit comments

Comments
 (0)