-
Notifications
You must be signed in to change notification settings - Fork 5.1k
DriveInfo.Linux: use procfs mountinfo for formats and mount point paths. #116102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
6c9c834
9c7b790
7f679e0
83995f0
3ae8d7a
72dff48
d86e0af
a8cc5cb
7c7ebb2
a18f097
efd1dfd
4f7e3f2
ab7338c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class @procfs | ||
{ | ||
internal ref struct ParsedMount | ||
{ | ||
public required ReadOnlySpan<char> Root { get; init; } | ||
public required ReadOnlySpan<char> MountPoint { get; init; } | ||
public required ReadOnlySpan<char> FileSystemType { get; init; } | ||
public required ReadOnlySpan<char> SuperOptions { get; init; } | ||
} | ||
|
||
internal static bool TryParseMountInfoLine(ReadOnlySpan<char> line, out ParsedMount result) | ||
{ | ||
result = default; | ||
|
||
// See man page for /proc/[pid]/mountinfo for details, e.g.: | ||
// (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) | ||
// 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue | ||
// but (7) is optional and could exist as multiple fields; the (8) separator marks | ||
// the end of the optional values. | ||
|
||
MemoryExtensions.SpanSplitEnumerator<char> fields = line.Split(' '); | ||
|
||
// (1) mount ID | ||
// (2) parent ID | ||
// (3) major:minor | ||
if (!fields.MoveNext() || !fields.MoveNext() || !fields.MoveNext()) | ||
{ | ||
return false; | ||
} | ||
|
||
// (4) root | ||
if (!fields.MoveNext()) | ||
{ | ||
return false; | ||
} | ||
ReadOnlySpan<char> root = line[fields.Current]; | ||
|
||
// (5) mount point | ||
if (!fields.MoveNext()) | ||
{ | ||
return false; | ||
} | ||
ReadOnlySpan<char> mountPoint = line[fields.Current]; | ||
|
||
// (8) separator | ||
const string Separator = " - "; | ||
int endOfOptionalFields = line.IndexOf(Separator, StringComparison.Ordinal); | ||
if (endOfOptionalFields == -1) | ||
{ | ||
return false; | ||
} | ||
line = line.Slice(endOfOptionalFields + Separator.Length); | ||
fields = line.Split(' '); | ||
|
||
// (9) filesystem type | ||
if (!fields.MoveNext()) | ||
{ | ||
return false; | ||
} | ||
ReadOnlySpan<char> fileSystemType = line[fields.Current]; | ||
|
||
// (10) mount source | ||
if (!fields.MoveNext()) | ||
{ | ||
return false; | ||
} | ||
|
||
// (11) super options | ||
if (!fields.MoveNext()) | ||
{ | ||
return false; | ||
} | ||
ReadOnlySpan<char> superOptions = line[fields.Current]; | ||
|
||
result = new ParsedMount() | ||
{ | ||
Root = root, | ||
MountPoint = mountPoint, | ||
FileSystemType = fileSystemType, | ||
SuperOptions = superOptions | ||
}; | ||
return true; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,74 @@ | ||||||||||||||||||||
// Licensed to the .NET Foundation under one or more agreements. | ||||||||||||||||||||
// The .NET Foundation licenses this file to you under the MIT license. | ||||||||||||||||||||
|
||||||||||||||||||||
using System; | ||||||||||||||||||||
using System.Diagnostics; | ||||||||||||||||||||
using System.IO; | ||||||||||||||||||||
|
||||||||||||||||||||
internal static partial class Interop | ||||||||||||||||||||
{ | ||||||||||||||||||||
internal static partial class @procfs | ||||||||||||||||||||
{ | ||||||||||||||||||||
internal const string ProcMountInfoFilePath = "/proc/self/mountinfo"; | ||||||||||||||||||||
|
||||||||||||||||||||
internal static Error GetFileSystemTypeForRealPath(string path, out string format) | ||||||||||||||||||||
{ | ||||||||||||||||||||
format = ""; | ||||||||||||||||||||
|
||||||||||||||||||||
if (File.Exists(ProcMountInfoFilePath)) | ||||||||||||||||||||
{ | ||||||||||||||||||||
try | ||||||||||||||||||||
{ | ||||||||||||||||||||
Comment on lines
+18
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reduce nesting.
Suggested change
|
||||||||||||||||||||
ReadOnlySpan<char> currentFormat = default; | ||||||||||||||||||||
int currentBestLength = 0; | ||||||||||||||||||||
|
||||||||||||||||||||
using StreamReader reader = new(ProcMountInfoFilePath); | ||||||||||||||||||||
|
||||||||||||||||||||
string? line; | ||||||||||||||||||||
while ((line = reader.ReadLine()) is not null) | ||||||||||||||||||||
{ | ||||||||||||||||||||
if (TryParseMountInfoLine(line, out ParsedMount mount)) | ||||||||||||||||||||
{ | ||||||||||||||||||||
if (mount.MountPoint.Length < currentBestLength) | ||||||||||||||||||||
{ | ||||||||||||||||||||
continue; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (!path.StartsWith(mount.MountPoint)) | ||||||||||||||||||||
{ | ||||||||||||||||||||
continue; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (mount.MountPoint.Length == path.Length) | ||||||||||||||||||||
{ | ||||||||||||||||||||
currentFormat = mount.FileSystemType; | ||||||||||||||||||||
break; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (path[mount.MountPoint.Length] != '/') | ||||||||||||||||||||
{ | ||||||||||||||||||||
continue; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
currentBestLength = mount.MountPoint.Length; | ||||||||||||||||||||
currentFormat = mount.FileSystemType; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
if (currentFormat.Length > 0) | ||||||||||||||||||||
{ | ||||||||||||||||||||
format = currentFormat.ToString(); | ||||||||||||||||||||
return Error.SUCCESS; | ||||||||||||||||||||
} | ||||||||||||||||||||
return Error.ENOENT; | ||||||||||||||||||||
} | ||||||||||||||||||||
catch (Exception e) | ||||||||||||||||||||
{ | ||||||||||||||||||||
Debug.Fail($"Failed to read \"{ProcMountInfoFilePath}\": {e}"); | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
return Error.ENOTSUP; | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -33,43 +33,53 @@ internal struct MountPointInformation | |||||
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetSpaceInfoForMountPoint", SetLastError = true)] | ||||||
internal static partial int GetSpaceInfoForMountPoint([MarshalAs(UnmanagedType.LPUTF8Str)] string name, out MountPointInformation mpi); | ||||||
|
||||||
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetFormatInfoForMountPoint", SetLastError = true)] | ||||||
internal static unsafe partial int GetFormatInfoForMountPoint( | ||||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string name, | ||||||
byte* formatNameBuffer, | ||||||
int bufferLength, | ||||||
long* formatType); | ||||||
|
||||||
internal static int GetFormatInfoForMountPoint(string name, out string format) | ||||||
internal static Error GetFileSystemTypeNameForMountPoint(string name, out string format) | ||||||
{ | ||||||
return GetFormatInfoForMountPoint(name, out format, out _); | ||||||
if (OperatingSystem.IsLinux()) | ||||||
{ | ||||||
// Canonicalize and resolve symbolic links. | ||||||
string? path = Sys.RealPath(name); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is now going to make several syscalls where there used to be one. If this turns out to be an performance issue, we can reduce the nr of calls:
I don't intend to include these changes as part of this PR (unless someone finds it important). |
||||||
if (path is null) | ||||||
{ | ||||||
format = ""; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. string.Empty is used on ln 68.
Suggested change
|
||||||
return GetLastError(); | ||||||
} | ||||||
tmds marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
return procfs.GetFileSystemTypeForRealPath(path, out format); | ||||||
} | ||||||
else | ||||||
{ | ||||||
return GetFileSystemTypeNameForMountPoint(name, out format); | ||||||
} | ||||||
} | ||||||
|
||||||
internal static int GetFormatInfoForMountPoint(string name, out DriveType type) | ||||||
internal static Error GetDriveTypeForMountPoint(string name, out DriveType type) | ||||||
{ | ||||||
return GetFormatInfoForMountPoint(name, out _, out type); | ||||||
Error error = GetFileSystemTypeNameForMountPoint(name, out string format); | ||||||
type = error == Error.SUCCESS ? GetDriveType(format) : DriveType.Unknown; | ||||||
return error; | ||||||
} | ||||||
|
||||||
private static unsafe int GetFormatInfoForMountPoint(string name, out string format, out DriveType type) | ||||||
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetFileSystemTypeNameForMountPoint", SetLastError = true)] | ||||||
private static unsafe partial int GetFileSystemTypeNameForMountPoint( | ||||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string name, | ||||||
byte* formatNameBuffer, | ||||||
int bufferLength); | ||||||
|
||||||
private static unsafe Error GetFileSystemTypeNameForMountPoint(string name, out ReadOnlySpan<char> format) | ||||||
{ | ||||||
byte* formatBuffer = stackalloc byte[MountPointFormatBufferSizeInBytes]; // format names should be small | ||||||
long numericFormat; | ||||||
int result = GetFormatInfoForMountPoint(name, formatBuffer, MountPointFormatBufferSizeInBytes, &numericFormat); | ||||||
int result = GetFileSystemTypeNameForMountPoint(name, formatBuffer, MountPointFormatBufferSizeInBytes); | ||||||
if (result == 0) | ||||||
{ | ||||||
// Check if we have a numeric answer or string | ||||||
format = numericFormat != -1 ? | ||||||
Enum.GetName(typeof(UnixFileSystemTypes), numericFormat) ?? string.Empty : | ||||||
Marshal.PtrToStringUTF8((IntPtr)formatBuffer)!; | ||||||
type = GetDriveType(format); | ||||||
format = Marshal.PtrToStringUTF8((IntPtr)formatBuffer)!; | ||||||
return Error.SUCCESS; | ||||||
} | ||||||
else | ||||||
{ | ||||||
format = string.Empty; | ||||||
type = DriveType.Unknown; | ||||||
return GetLastError(); | ||||||
} | ||||||
|
||||||
return result; | ||||||
} | ||||||
|
||||||
/// <summary>Categorizes a file system name into a drive type.</summary> | ||||||
|
@@ -261,8 +271,10 @@ private static DriveType GetDriveType(string fileSystemName) | |||||
case "aptfs": | ||||||
case "avfs": | ||||||
case "bdev": | ||||||
case "bpf": | ||||||
case "binfmt_misc": | ||||||
case "cgroup": | ||||||
case "cgroup2": | ||||||
case "cgroupfs": | ||||||
case "cgroup2fs": | ||||||
case "configfs": | ||||||
|
@@ -280,6 +292,7 @@ private static DriveType GetDriveType(string fileSystemName) | |||||
case "fd": | ||||||
case "fdesc": | ||||||
case "fuse.gvfsd-fuse": | ||||||
case "fuse.portal": | ||||||
case "fusectl": | ||||||
case "futexfs": | ||||||
case "hugetlbfs": | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each block handles the field that is in the comment at the first line.
// (8) separator
updatesfields
to be past the separator.