Repository navigation
mkfifo, mknod: set the umask through mode::with_umask - #14768
abendrothj wants to merge 4 commits into
Conversation
a85690e to
56b1e1d
Compare
| /// ensuring the directory is created atomically with the correct permissions. | ||
| /// This avoids a race condition where the directory briefly exists with | ||
| /// umask-based permissions. | ||
| /// Create a directory, shaping the umask only when it would block a mode bit |
There was a problem hiding this comment.
7 lines of doc + 5 more inside, nobody will read that :) please make it shorter
There was a problem hiding this comment.
Cut to four lines and folded the inner comments into it.
| }; | ||
|
|
||
| match create_dir_with_mode(path, mkdir_mode, shaped_umask) { | ||
| match create_dir_with_mode(path, mkdir_mode, is_parent, config.mode) { |
There was a problem hiding this comment.
mode, is_parent and config.mode all encode the same thing here - could it just take config?
There was a problem hiding this comment.
Right — it takes the config now: create_dir_with_mode(path, is_parent, config), and the mode comes from a single mkdir_mode(is_parent, config) that the SELinux labelling also uses, so there's one place deciding it.
| /// Run an operation with a temporary umask derived from the current value. | ||
| /// | ||
| /// Reading, deriving, setting, and restoring the umask are serialized as one | ||
| /// operation. The selector and operation must not call this module's umask |
There was a problem hiding this comment.
a closure calling get_umask() here deadlocks silently - can we debug_assert instead of only documenting it?
There was a problem hiding this comment.
Added an assertion: the lock sets a thread-local flag while held and debug_assert!s on reentry, so a nested call panics with a message instead of hanging. test_reentrant_umask_helper_is_rejected covers it, gated on debug_assertions so a release test run can't deadlock on it.
| /// operation. The selector and operation must not call this module's umask | ||
| /// helpers recursively. | ||
| #[cfg(unix)] | ||
| pub fn with_umask_from_current<T>( |
There was a problem hiding this comment.
with_umask has a test, with_umask_from_current none, please add one
There was a problem hiding this comment.
Added test_with_umask_from_current_derives_mask_and_restores_it: the selector gets the real current umask (0027), the operation runs under the derived one (0007), and 0027 is back afterwards. It shares the child-process helper with the existing test, which now also checks the child really ran a test instead of trusting the exit status.
There was a problem hiding this comment.
Small update since my earlier reply: the test is in-process now, not a child process. It checks the mask passed to the closure, the mask active inside it, and that the original comes back afterwards.
|
cool PR :) |
56b1e1d to
2f632ab
Compare
2f632ab to
42b8bc8
Compare
|
Ordering note: this branch sits on top of #12715, so the install hunks in the diff belong to that PR and will vanish once it lands. Say the word and I'll rebase it onto main afterwards if you'd rather review it standalone. The clippy and doc failures were mine: |
| } | ||
|
|
||
| #[cfg(unix)] | ||
| #[allow(clippy::unnecessary_cast)] // no-op where RawMode is already u32 |
There was a problem hiding this comment.
could u32::from(mode.bits()) work here? it works for both u16 and u32, so we could drop the #[allow]
There was a problem hiding this comment.
It compiles both ways, but it doesn't drop the #[allow]: where RawMode is already u32, u32::from(mode.bits()) trips clippy::useless_conversion instead of clippy::unnecessary_cast (checked with --target x86_64-unknown-linux-gnu). #[expect] doesn't work either, on the u16 targets the lint doesn't fire and the expectation goes unfulfilled.
mode.bits() as _ is clean on both, same truncation behaviour, and is how mode_from_umask above avoids an attribute. If you'd rather have no #[allow] here, I can switch to that.
There was a problem hiding this comment.
Yes we prefer no allow
There was a problem hiding this comment.
Dropped in 5bcac42; mode.bits() as _ is clean on both widths, so no #[allow] is needed.
Merging this PR will degrade performance by 17.48%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
|
GNU testsuite comparison: |
5a106d1 to
3896a36
Compare
Utilities that create files or directories at an exact mode, whatever the caller's umask, change the process umask around the call. Done by hand, overlapping calls can restore each other's mask and leave the process at the wrong value, and an unwind between the set and the restore leaks the temporary mask. with_umask runs an operation with a temporary umask, holds a mutex shared with get_umask, and restores the previous mask on return or unwind. Calls nest: the thread that holds the mutex skips it, and get_umask there returns the mask in effect. Only Unix sets the umask this way, so Windows' get_umask takes no lock.
Replace mkdir's own umask guard with the shared helper, so its change is serialized with get_umask and other callers. The shaped umask is computed as a plain mask, which drops mkdir's rustix dependency.
The test set and restored the umask with its own guard, outside the lock that get_umask and with_umask share.
Both changed the process-wide umask directly around a single syscall. Overlapping calls can restore each other's mask and leave the process at the wrong value, and a panic between the set and the restore leaks the temporary mask into the rest of the run. Use uucore::mode::with_umask, which serializes the change with get_umask and restores the mask on unwind. No behavior change: modes for mkfifo -m and plain mkfifo match GNU 9.12 at umasks 022, 077 and 002. Narrow rustix to the fs feature in uu_mkfifo, which no longer needs process.
3896a36 to
2b0978d
Compare
|
reworked after #15121: mkdir moved there, as you asked on that PR, and the lock is reentrant now, so this is just mkfifo and mknod, on #15121 directly instead of #12715. with_umask_from_current and the debug_assert are gone: mkdir is single-threaded and with_umask restores the mask before releasing the lock, so its read and set can't go stale. checked outside the suite that mkfifo and mknod modes still match GNU 9.12 at umasks 022, 077 and 002. |
Based on #15121; until it merges, the diff here shows its commits too.
mkfifoandmknodchange the process-wide umask directly around a single syscall. Overlapping calls can restore each other's mask, and a panic between set and restore leaks the temporary mask into the rest of the run.They now use
uucore::mode::with_umask, which holds the umask mutex and restores the previous mask on unwind; mkdir moves to it in #15121.No behavior change:
mkfifoandmknodmodes match GNU 9.12 at umasks 022, 077 and 002.rustixis narrowed to thefsfeature inuu_mkfifo.Closes #14994