Skip to content

Support fstat on non-file-backed FDs#4812

Merged
RalfJung merged 2 commits intorust-lang:masterfrom
enthropy7:master
Apr 26, 2026
Merged

Support fstat on non-file-backed FDs#4812
RalfJung merged 2 commits intorust-lang:masterfrom
enthropy7:master

Conversation

@enthropy7
Copy link
Copy Markdown
Contributor

@enthropy7 enthropy7 commented Jan 11, 2026

Implement fstat support for sockets, pipes, eventfd, epoll, and stdin/stdout/stderr file descriptors. Fixes scalar size mismatch error by using to_uint with scalar's own size instead of to_u32().

Fixes #4753

@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jan 11, 2026

Thank you for contributing to Miri! A reviewer will take a look at your PR, typically within a week or two.
Please remember to not force-push to the PR branch except when you need to rebase due to a conflict or when the reviewer asks you for it.

@rustbot rustbot added the S-waiting-on-review Status: Waiting for a review to complete label Jan 11, 2026
@rustbot

This comment has been minimized.

Copy link
Copy Markdown
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

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

Sorry for the long wait! The test looks good overall, but I think we should use a different implementation strategy. See the comments for details.

View changes since this review

Comment thread src/shims/unix/fs.rs Outdated
let buf = this.deref_pointer(buf_op)?;

// Extract mode value. write_int will handle size conversion when writing to st_mode field.
let mode = metadata.mode.to_uint(metadata.mode.size())?.try_into().unwrap_or(0u32);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This basically bypasses the sanity check that made has the right type. Why is this needed?

Comment thread src/shims/unix/fs.rs Outdated
Comment thread src/shims/unix/fs.rs Outdated
Comment thread tests/pass-dep/libc/libc-fstat-non-file.rs Outdated
Comment thread tests/pass-dep/libc/libc-fstat-non-file.rs Outdated
Comment thread src/shims/unix/fs.rs Outdated
Comment thread src/shims/unix/fs.rs Outdated
@rustbot rustbot added S-waiting-on-author Status: Waiting for the PR author to address review comments and removed S-waiting-on-review Status: Waiting for a review to complete labels Feb 14, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Feb 14, 2026

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@enthropy7
Copy link
Copy Markdown
Contributor Author

enthropy7 commented Feb 15, 2026

Hi, thanks for your answer! i’m still here, just a little busy with my work. i will answer to your comments as soon as i can and optimise my PR better. I think it would took around 3 days for me to fix this

@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@enthropy7 enthropy7 force-pushed the master branch 4 times, most recently from f39e0b5 to e825e90 Compare March 5, 2026 15:32
@enthropy7
Copy link
Copy Markdown
Contributor Author

enthropy7 commented Mar 5, 2026

I reworked fstat for non-file-backed FDs so it no longer depends on host metadata: for socketpair/pipe/eventfd/epoll we now return synthetic metadata based on the FD type (correct S_IF*, size 0, and no misleading host dev/uid/gid).
I also removed the Unix-only host-metadata path in unnamed_socket::metadata that was breaking Windows CI. removed fd.name() matching in fstat logic (so it no longer relies on diagnostic strings), applied the requested test cleanup (libc-fstat-non-file: extracted repeated checks into a helper and tightened stdout/stderr expectations).

upd: fixed a cross-host issue that showed up in CI: on Windows hosts running Linux targets, fstat for stdio (stdin/stdout/stderr) could fail because host metadata is unavailable there.
To make this host-independent, I added a synthetic stdio fallback in the Unix fstat path (same approach as for other non-file-backed FDs)

@RalfJung
Copy link
Copy Markdown
Member

So I take it you mean this is
@rustbot ready
? Please help us keep track of the PR state by using that command when the PR is ready for review again. :)

@rustbot rustbot added S-waiting-on-review Status: Waiting for a review to complete and removed S-waiting-on-author Status: Waiting for the PR author to address review comments labels Mar 15, 2026
Comment thread src/shims/unix/linux_like/epoll.rs Outdated
@RalfJung
Copy link
Copy Markdown
Member

RalfJung commented Mar 15, 2026

That said, with all the cfg usage this is definitely not the right approach. The way we implement these APIs is by reading the documentation (ideally the POSIX documentation, not the glibc/Linux one), and then manually implementing what the docs say. If the docs aren't precise enough, we do experiments to figure out what happens on real systems. Ideally we do these experiments on multiple platforms (in the issue description you said you already did this). Then again we implement manually what the experiments showed the behavior is. Either way, there should be comments in the code explaining how the behavior was determined to be what eventually got implemented.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: Waiting for the PR author to address review comments and removed S-waiting-on-review Status: Waiting for a review to complete labels Mar 15, 2026
Comment thread src/shims/unix/fs.rs Outdated
@enthropy7 enthropy7 force-pushed the master branch 2 times, most recently from d67ffc2 to 59e01ba Compare April 2, 2026 11:39
@enthropy7
Copy link
Copy Markdown
Contributor Author

Reworked approach based on review - replaced the downcast chain and fd.name() matching in from_fd_num with a synthetic_stat_mode() trait method, so each FD type provides its own mode via proper dynamic dispatch. Removed all cfg blocks, host operations, /tmp metadata fetching, and permission bit computation. synthetic metadata now just uses the mode constant and zeros. Re eval_libc_u32 for mode: mode_t is u16 on macOS in the libc crate, so to_u32() panics. kept eval_libc with Scalar.

@enthropy7
Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Waiting for a review to complete and removed S-waiting-on-author Status: Waiting for the PR author to address review comments labels Apr 2, 2026
@rustbot

This comment has been minimized.

Copy link
Copy Markdown
Member

@RalfJung RalfJung left a comment

Choose a reason for hiding this comment

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

Sorry for the slow reply. This looks much better, thanks a lot! I still have some suggestions, but we're getting there. :)

@rustbot author

View changes since this review

Comment thread src/shims/files.rs Outdated
Comment on lines +205 to +208
/// If this FD supports fstat but is not backed by a host file, return the
/// mode flag name (e.g. "S_IFCHR") for synthetic stat metadata.
/// FDs that return `None` (default) will use real host file metadata via `metadata()`.
fn synthetic_stat_mode(&self) -> Option<&'static str> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This API makes me wonder, why are those separate functions? Why don't have have a single function that returns something like Either<io::Result<fs::Metadata>, &'static str>?

Or even better, make it Result<FileMetadata, IoError>. Then file-backed FDs will call FileMetadata::from_meta and others can call FileMetadata::synthetic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, merged into a single fstat() trait method returning Result<FileMetadata, IoError>. FileHandle calls FileMetadata::from_meta, non-file FDs call FileMetadata::synthetic. FileMetadata is re-exported through unix/mod.rs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Now we still have both metadata and fstat. The point was to merge them, to avoid duplication. Does that not work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fully merged now. refactored FileMetadata to be cross-platform: mode is stored as &'static str name (e.g. "S_IFREG") and resolved to a target-specific Scalar only at write time; timestamps are stored as raw Option<SystemTime> and converted to Unix-epoch-secs or Windows-epoch-ticks at their respective writers. Single fstat() trait method on FileDescription, no more host_metadata. Windows GetFileInformationByHandle now goes through fstat() too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

and yes, FileMetadata wasn't cross-platform at all. mode: Scalar came from eval_libc, which panics on Windows. timestamps were kept as (u64, u32) in Unix epoch, Windows uses a different approach. fixed both, now fstat() is the only method and GetFileInformationByHandle goes through it too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah it's because of Windows support -- makes sense.

Thanks for putting in all this work! However, it's generally better to discuss design decisions with the maintainers and get their feedback before running ahead and implement something suboptimal. FileMetadata is still not target-independent if it has a mode_name field that stores a libc name (which doesn't cover Windows). Having the Windows code match on libc names is an abstraction layering violation.

I'll take over from here to avoid further miscommunication.

Comment thread src/shims/unix/fs.rs Outdated
let mode: u16 = metadata
.mode
.to_u32()?
let mode: u16 = (metadata.mode.to_uint(metadata.mode.size())? & u128::from(u16::MAX))
Copy link
Copy Markdown
Member

@RalfJung RalfJung Apr 24, 2026

Choose a reason for hiding this comment

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

What is the point of this change? (I have asked about this before.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh you replied elsewhere. Please put the replies in the proper threads so that I have a chance to find them.

Re eval_libc_u32 for mode: mode_t is u16 on macOS in the libc crate, so to_u32() panics. kept eval_libc with Scalar.

Why was this not a problem before? We're already running tests like this on macOS, aren't we?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

But the &-masking still doesn't make sense to me. This should then be

metadata.mode.to_uint(this.libc_ty_layout("mode_t").size)?.try_into().unwrap_or_else(...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

replaced the & u16::MAX mask with libc_ty_layout("mode_t").size to read the scalar at mode_t's actual target width (u16 on macOS, u32 on Linux). same fix applied in the statx writer. sorry about the misplaced reply earlier.

Comment thread src/shims/unix/fs.rs Outdated
"S_IFBLK"
} else {
"S_IFREG"
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please have a look at this for how to write this case distinction with less code duplication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

extracted a free function file_type_to_mode_name(FileType) -> &'static str using #[cfg(unix)] per arm, matching the shape of file_type_to_d_type. the cfg_select! block is gone.

Comment thread src/shims/unix/fs.rs Outdated
Comment on lines +1758 to +1764
let is_regular_fs_entry =
file_type.is_file() || file_type.is_dir() || file_type.is_symlink();
let (dev, uid, gid) = if is_regular_fs_entry {
(metadata.dev(), metadata.uid(), metadata.gid())
} else {
(0, 0, 0)
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we actively ignore the metadata info from the host here if this is not a regular file/dir/symlink?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

my mistake, removed the is_regular_fs_entry zeroing. from_meta now always use MetadataExt::dev/uid/gid as we always called with real host metadata.

Comment thread src/shims/files.rs Outdated
}

fn synthetic_stat_mode(&self) -> Option<&'static str> {
Some("S_IFCHR")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We don't actually know that stdin/stdout/stderr are S_IFCHR, right? It could be a file if some redirected the output there.

I'd propose to skip stdin/stdout/stderr in this first PR. We have to either use non-portable APIs to give a correct answer or make a guess, neither of which is great, so until we have a clear usecase IMO it's better to just not support them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

agreed, dropped stdio support from this PR. Removed the fstat impls on io::Stdin/Stdout/Stderr/NullOutput and the corresponding tests.

@rustbot rustbot added S-waiting-on-author Status: Waiting for the PR author to address review comments and removed S-waiting-on-review Status: Waiting for a review to complete labels Apr 24, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 24, 2026

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

Comment thread src/shims/unix/fs.rs
@RalfJung
Copy link
Copy Markdown
Member

I took the version of this PR before your mos recent changes and did a slightly different refactor., to avoid funneling the Windows logic through something from the unix shims. Thanks a lot for all your help!

@enthropy7
Copy link
Copy Markdown
Contributor Author

I took the version of this PR before your mos recent changes and did a slightly different refactor., to avoid funneling the Windows logic through something from the unix shims. Thanks a lot for all your help!

Thank you too for collaborating! It was a pleasure to work on Miri. Will try to find new issues when I have free time and work together again. And about FileMetadata - I agree with your approach, I just thought you knew it's different for Windows and Unix and unifying it was on purpose.

@RalfJung
Copy link
Copy Markdown
Member

That's fair, but I had indeed entirely forgotten this and was just making guesses in this PR. :)

@RalfJung RalfJung added this pull request to the merge queue Apr 26, 2026
Merged via the queue into rust-lang:master with commit f3f6dca Apr 26, 2026
13 checks passed
@rustbot rustbot removed the S-waiting-on-author Status: Waiting for the PR author to address review comments label Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support fstat on non-file-backed FDs

4 participants