feat(store): the data root under LOCALAPPDATA with an owner-only DACL, one lock seam, MoveFileEx (P11c)
Windows step 1 and 2 of D-62. Where Unix keeps the books' private folder at mode 0700 and refuses one others can open, Windows now makes the same promise with a DACL: `winsec::set_owner_only` gives the folder (and, inherited, everything made in it) one protected allow entry for this account's SID; `winsec::is_private` reads the effective DACL and owner back and answers false for anything else (a missing DACL, an entry for Everyone), so `Root::open` refuses a loosened root and `Root::setup` makes it private again, exactly as the 0700 checks do. Every question the system cannot answer is "not private". The root itself comes from the shell's known folders (`SHGetKnownFolderPath`: the profile, and LocalAppData wherever it was redirected), never from environment variables the process that started us chose. `rename_new` (the move that must never go over what is there, renameat2 RENAME_NOREPLACE on Linux) is `MoveFileExW` without the replace flag on Windows, written through. File locking sat behind `libc::flock` in ops.rs alone (the rest already used std's File::try_lock, which is LockFileEx on Windows). It now goes through `lock.rs`, which uses only std, so there is no second unsafe copy of either call. New dependency: windows-sys 0.61 behind cfg(windows), the raw bindings only (no runtime), already in the lockfile through other crates; the smallest Microsoft crate that covers SIDs, DACLs, the known-folder API, pipe process ids and MoveFileExW. mise.toml adds the Windows target to the pinned toolchain so `cargo check --target x86_64-pc-windows-msvc` type-checks this arm on Linux. CHOICE (P11c): nothing above the root is checked on Windows (the Unix rule about folders above it being swappable has no Windows authority; the profile's folders belong to the account and the Administrators). Tests: tests/windows_root.rs (Windows job only), the cross-platform `a_viewer_s_presence_is_a_shared_lock_others_can_see` in ops.rs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
b97234ac7e
commit
5f5dd8f7da
10 files changed
+733
-43
No files matched your search
@@ -0,0 +1,36 @@
|
||||
//! File locks, behind one seam (P11c, D-62).
|
||||
//!
|
||||
//! ```text
|
||||
//! GOAL: "some process holds this file" is asked and answered the same
|
||||
//! way on every system.
|
||||
//! SOURCE: the standard library's `File::try_lock` and `try_lock_shared`
|
||||
//! (flock(2) on Unix, LockFileEx on Windows), plan/served.md 3.9
|
||||
//! ("the viewer's presence is a shared lock").
|
||||
//! INVARIANTS:
|
||||
//! 1. Only the standard library's locks are used: no second copy of an
|
||||
//! `unsafe` flock or LockFileEx call lives anywhere else.
|
||||
//! 2. A lock is let go when its file is closed, by this process ending
|
||||
//! or not (the system does it; nothing here keeps a record).
|
||||
//! 3. A question that cannot be answered is answered "not held".
|
||||
//! ```
|
||||
|
||||
use std::fs::{File, TryLockError};
|
||||
|
||||
/// Take a shared lock on `file` without waiting: whether it is now held.
|
||||
pub fn try_shared(file: &File) -> bool {
|
||||
file.try_lock_shared().is_ok()
|
||||
}
|
||||
|
||||
/// Whether another holder keeps a shared lock on `file` (so that an
|
||||
/// exclusive one can't be taken now). A lock taken to find out is let go
|
||||
/// at once.
|
||||
pub fn held_elsewhere(file: &File) -> bool {
|
||||
match file.try_lock() {
|
||||
Ok(()) => {
|
||||
let _ = file.unlock();
|
||||
false
|
||||
}
|
||||
Err(TryLockError::WouldBlock) => true,
|
||||
Err(TryLockError::Error(_)) => false,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user