Skip to content

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

Merged
merged 13 commits into from
Jun 16, 2025

Conversation

tmds
Copy link
Member

@tmds tmds commented May 29, 2025

For the format, mountinfo includes a string 'filesystem type' field which is more granular than the statfs f_type numeric field. For example: the ext2, ext3 and ext4 are distinct mountinfo values which have the same f_type value.

For the mount paths, mountinfo provides the mount point paths relative to the process's root directory. When the process runs with changed root dir (chroot), the paths are adjusted for it.

This is a breaking change. On Linux, the DriveInfo.DriveFormat previously were names from the UnixFileSystemTypes enum members. With this change, they will be Linux kernel filesystem type strings.

Fixes #95099.

@dotnet/area-system-io @stephentoub ptal.

For the format, mountinfo includes a string 'filesystem type' field which is more granular than the statfs f_type numeric field.
For example: the ext2, ext3 and ext4 are distinct mountinfo values which have the same same f_type value.

For the mount paths, mountinfo provides the mount point paths relative to the process's root directory.
When the process runs with changed root dir (chroot), the paths are adjusted for it.
@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 29, 2025
long numericFormat;
int result = Interop.Sys.GetFormatInfoForMountPoint(SysFsCgroupFileSystemPath, formatBuffer, MountPointFormatBufferSizeInBytes, &numericFormat);
if (result == 0)
Span<char> buffer = stackalloc char[16];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file was also parsing mountinfo, so I've updated it to share the parsing logic with DriveInfo.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label May 29, 2025
@tmds tmds added area-System.IO and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels May 29, 2025
Marshal.PtrToStringUTF8((IntPtr)formatBuffer)!;
type = GetDriveType(format);
// Canonicalize and resolve symbolic links.
string? path = Sys.RealPath(name);
Copy link
Member Author

Choose a reason for hiding this comment

The 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:

  • If we use statx then we can get the STATX_MNT_ID which corresponds to the first field in mountinfo. This replaces multiple syscalls made by realpath into a single syscall.

  • For Linux 6.8+, we can call statx with STATX_MNT_ID_UNIQUE and then use that with the statmount syscall with STATMOUNT_FS_TYPE which avoids the syscalls for opening and reading mountinfo to get the filesystem type name.

I don't intend to include these changes as part of this PR (unless someone finds it important).

@tmds
Copy link
Member Author

tmds commented Jun 2, 2025

@dotnet/area-system-io this is up for review.

@am11
Copy link
Member

am11 commented Jun 2, 2025

@tmds, was the refactoring on native side essential for this to work? I am asking because we implemented the fallbacks when I was on a "many different OS / filesystems configuration testing" trips (Android emulator on macOS vs. Windows, illumos various filesystems ZFS-based fs vs. the others, freebsd UFS vs. ZFS-based fs etc. etc.) and it took some time to figure out the magic values (this is the only place on the entire GitHub where we find such an exhaustive list of filesystem magic vals).

I am pretty sure you have kept them logically equivalent, but on an off chance refactoring might break some prior contract, I suggest we keep this implementation to Linux only on C# side and refactor stuff in a separate PR (or leave it be to avoid going through laborious testing).

@tmds
Copy link
Member Author

tmds commented Jun 2, 2025

@tmds, was the refactoring on native side essential for this to work? I am asking because we implemented the fallbacks when I was on a "many different OS / filesystems configuration testing" trips (Android emulator on macOS vs. Windows, illumos various filesystems ZFS-based fs vs. the others, freebsd UFS vs. ZFS-based fs etc. etc.) and it took some time to figure out the magic values (this is the only place on the entire GitHub where we find such an exhaustive list of filesystem magic vals).

This is about #116102 (comment).

The main problem with UnixFileSystemTypes is that it should be split per OS because these magic values are not cross-platform.

I removed it because I assumed it was only used for Linux mapping for which the PR is now using mountinfo.

I'll add back the f_type + UnixFileSystemTypes mapping.

@tmds
Copy link
Member Author

tmds commented Jun 4, 2025

This is up for review.

Comment on lines +58 to +63
}
line = line.Slice(endOfOptionalFields + Separator.Length);
fields = line.Split(' ');

// (9) filesystem type
if (!fields.MoveNext())
Copy link
Member Author

@tmds tmds Jun 4, 2025

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 updates fields to be past the separator.

Comment on lines +58 to +63
}
line = line.Slice(endOfOptionalFields + Separator.Length);
fields = line.Split(' ');

// (9) filesystem type
if (!fields.MoveNext())
Copy link
Member Author

@tmds tmds Jun 4, 2025

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 updates fields to be past the separator.

@tmds
Copy link
Member Author

tmds commented Jun 9, 2025

@dotnet/area-system-io ptal.

@tmds
Copy link
Member Author

tmds commented Jun 16, 2025

@jeffhandley @adamsitnik can you take a look? It would be nice if this gets some usage in preview7.

Copy link
Member

@jozkee jozkee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, LGTM. Thanks, @tmds.

string? path = Sys.RealPath(name);
if (path is null)
{
format = "";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.Empty is used on ln 68.

Suggested change
format = "";
format = string.Empty;

Comment on lines +18 to +21
if (File.Exists(ProcMountInfoFilePath))
{
try
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce nesting.

Suggested change
if (File.Exists(ProcMountInfoFilePath))
{
try
{
if (!File.Exists(ProcMountInfoFilePath))
return Error.ENOTSUP
try
{

/// where this enum must be a subset of the GetDriveType list, with the enum
/// values here exactly matching a string there.
/// where this enum must be a subset of the GetDriveType list, with the enum values here exactly matching a string there.
/// If possible, use names that match the Linux file system type strings.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Suggested change
/// If possible, use names that match the Linux file system type strings.
/// If possible, use names that match the Linux file system type strings in /proc/filesystems.

Comment on lines +41 to +43
if (OperatingSystem.IsLinux())
{
if (File.Exists(Interop.procfs.ProcMountInfoFilePath))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (OperatingSystem.IsLinux())
{
if (File.Exists(Interop.procfs.ProcMountInfoFilePath))
if (OperatingSystem.IsLinux() && File.Exists(Interop.procfs.ProcMountInfoFilePath))

selinux = 0xF97CFF8C,
sffs = 0x786F4256, // same as vboxfs
sharefs = 0x01021994, // same as tmpfs
selinuxfs = 0xF97CFF8C,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these changes (add/del fs suffix) seem to be worth including in the breaking change doc.

@jozkee jozkee closed this Jun 16, 2025
@jozkee jozkee reopened this Jun 16, 2025
@jozkee
Copy link
Member

jozkee commented Jun 16, 2025

/ba-g

  • build failure for browser-wasm windows Release LibraryTests FAILED: mono/metadata/CMakeFiles/metadata_objects.dir/mono-hash.c.o, unrealated, these changes are for Unix.
  • System.Runtime.Tests.WorkItemExecution: DeadLetter
  • Wasm.Build.NativeRebuild.Tests.ReferenceNewAssemblyRebuildTest.ReferenceNewAssembly - Wasm.Build.Tests timeout #116697

Merging to include in preview6.

@jozkee jozkee merged commit 9b493d2 into dotnet:main Jun 16, 2025
154 of 159 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-System.IO breaking-change Issue or PR that represents a breaking API or functional change over a prerelease. community-contribution Indicates that the PR has been added by a community member needs-breaking-change-doc-created Breaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

DriveInfo.DriveFormat property reports ext3 format on ext4 formatted filesystems (Unix)
6 participants