Repository navigation
chown, chgrp: hold the --from file without O_PATH - #15129
abendrothj wants to merge 8 commits into
Conversation
Merging this PR will not alter performance
|
| use libc::{gid_t, uid_t}; | ||
| use options::traverse; | ||
| #[cfg(target_os = "linux")] | ||
| #[cfg(not(any(target_os = "aix", target_os = "hurd", target_os = "redox")))] |
There was a problem hiding this comment.
this cfg is now repeated 23 times, could we use a cfg alias or move this code into its own module?
| /// Whether the file `meta` describes can be held open by [`PinnedFile`]. | ||
| #[cfg(not(any(target_os = "aix", target_os = "hurd", target_os = "redox")))] | ||
| // mode_t is u16 on macOS and u32 on Linux | ||
| #[allow(clippy::unnecessary_cast)] |
There was a problem hiding this comment.
could you please use meta.file_type() with FileTypeExt (is_socket, is_char_device...)? then you don't need the cast or the allow
There was a problem hiding this comment.
the -R entries come with safe_traversal's Metadata, which has no std FileType, so it uses rustix's FileType::from_raw_mode(meta.mode() as _) for both kinds. the allow is gone.
| open: impl FnOnce() -> IOResult<PinnedFile>, | ||
| ) -> IOResult<Hold> { | ||
| if !can_hold(meta, follow) { | ||
| return if unprivileged { |
There was a problem hiding this comment.
as root on macOS, chown --from=0 0 /dev/null now fails with "Operation not supported" where it used to work. is that really what we want?
same for -h on a symlink on OpenBSD/NetBSD
There was a problem hiding this comment.
no: root now changes such a file by name like everyone else, as on main. that's a socket, a device node, or a symlink under -h where it can't be held. it leaves the rename race open for those files only; the comment on hold says so.
| /// passed `--from`, through [`Self::hold`] if `--from` was given. Returns | ||
| /// whether it was changed. | ||
| #[cfg(not(any(target_os = "aix", target_os = "hurd", target_os = "redox")))] | ||
| fn chown_entry( |
There was a problem hiding this comment.
the condition in this function is hard to follow, could it be simplified?
There was a problem hiding this comment.
split it: changes_link_itself names the -L -h symlink case, and the function now picks how to change the file once, then changes it.
| // FIFO from waiting for a writer. | ||
| #[cfg(not(any(target_os = "linux", target_os = "android", target_os = "freebsd")))] | ||
| let flags = OFlag::O_RDONLY | ||
| | OFlag::O_NONBLOCK |
There was a problem hiding this comment.
please add a test in tests/by-util/test_chown.rs with --from on a fifo and on a 000 file
There was a problem hiding this comment.
added test_chown_from_fifo_and_unreadable_file, as operands and under -R. on macOS the 000 file goes through the by-name fallback, and the test fails without it; on Linux O_PATH holds both. a missing O_NONBLOCK would make it hang rather than fail.
| /// FreeBSD (14.5 at least) fails a change through an O_PATH descriptor | ||
| /// with EBADF whatever the reason. Report what a change by name would. | ||
| #[cfg(target_os = "freebsd")] | ||
| fn freebsd_chown_errno(&self, e: nix::errno::Errno) -> nix::errno::Errno { |
There was a problem hiding this comment.
guessing EPERM/EROFS from fstatfs seems fragile.
why not use the read-only open on FreeBSD too?
There was a problem hiding this comment.
done, FreeBSD takes the read-only open now and the errno guessing is gone. O_PATH is only used on Linux and Android, named by a has_o_path alias. FreeBSD is only cross-checked with clippy.
|
a bit too large for my taste |
abc5740 to
0c67785
Compare
|
smaller now: +219/-77 (it was +258/-66), about 85 of it tests. the alias moved to #15128, and the macOS O_SYMLINK part is its own commit. |
0c67785 to
24c9c2f
Compare
--from was checked on a stat of the name and the change was then made by name, so a rename landing in between changed a file that never had the required owner. On Linux, hold the file open with O_PATH, check that it is the one that was stat'd and still passes --from, and change it through that descriptor with fchownat(AT_EMPTY_PATH). This covers operands and -R entries. A file found replaced is left alone; as with GNU, that fails without a message, and -v reports the change that was not made.
On Linux, a directory operand was changed through a descriptor opened for reading, and when that open failed it was skipped without a message. Change it by name then, or through an O_PATH descriptor with --from, as for any other file. Under -R it is still left alone and the failure to read it reported, as with GNU.
Under -R -H, a symlink met in the tree was stat'd itself, while the change went to the file it points to. --from was judged on, and -v reported, the link's owner. Use the target's, as GNU does.
With -R on Linux, a change below the operand that failed was reported without the "failed to change" line that -v adds for the operand and on other platforms.
With -h, `-R -H` and `-R -L` did not descend into a symlinked directory operand, and `-R -L -h` changed the files that symlinks in the tree point to rather than the links. As GNU does, follow such a symlink to descend and to judge --from, and change the link itself.
The descriptor-based -R walk ran only on Linux; other platforms used walkdir, which stops at a symlink loop under -L with an error where GNU just skips the directory already being visited. Use the descriptor walk wherever safe_traversal is available, keeping walkdir for Redox. Outside Linux, --from entries are still changed by name. safe_traversal's platforms are named once, by a has_safe_traversal cfg alias in uucore's build script.
With --from, a file was held open and changed through that descriptor only on Linux; elsewhere it was changed by name, so a rename landing after the check changed a file that never passed --from. Hold it on every platform the descriptor walk runs on: with O_PATH on Linux and Android, and with a read-only non-blocking open elsewhere. A file that cannot be held that way, such as a socket, a device node, a symlink under -h or a file the caller cannot read, is still changed by name, which leaves the race open for it as before.
Under -h with --from, a symlink is changed itself. Without O_PATH it could not be held, so it was changed by name. Apple systems can open the symlink itself with O_SYMLINK; hold it that way there, as on Linux.
24c9c2f to
ccf8100
Compare
|
Rebased. main now has |
Outside Linux, --from was still decided on a stat and the change made by name, so a rename in between changed a file that never matched.
The file is now held on every platform with the descriptor walk: O_PATH with
fchownat(AT_EMPTY_PATH)on Linux and Android, and a read-only non-blocking open withfchownelsewhere. On macOS, -h holds a symlink itself withO_SYMLINK(second commit). A file that can't be held that way (a socket, a device node, a symlink under -h elsewhere, or a file the caller can't read) is still changed by name, as before. Linux behaviour doesn't change. Run on macOS, Linux and Debian GNU/Hurd (on Hurd, the tests that fail also fail on main); FreeBSD and AIX are only cross-checked with clippy.Based on #15128.
Closes #15027