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. ///