diff --git a/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs b/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs
index ce628a04d0..0b8e2830d2 100644
--- a/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs
+++ b/Jellyfin.Server.Implementations/StorageHelpers/StorageHelper.cs
@@ -28,22 +28,41 @@ public static class StorageHelper
}
///
- /// Gets the free space of a specific directory.
+ /// Gets the free space of the parent filesystem of a specific directory.
///
/// Path to a folder.
- /// The number of bytes available space.
+ /// Various details about the parent filesystem containing the directory.
public static FolderStorageInfo GetFreeSpaceOf(string path)
{
try
{
- var driveInfo = new DriveInfo(path);
+ // Fully resolve the given path to an actual filesystem target, in case it's a symlink or similar.
+ resolvedPath = ResolvePath(path);
+ // We iterate all filesystems reported by GetDrives() here, and attempt to find the best
+ // match that contains, as deep as possible, the given path.
+ // This is required because simply calling `DriveInfo` on a path returns that path as
+ // the Name and RootDevice, which is not at all how this should work.
+ DriveInfo[] allDrives = DriveInfo.GetDrives();
+ DriveInfo bestMatch = null;
+ foreach (DriveInfo d in allDrives)
+ {
+ if (resolvedPath.StartsWith(d.RootDirectory.FullName) &&
+ (bestMatch == null || d.RootDirectory.FullName.Length > bestMatch.RootDirectory.FullName.Length))
+ {
+ bestMatch = d;
+ }
+ }
+ if (bestMatch is null) {
+ throw new InvalidOperationException($"The path `{path}` has no matching parent device. Space check invalid.");
+ }
return new FolderStorageInfo()
{
Path = path,
- FreeSpace = driveInfo.AvailableFreeSpace,
- UsedSpace = driveInfo.TotalSize - driveInfo.AvailableFreeSpace,
- StorageType = driveInfo.DriveType.ToString(),
- DeviceId = driveInfo.Name,
+ ResolvedPath = resolvedPath,
+ FreeSpace = bestMatch.AvailableFreeSpace,
+ UsedSpace = bestMatch.TotalSize - bestMatch.AvailableFreeSpace,
+ StorageType = bestMatch.DriveType.ToString(),
+ DeviceId = bestMatch.Name,
};
}
catch
@@ -59,6 +78,26 @@ public static class StorageHelper
}
}
+ ///
+ /// Walk a path and fully resolve any symlinks within it.
+ ///
+ private static string ResolvePath(string path)
+ {
+ var parts = path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
+ var current = Path.DirectorySeparatorChar.ToString();
+ foreach (var part in parts)
+ {
+ current = Path.Combine(current, part);
+ var resolved = new DirectoryInfo(current).ResolveLinkTarget(returnFinalTarget: true);
+ if (resolved is not null)
+ {
+ current = resolved.FullName;
+ }
+ }
+
+ return current;
+ }
+
///
/// Gets the underlying drive data from a given path and checks if the available storage capacity matches the threshold.
///