Skip to main content

rustferry_core/
windows_private_directory.rs

1//! Atomic private filesystem-object creation and handle validation on Windows.
2
3#![cfg_attr(windows, allow(unsafe_code))]
4
5use std::{error::Error, fmt};
6
7#[cfg(any(windows, test))]
8const ACCESS_ALLOWED_ACE_TYPE: u8 = 0;
9#[cfg(any(windows, test))]
10const ACL_REVISION: u8 = 2;
11#[cfg(any(windows, test))]
12const DIRECTORY_INHERIT_FLAGS: u8 = 0x01 | 0x02;
13#[cfg(any(windows, test))]
14const FILE_ACE_FLAGS: u8 = 0;
15#[cfg(any(windows, test))]
16const FILE_ALL_ACCESS_MASK: u32 = 0x001f_01ff;
17#[cfg(any(windows, test))]
18const ACL_HEADER_BYTES: usize = 8;
19#[cfg(any(windows, test))]
20const ACE_HEADER_BYTES: usize = 4;
21#[cfg(any(windows, test))]
22const ACCESS_ALLOWED_ACE_PREFIX_BYTES: usize = 8;
23#[cfg(any(windows, test))]
24const SID_HEADER_BYTES: usize = 8;
25#[cfg(any(windows, test))]
26const SID_REVISION: u8 = 1;
27#[cfg(any(windows, test))]
28const SID_MAX_SUB_AUTHORITIES: u8 = 15;
29
30/// Windows operation that failed while creating or inspecting a private directory.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32#[non_exhaustive]
33pub enum PrivateDirectoryOperation {
34    /// Read the current process-token user SID.
35    QueryProcessToken,
36    /// Construct a well-known Windows SID.
37    CreateWellKnownSid,
38    /// Construct the explicit directory DACL.
39    BuildAcl,
40    /// Construct the absolute security descriptor.
41    BuildSecurityDescriptor,
42    /// Atomically create the named directory.
43    CreateDirectory,
44    /// Atomically create the named regular file.
45    CreateFile,
46    /// Open an existing regular file without following a reparse point.
47    OpenFile,
48    /// Open the newly created directory without following a reparse point.
49    OpenDirectory,
50    /// Read directory attributes from the retained handle.
51    QueryAttributes,
52    /// Resolve the stable DOS path of a retained no-delete-sharing directory handle.
53    ResolvePath,
54    /// Enumerate file identifiers through a retained directory handle.
55    EnumerateDirectory,
56    /// Open one enumerated directory-tree entry without following reparse points.
57    OpenTreeEntry,
58    /// Read filesystem capabilities from the retained handle.
59    QueryFileSystem,
60    /// Flush retained directory metadata through the exact handle.
61    FlushDirectory,
62    /// Read the security descriptor from the retained handle.
63    QuerySecurityDescriptor,
64    /// Mark the exact retained directory object for deletion.
65    RemoveDirectory,
66    /// Mark the exact retained regular-file link for deletion.
67    RemoveFile,
68    /// Atomically rename a retained private file into a retained directory.
69    RenameFile,
70}
71
72/// Expected hard-link state for a private regular-file handle.
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74#[non_exhaustive]
75pub enum PrivateFileLinkState {
76    /// A normal private file with one filesystem link.
77    Single,
78    /// A staging file during create-only publication, with staging and final links.
79    PublicationPair,
80}
81
82impl PrivateFileLinkState {
83    #[cfg(windows)]
84    const fn expected_links(self) -> u32 {
85        match self {
86            Self::Single => 1,
87            Self::PublicationPair => 2,
88        }
89    }
90}
91
92/// Exact ACL-policy mismatch found on a retained directory handle.
93#[derive(Clone, Copy, Debug, Eq, PartialEq)]
94#[non_exhaustive]
95pub enum PrivateDirectoryAclViolation {
96    /// The ACL or one of its ACE records is structurally malformed.
97    Malformed,
98    /// The ACL uses a revision other than the supported basic ACL revision.
99    WrongRevision,
100    /// The ACL does not contain exactly one ACE per unique allowed principal.
101    WrongAceCount,
102    /// An ACE is not a basic access-allowed ACE.
103    UnsupportedAceType,
104    /// An ACE has flags other than object and container inheritance.
105    UnexpectedAceFlags,
106    /// An ACE grants a mask other than full file access.
107    UnexpectedAccessMask,
108    /// An ACE contains a malformed SID.
109    InvalidSid,
110    /// An ACE grants access to a principal outside the allowlist.
111    UnknownPrincipal,
112    /// More than one ACE grants access to the same allowed principal.
113    DuplicatePrincipal,
114    /// One of the three required principals is absent.
115    MissingPrincipal,
116}
117
118/// Security or platform reason a private directory was rejected.
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120#[non_exhaustive]
121pub enum PrivateDirectoryErrorKind {
122    /// The supplied Windows path contains an interior NUL.
123    InvalidPath,
124    /// Atomic creation failed because an object already occupies the path.
125    AlreadyExists,
126    /// A Windows API failed.
127    WindowsApi(PrivateDirectoryOperation),
128    /// The filesystem does not advertise persistent ACL support.
129    UnsupportedFileSystem,
130    /// The retained handle does not identify a directory.
131    NotDirectory,
132    /// The retained handle does not identify a regular file.
133    NotRegularFile,
134    /// The retained handle identifies a reparse point.
135    ReparsePoint,
136    /// A private regular file has a hard-link count that differs from the expected state.
137    MultipleLinks,
138    /// A pathname entry no longer identifies the object enumerated through its parent handle.
139    IdentityMismatch,
140    /// The security descriptor has no owner SID.
141    OwnerMissing,
142    /// The directory owner is not the current process-token user.
143    OwnerMismatch,
144    /// The security descriptor has no non-NULL DACL.
145    DaclMissing,
146    /// The DACL is not protected from parent inheritance.
147    DaclUnprotected,
148    /// The DACL does not exactly match the private-directory allowlist.
149    DaclPolicy(PrivateDirectoryAclViolation),
150}
151
152/// Cleanup state after private-directory creation fails.
153#[derive(Clone, Copy, Debug, Eq, PartialEq)]
154#[non_exhaustive]
155pub enum PrivateDirectoryCleanupStatus {
156    /// No directory was created, or this was a standalone handle verification.
157    NotRequired,
158    /// The exact retained directory object was marked for deletion.
159    Confirmed,
160    /// Identity-bound cleanup could not be performed or confirmed.
161    Uncertain,
162}
163
164/// Sanitized, typed failure from private-directory creation or verification.
165#[derive(Debug)]
166pub struct PrivateDirectoryError {
167    kind: PrivateDirectoryErrorKind,
168    cleanup: PrivateDirectoryCleanupStatus,
169    os_code: Option<i32>,
170    cleanup_os_code: Option<i32>,
171}
172
173impl PrivateDirectoryError {
174    #[cfg(windows)]
175    const fn new(kind: PrivateDirectoryErrorKind, os_code: Option<i32>) -> Self {
176        Self {
177            kind,
178            cleanup: PrivateDirectoryCleanupStatus::NotRequired,
179            os_code,
180            cleanup_os_code: None,
181        }
182    }
183
184    /// Return the stable failure classification.
185    pub const fn kind(&self) -> PrivateDirectoryErrorKind {
186        self.kind
187    }
188
189    /// Return whether identity-bound cleanup was needed and confirmed.
190    pub const fn cleanup_status(&self) -> PrivateDirectoryCleanupStatus {
191        self.cleanup
192    }
193
194    /// Return the raw Windows error code, when the primary failure came from Windows.
195    pub const fn os_code(&self) -> Option<i32> {
196        self.os_code
197    }
198
199    /// Return the raw Windows cleanup error code when cleanup is uncertain.
200    pub const fn cleanup_os_code(&self) -> Option<i32> {
201        self.cleanup_os_code
202    }
203
204    #[cfg(windows)]
205    fn with_cleanup(
206        mut self,
207        cleanup: PrivateDirectoryCleanupStatus,
208        cleanup_os_code: Option<i32>,
209    ) -> Self {
210        self.cleanup = cleanup;
211        self.cleanup_os_code = cleanup_os_code;
212        self
213    }
214}
215
216impl fmt::Display for PrivateDirectoryError {
217    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
218        write!(
219            formatter,
220            "private filesystem object rejected: {:?}",
221            self.kind
222        )?;
223        if let Some(code) = self.os_code {
224            write!(formatter, " (Windows error {code})")?;
225        }
226        if self.cleanup == PrivateDirectoryCleanupStatus::Uncertain {
227            write!(formatter, "; identity-bound cleanup is uncertain")?;
228            if let Some(code) = self.cleanup_os_code {
229                write!(formatter, " (Windows error {code})")?;
230            }
231        }
232        Ok(())
233    }
234}
235
236impl Error for PrivateDirectoryError {}
237
238/// Whether a failed handle-bound private-file publication changed the destination namespace.
239#[cfg(windows)]
240#[derive(Clone, Copy, Debug, Eq, PartialEq)]
241#[non_exhaustive]
242pub enum PrivatePublicationPhase {
243    /// The handle-bound rename was not reported successful, so the staging name remains authoritative.
244    Unpublished,
245    /// The rename succeeded, but a handle-bound postcondition could not be confirmed.
246    CommitUncertain,
247}
248
249/// Recoverable failure from handle-bound private-file publication.
250///
251/// The exact input file handle is always retained. Callers must branch on [`Self::phase`] before
252/// deciding which durable record or namespace entry is authoritative; they must never fall back to
253/// deleting a pathname after this error.
254#[cfg(windows)]
255#[derive(Debug)]
256pub struct PrivatePublicationError {
257    phase: PrivatePublicationPhase,
258    file: std::fs::File,
259    error: PrivateDirectoryError,
260}
261
262#[cfg(windows)]
263impl PrivatePublicationError {
264    fn new(
265        phase: PrivatePublicationPhase,
266        file: std::fs::File,
267        error: PrivateDirectoryError,
268    ) -> Self {
269        Self { phase, file, error }
270    }
271
272    /// Return the namespace phase reached before publication failed.
273    pub const fn phase(&self) -> PrivatePublicationPhase {
274        self.phase
275    }
276
277    /// Return the underlying strict-policy or Windows failure.
278    pub const fn error(&self) -> &PrivateDirectoryError {
279        &self.error
280    }
281
282    /// Borrow the exact retained file involved in the attempted publication.
283    pub const fn retained_file(&self) -> &std::fs::File {
284        &self.file
285    }
286
287    /// Recover the phase, exact retained file, and underlying failure.
288    pub fn into_parts(
289        self,
290    ) -> (
291        PrivatePublicationPhase,
292        std::fs::File,
293        PrivateDirectoryError,
294    ) {
295        (self.phase, self.file, self.error)
296    }
297}
298
299#[cfg(windows)]
300impl fmt::Display for PrivatePublicationError {
301    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
302        write!(
303            formatter,
304            "private file publication failed in phase {:?}: {}",
305            self.phase, self.error
306        )
307    }
308}
309
310#[cfg(windows)]
311impl Error for PrivatePublicationError {
312    fn source(&self) -> Option<&(dyn Error + 'static)> {
313        Some(&self.error)
314    }
315}
316
317/// Exact-handle state retained when legacy hard-link publication recovery fails.
318#[cfg(windows)]
319#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320#[non_exhaustive]
321pub enum PrivatePublicationPairPhase {
322    /// Removal of the staging link was not reported successful; the retained handle is staging.
323    PairIntact,
324    /// The staging link was removed; the retained exact final handle still permits delete sharing.
325    FinalSingleSharedDelete,
326    /// A no-write/no-delete-sharing exact final handle was acquired, but validation failed.
327    FinalSingleSealed,
328}
329
330/// Recoverable failure while collapsing a legacy private publication hard-link pair.
331#[cfg(windows)]
332#[derive(Debug)]
333pub struct PrivatePublicationPairError {
334    phase: PrivatePublicationPairPhase,
335    file: std::fs::File,
336    error: PrivateDirectoryError,
337}
338
339#[cfg(windows)]
340impl PrivatePublicationPairError {
341    fn new(
342        phase: PrivatePublicationPairPhase,
343        file: std::fs::File,
344        error: PrivateDirectoryError,
345    ) -> Self {
346        Self { phase, file, error }
347    }
348
349    /// Return the exact-handle phase reached before recovery failed.
350    pub const fn phase(&self) -> PrivatePublicationPairPhase {
351        self.phase
352    }
353
354    /// Return the underlying strict-policy or Windows failure.
355    pub const fn error(&self) -> &PrivateDirectoryError {
356        &self.error
357    }
358
359    /// Borrow the exact retained staging or final file.
360    pub const fn retained_file(&self) -> &std::fs::File {
361        &self.file
362    }
363
364    /// Recover the phase, exact retained file, and underlying failure.
365    pub fn into_parts(
366        self,
367    ) -> (
368        PrivatePublicationPairPhase,
369        std::fs::File,
370        PrivateDirectoryError,
371    ) {
372        (self.phase, self.file, self.error)
373    }
374}
375
376#[cfg(windows)]
377impl fmt::Display for PrivatePublicationPairError {
378    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
379        write!(
380            formatter,
381            "private publication-pair recovery failed in phase {:?}: {}",
382            self.phase, self.error
383        )
384    }
385}
386
387#[cfg(windows)]
388impl Error for PrivatePublicationPairError {
389    fn source(&self) -> Option<&(dyn Error + 'static)> {
390        Some(&self.error)
391    }
392}
393
394#[cfg(test)]
395fn validate_acl(raw: &[u8], principals: &[&[u8]]) -> Result<(), PrivateDirectoryAclViolation> {
396    validate_acl_with_flags(raw, principals, DIRECTORY_INHERIT_FLAGS)
397}
398
399#[cfg(any(windows, test))]
400fn validate_acl_with_flags(
401    raw: &[u8],
402    principals: &[&[u8]],
403    expected_flags: u8,
404) -> Result<(), PrivateDirectoryAclViolation> {
405    if raw.len() < ACL_HEADER_BYTES {
406        return Err(PrivateDirectoryAclViolation::Malformed);
407    }
408    if raw[0] != ACL_REVISION {
409        return Err(PrivateDirectoryAclViolation::WrongRevision);
410    }
411    let declared_size = usize::from(u16::from_le_bytes([raw[2], raw[3]]));
412    if declared_size != raw.len() {
413        return Err(PrivateDirectoryAclViolation::Malformed);
414    }
415    let ace_count = usize::from(u16::from_le_bytes([raw[4], raw[5]]));
416    if ace_count != principals.len() {
417        return Err(PrivateDirectoryAclViolation::WrongAceCount);
418    }
419
420    let mut found = vec![false; principals.len()];
421    let mut offset = ACL_HEADER_BYTES;
422    for _ in 0..ace_count {
423        let header = raw
424            .get(offset..offset + ACE_HEADER_BYTES)
425            .ok_or(PrivateDirectoryAclViolation::Malformed)?;
426        if header[0] != ACCESS_ALLOWED_ACE_TYPE {
427            return Err(PrivateDirectoryAclViolation::UnsupportedAceType);
428        }
429        if header[1] != expected_flags {
430            return Err(PrivateDirectoryAclViolation::UnexpectedAceFlags);
431        }
432        let ace_size = usize::from(u16::from_le_bytes([header[2], header[3]]));
433        if ace_size < ACCESS_ALLOWED_ACE_PREFIX_BYTES + SID_HEADER_BYTES || ace_size % 4 != 0 {
434            return Err(PrivateDirectoryAclViolation::Malformed);
435        }
436        let end = offset
437            .checked_add(ace_size)
438            .filter(|end| *end <= raw.len())
439            .ok_or(PrivateDirectoryAclViolation::Malformed)?;
440        let mask = u32::from_le_bytes(
441            raw[offset + ACE_HEADER_BYTES..offset + ACCESS_ALLOWED_ACE_PREFIX_BYTES]
442                .try_into()
443                .map_err(|_| PrivateDirectoryAclViolation::Malformed)?,
444        );
445        if mask != FILE_ALL_ACCESS_MASK {
446            return Err(PrivateDirectoryAclViolation::UnexpectedAccessMask);
447        }
448        let sid = &raw[offset + ACCESS_ALLOWED_ACE_PREFIX_BYTES..end];
449        if !valid_sid_bytes(sid) {
450            return Err(PrivateDirectoryAclViolation::InvalidSid);
451        }
452        let principal = principals
453            .iter()
454            .position(|expected| *expected == sid)
455            .ok_or(PrivateDirectoryAclViolation::UnknownPrincipal)?;
456        if found[principal] {
457            return Err(PrivateDirectoryAclViolation::DuplicatePrincipal);
458        }
459        found[principal] = true;
460        offset = end;
461    }
462    if !raw[offset..].iter().all(|byte| *byte == 0) {
463        return Err(PrivateDirectoryAclViolation::Malformed);
464    }
465    if found.iter().all(|present| *present) {
466        Ok(())
467    } else {
468        Err(PrivateDirectoryAclViolation::MissingPrincipal)
469    }
470}
471
472#[cfg(any(windows, test))]
473fn valid_sid_bytes(sid: &[u8]) -> bool {
474    let Some(header) = sid.get(..SID_HEADER_BYTES) else {
475        return false;
476    };
477    let sub_authorities = header[1];
478    if header[0] != SID_REVISION || sub_authorities > SID_MAX_SUB_AUTHORITIES {
479        return false;
480    }
481    SID_HEADER_BYTES
482        .checked_add(usize::from(sub_authorities) * size_of::<u32>())
483        .is_some_and(|expected| expected == sid.len())
484}
485
486#[cfg(windows)]
487mod platform {
488    use std::{
489        collections::BTreeSet,
490        ffi::c_void,
491        ffi::{OsStr, OsString},
492        fs::File,
493        io,
494        mem::{offset_of, size_of, size_of_val},
495        os::windows::{
496            ffi::{OsStrExt as _, OsStringExt as _},
497            io::{AsRawHandle as _, BorrowedHandle, FromRawHandle as _, OwnedHandle},
498        },
499        path::{Component, Path, PathBuf},
500        ptr, slice,
501    };
502
503    use windows_sys::Wdk::Storage::FileSystem::{
504        FILE_RENAME_INFORMATION, FileRenameInformation, NtFlushBuffersFileEx, NtSetInformationFile,
505    };
506    use windows_sys::Win32::{
507        Foundation::{
508            ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS, ERROR_INSUFFICIENT_BUFFER,
509            ERROR_NO_MORE_FILES, HANDLE, INVALID_HANDLE_VALUE, RtlNtStatusToDosError,
510        },
511        Security::{
512            ACCESS_ALLOWED_ACE, ACL, ACL_REVISION, AddAccessAllowedAceEx, CopySid,
513            CreateWellKnownSid, DACL_SECURITY_INFORMATION, GetKernelObjectSecurity, GetLengthSid,
514            GetSecurityDescriptorControl, GetSecurityDescriptorDacl, GetSecurityDescriptorOwner,
515            GetTokenInformation, InitializeAcl, InitializeSecurityDescriptor,
516            IsValidSecurityDescriptor, IsValidSid, OWNER_SECURITY_INFORMATION, PSID,
517            SE_DACL_PROTECTED, SECURITY_ATTRIBUTES, SECURITY_DESCRIPTOR, SECURITY_MAX_SID_SIZE,
518            SetSecurityDescriptorControl, SetSecurityDescriptorDacl, SetSecurityDescriptorOwner,
519            TOKEN_QUERY, TOKEN_USER, TokenUser, WinBuiltinAdministratorsSid, WinLocalSystemSid,
520        },
521        Storage::FileSystem::{
522            BY_HANDLE_FILE_INFORMATION, CREATE_NEW, CreateDirectoryW, CreateFileW, DELETE,
523            FILE_ALL_ACCESS, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL,
524            FILE_ATTRIBUTE_REPARSE_POINT, FILE_DISPOSITION_FLAG_DELETE,
525            FILE_DISPOSITION_FLAG_POSIX_SEMANTICS, FILE_DISPOSITION_INFO, FILE_DISPOSITION_INFO_EX,
526            FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ,
527            FILE_GENERIC_WRITE, FILE_ID_EXTD_DIR_INFO, FILE_ID_INFO, FILE_SHARE_DELETE,
528            FILE_SHARE_READ, FILE_SHARE_WRITE, FILE_WRITE_DATA, FileDispositionInfo,
529            FileDispositionInfoEx, FileIdExtdDirectoryInfo, FileIdExtdDirectoryRestartInfo,
530            FileIdInfo, GetFileInformationByHandle, GetFileInformationByHandleEx,
531            GetFinalPathNameByHandleW, GetVolumeInformationByHandleW, OPEN_EXISTING, ReOpenFile,
532            SetFileInformationByHandle, WRITE_DAC,
533        },
534        System::{
535            IO::IO_STATUS_BLOCK,
536            SystemServices::{FILE_PERSISTENT_ACLS, SECURITY_DESCRIPTOR_REVISION},
537            Threading::{GetCurrentProcess, OpenProcessToken},
538        },
539    };
540
541    use super::{
542        DIRECTORY_INHERIT_FLAGS, FILE_ACE_FLAGS, PrivateDirectoryAclViolation,
543        PrivateDirectoryCleanupStatus, PrivateDirectoryError, PrivateDirectoryErrorKind,
544        PrivateDirectoryOperation, PrivateFileLinkState, PrivatePublicationError,
545        PrivatePublicationPairError, PrivatePublicationPairPhase, PrivatePublicationPhase,
546        SID_HEADER_BYTES, valid_sid_bytes, validate_acl_with_flags,
547    };
548
549    const MAX_SECURITY_DESCRIPTOR_BYTES: usize = 128 * 1024;
550    const DIRECTORY_ENUMERATION_BUFFER_BYTES: usize = 64 * 1024;
551
552    #[derive(Debug, Eq, PartialEq)]
553    struct DirectoryEntryIdentity {
554        name: OsString,
555        file_id: [u8; 16],
556    }
557
558    enum BoundTreeEntry {
559        Directory { handle: File, path: PathBuf },
560        RegularFile(File),
561    }
562
563    #[derive(Clone, Copy)]
564    enum PrivateObjectKind {
565        Directory,
566        RegularFile(PrivateFileLinkState),
567    }
568
569    impl PrivateObjectKind {
570        const fn ace_flags(self) -> u8 {
571            match self {
572                Self::Directory => DIRECTORY_INHERIT_FLAGS,
573                Self::RegularFile(_) => FILE_ACE_FLAGS,
574            }
575        }
576
577        const fn remove_operation(self) -> PrivateDirectoryOperation {
578            match self {
579                Self::Directory => PrivateDirectoryOperation::RemoveDirectory,
580                Self::RegularFile(_) => PrivateDirectoryOperation::RemoveFile,
581            }
582        }
583    }
584
585    pub(super) fn create(path: &Path) -> Result<File, PrivateDirectoryError> {
586        let wide_path = wide_path(path)?;
587        let ((), user) =
588            with_private_security_attributes(PrivateObjectKind::Directory, |attributes| {
589                // SAFETY: `wide_path` is NUL-terminated and `attributes` keeps its complete
590                // security-descriptor graph alive for the atomic creation call.
591                let created = unsafe { CreateDirectoryW(wide_path.as_ptr(), attributes) };
592                if created == 0 {
593                    let code = last_os_code();
594                    return Err(PrivateDirectoryError::new(
595                        if is_win32_error(code, ERROR_ALREADY_EXISTS) {
596                            PrivateDirectoryErrorKind::AlreadyExists
597                        } else {
598                            PrivateDirectoryErrorKind::WindowsApi(
599                                PrivateDirectoryOperation::CreateDirectory,
600                            )
601                        },
602                        code,
603                    ));
604                }
605                Ok(())
606            })?;
607
608        let directory = match open_directory(&wide_path, false) {
609            Ok(directory) => directory,
610            Err(error) => {
611                // `CreateDirectoryW` returns no identity. Reopening this pathname after the first
612                // open failed could target a replacement controlled through the parent directory.
613                return Err(error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, None));
614            }
615        };
616        if let Err(error) = verify_with_user(
617            directory.as_raw_handle(),
618            &user,
619            PrivateObjectKind::Directory,
620        ) {
621            let cleanup = remove_verified_private(
622                directory.as_raw_handle(),
623                &user,
624                PrivateObjectKind::Directory,
625            );
626            return Err(match cleanup {
627                Ok(()) => error.with_cleanup(PrivateDirectoryCleanupStatus::Confirmed, None),
628                Err(cleanup) => {
629                    error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, cleanup.os_code())
630                }
631            });
632        }
633        Ok(directory)
634    }
635
636    pub(super) fn open(path: &Path) -> Result<File, PrivateDirectoryError> {
637        let wide_path = wide_path(path)?;
638        let directory = open_directory(&wide_path, false)?;
639        let user = current_process_user_sid()?;
640        verify_with_user(
641            directory.as_raw_handle(),
642            &user,
643            PrivateObjectKind::Directory,
644        )?;
645        Ok(directory)
646    }
647
648    pub(super) fn open_read_guard(path: &Path) -> Result<File, PrivateDirectoryError> {
649        let wide_path = wide_path(path)?;
650        let directory = open_directory_read_guard(&wide_path)?;
651        let user = current_process_user_sid()?;
652        verify_with_user(
653            directory.as_raw_handle(),
654            &user,
655            PrivateObjectKind::Directory,
656        )?;
657        Ok(directory)
658    }
659
660    pub(super) fn create_file(
661        path: &Path,
662        share_delete: bool,
663    ) -> Result<File, PrivateDirectoryError> {
664        let wide_path = wide_path(path)?;
665        let share_mode = FILE_SHARE_READ | if share_delete { FILE_SHARE_DELETE } else { 0 };
666        let (file, user) = with_private_security_attributes(
667            PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
668            |attributes| {
669                // SAFETY: `wide_path` is NUL-terminated and `attributes` keeps its complete
670                // security-descriptor graph alive. `CREATE_NEW` never replaces an existing entry.
671                let handle = unsafe {
672                    CreateFileW(
673                        wide_path.as_ptr(),
674                        FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE,
675                        share_mode,
676                        attributes,
677                        CREATE_NEW,
678                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,
679                        ptr::null_mut(),
680                    )
681                };
682                if handle == INVALID_HANDLE_VALUE || handle.is_null() {
683                    let code = last_os_code();
684                    return Err(PrivateDirectoryError::new(
685                        if is_win32_error(code, ERROR_ALREADY_EXISTS)
686                            || is_win32_error(code, ERROR_FILE_EXISTS)
687                        {
688                            PrivateDirectoryErrorKind::AlreadyExists
689                        } else {
690                            PrivateDirectoryErrorKind::WindowsApi(
691                                PrivateDirectoryOperation::CreateFile,
692                            )
693                        },
694                        code,
695                    ));
696                }
697                // SAFETY: successful `CreateFileW` transfers exactly one owned real handle.
698                Ok(unsafe { File::from_raw_handle(handle) })
699            },
700        )?;
701        let kind = PrivateObjectKind::RegularFile(PrivateFileLinkState::Single);
702        if let Err(error) = verify_with_user(file.as_raw_handle(), &user, kind) {
703            let cleanup = remove_verified_private(file.as_raw_handle(), &user, kind);
704            return Err(match cleanup {
705                Ok(()) => error.with_cleanup(PrivateDirectoryCleanupStatus::Confirmed, None),
706                Err(cleanup) => {
707                    error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, cleanup.os_code())
708                }
709            });
710        }
711        Ok(file)
712    }
713
714    pub(super) fn create_lock_file(path: &Path) -> Result<File, PrivateDirectoryError> {
715        create_lock_file_with_callback(path, || {})
716    }
717
718    fn create_lock_file_with_callback(
719        path: &Path,
720        after_create: impl FnOnce(),
721    ) -> Result<File, PrivateDirectoryError> {
722        let wide_path = wide_path(path)?;
723        let (file, user) = with_private_security_attributes(
724            PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
725            |attributes| {
726                // SAFETY: the path and strict security descriptor are retained for this call.
727                // CREATE_NEW cannot replace a peer's existing lock file.
728                let handle = unsafe {
729                    CreateFileW(
730                        wide_path.as_ptr(),
731                        FILE_GENERIC_READ | FILE_GENERIC_WRITE,
732                        FILE_SHARE_READ | FILE_SHARE_WRITE,
733                        attributes,
734                        CREATE_NEW,
735                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,
736                        ptr::null_mut(),
737                    )
738                };
739                if handle == INVALID_HANDLE_VALUE || handle.is_null() {
740                    let code = last_os_code();
741                    return Err(PrivateDirectoryError::new(
742                        if is_win32_error(code, ERROR_ALREADY_EXISTS)
743                            || is_win32_error(code, ERROR_FILE_EXISTS)
744                        {
745                            PrivateDirectoryErrorKind::AlreadyExists
746                        } else {
747                            PrivateDirectoryErrorKind::WindowsApi(
748                                PrivateDirectoryOperation::CreateFile,
749                            )
750                        },
751                        code,
752                    ));
753                }
754                // SAFETY: successful CreateFileW returns one owned real handle.
755                Ok(unsafe { File::from_raw_handle(handle) })
756            },
757        )?;
758        after_create();
759        let kind = PrivateObjectKind::RegularFile(PrivateFileLinkState::Single);
760        if let Err(error) = verify_with_user(file.as_raw_handle(), &user, kind) {
761            drop(file);
762            return Err(error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, None));
763        }
764        Ok(file)
765    }
766
767    pub(super) fn open_lock_file(path: &Path) -> Result<File, PrivateDirectoryError> {
768        let wide_path = wide_path(path)?;
769        let file = open_regular_file_with_access(
770            &wide_path,
771            FILE_GENERIC_READ | FILE_GENERIC_WRITE,
772            FILE_SHARE_READ | FILE_SHARE_WRITE,
773        )?;
774        let user = current_process_user_sid()?;
775        verify_with_user(
776            file.as_raw_handle(),
777            &user,
778            PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
779        )?;
780        Ok(file)
781    }
782
783    pub(super) fn publish_file_create_new(
784        file: File,
785        destination_directory: BorrowedHandle<'_>,
786        destination_name: &OsStr,
787    ) -> Result<File, PrivatePublicationError> {
788        publish_file_create_new_with_callback(file, destination_directory, destination_name, || {})
789    }
790
791    fn publish_file_create_new_with_callback(
792        file: File,
793        destination_directory: BorrowedHandle<'_>,
794        destination_name: &OsStr,
795        after_rename: impl FnOnce(),
796    ) -> Result<File, PrivatePublicationError> {
797        let encoded_name = match private_destination_name(destination_name) {
798            Ok(name) => name,
799            Err(error) => {
800                return Err(PrivatePublicationError::new(
801                    PrivatePublicationPhase::Unpublished,
802                    file,
803                    error,
804                ));
805            }
806        };
807        let user = match current_process_user_sid() {
808            Ok(user) => user,
809            Err(error) => {
810                return Err(PrivatePublicationError::new(
811                    PrivatePublicationPhase::Unpublished,
812                    file,
813                    error,
814                ));
815            }
816        };
817        if let Err(error) = verify_with_user(
818            destination_directory.as_raw_handle(),
819            &user,
820            PrivateObjectKind::Directory,
821        ) {
822            return Err(PrivatePublicationError::new(
823                PrivatePublicationPhase::Unpublished,
824                file,
825                error,
826            ));
827        }
828        let file_kind = PrivateObjectKind::RegularFile(PrivateFileLinkState::Single);
829        if let Err(error) = verify_with_user(file.as_raw_handle(), &user, file_kind) {
830            return Err(PrivatePublicationError::new(
831                PrivatePublicationPhase::Unpublished,
832                file,
833                error,
834            ));
835        }
836        if let Err(error) = verify_directory_contains_file_identity(
837            destination_directory.as_raw_handle(),
838            file.as_raw_handle(),
839        ) {
840            return Err(PrivatePublicationError::new(
841                PrivatePublicationPhase::Unpublished,
842                file,
843                error,
844            ));
845        }
846        if let Err(error) = rename_file_handle_create_new(
847            file.as_raw_handle(),
848            destination_directory.as_raw_handle(),
849            &encoded_name,
850        ) {
851            return Err(PrivatePublicationError::new(
852                PrivatePublicationPhase::Unpublished,
853                file,
854                error,
855            ));
856        }
857
858        after_rename();
859        let postcondition = (|| {
860            verify_with_user(
861                destination_directory.as_raw_handle(),
862                &user,
863                PrivateObjectKind::Directory,
864            )?;
865            verify_with_user(file.as_raw_handle(), &user, file_kind)?;
866            verify_directory_entry_identity(
867                destination_directory.as_raw_handle(),
868                destination_name,
869                file.as_raw_handle(),
870            )
871        })();
872        if let Err(error) = postcondition {
873            return Err(PrivatePublicationError::new(
874                PrivatePublicationPhase::CommitUncertain,
875                file,
876                error,
877            ));
878        }
879        Ok(file)
880    }
881
882    pub(super) fn complete_publication_pair(
883        staging: File,
884        destination_directory: BorrowedHandle<'_>,
885        destination_name: &OsStr,
886    ) -> Result<File, PrivatePublicationPairError> {
887        complete_publication_pair_with_callback(
888            staging,
889            destination_directory,
890            destination_name,
891            || {},
892        )
893    }
894
895    fn complete_publication_pair_with_callback(
896        staging: File,
897        destination_directory: BorrowedHandle<'_>,
898        destination_name: &OsStr,
899        after_staging_unlink: impl FnOnce(),
900    ) -> Result<File, PrivatePublicationPairError> {
901        let user = match current_process_user_sid() {
902            Ok(user) => user,
903            Err(error) => {
904                return Err(PrivatePublicationPairError::new(
905                    PrivatePublicationPairPhase::PairIntact,
906                    staging,
907                    error,
908                ));
909            }
910        };
911        let final_transient = match open_bound_publication_pair_final(
912            &staging,
913            destination_directory,
914            destination_name,
915            &user,
916        ) {
917            Ok(file) => file,
918            Err(error) => {
919                return Err(PrivatePublicationPairError::new(
920                    PrivatePublicationPairPhase::PairIntact,
921                    staging,
922                    error,
923                ));
924            }
925        };
926        if let Err(error) = remove_link_by_handle_posix(staging.as_raw_handle()) {
927            drop(final_transient);
928            return Err(PrivatePublicationPairError::new(
929                PrivatePublicationPairPhase::PairIntact,
930                staging,
931                error,
932            ));
933        }
934        drop(staging);
935
936        after_staging_unlink();
937        let sealed = match reopen_private_file(
938            final_transient.as_raw_handle(),
939            FILE_GENERIC_READ | DELETE,
940            FILE_SHARE_READ,
941        ) {
942            Ok(file) => file,
943            Err(error) => {
944                return Err(PrivatePublicationPairError::new(
945                    PrivatePublicationPairPhase::FinalSingleSharedDelete,
946                    final_transient,
947                    error,
948                ));
949            }
950        };
951        drop(final_transient);
952
953        if let Err(error) =
954            verify_completed_publication(&sealed, destination_directory, destination_name, &user)
955        {
956            return Err(PrivatePublicationPairError::new(
957                PrivatePublicationPairPhase::FinalSingleSealed,
958                sealed,
959                error,
960            ));
961        }
962        Ok(sealed)
963    }
964
965    fn open_bound_publication_pair_final(
966        staging: &File,
967        destination_directory: BorrowedHandle<'_>,
968        destination_name: &OsStr,
969        user: &Sid,
970    ) -> Result<File, PrivateDirectoryError> {
971        private_destination_name(destination_name)?;
972        let pair_kind = PrivateObjectKind::RegularFile(PrivateFileLinkState::PublicationPair);
973        verify_with_user(
974            destination_directory.as_raw_handle(),
975            user,
976            PrivateObjectKind::Directory,
977        )?;
978        verify_with_user(staging.as_raw_handle(), user, pair_kind)?;
979        verify_directory_entry_identity(
980            destination_directory.as_raw_handle(),
981            destination_name,
982            staging.as_raw_handle(),
983        )?;
984        let destination_path =
985            final_path_from_handle(destination_directory.as_raw_handle())?.join(destination_name);
986        let destination_path = wide_path(&destination_path)?;
987        let final_file = open_regular_file_with_access(
988            &destination_path,
989            FILE_GENERIC_READ,
990            FILE_SHARE_READ | FILE_SHARE_DELETE,
991        )?;
992        verify_with_user(final_file.as_raw_handle(), user, pair_kind)?;
993        verify_same_file(staging.as_raw_handle(), final_file.as_raw_handle())?;
994        verify_directory_entry_identity(
995            destination_directory.as_raw_handle(),
996            destination_name,
997            final_file.as_raw_handle(),
998        )?;
999        verify_with_user(staging.as_raw_handle(), user, pair_kind)?;
1000        Ok(final_file)
1001    }
1002
1003    fn verify_completed_publication(
1004        final_file: &File,
1005        destination_directory: BorrowedHandle<'_>,
1006        destination_name: &OsStr,
1007        user: &Sid,
1008    ) -> Result<(), PrivateDirectoryError> {
1009        verify_with_user(
1010            final_file.as_raw_handle(),
1011            user,
1012            PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
1013        )?;
1014        verify_with_user(
1015            destination_directory.as_raw_handle(),
1016            user,
1017            PrivateObjectKind::Directory,
1018        )?;
1019        verify_directory_entry_identity(
1020            destination_directory.as_raw_handle(),
1021            destination_name,
1022            final_file.as_raw_handle(),
1023        )
1024    }
1025
1026    pub(super) fn seal_staging_file(file: File) -> Result<File, PrivateDirectoryError> {
1027        seal_staging_file_with_transition(file, || {})
1028    }
1029
1030    fn seal_staging_file_with_transition(
1031        file: File,
1032        after_writer_drop: impl FnOnce(),
1033    ) -> Result<File, PrivateDirectoryError> {
1034        let kind = PrivateObjectKind::RegularFile(PrivateFileLinkState::Single);
1035        let user = match current_process_user_sid() {
1036            Ok(user) => user,
1037            Err(error) => {
1038                drop(file);
1039                return Err(error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, None));
1040            }
1041        };
1042        if let Err(error) = verify_with_user(file.as_raw_handle(), &user, kind) {
1043            let cleanup = remove_verified_private(file.as_raw_handle(), &user, kind);
1044            drop(file);
1045            return Err(error_after_cleanup(error, cleanup));
1046        }
1047        let transient = match reopen_private_staging_file(
1048            file.as_raw_handle(),
1049            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1050        ) {
1051            Ok(transient) => transient,
1052            Err(error) => {
1053                let cleanup = remove_verified_private(file.as_raw_handle(), &user, kind);
1054                drop(file);
1055                return Err(error_after_cleanup(error, cleanup));
1056            }
1057        };
1058        drop(file);
1059        after_writer_drop();
1060
1061        let sealed = match reopen_private_staging_file(
1062            transient.as_raw_handle(),
1063            FILE_SHARE_READ | FILE_SHARE_DELETE,
1064        ) {
1065            Ok(sealed) => sealed,
1066            Err(error) => {
1067                let cleanup = remove_verified_private(transient.as_raw_handle(), &user, kind);
1068                drop(transient);
1069                return Err(error_after_cleanup(error, cleanup));
1070            }
1071        };
1072        drop(transient);
1073        if let Err(error) = verify_with_user(sealed.as_raw_handle(), &user, kind) {
1074            let cleanup = remove_verified_private(sealed.as_raw_handle(), &user, kind);
1075            drop(sealed);
1076            return Err(error_after_cleanup(error, cleanup));
1077        }
1078        Ok(sealed)
1079    }
1080
1081    pub(super) fn verify(handle: BorrowedHandle<'_>) -> Result<(), PrivateDirectoryError> {
1082        let user = current_process_user_sid()?;
1083        verify_with_user(handle.as_raw_handle(), &user, PrivateObjectKind::Directory)
1084    }
1085
1086    pub(super) fn sync_directory(
1087        directory: BorrowedHandle<'_>,
1088    ) -> Result<(), PrivateDirectoryError> {
1089        let user = current_process_user_sid()?;
1090        verify_with_user(
1091            directory.as_raw_handle(),
1092            &user,
1093            PrivateObjectKind::Directory,
1094        )?;
1095
1096        let mut io_status = IO_STATUS_BLOCK::default();
1097        // SAFETY: `directory` is a retained, verified directory handle with directory-write access.
1098        // Normal mode (`flags == 0`) synchronously requests cached data, metadata, and the backing
1099        // storage cache; this mode accepts no optional parameter block.
1100        let status = unsafe {
1101            NtFlushBuffersFileEx(
1102                directory.as_raw_handle(),
1103                0,
1104                ptr::null(),
1105                0,
1106                &raw mut io_status,
1107            )
1108        };
1109        if status != 0 {
1110            // SAFETY: `status` is the exact failure returned by `NtFlushBuffersFileEx`.
1111            let code = i32::try_from(unsafe { RtlNtStatusToDosError(status) }).ok();
1112            return Err(operation_error(
1113                PrivateDirectoryOperation::FlushDirectory,
1114                code,
1115            ));
1116        }
1117
1118        verify_with_user(
1119            directory.as_raw_handle(),
1120            &user,
1121            PrivateObjectKind::Directory,
1122        )
1123    }
1124
1125    pub(super) fn open_file(path: &Path) -> Result<File, PrivateDirectoryError> {
1126        let wide_path = wide_path(path)?;
1127        let file = open_regular_file(&wide_path)?;
1128        let user = current_process_user_sid()?;
1129        verify_with_user(
1130            file.as_raw_handle(),
1131            &user,
1132            PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
1133        )?;
1134        Ok(file)
1135    }
1136
1137    pub(super) fn open_file_for_sync(path: &Path) -> Result<File, PrivateDirectoryError> {
1138        let wide_path = wide_path(path)?;
1139        let file = open_regular_file_for_sync(&wide_path)?;
1140        let user = current_process_user_sid()?;
1141        verify_with_user(
1142            file.as_raw_handle(),
1143            &user,
1144            PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
1145        )?;
1146        Ok(file)
1147    }
1148
1149    pub(super) fn open_file_for_removal(path: &Path) -> Result<File, PrivateDirectoryError> {
1150        open_file_for_removal_in_state(path, PrivateFileLinkState::Single)
1151    }
1152
1153    pub(super) fn open_file_for_removal_in_state(
1154        path: &Path,
1155        link_state: PrivateFileLinkState,
1156    ) -> Result<File, PrivateDirectoryError> {
1157        let wide_path = wide_path(path)?;
1158        let file = open_regular_file_for_removal(&wide_path)?;
1159        let user = current_process_user_sid()?;
1160        verify_with_user(
1161            file.as_raw_handle(),
1162            &user,
1163            PrivateObjectKind::RegularFile(link_state),
1164        )?;
1165        Ok(file)
1166    }
1167
1168    pub(super) fn verify_file(
1169        handle: BorrowedHandle<'_>,
1170        link_state: PrivateFileLinkState,
1171    ) -> Result<(), PrivateDirectoryError> {
1172        let user = current_process_user_sid()?;
1173        verify_with_user(
1174            handle.as_raw_handle(),
1175            &user,
1176            PrivateObjectKind::RegularFile(link_state),
1177        )
1178    }
1179
1180    pub(super) fn regular_file_link_count(
1181        handle: BorrowedHandle<'_>,
1182    ) -> Result<u32, PrivateDirectoryError> {
1183        let information = query_attributes(handle.as_raw_handle())?;
1184        verify_regular_file_attributes(&information)?;
1185        Ok(information.nNumberOfLinks)
1186    }
1187
1188    pub(super) fn remove(directory: File) -> Result<(), PrivateDirectoryError> {
1189        remove_private_object(directory, PrivateObjectKind::Directory)
1190    }
1191
1192    pub(super) fn remove_file(
1193        file: File,
1194        link_state: PrivateFileLinkState,
1195    ) -> Result<(), PrivateDirectoryError> {
1196        remove_private_object(file, PrivateObjectKind::RegularFile(link_state))
1197    }
1198
1199    pub(super) fn remove_directory_tree(directory: File) -> Result<(), PrivateDirectoryError> {
1200        remove_directory_tree_with_callback(directory, |_| {})
1201    }
1202
1203    fn remove_directory_tree_with_callback(
1204        directory: File,
1205        before_removal: impl FnOnce(&Path),
1206    ) -> Result<(), PrivateDirectoryError> {
1207        let result = (|| {
1208            let user = current_process_user_sid()?;
1209            verify_with_user(
1210                directory.as_raw_handle(),
1211                &user,
1212                PrivateObjectKind::Directory,
1213            )?;
1214            let path = final_path_from_handle(directory.as_raw_handle())?;
1215            let children = bind_directory_children(&directory, &path)?;
1216            before_removal(&path);
1217            remove_bound_directory(directory, children)
1218        })();
1219        result.map_err(|error| {
1220            let code = error.os_code();
1221            error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, code)
1222        })
1223    }
1224
1225    fn bind_directory_children(
1226        directory: &File,
1227        path: &Path,
1228    ) -> Result<Vec<BoundTreeEntry>, PrivateDirectoryError> {
1229        let entries = enumerate_directory(directory.as_raw_handle())?;
1230        let mut children = Vec::with_capacity(entries.len());
1231        for entry in entries {
1232            let child_path = path.join(&entry.name);
1233            let child = open_tree_entry(&child_path)?;
1234            let identity = query_file_identity(child.as_raw_handle())?;
1235            if identity.FileId.Identifier != entry.file_id {
1236                return Err(PrivateDirectoryError::new(
1237                    PrivateDirectoryErrorKind::IdentityMismatch,
1238                    None,
1239                ));
1240            }
1241            let information = query_attributes(child.as_raw_handle())?;
1242            if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1243                return Err(PrivateDirectoryError::new(
1244                    PrivateDirectoryErrorKind::ReparsePoint,
1245                    None,
1246                ));
1247            }
1248            if information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
1249                let child_path = final_path_from_handle(child.as_raw_handle())?;
1250                children.push(BoundTreeEntry::Directory {
1251                    handle: child,
1252                    path: child_path,
1253                });
1254            } else {
1255                verify_regular_file_attributes(&information)?;
1256                if information.nNumberOfLinks != 1 {
1257                    return Err(PrivateDirectoryError::new(
1258                        PrivateDirectoryErrorKind::MultipleLinks,
1259                        None,
1260                    ));
1261                }
1262                children.push(BoundTreeEntry::RegularFile(child));
1263            }
1264        }
1265        Ok(children)
1266    }
1267
1268    fn remove_bound_directory(
1269        directory: File,
1270        children: Vec<BoundTreeEntry>,
1271    ) -> Result<(), PrivateDirectoryError> {
1272        for child in children {
1273            match child {
1274                BoundTreeEntry::Directory { handle, path } => {
1275                    let information = query_attributes(handle.as_raw_handle())?;
1276                    if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1277                        return Err(PrivateDirectoryError::new(
1278                            PrivateDirectoryErrorKind::ReparsePoint,
1279                            None,
1280                        ));
1281                    }
1282                    if information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 {
1283                        return Err(PrivateDirectoryError::new(
1284                            PrivateDirectoryErrorKind::NotDirectory,
1285                            None,
1286                        ));
1287                    }
1288                    let grandchildren = bind_directory_children(&handle, &path)?;
1289                    remove_bound_directory(handle, grandchildren)?;
1290                }
1291                BoundTreeEntry::RegularFile(file) => {
1292                    let information = query_attributes(file.as_raw_handle())?;
1293                    verify_regular_file_attributes(&information)?;
1294                    if information.nNumberOfLinks != 1 {
1295                        return Err(PrivateDirectoryError::new(
1296                            PrivateDirectoryErrorKind::MultipleLinks,
1297                            None,
1298                        ));
1299                    }
1300                    remove_by_handle(
1301                        file.as_raw_handle(),
1302                        PrivateObjectKind::RegularFile(PrivateFileLinkState::Single),
1303                    )?;
1304                    drop(file);
1305                }
1306            }
1307        }
1308        let information = query_attributes(directory.as_raw_handle())?;
1309        if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1310            return Err(PrivateDirectoryError::new(
1311                PrivateDirectoryErrorKind::ReparsePoint,
1312                None,
1313            ));
1314        }
1315        if information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 {
1316            return Err(PrivateDirectoryError::new(
1317                PrivateDirectoryErrorKind::NotDirectory,
1318                None,
1319            ));
1320        }
1321        remove_by_handle(directory.as_raw_handle(), PrivateObjectKind::Directory)?;
1322        drop(directory);
1323        Ok(())
1324    }
1325
1326    fn remove_private_object(
1327        object: File,
1328        kind: PrivateObjectKind,
1329    ) -> Result<(), PrivateDirectoryError> {
1330        let cleanup = current_process_user_sid().and_then(|user| {
1331            verify_with_user(object.as_raw_handle(), &user, kind)?;
1332            remove_by_handle(object.as_raw_handle(), kind)
1333        });
1334        match cleanup {
1335            Ok(()) => {
1336                drop(object);
1337                Ok(())
1338            }
1339            Err(error) => {
1340                let code = error.os_code();
1341                Err(error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, code))
1342            }
1343        }
1344    }
1345
1346    fn verify_with_user(
1347        handle: HANDLE,
1348        user: &Sid,
1349        kind: PrivateObjectKind,
1350    ) -> Result<(), PrivateDirectoryError> {
1351        verify_object_policy(handle, user, kind)?;
1352        verify_filesystem(handle)
1353    }
1354
1355    fn verify_object_policy(
1356        handle: HANDLE,
1357        user: &Sid,
1358        kind: PrivateObjectKind,
1359    ) -> Result<(), PrivateDirectoryError> {
1360        verify_attributes(handle, kind)?;
1361        let descriptor = read_security_descriptor(handle)?;
1362        verify_security_descriptor(&descriptor, user, kind)
1363    }
1364
1365    fn remove_verified_private(
1366        handle: HANDLE,
1367        user: &Sid,
1368        kind: PrivateObjectKind,
1369    ) -> Result<(), PrivateDirectoryError> {
1370        verify_object_policy(handle, user, kind)?;
1371        remove_by_handle(handle, kind)
1372    }
1373
1374    fn verify_attributes(
1375        handle: HANDLE,
1376        kind: PrivateObjectKind,
1377    ) -> Result<(), PrivateDirectoryError> {
1378        let information = query_attributes(handle)?;
1379        if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1380            return Err(PrivateDirectoryError::new(
1381                PrivateDirectoryErrorKind::ReparsePoint,
1382                None,
1383            ));
1384        }
1385        match kind {
1386            PrivateObjectKind::Directory
1387                if information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 =>
1388            {
1389                return Err(PrivateDirectoryError::new(
1390                    PrivateDirectoryErrorKind::NotDirectory,
1391                    None,
1392                ));
1393            }
1394            PrivateObjectKind::RegularFile(link_state) => {
1395                verify_regular_file_attributes(&information)?;
1396                if information.nNumberOfLinks != link_state.expected_links() {
1397                    return Err(PrivateDirectoryError::new(
1398                        PrivateDirectoryErrorKind::MultipleLinks,
1399                        None,
1400                    ));
1401                }
1402            }
1403            PrivateObjectKind::Directory => {}
1404        }
1405        Ok(())
1406    }
1407
1408    fn query_attributes(
1409        handle: HANDLE,
1410    ) -> Result<BY_HANDLE_FILE_INFORMATION, PrivateDirectoryError> {
1411        let mut information = BY_HANDLE_FILE_INFORMATION::default();
1412        // SAFETY: `handle` is borrowed for the call and `information` is writable initialized
1413        // storage of the exact structure expected by `GetFileInformationByHandle`.
1414        unsafe {
1415            win_bool(
1416                GetFileInformationByHandle(handle, &raw mut information),
1417                PrivateDirectoryOperation::QueryAttributes,
1418            )?;
1419        }
1420        Ok(information)
1421    }
1422
1423    fn query_file_identity(handle: HANDLE) -> Result<FILE_ID_INFO, PrivateDirectoryError> {
1424        let mut identity = FILE_ID_INFO::default();
1425        let size = u32::try_from(size_of::<FILE_ID_INFO>())
1426            .map_err(|_| operation_error(PrivateDirectoryOperation::QueryAttributes, None))?;
1427        // SAFETY: `handle` is borrowed and `identity` is writable storage of the advertised size.
1428        unsafe {
1429            win_bool(
1430                GetFileInformationByHandleEx(handle, FileIdInfo, (&raw mut identity).cast(), size),
1431                PrivateDirectoryOperation::QueryAttributes,
1432            )?;
1433        }
1434        Ok(identity)
1435    }
1436
1437    fn verify_directory_entry_identity(
1438        directory: HANDLE,
1439        name: &OsStr,
1440        file: HANDLE,
1441    ) -> Result<(), PrivateDirectoryError> {
1442        let directory_identity = query_file_identity(directory)?;
1443        let file_identity = query_file_identity(file)?;
1444        if directory_identity.VolumeSerialNumber != file_identity.VolumeSerialNumber {
1445            return Err(directory_identity_error());
1446        }
1447        let entry = enumerate_directory(directory)?
1448            .into_iter()
1449            .find(|entry| entry.name == name)
1450            .ok_or_else(directory_identity_error)?;
1451        if entry.file_id != file_identity.FileId.Identifier {
1452            return Err(directory_identity_error());
1453        }
1454        Ok(())
1455    }
1456
1457    fn verify_directory_contains_file_identity(
1458        directory: HANDLE,
1459        file: HANDLE,
1460    ) -> Result<(), PrivateDirectoryError> {
1461        let directory_identity = query_file_identity(directory)?;
1462        let file_identity = query_file_identity(file)?;
1463        if directory_identity.VolumeSerialNumber != file_identity.VolumeSerialNumber {
1464            return Err(directory_identity_error());
1465        }
1466        let matching_entries = enumerate_directory(directory)?
1467            .into_iter()
1468            .filter(|entry| entry.file_id == file_identity.FileId.Identifier)
1469            .count();
1470        if matching_entries != 1 {
1471            return Err(directory_identity_error());
1472        }
1473        Ok(())
1474    }
1475
1476    fn verify_same_file(left: HANDLE, right: HANDLE) -> Result<(), PrivateDirectoryError> {
1477        let left = query_file_identity(left)?;
1478        let right = query_file_identity(right)?;
1479        if left.VolumeSerialNumber != right.VolumeSerialNumber
1480            || left.FileId.Identifier != right.FileId.Identifier
1481        {
1482            return Err(directory_identity_error());
1483        }
1484        Ok(())
1485    }
1486
1487    fn enumerate_directory(
1488        handle: HANDLE,
1489    ) -> Result<Vec<DirectoryEntryIdentity>, PrivateDirectoryError> {
1490        let words = DIRECTORY_ENUMERATION_BUFFER_BYTES / size_of::<u64>();
1491        let buffer_size = u32::try_from(DIRECTORY_ENUMERATION_BUFFER_BYTES)
1492            .map_err(|_| operation_error(PrivateDirectoryOperation::EnumerateDirectory, None))?;
1493        let mut buffer = vec![0_u64; words];
1494        let mut entries = Vec::new();
1495        let mut names = BTreeSet::new();
1496        let mut restart = true;
1497        loop {
1498            let information_class = if restart {
1499                FileIdExtdDirectoryRestartInfo
1500            } else {
1501                FileIdExtdDirectoryInfo
1502            };
1503            // SAFETY: `handle` is a retained directory handle and `buffer` is aligned writable
1504            // storage of exactly `buffer_size` bytes for the requested directory records.
1505            let succeeded = unsafe {
1506                GetFileInformationByHandleEx(
1507                    handle,
1508                    information_class,
1509                    buffer.as_mut_ptr().cast(),
1510                    buffer_size,
1511                )
1512            };
1513            if succeeded == 0 {
1514                let code = last_os_code();
1515                if is_win32_error(code, ERROR_NO_MORE_FILES) {
1516                    break;
1517                }
1518                return Err(operation_error(
1519                    PrivateDirectoryOperation::EnumerateDirectory,
1520                    code,
1521                ));
1522            }
1523            restart = false;
1524            for entry in parse_directory_entries(&buffer)? {
1525                if entry.name.as_os_str() == "." || entry.name.as_os_str() == ".." {
1526                    continue;
1527                }
1528                let mut components = Path::new(&entry.name).components();
1529                if !matches!(components.next(), Some(Component::Normal(_)))
1530                    || components.next().is_some()
1531                    || !names.insert(entry.name.clone())
1532                {
1533                    return Err(PrivateDirectoryError::new(
1534                        PrivateDirectoryErrorKind::IdentityMismatch,
1535                        None,
1536                    ));
1537                }
1538                entries.push(entry);
1539            }
1540        }
1541        Ok(entries)
1542    }
1543
1544    fn parse_directory_entries(
1545        buffer: &[u64],
1546    ) -> Result<Vec<DirectoryEntryIdentity>, PrivateDirectoryError> {
1547        let bytes = size_of_val(buffer);
1548        // SAFETY: viewing initialized `u64` storage as bytes preserves its allocation and bounds.
1549        let raw_buffer = unsafe { slice::from_raw_parts(buffer.as_ptr().cast::<u8>(), bytes) };
1550        let name_offset = offset_of!(FILE_ID_EXTD_DIR_INFO, FileName);
1551        let mut cursor = 0_usize;
1552        let mut entries = Vec::new();
1553        loop {
1554            let _header_end = cursor
1555                .checked_add(size_of::<FILE_ID_EXTD_DIR_INFO>())
1556                .filter(|end| *end <= bytes)
1557                .ok_or_else(directory_identity_error)?;
1558            // SAFETY: the bounds above cover one complete fixed record header. Windows aligns
1559            // records, but `read_unaligned` also keeps this parser sound if an invalid offset does
1560            // not preserve that alignment.
1561            let information = unsafe {
1562                ptr::read_unaligned(
1563                    raw_buffer
1564                        .as_ptr()
1565                        .add(cursor)
1566                        .cast::<FILE_ID_EXTD_DIR_INFO>(),
1567                )
1568            };
1569            let name_bytes = usize::try_from(information.FileNameLength)
1570                .map_err(|_| directory_identity_error())?;
1571            if name_bytes % size_of::<u16>() != 0 {
1572                return Err(directory_identity_error());
1573            }
1574            let name_start = cursor
1575                .checked_add(name_offset)
1576                .ok_or_else(directory_identity_error)?;
1577            let name_end = name_start
1578                .checked_add(name_bytes)
1579                .filter(|end| *end <= bytes)
1580                .ok_or_else(directory_identity_error)?;
1581            let name = raw_buffer[name_start..name_end]
1582                .chunks_exact(size_of::<u16>())
1583                .map(|unit| u16::from_le_bytes([unit[0], unit[1]]))
1584                .collect::<Vec<_>>();
1585            entries.push(DirectoryEntryIdentity {
1586                name: OsString::from_wide(&name),
1587                file_id: information.FileId.Identifier,
1588            });
1589
1590            if information.NextEntryOffset == 0 {
1591                break;
1592            }
1593            let next = usize::try_from(information.NextEntryOffset)
1594                .map_err(|_| directory_identity_error())?;
1595            if next < name_end - cursor {
1596                return Err(directory_identity_error());
1597            }
1598            cursor = cursor
1599                .checked_add(next)
1600                .filter(|next| *next < bytes)
1601                .ok_or_else(directory_identity_error)?;
1602        }
1603        Ok(entries)
1604    }
1605
1606    fn directory_identity_error() -> PrivateDirectoryError {
1607        PrivateDirectoryError::new(PrivateDirectoryErrorKind::IdentityMismatch, None)
1608    }
1609
1610    fn verify_regular_file_attributes(
1611        information: &BY_HANDLE_FILE_INFORMATION,
1612    ) -> Result<(), PrivateDirectoryError> {
1613        if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
1614            return Err(PrivateDirectoryError::new(
1615                PrivateDirectoryErrorKind::ReparsePoint,
1616                None,
1617            ));
1618        }
1619        if information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
1620            return Err(PrivateDirectoryError::new(
1621                PrivateDirectoryErrorKind::NotRegularFile,
1622                None,
1623            ));
1624        }
1625        Ok(())
1626    }
1627
1628    fn verify_filesystem(handle: HANDLE) -> Result<(), PrivateDirectoryError> {
1629        let mut flags = 0;
1630        // SAFETY: all optional output buffers are null with zero lengths; `flags` is valid writable
1631        // storage and `handle` remains borrowed for the duration of the call.
1632        let succeeded = unsafe {
1633            GetVolumeInformationByHandleW(
1634                handle,
1635                ptr::null_mut(),
1636                0,
1637                ptr::null_mut(),
1638                ptr::null_mut(),
1639                &raw mut flags,
1640                ptr::null_mut(),
1641                0,
1642            )
1643        };
1644        win_bool(succeeded, PrivateDirectoryOperation::QueryFileSystem)?;
1645        if flags & FILE_PERSISTENT_ACLS == 0 {
1646            return Err(PrivateDirectoryError::new(
1647                PrivateDirectoryErrorKind::UnsupportedFileSystem,
1648                None,
1649            ));
1650        }
1651        Ok(())
1652    }
1653
1654    fn read_security_descriptor(handle: HANDLE) -> Result<AlignedBuffer, PrivateDirectoryError> {
1655        let requested = OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;
1656        let mut required = 0;
1657        // SAFETY: a null output buffer with length zero is the documented sizing query; `required`
1658        // is valid writable storage and `handle` remains borrowed.
1659        let sized = unsafe {
1660            GetKernelObjectSecurity(handle, requested, ptr::null_mut(), 0, &raw mut required)
1661        };
1662        let sizing_code = last_os_code();
1663        if sized != 0 || !is_win32_error(sizing_code, ERROR_INSUFFICIENT_BUFFER) {
1664            return Err(security_query_error(sizing_code));
1665        }
1666        let required = usize::try_from(required).map_err(|_| security_query_error(None))?;
1667        if required == 0 || required > MAX_SECURITY_DESCRIPTOR_BYTES {
1668            return Err(security_query_error(None));
1669        }
1670        let mut descriptor = AlignedBuffer::new(required);
1671        let length = u32::try_from(descriptor.len()).map_err(|_| security_query_error(None))?;
1672        let mut returned = length;
1673        // SAFETY: the aligned output buffer is writable for `length` bytes and the kernel fills a
1674        // self-relative security descriptor on success.
1675        let succeeded = unsafe {
1676            GetKernelObjectSecurity(
1677                handle,
1678                requested,
1679                descriptor.as_mut_ptr(),
1680                length,
1681                &raw mut returned,
1682            )
1683        };
1684        win_bool(
1685            succeeded,
1686            PrivateDirectoryOperation::QuerySecurityDescriptor,
1687        )?;
1688        if returned == 0 || returned > length {
1689            return Err(security_query_error(None));
1690        }
1691        descriptor.len = usize::try_from(returned).map_err(|_| security_query_error(None))?;
1692        Ok(descriptor)
1693    }
1694
1695    fn verify_security_descriptor(
1696        descriptor: &AlignedBuffer,
1697        user: &Sid,
1698        kind: PrivateObjectKind,
1699    ) -> Result<(), PrivateDirectoryError> {
1700        // SAFETY: `descriptor` is an aligned buffer populated by `GetKernelObjectSecurity` and
1701        // remains alive while Windows returns pointers into it.
1702        let valid = unsafe { IsValidSecurityDescriptor(descriptor.as_ptr()) };
1703        if valid == 0 {
1704            return Err(security_query_error(last_os_code()));
1705        }
1706        let mut control = 0;
1707        let mut revision = 0;
1708        // SAFETY: both outputs are writable and the descriptor has passed Windows validation.
1709        unsafe {
1710            win_bool(
1711                GetSecurityDescriptorControl(
1712                    descriptor.as_ptr(),
1713                    &raw mut control,
1714                    &raw mut revision,
1715                ),
1716                PrivateDirectoryOperation::QuerySecurityDescriptor,
1717            )?;
1718        }
1719        if control & SE_DACL_PROTECTED == 0 {
1720            return Err(PrivateDirectoryError::new(
1721                PrivateDirectoryErrorKind::DaclUnprotected,
1722                None,
1723            ));
1724        }
1725
1726        let mut owner = ptr::null_mut();
1727        let mut owner_defaulted = 0;
1728        // SAFETY: outputs are writable and the validated descriptor remains alive.
1729        unsafe {
1730            win_bool(
1731                GetSecurityDescriptorOwner(
1732                    descriptor.as_ptr(),
1733                    &raw mut owner,
1734                    &raw mut owner_defaulted,
1735                ),
1736                PrivateDirectoryOperation::QuerySecurityDescriptor,
1737            )?;
1738        }
1739        if owner.is_null() {
1740            return Err(PrivateDirectoryError::new(
1741                PrivateDirectoryErrorKind::OwnerMissing,
1742                None,
1743            ));
1744        }
1745        let owner = descriptor_sid(descriptor, owner).ok_or_else(|| {
1746            PrivateDirectoryError::new(PrivateDirectoryErrorKind::OwnerMismatch, None)
1747        })?;
1748        if owner != user.as_bytes() {
1749            return Err(PrivateDirectoryError::new(
1750                PrivateDirectoryErrorKind::OwnerMismatch,
1751                None,
1752            ));
1753        }
1754
1755        let mut dacl_present = 0;
1756        let mut dacl = ptr::null_mut();
1757        let mut dacl_defaulted = 0;
1758        // SAFETY: outputs are writable and the validated descriptor remains alive.
1759        unsafe {
1760            win_bool(
1761                GetSecurityDescriptorDacl(
1762                    descriptor.as_ptr(),
1763                    &raw mut dacl_present,
1764                    &raw mut dacl,
1765                    &raw mut dacl_defaulted,
1766                ),
1767                PrivateDirectoryOperation::QuerySecurityDescriptor,
1768            )?;
1769        }
1770        if dacl_present == 0 || dacl.is_null() {
1771            return Err(PrivateDirectoryError::new(
1772                PrivateDirectoryErrorKind::DaclMissing,
1773                None,
1774            ));
1775        }
1776        let raw_acl = descriptor_acl(descriptor, dacl).ok_or_else(|| {
1777            PrivateDirectoryError::new(
1778                PrivateDirectoryErrorKind::DaclPolicy(PrivateDirectoryAclViolation::Malformed),
1779                None,
1780            )
1781        })?;
1782        let system = Sid::well_known(WinLocalSystemSid)?;
1783        let administrators = Sid::well_known(WinBuiltinAdministratorsSid)?;
1784        let allowed = allowed_sids(user, &system, &administrators);
1785        let allowed = allowed.iter().map(|sid| sid.as_bytes()).collect::<Vec<_>>();
1786        validate_acl_with_flags(raw_acl, &allowed, kind.ace_flags()).map_err(|violation| {
1787            PrivateDirectoryError::new(PrivateDirectoryErrorKind::DaclPolicy(violation), None)
1788        })
1789    }
1790
1791    fn allowed_sids<'a>(user: &'a Sid, system: &'a Sid, administrators: &'a Sid) -> Vec<&'a Sid> {
1792        let mut allowed = Vec::with_capacity(3);
1793        for sid in [user, system, administrators] {
1794            if !allowed
1795                .iter()
1796                .any(|present: &&Sid| present.as_bytes() == sid.as_bytes())
1797            {
1798                allowed.push(sid);
1799            }
1800        }
1801        allowed
1802    }
1803
1804    fn descriptor_sid(descriptor: &AlignedBuffer, sid: PSID) -> Option<&[u8]> {
1805        let bytes = descriptor.as_bytes();
1806        let offset = pointer_offset(bytes, sid.cast(), SID_HEADER_BYTES)?;
1807        let header = bytes.get(offset..offset + SID_HEADER_BYTES)?;
1808        let length = SID_HEADER_BYTES.checked_add(usize::from(header[1]) * size_of::<u32>())?;
1809        let sid = bytes.get(offset..offset.checked_add(length)?)?;
1810        valid_sid_bytes(sid).then_some(sid)
1811    }
1812
1813    fn descriptor_acl(descriptor: &AlignedBuffer, acl: *const ACL) -> Option<&[u8]> {
1814        let bytes = descriptor.as_bytes();
1815        let offset = pointer_offset(bytes, acl.cast(), size_of::<ACL>())?;
1816        let header = bytes.get(offset..offset + size_of::<ACL>())?;
1817        let length = usize::from(u16::from_le_bytes([header[2], header[3]]));
1818        bytes.get(offset..offset.checked_add(length)?)
1819    }
1820
1821    fn pointer_offset(bytes: &[u8], pointer: *const u8, minimum: usize) -> Option<usize> {
1822        let base = bytes.as_ptr() as usize;
1823        let end = base.checked_add(bytes.len())?;
1824        let pointer = pointer as usize;
1825        let required_end = pointer.checked_add(minimum)?;
1826        (pointer >= base && required_end <= end).then_some(pointer - base)
1827    }
1828
1829    fn current_process_user_sid() -> Result<Sid, PrivateDirectoryError> {
1830        let mut token: HANDLE = ptr::null_mut();
1831        // SAFETY: `token` is writable and receives an owned real handle when the call succeeds.
1832        unsafe {
1833            win_bool(
1834                OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &raw mut token),
1835                PrivateDirectoryOperation::QueryProcessToken,
1836            )?;
1837        }
1838        if token.is_null() {
1839            return Err(operation_error(
1840                PrivateDirectoryOperation::QueryProcessToken,
1841                None,
1842            ));
1843        }
1844        // SAFETY: successful `OpenProcessToken` transfers one owned handle, closed by `OwnedHandle`.
1845        let token = unsafe { OwnedHandle::from_raw_handle(token) };
1846        let mut required = 0;
1847        // SAFETY: this is the documented zero-length sizing query.
1848        let sized = unsafe {
1849            GetTokenInformation(
1850                token.as_raw_handle(),
1851                TokenUser,
1852                ptr::null_mut(),
1853                0,
1854                &raw mut required,
1855            )
1856        };
1857        let sizing_code = last_os_code();
1858        if sized != 0 || !is_win32_error(sizing_code, ERROR_INSUFFICIENT_BUFFER) || required == 0 {
1859            return Err(operation_error(
1860                PrivateDirectoryOperation::QueryProcessToken,
1861                sizing_code,
1862            ));
1863        }
1864        let mut information =
1865            AlignedBuffer::new(usize::try_from(required).map_err(|_| {
1866                operation_error(PrivateDirectoryOperation::QueryProcessToken, None)
1867            })?);
1868        // SAFETY: the aligned buffer is writable for the requested number of bytes.
1869        unsafe {
1870            win_bool(
1871                GetTokenInformation(
1872                    token.as_raw_handle(),
1873                    TokenUser,
1874                    information.as_mut_ptr(),
1875                    required,
1876                    &raw mut required,
1877                ),
1878                PrivateDirectoryOperation::QueryProcessToken,
1879            )?;
1880            let token_user = information.as_ptr().cast::<TOKEN_USER>();
1881            Sid::copy_from((*token_user).User.Sid)
1882        }
1883    }
1884
1885    fn with_private_security_attributes<T>(
1886        kind: PrivateObjectKind,
1887        operation: impl FnOnce(*const SECURITY_ATTRIBUTES) -> Result<T, PrivateDirectoryError>,
1888    ) -> Result<(T, Sid), PrivateDirectoryError> {
1889        let user = current_process_user_sid()?;
1890        let system = Sid::well_known(WinLocalSystemSid)?;
1891        let administrators = Sid::well_known(WinBuiltinAdministratorsSid)?;
1892        let allowed = allowed_sids(&user, &system, &administrators);
1893        let mut acl = build_acl_with_flags(&allowed, kind.ace_flags())?;
1894        let mut descriptor = SECURITY_DESCRIPTOR::default();
1895
1896        // SAFETY: the absolute descriptor and every referenced SID/ACL remain alive and fixed in
1897        // memory through `operation`; each API receives its documented initialized buffer.
1898        unsafe {
1899            win_bool(
1900                InitializeSecurityDescriptor(
1901                    (&raw mut descriptor).cast(),
1902                    SECURITY_DESCRIPTOR_REVISION,
1903                ),
1904                PrivateDirectoryOperation::BuildSecurityDescriptor,
1905            )?;
1906            win_bool(
1907                SetSecurityDescriptorOwner((&raw mut descriptor).cast(), user.as_psid(), 0),
1908                PrivateDirectoryOperation::BuildSecurityDescriptor,
1909            )?;
1910            win_bool(
1911                SetSecurityDescriptorDacl(
1912                    (&raw mut descriptor).cast(),
1913                    1,
1914                    acl.as_mut_ptr().cast(),
1915                    0,
1916                ),
1917                PrivateDirectoryOperation::BuildSecurityDescriptor,
1918            )?;
1919            win_bool(
1920                SetSecurityDescriptorControl(
1921                    (&raw mut descriptor).cast(),
1922                    SE_DACL_PROTECTED,
1923                    SE_DACL_PROTECTED,
1924                ),
1925                PrivateDirectoryOperation::BuildSecurityDescriptor,
1926            )?;
1927        }
1928
1929        let attributes = SECURITY_ATTRIBUTES {
1930            nLength: u32::try_from(size_of::<SECURITY_ATTRIBUTES>()).map_err(|_| {
1931                operation_error(PrivateDirectoryOperation::BuildSecurityDescriptor, None)
1932            })?,
1933            lpSecurityDescriptor: (&raw mut descriptor).cast(),
1934            bInheritHandle: 0,
1935        };
1936        let value = operation(&raw const attributes)?;
1937        Ok((value, user))
1938    }
1939
1940    #[cfg(test)]
1941    fn build_acl(sids: &[&Sid]) -> Result<AlignedBuffer, PrivateDirectoryError> {
1942        build_acl_with_flags(sids, DIRECTORY_INHERIT_FLAGS)
1943    }
1944
1945    fn build_acl_with_flags(
1946        sids: &[&Sid],
1947        ace_flags: u8,
1948    ) -> Result<AlignedBuffer, PrivateDirectoryError> {
1949        let length = size_of::<ACL>()
1950            + sids
1951                .iter()
1952                .map(|sid| {
1953                    size_of::<ACCESS_ALLOWED_ACE>() - size_of::<u32>() + sid.as_bytes().len()
1954                })
1955                .sum::<usize>();
1956        let mut acl = AlignedBuffer::new(length);
1957        let length = u32::try_from(length)
1958            .map_err(|_| operation_error(PrivateDirectoryOperation::BuildAcl, None))?;
1959        // SAFETY: `acl` is aligned writable storage of `length` bytes and every SID is valid and
1960        // remains alive while its access-allowed ACE is copied into the ACL.
1961        unsafe {
1962            win_bool(
1963                InitializeAcl(acl.as_mut_ptr().cast(), length, ACL_REVISION),
1964                PrivateDirectoryOperation::BuildAcl,
1965            )?;
1966            for sid in sids {
1967                win_bool(
1968                    AddAccessAllowedAceEx(
1969                        acl.as_mut_ptr().cast(),
1970                        ACL_REVISION,
1971                        u32::from(ace_flags),
1972                        FILE_ALL_ACCESS,
1973                        sid.as_psid(),
1974                    ),
1975                    PrivateDirectoryOperation::BuildAcl,
1976                )?;
1977            }
1978        }
1979        Ok(acl)
1980    }
1981
1982    fn open_directory(path: &[u16], write_dacl: bool) -> Result<File, PrivateDirectoryError> {
1983        let desired_access =
1984            FILE_GENERIC_READ | FILE_WRITE_DATA | DELETE | if write_dacl { WRITE_DAC } else { 0 };
1985        open_directory_with_access(path, desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE)
1986    }
1987
1988    fn open_directory_read_guard(path: &[u16]) -> Result<File, PrivateDirectoryError> {
1989        open_directory_with_access(
1990            path,
1991            FILE_GENERIC_READ | FILE_WRITE_DATA,
1992            FILE_SHARE_READ | FILE_SHARE_WRITE,
1993        )
1994    }
1995
1996    fn open_directory_with_access(
1997        path: &[u16],
1998        desired_access: u32,
1999        share_mode: u32,
2000    ) -> Result<File, PrivateDirectoryError> {
2001        // SAFETY: `path` is NUL-terminated; null security/template pointers are permitted. Opening
2002        // with `OPEN_REPARSE_POINT` ensures verification observes the named object itself.
2003        let handle = unsafe {
2004            CreateFileW(
2005                path.as_ptr(),
2006                desired_access,
2007                share_mode,
2008                ptr::null(),
2009                OPEN_EXISTING,
2010                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
2011                ptr::null_mut(),
2012            )
2013        };
2014        if handle == INVALID_HANDLE_VALUE || handle.is_null() {
2015            return Err(operation_error(
2016                PrivateDirectoryOperation::OpenDirectory,
2017                last_os_code(),
2018            ));
2019        }
2020        // SAFETY: `CreateFileW` returned one owned real handle, transferred exactly once to File.
2021        Ok(unsafe { File::from_raw_handle(handle) })
2022    }
2023
2024    fn open_tree_entry(path: &Path) -> Result<File, PrivateDirectoryError> {
2025        let wide_path = wide_path(path)?;
2026        // SAFETY: `wide_path` is NUL-terminated. `OPEN_REPARSE_POINT` binds the named entry rather
2027        // than its target, while omitting write/delete sharing keeps that entry stable until the
2028        // retained handle is either discarded or used for exact disposition.
2029        let handle = unsafe {
2030            CreateFileW(
2031                wide_path.as_ptr(),
2032                FILE_GENERIC_READ | DELETE,
2033                FILE_SHARE_READ,
2034                ptr::null(),
2035                OPEN_EXISTING,
2036                FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
2037                ptr::null_mut(),
2038            )
2039        };
2040        if handle == INVALID_HANDLE_VALUE || handle.is_null() {
2041            return Err(operation_error(
2042                PrivateDirectoryOperation::OpenTreeEntry,
2043                last_os_code(),
2044            ));
2045        }
2046        // SAFETY: `CreateFileW` returned one owned real handle, transferred exactly once to File.
2047        Ok(unsafe { File::from_raw_handle(handle) })
2048    }
2049
2050    fn final_path_from_handle(handle: HANDLE) -> Result<PathBuf, PrivateDirectoryError> {
2051        let mut buffer = vec![0_u16; 512];
2052        loop {
2053            let capacity = u32::try_from(buffer.len())
2054                .map_err(|_| operation_error(PrivateDirectoryOperation::ResolvePath, None))?;
2055            // SAFETY: `handle` is retained and `buffer` is writable for `capacity` UTF-16 units.
2056            let length =
2057                unsafe { GetFinalPathNameByHandleW(handle, buffer.as_mut_ptr(), capacity, 0) };
2058            if length == 0 {
2059                return Err(operation_error(
2060                    PrivateDirectoryOperation::ResolvePath,
2061                    last_os_code(),
2062                ));
2063            }
2064            let length = usize::try_from(length)
2065                .map_err(|_| operation_error(PrivateDirectoryOperation::ResolvePath, None))?;
2066            if length < buffer.len() {
2067                buffer.truncate(length);
2068                return Ok(PathBuf::from(OsString::from_wide(&buffer)));
2069            }
2070            let required = length
2071                .checked_add(1)
2072                .ok_or_else(|| operation_error(PrivateDirectoryOperation::ResolvePath, None))?;
2073            buffer.resize(required, 0);
2074        }
2075    }
2076
2077    fn open_regular_file(path: &[u16]) -> Result<File, PrivateDirectoryError> {
2078        open_regular_file_with_access(path, FILE_GENERIC_READ, FILE_SHARE_READ)
2079    }
2080
2081    fn open_regular_file_for_sync(path: &[u16]) -> Result<File, PrivateDirectoryError> {
2082        open_regular_file_with_access(
2083            path,
2084            FILE_GENERIC_READ | FILE_GENERIC_WRITE,
2085            FILE_SHARE_READ,
2086        )
2087    }
2088
2089    fn open_regular_file_for_removal(path: &[u16]) -> Result<File, PrivateDirectoryError> {
2090        open_regular_file_with_access(
2091            path,
2092            FILE_GENERIC_READ | DELETE,
2093            FILE_SHARE_READ | FILE_SHARE_DELETE,
2094        )
2095    }
2096
2097    fn reopen_private_staging_file(
2098        original: HANDLE,
2099        share_mode: u32,
2100    ) -> Result<File, PrivateDirectoryError> {
2101        reopen_private_file(original, FILE_GENERIC_READ | DELETE, share_mode)
2102    }
2103
2104    fn reopen_private_file(
2105        original: HANDLE,
2106        desired_access: u32,
2107        share_mode: u32,
2108    ) -> Result<File, PrivateDirectoryError> {
2109        // SAFETY: `original` is a retained verified file handle. `ReOpenFile` binds the new handle
2110        // to that exact file object and performs no pathname lookup.
2111        let handle = unsafe {
2112            ReOpenFile(
2113                original,
2114                desired_access,
2115                share_mode,
2116                FILE_FLAG_OPEN_REPARSE_POINT,
2117            )
2118        };
2119        if handle == INVALID_HANDLE_VALUE || handle.is_null() {
2120            return Err(operation_error(
2121                PrivateDirectoryOperation::OpenFile,
2122                last_os_code(),
2123            ));
2124        }
2125        // SAFETY: `ReOpenFile` returned one owned real handle, transferred exactly once to `File`.
2126        Ok(unsafe { File::from_raw_handle(handle) })
2127    }
2128
2129    fn open_regular_file_with_access(
2130        path: &[u16],
2131        desired_access: u32,
2132        share_mode: u32,
2133    ) -> Result<File, PrivateDirectoryError> {
2134        // SAFETY: `path` is NUL-terminated; null security/template pointers are permitted. The
2135        // caller selects access and sharing appropriate to the lifetime contract of its handle.
2136        let handle = unsafe {
2137            CreateFileW(
2138                path.as_ptr(),
2139                desired_access,
2140                share_mode,
2141                ptr::null(),
2142                OPEN_EXISTING,
2143                FILE_FLAG_OPEN_REPARSE_POINT,
2144                ptr::null_mut(),
2145            )
2146        };
2147        if handle == INVALID_HANDLE_VALUE || handle.is_null() {
2148            return Err(operation_error(
2149                PrivateDirectoryOperation::OpenFile,
2150                last_os_code(),
2151            ));
2152        }
2153        // SAFETY: `CreateFileW` returned one owned real handle, transferred exactly once to File.
2154        Ok(unsafe { File::from_raw_handle(handle) })
2155    }
2156
2157    fn remove_by_handle(
2158        handle: HANDLE,
2159        kind: PrivateObjectKind,
2160    ) -> Result<(), PrivateDirectoryError> {
2161        let operation = kind.remove_operation();
2162        let disposition = FILE_DISPOSITION_INFO { DeleteFile: true };
2163        let size = u32::try_from(size_of::<FILE_DISPOSITION_INFO>())
2164            .map_err(|_| operation_error(operation, None))?;
2165        // SAFETY: `handle` was opened with DELETE access and `disposition` is readable for `size`.
2166        let succeeded = unsafe {
2167            SetFileInformationByHandle(
2168                handle,
2169                FileDispositionInfo,
2170                (&raw const disposition).cast(),
2171                size,
2172            )
2173        };
2174        win_bool(succeeded, operation)
2175    }
2176
2177    fn remove_link_by_handle_posix(handle: HANDLE) -> Result<(), PrivateDirectoryError> {
2178        let disposition = FILE_DISPOSITION_INFO_EX {
2179            Flags: FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS,
2180        };
2181        let size = u32::try_from(size_of::<FILE_DISPOSITION_INFO_EX>())
2182            .map_err(|_| operation_error(PrivateDirectoryOperation::RemoveFile, None))?;
2183        // SAFETY: `handle` is the verified publication-pair staging handle with DELETE access.
2184        // POSIX disposition removes that exact opened link while other shared-delete handles live.
2185        let succeeded = unsafe {
2186            SetFileInformationByHandle(
2187                handle,
2188                FileDispositionInfoEx,
2189                (&raw const disposition).cast(),
2190                size,
2191            )
2192        };
2193        win_bool(succeeded, PrivateDirectoryOperation::RemoveFile)
2194    }
2195
2196    fn rename_file_handle_create_new(
2197        file: HANDLE,
2198        destination_directory: HANDLE,
2199        destination_name: &[u16],
2200    ) -> Result<(), PrivateDirectoryError> {
2201        let name_bytes = destination_name
2202            .len()
2203            .checked_mul(size_of::<u16>())
2204            .ok_or_else(|| operation_error(PrivateDirectoryOperation::RenameFile, None))?;
2205        // Windows requires at least the fixed structure size plus the complete variable name,
2206        // even though that fixed size already includes the first UTF-16 array element.
2207        let total_bytes = size_of::<FILE_RENAME_INFORMATION>()
2208            .checked_add(name_bytes)
2209            .ok_or_else(|| operation_error(PrivateDirectoryOperation::RenameFile, None))?;
2210        let name_bytes = u32::try_from(name_bytes)
2211            .map_err(|_| operation_error(PrivateDirectoryOperation::RenameFile, None))?;
2212        let total_bytes = u32::try_from(total_bytes)
2213            .map_err(|_| operation_error(PrivateDirectoryOperation::RenameFile, None))?;
2214        let mut buffer = AlignedBuffer::new(total_bytes as usize);
2215        let information = buffer.as_mut_ptr().cast::<FILE_RENAME_INFORMATION>();
2216        let mut io_status = IO_STATUS_BLOCK::default();
2217        // SAFETY: `AlignedBuffer` is aligned for `FILE_RENAME_INFORMATION` and sized through the
2218        // complete variable-length UTF-16 name. Both handles remain retained for the atomic native
2219        // rename call, and `io_status` is writable for the synchronous result.
2220        let status = unsafe {
2221            information.write(FILE_RENAME_INFORMATION::default());
2222            (*information).Anonymous.ReplaceIfExists = false;
2223            (*information).RootDirectory = destination_directory;
2224            (*information).FileNameLength = name_bytes;
2225            ptr::copy_nonoverlapping(
2226                destination_name.as_ptr(),
2227                ptr::addr_of_mut!((*information).FileName).cast::<u16>(),
2228                destination_name.len(),
2229            );
2230            NtSetInformationFile(
2231                file,
2232                &raw mut io_status,
2233                buffer.as_ptr(),
2234                total_bytes,
2235                FileRenameInformation,
2236            )
2237        };
2238        if status >= 0 {
2239            return Ok(());
2240        }
2241        // SAFETY: `status` is the failure value returned directly by `NtSetInformationFile`.
2242        let code = i32::try_from(unsafe { RtlNtStatusToDosError(status) }).ok();
2243        Err(PrivateDirectoryError::new(
2244            if is_win32_error(code, ERROR_ALREADY_EXISTS) || is_win32_error(code, ERROR_FILE_EXISTS)
2245            {
2246                PrivateDirectoryErrorKind::AlreadyExists
2247            } else {
2248                PrivateDirectoryErrorKind::WindowsApi(PrivateDirectoryOperation::RenameFile)
2249            },
2250            code,
2251        ))
2252    }
2253
2254    fn private_destination_name(name: &OsStr) -> Result<Vec<u16>, PrivateDirectoryError> {
2255        const INVALID_ASCII: [u16; 9] = [0x22, 0x2a, 0x2f, 0x3a, 0x3c, 0x3e, 0x3f, 0x5c, 0x7c];
2256
2257        let mut components = Path::new(name).components();
2258        if !matches!(components.next(), Some(Component::Normal(component)) if component == name)
2259            || components.next().is_some()
2260        {
2261            return Err(PrivateDirectoryError::new(
2262                PrivateDirectoryErrorKind::InvalidPath,
2263                None,
2264            ));
2265        }
2266        let encoded = name.encode_wide().collect::<Vec<_>>();
2267        let invalid_ascii = |unit: u16| unit < 32 || INVALID_ASCII.contains(&unit);
2268        if encoded.is_empty()
2269            || encoded.iter().copied().any(invalid_ascii)
2270            || matches!(encoded.last(), Some(unit) if *unit == u16::from(b'.') || *unit == u16::from(b' '))
2271        {
2272            return Err(PrivateDirectoryError::new(
2273                PrivateDirectoryErrorKind::InvalidPath,
2274                None,
2275            ));
2276        }
2277        Ok(encoded)
2278    }
2279
2280    fn wide_path(path: &Path) -> Result<Vec<u16>, PrivateDirectoryError> {
2281        const BACKSLASH: u16 = b'\\' as u16;
2282        const FORWARD_SLASH: u16 = b'/' as u16;
2283        const VERBATIM_PREFIX: &[u16] = &[BACKSLASH, BACKSLASH, b'?' as u16, BACKSLASH];
2284        const VERBATIM_UNC_PREFIX: &[u16] = &[
2285            BACKSLASH,
2286            BACKSLASH,
2287            b'?' as u16,
2288            BACKSLASH,
2289            b'U' as u16,
2290            b'N' as u16,
2291            b'C' as u16,
2292            BACKSLASH,
2293        ];
2294
2295        let mut encoded = path.as_os_str().encode_wide().collect::<Vec<_>>();
2296        if !path.is_absolute() || encoded.contains(&0) {
2297            return Err(PrivateDirectoryError::new(
2298                PrivateDirectoryErrorKind::InvalidPath,
2299                None,
2300            ));
2301        }
2302        if encoded.starts_with(VERBATIM_PREFIX) {
2303            encoded.push(0);
2304            return Ok(encoded);
2305        }
2306        for unit in &mut encoded {
2307            if *unit == FORWARD_SLASH {
2308                *unit = BACKSLASH;
2309            }
2310        }
2311        let mut wide = if encoded.starts_with(&[BACKSLASH, BACKSLASH]) {
2312            let mut wide = Vec::with_capacity(VERBATIM_UNC_PREFIX.len() + encoded.len() - 2 + 1);
2313            wide.extend_from_slice(VERBATIM_UNC_PREFIX);
2314            wide.extend_from_slice(&encoded[2..]);
2315            wide
2316        } else {
2317            let mut wide = Vec::with_capacity(VERBATIM_PREFIX.len() + encoded.len() + 1);
2318            wide.extend_from_slice(VERBATIM_PREFIX);
2319            wide.extend_from_slice(&encoded);
2320            wide
2321        };
2322        wide.push(0);
2323        Ok(wide)
2324    }
2325
2326    fn security_query_error(code: Option<i32>) -> PrivateDirectoryError {
2327        operation_error(PrivateDirectoryOperation::QuerySecurityDescriptor, code)
2328    }
2329
2330    fn error_after_cleanup(
2331        error: PrivateDirectoryError,
2332        cleanup: Result<(), PrivateDirectoryError>,
2333    ) -> PrivateDirectoryError {
2334        match cleanup {
2335            Ok(()) => error.with_cleanup(PrivateDirectoryCleanupStatus::Confirmed, None),
2336            Err(cleanup) => {
2337                error.with_cleanup(PrivateDirectoryCleanupStatus::Uncertain, cleanup.os_code())
2338            }
2339        }
2340    }
2341
2342    fn operation_error(
2343        operation: PrivateDirectoryOperation,
2344        code: Option<i32>,
2345    ) -> PrivateDirectoryError {
2346        PrivateDirectoryError::new(PrivateDirectoryErrorKind::WindowsApi(operation), code)
2347    }
2348
2349    fn win_bool(
2350        result: i32,
2351        operation: PrivateDirectoryOperation,
2352    ) -> Result<(), PrivateDirectoryError> {
2353        if result != 0 {
2354            Ok(())
2355        } else {
2356            Err(operation_error(operation, last_os_code()))
2357        }
2358    }
2359
2360    fn last_os_code() -> Option<i32> {
2361        io::Error::last_os_error().raw_os_error()
2362    }
2363
2364    fn is_win32_error(code: Option<i32>, expected: u32) -> bool {
2365        code.and_then(|code| u32::try_from(code).ok()) == Some(expected)
2366    }
2367
2368    struct AlignedBuffer {
2369        words: Vec<usize>,
2370        len: usize,
2371    }
2372
2373    impl AlignedBuffer {
2374        fn new(len: usize) -> Self {
2375            let words = len.div_ceil(size_of::<usize>()).max(1);
2376            Self {
2377                words: vec![0; words],
2378                len,
2379            }
2380        }
2381
2382        fn len(&self) -> usize {
2383            self.len
2384        }
2385
2386        fn as_ptr(&self) -> *mut c_void {
2387            self.words.as_ptr().cast_mut().cast()
2388        }
2389
2390        fn as_mut_ptr(&mut self) -> *mut c_void {
2391            self.words.as_mut_ptr().cast()
2392        }
2393
2394        fn as_bytes(&self) -> &[u8] {
2395            // SAFETY: the backing allocation is live and initialized; `len` never exceeds it.
2396            unsafe { slice::from_raw_parts(self.words.as_ptr().cast(), self.len) }
2397        }
2398    }
2399
2400    struct Sid {
2401        bytes: AlignedBuffer,
2402    }
2403
2404    impl Sid {
2405        fn well_known(kind: i32) -> Result<Self, PrivateDirectoryError> {
2406            let mut bytes = AlignedBuffer::new(SECURITY_MAX_SID_SIZE as usize);
2407            let mut length = SECURITY_MAX_SID_SIZE;
2408            // SAFETY: the aligned output buffer is writable for `length`; a null domain SID is
2409            // required for these machine-independent well-known SID kinds.
2410            let succeeded = unsafe {
2411                CreateWellKnownSid(kind, ptr::null_mut(), bytes.as_mut_ptr(), &raw mut length)
2412            };
2413            win_bool(succeeded, PrivateDirectoryOperation::CreateWellKnownSid)?;
2414            bytes.len = usize::try_from(length).map_err(|_| {
2415                operation_error(PrivateDirectoryOperation::CreateWellKnownSid, None)
2416            })?;
2417            Ok(Self { bytes })
2418        }
2419
2420        unsafe fn copy_from(source: PSID) -> Result<Self, PrivateDirectoryError> {
2421            if source.is_null() || unsafe { IsValidSid(source) } == 0 {
2422                return Err(operation_error(
2423                    PrivateDirectoryOperation::QueryProcessToken,
2424                    last_os_code(),
2425                ));
2426            }
2427            let length = unsafe { GetLengthSid(source) };
2428            let mut bytes = AlignedBuffer::new(usize::try_from(length).map_err(|_| {
2429                operation_error(PrivateDirectoryOperation::QueryProcessToken, None)
2430            })?);
2431            // SAFETY: the source is a valid SID from TokenUser and the destination has its exact
2432            // reported length.
2433            unsafe {
2434                win_bool(
2435                    CopySid(length, bytes.as_mut_ptr(), source),
2436                    PrivateDirectoryOperation::QueryProcessToken,
2437                )?;
2438            }
2439            Ok(Self { bytes })
2440        }
2441
2442        fn as_psid(&self) -> PSID {
2443            self.bytes.as_ptr()
2444        }
2445
2446        fn as_bytes(&self) -> &[u8] {
2447            self.bytes.as_bytes()
2448        }
2449    }
2450
2451    #[cfg(test)]
2452    mod tests {
2453        use std::{
2454            fs,
2455            os::windows::{
2456                fs::{symlink_dir, symlink_file},
2457                io::AsHandle as _,
2458            },
2459        };
2460
2461        use super::*;
2462        use windows_sys::Win32::Security::{
2463            SetKernelObjectSecurity, UNPROTECTED_DACL_SECURITY_INFORMATION, WinWorldSid,
2464        };
2465        use windows_sys::Win32::Storage::FileSystem::{
2466            FILE_GENERIC_READ, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ,
2467            FILE_SHARE_WRITE, READ_CONTROL,
2468        };
2469
2470        #[test]
2471        fn protected_child_ignores_permissive_parent_inheritance() {
2472            let temporary = tempfile::tempdir().expect("temporary directory");
2473            let parent = temporary.path().join("permissive");
2474            fs::create_dir(&parent).expect("parent directory");
2475            set_world_writable(&parent);
2476            let child = parent.join("private");
2477
2478            let handle = create(&child).expect("protected private directory");
2479            verify(handle.as_handle()).expect("retained handle policy");
2480            drop(handle);
2481            fs::remove_dir(&child).expect("remove child");
2482        }
2483
2484        #[test]
2485        fn retained_handle_detects_dacl_tampering() {
2486            let temporary = tempfile::tempdir().expect("temporary directory");
2487            let child = temporary.path().join("private");
2488            let handle = create(&child).expect("protected private directory");
2489            set_world_writable(&child);
2490
2491            let error = verify(handle.as_handle()).expect_err("tampered DACL");
2492            assert!(matches!(
2493                error.kind(),
2494                PrivateDirectoryErrorKind::DaclUnprotected
2495                    | PrivateDirectoryErrorKind::DaclPolicy(_)
2496            ));
2497            remove(handle).expect_err("tampered object must not be cleanup-eligible");
2498            assert!(child.is_dir());
2499            fs::remove_dir(&child).expect("remove child");
2500        }
2501
2502        #[test]
2503        fn retained_reparse_handle_is_rejected_when_symlinks_are_available() {
2504            let temporary = tempfile::tempdir().expect("temporary directory");
2505            let target = temporary.path().join("target");
2506            let link = temporary.path().join("link");
2507            fs::create_dir(&target).expect("target directory");
2508            if symlink_dir(&target, &link).is_err() {
2509                return;
2510            }
2511            let wide = wide_path(&link).expect("wide path");
2512            let handle = open_directory(&wide, false).expect("open reparse point");
2513            let error = verify(handle.as_handle()).expect_err("reparse rejected");
2514            assert_eq!(error.kind(), PrivateDirectoryErrorKind::ReparsePoint);
2515        }
2516
2517        #[test]
2518        fn existing_directory_is_never_removed() {
2519            let temporary = tempfile::tempdir().expect("temporary directory");
2520            let existing = temporary.path().join("existing");
2521            fs::create_dir(&existing).expect("existing directory");
2522
2523            let error = create(&existing).expect_err("atomic create must reject an existing path");
2524            assert_eq!(error.kind(), PrivateDirectoryErrorKind::AlreadyExists);
2525            assert_eq!(
2526                error.cleanup_status(),
2527                PrivateDirectoryCleanupStatus::NotRequired
2528            );
2529            assert!(existing.is_dir());
2530        }
2531
2532        #[test]
2533        fn retained_handle_blocks_path_replacement_until_drop() {
2534            let temporary = tempfile::tempdir().expect("temporary directory");
2535            let child = temporary.path().join("private");
2536            let renamed = temporary.path().join("renamed");
2537            let handle = create(&child).expect("protected private directory");
2538
2539            assert!(fs::rename(&child, &renamed).is_err());
2540            assert!(fs::remove_dir(&child).is_err());
2541            verify(handle.as_handle()).expect("retained handle policy");
2542
2543            drop(handle);
2544            fs::rename(&child, &renamed).expect("rename after retained handle drop");
2545            fs::remove_dir(&renamed).expect("cleanup after retained handle drop");
2546        }
2547
2548        #[test]
2549        fn private_directory_read_guards_coexist_and_collectively_block_rename() {
2550            let temporary = tempfile::tempdir().expect("temporary directory");
2551            let child = temporary.path().join("private");
2552            let renamed = temporary.path().join("renamed");
2553            drop(create(&child).expect("protected private directory"));
2554
2555            let first = open_read_guard(&child).expect("first private read guard");
2556            let second = open_read_guard(&child).expect("second private read guard");
2557            verify(first.as_handle()).expect("first strict directory policy");
2558            verify(second.as_handle()).expect("second strict directory policy");
2559            assert!(fs::rename(&child, &renamed).is_err());
2560
2561            drop(first);
2562            assert!(fs::rename(&child, &renamed).is_err());
2563            drop(second);
2564            fs::rename(&child, &renamed).expect("rename after all read guards drop");
2565            fs::remove_dir(&renamed).expect("cleanup renamed directory");
2566        }
2567
2568        #[test]
2569        fn private_directory_metadata_flush_accepts_mutation_and_read_guards() {
2570            let temporary = tempfile::tempdir().expect("temporary directory");
2571            let directory_path = temporary.path().join("private");
2572            let staging = directory_path.join("staging");
2573            let final_path = directory_path.join("final");
2574            let directory = create(&directory_path).expect("protected private directory");
2575            fs::write(&staging, b"durable namespace").expect("write staging file");
2576            fs::rename(&staging, &final_path).expect("publish final file");
2577
2578            crate::windows_private_directory::sync_private_directory_handle(directory.as_handle())
2579                .expect("flush mutation guard");
2580            verify(directory.as_handle()).expect("mutation guard remains strict");
2581            drop(directory);
2582
2583            let read_guard = open_read_guard(&directory_path).expect("private read guard");
2584            crate::windows_private_directory::sync_private_directory_handle(read_guard.as_handle())
2585                .expect("flush read guard");
2586            verify(read_guard.as_handle()).expect("read guard remains strict");
2587            assert_eq!(
2588                fs::read(&final_path).expect("read final file"),
2589                b"durable namespace"
2590            );
2591
2592            drop(read_guard);
2593            fs::remove_file(&final_path).expect("remove final file");
2594            fs::remove_dir(&directory_path).expect("remove private directory");
2595        }
2596
2597        #[test]
2598        fn private_directory_metadata_flush_fails_closed_without_write_access() {
2599            let temporary = tempfile::tempdir().expect("temporary directory");
2600            let directory_path = temporary.path().join("private");
2601            drop(create(&directory_path).expect("protected private directory"));
2602            let wide = wide_path(&directory_path).expect("wide directory path");
2603            let read_only = open_directory_with_access(
2604                &wide,
2605                FILE_GENERIC_READ,
2606                FILE_SHARE_READ | FILE_SHARE_WRITE,
2607            )
2608            .expect("strict read-only directory handle");
2609            verify(read_only.as_handle()).expect("read-only handle remains strict");
2610
2611            let error = crate::windows_private_directory::sync_private_directory_handle(
2612                read_only.as_handle(),
2613            )
2614            .expect_err("Windows must reject a flush without directory-write access");
2615            assert_eq!(
2616                error.kind(),
2617                PrivateDirectoryErrorKind::WindowsApi(PrivateDirectoryOperation::FlushDirectory)
2618            );
2619            assert_eq!(error.os_code(), Some(5));
2620            verify(read_only.as_handle()).expect("failed flush leaves exact handle usable");
2621            assert!(directory_path.is_dir());
2622
2623            drop(read_only);
2624            fs::remove_dir(&directory_path).expect("remove private directory");
2625        }
2626
2627        #[test]
2628        fn retained_handle_removes_the_exact_empty_directory() {
2629            let temporary = tempfile::tempdir().expect("temporary directory");
2630            let child = temporary.path().join("private");
2631            let handle = create(&child).expect("protected private directory");
2632
2633            remove(handle).expect("handle-bound removal");
2634            assert!(!child.exists());
2635        }
2636
2637        #[test]
2638        fn private_lock_file_peers_coexist_and_fs2_arbitrates() {
2639            let temporary = tempfile::tempdir().expect("temporary directory");
2640            let root = temporary.path().join("private");
2641            let moved_root = temporary.path().join("moved-private");
2642            let lock_path = root.join("store.lock");
2643            let directory = create(&root).expect("protected private directory");
2644            let creator = create_lock_file(&lock_path).expect("create strict private lock file");
2645            let peer = open_lock_file(&lock_path).expect("open compatible lock-file peer");
2646
2647            fs2::FileExt::try_lock_exclusive(&creator).expect("creator exclusive lock");
2648            assert!(fs2::FileExt::try_lock_shared(&peer).is_err());
2649            fs2::FileExt::unlock(&creator).expect("unlock creator");
2650            fs2::FileExt::try_lock_shared(&creator).expect("creator shared lock");
2651            fs2::FileExt::try_lock_shared(&peer).expect("peer shared lock");
2652            let contender = open_lock_file(&lock_path).expect("open exclusive contender");
2653            assert!(fs2::FileExt::try_lock_exclusive(&contender).is_err());
2654
2655            assert!(fs::remove_file(&lock_path).is_err());
2656            assert!(fs::rename(&root, &moved_root).is_err());
2657            assert!(!moved_root.exists());
2658            fs2::FileExt::unlock(&peer).expect("unlock peer");
2659            fs2::FileExt::unlock(&creator).expect("unlock creator shared lock");
2660            drop(contender);
2661            drop(peer);
2662            drop(creator);
2663
2664            let removal = open_file_for_removal(&lock_path).expect("open exact lock removal");
2665            remove_file(removal, PrivateFileLinkState::Single).expect("remove exact lock file");
2666            remove(directory).expect("remove empty private directory");
2667        }
2668
2669        #[test]
2670        fn private_lock_file_creation_never_replaces_existing_peer() {
2671            let temporary = tempfile::tempdir().expect("temporary directory");
2672            let root = temporary.path().join("private");
2673            let lock_path = root.join("store.lock");
2674            let directory = create(&root).expect("protected private directory");
2675            let existing = create_lock_file(&lock_path).expect("create strict private lock file");
2676
2677            let error = create_lock_file(&lock_path).expect_err("CREATE_NEW must reject peer");
2678            assert_eq!(error.kind(), PrivateDirectoryErrorKind::AlreadyExists);
2679            assert_eq!(
2680                error.cleanup_status(),
2681                PrivateDirectoryCleanupStatus::NotRequired
2682            );
2683            verify_file(existing.as_handle(), PrivateFileLinkState::Single)
2684                .expect("existing peer remains strict");
2685
2686            drop(existing);
2687            let removal = open_file_for_removal(&lock_path).expect("open exact lock removal");
2688            remove_file(removal, PrivateFileLinkState::Single).expect("remove exact lock file");
2689            remove(directory).expect("remove empty private directory");
2690        }
2691
2692        #[test]
2693        fn private_lock_file_post_create_policy_failure_is_uncertain() {
2694            let temporary = tempfile::tempdir().expect("temporary directory");
2695            let path = temporary.path().join("store.lock");
2696
2697            let error = create_lock_file_with_callback(&path, || set_world_writable(&path))
2698                .expect_err("post-create DACL tamper must fail strict verification");
2699            assert!(matches!(
2700                error.kind(),
2701                PrivateDirectoryErrorKind::DaclUnprotected
2702                    | PrivateDirectoryErrorKind::DaclPolicy(_)
2703            ));
2704            assert_eq!(
2705                error.cleanup_status(),
2706                PrivateDirectoryCleanupStatus::Uncertain
2707            );
2708            assert!(path.is_file());
2709            fs::remove_file(&path).expect("remove test-owned uncertain residue");
2710        }
2711
2712        #[test]
2713        fn handle_bound_publication_is_create_new_and_retains_final_guard() {
2714            use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
2715
2716            let temporary = tempfile::tempdir().expect("temporary directory");
2717            let root = temporary.path().join("private");
2718            let staging_path = root.join("artifact.tmp");
2719            let final_path = root.join("artifact.bin");
2720            let renamed_path = root.join("renamed.bin");
2721            let directory = create(&root).expect("protected private directory");
2722            let mut staging = create_file(&staging_path, false).expect("protected staging file");
2723            staging
2724                .write_all(b"complete artifact\n")
2725                .expect("write complete staging file");
2726            staging.sync_all().expect("sync complete staging file");
2727
2728            let mut published = publish_file_create_new_with_callback(
2729                staging,
2730                directory.as_handle(),
2731                OsStr::new("artifact.bin"),
2732                || {
2733                    assert!(fs::rename(&final_path, &renamed_path).is_err());
2734                    assert!(fs::remove_file(&final_path).is_err());
2735                },
2736            )
2737            .expect("atomic handle-bound publication");
2738
2739            assert!(!staging_path.exists());
2740            assert!(final_path.is_file());
2741            assert!(!renamed_path.exists());
2742            verify_file(published.as_handle(), PrivateFileLinkState::Single)
2743                .expect("retained strict final handle");
2744            published
2745                .seek(SeekFrom::Start(0))
2746                .expect("rewind final handle");
2747            let mut bytes = Vec::new();
2748            published
2749                .read_to_end(&mut bytes)
2750                .expect("read final through retained handle");
2751            assert_eq!(bytes, b"complete artifact\n");
2752
2753            remove_file(published, PrivateFileLinkState::Single)
2754                .expect("remove exact published file");
2755            remove(directory).expect("remove empty private directory");
2756        }
2757
2758        #[test]
2759        fn handle_bound_publication_preserves_occupied_destination() {
2760            use std::io::Write as _;
2761
2762            let temporary = tempfile::tempdir().expect("temporary directory");
2763            let root = temporary.path().join("private");
2764            let staging_path = root.join("artifact.tmp");
2765            let final_path = root.join("artifact.bin");
2766            let directory = create(&root).expect("protected private directory");
2767            let mut staging = create_file(&staging_path, false).expect("protected staging file");
2768            staging
2769                .write_all(b"new artifact\n")
2770                .expect("write staging file");
2771            staging.sync_all().expect("sync staging file");
2772            let mut existing = create_file(&final_path, false).expect("existing private file");
2773            existing
2774                .write_all(b"existing artifact\n")
2775                .expect("write existing file");
2776            existing.sync_all().expect("sync existing file");
2777            drop(existing);
2778
2779            let publication =
2780                publish_file_create_new(staging, directory.as_handle(), OsStr::new("artifact.bin"))
2781                    .expect_err("occupied destination must not be replaced");
2782            assert_eq!(publication.phase(), PrivatePublicationPhase::Unpublished);
2783            assert_eq!(
2784                publication.error().kind(),
2785                PrivateDirectoryErrorKind::AlreadyExists
2786            );
2787            let (_, staging, _) = publication.into_parts();
2788            verify_file(staging.as_handle(), PrivateFileLinkState::Single)
2789                .expect("exact staging handle retained");
2790            assert_eq!(
2791                fs::read(&final_path).expect("read existing destination"),
2792                b"existing artifact\n"
2793            );
2794
2795            remove_file(staging, PrivateFileLinkState::Single).expect("remove exact staging file");
2796            let existing = open_file_for_removal(&final_path).expect("open existing destination");
2797            remove_file(existing, PrivateFileLinkState::Single)
2798                .expect("remove existing destination");
2799            remove(directory).expect("remove empty private directory");
2800        }
2801
2802        #[test]
2803        fn handle_bound_publication_rejects_unsafe_names_before_mutation() {
2804            let temporary = tempfile::tempdir().expect("temporary directory");
2805            let root = temporary.path().join("private");
2806            let staging_path = root.join("artifact.tmp");
2807            let directory = create(&root).expect("protected private directory");
2808            let mut staging = create_file(&staging_path, false).expect("protected staging file");
2809
2810            for name in ["", "..", r"nested\artifact", "artifact:stream", "artifact."] {
2811                let error =
2812                    publish_file_create_new(staging, directory.as_handle(), OsStr::new(name))
2813                        .expect_err("unsafe destination name must be rejected");
2814                assert_eq!(error.phase(), PrivatePublicationPhase::Unpublished);
2815                assert_eq!(error.error().kind(), PrivateDirectoryErrorKind::InvalidPath);
2816                let (_, retained, _) = error.into_parts();
2817                staging = retained;
2818                assert!(staging_path.is_file());
2819            }
2820
2821            remove_file(staging, PrivateFileLinkState::Single).expect("remove exact staging file");
2822            remove(directory).expect("remove empty private directory");
2823        }
2824
2825        #[test]
2826        fn post_rename_link_injection_returns_recoverable_exact_handle() {
2827            let temporary = tempfile::tempdir().expect("temporary directory");
2828            let root = temporary.path().join("private");
2829            let staging_path = root.join("artifact.tmp");
2830            let final_path = root.join("artifact.bin");
2831            let injected_link = root.join("injected.bin");
2832            let directory = create(&root).expect("protected private directory");
2833            let staging = create_file(&staging_path, false).expect("protected staging file");
2834
2835            let publication = publish_file_create_new_with_callback(
2836                staging,
2837                directory.as_handle(),
2838                OsStr::new("artifact.bin"),
2839                || fs::hard_link(&final_path, &injected_link).expect("inject second hard link"),
2840            )
2841            .expect_err("post-rename link-state mismatch must fail closed");
2842            assert_eq!(
2843                publication.phase(),
2844                PrivatePublicationPhase::CommitUncertain
2845            );
2846            assert_eq!(
2847                publication.error().kind(),
2848                PrivateDirectoryErrorKind::MultipleLinks
2849            );
2850            let (_, published, _) = publication.into_parts();
2851            verify_file(published.as_handle(), PrivateFileLinkState::PublicationPair)
2852                .expect("exact publication-pair handle retained");
2853
2854            remove_file(published, PrivateFileLinkState::PublicationPair)
2855                .expect("remove exact final link");
2856            assert!(!final_path.exists());
2857            assert!(injected_link.is_file());
2858            let injected =
2859                open_file_for_removal(&injected_link).expect("open remaining exact link");
2860            remove_file(injected, PrivateFileLinkState::Single)
2861                .expect("remove remaining injected link");
2862            remove(directory).expect("remove empty private directory");
2863        }
2864
2865        #[test]
2866        fn publication_pair_completion_returns_sealed_exact_final_handle() {
2867            use std::io::{Read as _, Seek as _, SeekFrom, Write as _};
2868
2869            let temporary = tempfile::tempdir().expect("temporary directory");
2870            let root = temporary.path().join("private");
2871            let staging_path = root.join("artifact.tmp");
2872            let final_path = root.join("artifact.bin");
2873            let moved_path = root.join("moved.bin");
2874            let directory = create(&root).expect("protected private directory");
2875            let mut writer = create_file(&staging_path, true).expect("protected staging file");
2876            writer
2877                .write_all(b"legacy artifact\n")
2878                .expect("write complete staging file");
2879            writer.sync_all().expect("sync complete staging file");
2880            let staging = seal_staging_file(writer).expect("seal staging handle");
2881            fs::hard_link(&staging_path, &final_path).expect("create legacy final hard link");
2882
2883            let mut published = complete_publication_pair(
2884                staging,
2885                directory.as_handle(),
2886                OsStr::new("artifact.bin"),
2887            )
2888            .expect("collapse publication pair");
2889
2890            assert!(!staging_path.exists());
2891            assert!(final_path.is_file());
2892            assert!(fs::rename(&final_path, &moved_path).is_err());
2893            assert!(fs::remove_file(&final_path).is_err());
2894            verify_file(published.as_handle(), PrivateFileLinkState::Single)
2895                .expect("strict single final handle");
2896            published
2897                .seek(SeekFrom::Start(0))
2898                .expect("rewind final handle");
2899            let mut bytes = Vec::new();
2900            published
2901                .read_to_end(&mut bytes)
2902                .expect("read final through sealed handle");
2903            assert_eq!(bytes, b"legacy artifact\n");
2904
2905            remove_file(published, PrivateFileLinkState::Single).expect("remove exact final link");
2906            remove(directory).expect("remove empty private directory");
2907        }
2908
2909        #[test]
2910        fn publication_pair_boundary_replacement_is_preserved_and_reported() {
2911            use std::io::Write as _;
2912
2913            let temporary = tempfile::tempdir().expect("temporary directory");
2914            let root = temporary.path().join("private");
2915            let staging_path = root.join("artifact.tmp");
2916            let final_path = root.join("artifact.bin");
2917            let moved_path = root.join("moved.bin");
2918            let directory = create(&root).expect("protected private directory");
2919            let mut writer = create_file(&staging_path, true).expect("protected staging file");
2920            writer.write_all(b"original\n").expect("write staging file");
2921            writer.sync_all().expect("sync staging file");
2922            let staging = seal_staging_file(writer).expect("seal staging handle");
2923            fs::hard_link(&staging_path, &final_path).expect("create legacy final hard link");
2924
2925            let recovery = complete_publication_pair_with_callback(
2926                staging,
2927                directory.as_handle(),
2928                OsStr::new("artifact.bin"),
2929                || {
2930                    fs::rename(&final_path, &moved_path).expect("move original final link");
2931                    fs::write(&final_path, b"replacement\n").expect("create replacement");
2932                },
2933            )
2934            .expect_err("replacement boundary must fail closed");
2935            assert_eq!(
2936                recovery.phase(),
2937                PrivatePublicationPairPhase::FinalSingleSealed
2938            );
2939            assert_eq!(
2940                recovery.error().kind(),
2941                PrivateDirectoryErrorKind::IdentityMismatch
2942            );
2943            let (_, original, _) = recovery.into_parts();
2944            verify_file(original.as_handle(), PrivateFileLinkState::Single)
2945                .expect("exact original retained and sealed");
2946
2947            remove_file(original, PrivateFileLinkState::Single)
2948                .expect("remove exact moved original");
2949            assert!(!moved_path.exists());
2950            assert_eq!(
2951                fs::read(&final_path).expect("read preserved replacement"),
2952                b"replacement\n"
2953            );
2954            fs::remove_file(&final_path).expect("remove preserved replacement");
2955            remove(directory).expect("remove empty private directory");
2956        }
2957
2958        #[test]
2959        fn private_regular_file_is_created_verified_and_removed_by_handle() {
2960            use std::io::Write as _;
2961
2962            let temporary = tempfile::tempdir().expect("temporary directory");
2963            let path = temporary.path().join("endpoint.json");
2964            let mut handle = create_file(&path, false).expect("protected private file");
2965            handle.write_all(b"{}\n").expect("write private file");
2966            handle.sync_all().expect("sync private file");
2967            verify_file(handle.as_handle(), PrivateFileLinkState::Single)
2968                .expect("retained private-file policy");
2969            assert!(fs::rename(&path, temporary.path().join("moved.json")).is_err());
2970            assert!(fs::remove_file(&path).is_err());
2971
2972            remove_file(handle, PrivateFileLinkState::Single)
2973                .expect("handle-bound private-file removal");
2974            assert!(!path.exists());
2975        }
2976
2977        #[test]
2978        fn private_file_sync_handle_flushes_and_blocks_mutation_or_replacement() {
2979            use std::io::{Read as _, Write as _};
2980
2981            let temporary = tempfile::tempdir().expect("temporary directory");
2982            let path = temporary.path().join("revision.json");
2983            let moved = temporary.path().join("moved.json");
2984            let mut creator = create_file(&path, false).expect("protected private file");
2985            creator
2986                .write_all(b"published\n")
2987                .expect("write final bytes");
2988            creator.sync_all().expect("initial file sync");
2989            drop(creator);
2990
2991            let sync = crate::windows_private_directory::open_private_file_for_sync(&path)
2992                .expect("open strict sync handle");
2993            sync.sync_all().expect("retry final file sync");
2994            verify_file(sync.as_handle(), PrivateFileLinkState::Single)
2995                .expect("sync handle remains strict");
2996            let mut reader = fs::File::open(&path).expect("compatible read peer");
2997            let mut bytes = Vec::new();
2998            reader.read_to_end(&mut bytes).expect("read final bytes");
2999            assert_eq!(bytes, b"published\n");
3000
3001            assert!(fs::OpenOptions::new().write(true).open(&path).is_err());
3002            assert!(fs::rename(&path, &moved).is_err());
3003            assert!(fs::remove_file(&path).is_err());
3004            assert!(!moved.exists());
3005
3006            drop(reader);
3007            drop(sync);
3008            fs::rename(&path, &moved).expect("rename after sync guard drop");
3009            fs::write(&path, b"replacement\n").expect("create replacement");
3010            assert_eq!(fs::read(&moved).expect("read original"), b"published\n");
3011            assert_eq!(fs::read(&path).expect("read replacement"), b"replacement\n");
3012            fs::remove_file(&path).expect("remove replacement");
3013            fs::remove_file(&moved).expect("remove original");
3014        }
3015
3016        #[test]
3017        fn private_file_sync_open_rejects_dacl_and_hardlink_policy_violations() {
3018            let temporary = tempfile::tempdir().expect("temporary directory");
3019            let dacl_path = temporary.path().join("dacl.json");
3020            drop(create_file(&dacl_path, false).expect("protected private file"));
3021            set_world_writable(&dacl_path);
3022
3023            let dacl_error =
3024                crate::windows_private_directory::open_private_file_for_sync(&dacl_path)
3025                    .expect_err("permissive DACL must be rejected");
3026            assert!(matches!(
3027                dacl_error.kind(),
3028                PrivateDirectoryErrorKind::DaclUnprotected
3029                    | PrivateDirectoryErrorKind::DaclPolicy(_)
3030            ));
3031            fs::remove_file(&dacl_path).expect("remove permissive test file");
3032
3033            let linked_path = temporary.path().join("linked.json");
3034            let extra_link = temporary.path().join("extra-link.json");
3035            drop(create_file(&linked_path, false).expect("protected private file"));
3036            fs::hard_link(&linked_path, &extra_link).expect("create extra hard link");
3037            let link_error =
3038                crate::windows_private_directory::open_private_file_for_sync(&linked_path)
3039                    .expect_err("multiple links must be rejected");
3040            assert_eq!(link_error.kind(), PrivateDirectoryErrorKind::MultipleLinks);
3041            fs::remove_file(&extra_link).expect("remove extra hard link");
3042            fs::remove_file(&linked_path).expect("remove original hard link");
3043        }
3044
3045        #[test]
3046        fn private_file_sync_open_rejects_final_reparse_point_when_supported() {
3047            let temporary = tempfile::tempdir().expect("temporary directory");
3048            let target = temporary.path().join("target.json");
3049            let link = temporary.path().join("link.json");
3050            drop(create_file(&target, false).expect("protected private file"));
3051            if symlink_file(&target, &link).is_err() {
3052                fs::remove_file(&target).expect("remove target after skipped symlink test");
3053                return;
3054            }
3055
3056            let error = crate::windows_private_directory::open_private_file_for_sync(&link)
3057                .expect_err("final reparse point must be rejected");
3058            assert_eq!(error.kind(), PrivateDirectoryErrorKind::ReparsePoint);
3059            fs::remove_file(&link).expect("remove test symlink");
3060            fs::remove_file(&target).expect("remove target");
3061        }
3062
3063        #[test]
3064        fn private_regular_file_rejects_additional_hard_links() {
3065            let temporary = tempfile::tempdir().expect("temporary directory");
3066            let path = temporary.path().join("endpoint.json");
3067            let linked = temporary.path().join("linked.json");
3068            let handle = create_file(&path, false).expect("protected private file");
3069            fs::hard_link(&path, &linked).expect("test hard link");
3070
3071            let error = verify_file(handle.as_handle(), PrivateFileLinkState::Single)
3072                .expect_err("multiple links rejected");
3073            assert_eq!(error.kind(), PrivateDirectoryErrorKind::MultipleLinks);
3074            drop(handle);
3075            fs::remove_file(&linked).expect("remove added link");
3076            fs::remove_file(&path).expect("remove original link");
3077        }
3078
3079        #[test]
3080        fn removal_handle_survives_rename_and_removes_the_exact_file() {
3081            let temporary = tempfile::tempdir().expect("temporary directory");
3082            let original = temporary.path().join("endpoint.json");
3083            let renamed = temporary.path().join("renamed.json");
3084            let replacement = b"replacement\n";
3085            drop(create_file(&original, false).expect("protected private file"));
3086
3087            let handle = open_file_for_removal(&original).expect("open private file for removal");
3088            fs::rename(&original, &renamed).expect("rename while removal handle is retained");
3089            fs::write(&original, replacement).expect("create replacement at original path");
3090
3091            remove_file(handle, PrivateFileLinkState::Single)
3092                .expect("remove renamed object by retained handle");
3093            assert!(!renamed.exists());
3094            assert_eq!(fs::read(&original).expect("read replacement"), replacement);
3095            fs::remove_file(&original).expect("remove replacement");
3096        }
3097
3098        #[test]
3099        fn removal_open_rejects_additional_hard_links() {
3100            let temporary = tempfile::tempdir().expect("temporary directory");
3101            let original = temporary.path().join("endpoint.json");
3102            let linked = temporary.path().join("linked.json");
3103            drop(create_file(&original, false).expect("protected private file"));
3104            fs::hard_link(&original, &linked).expect("test hard link");
3105
3106            let error = open_file_for_removal(&original)
3107                .expect_err("removal open must reject multiple links");
3108            assert_eq!(error.kind(), PrivateDirectoryErrorKind::MultipleLinks);
3109            fs::remove_file(&linked).expect("remove added link");
3110            fs::remove_file(&original).expect("remove original link");
3111        }
3112
3113        #[test]
3114        fn publication_pair_can_be_reopened_and_staging_link_removed_by_handle() {
3115            let temporary = tempfile::tempdir().expect("temporary directory");
3116            let staged = temporary.path().join("endpoint.tmp");
3117            let published = temporary.path().join("endpoint.json");
3118            drop(create_file(&staged, true).expect("protected staging file"));
3119            fs::hard_link(&staged, &published).expect("publish final hard link");
3120
3121            let handle =
3122                open_file_for_removal_in_state(&staged, PrivateFileLinkState::PublicationPair)
3123                    .expect("reopen publication pair for removal");
3124            remove_file(handle, PrivateFileLinkState::PublicationPair)
3125                .expect("remove exact staging link");
3126
3127            assert!(!staged.exists());
3128            let final_handle = open_file(&published).expect("open final private file");
3129            verify_file(final_handle.as_handle(), PrivateFileLinkState::Single)
3130                .expect("final file returned to single-link state");
3131            drop(final_handle);
3132            fs::remove_file(&published).expect("remove final file");
3133        }
3134
3135        #[test]
3136        fn publication_pair_removal_open_rejects_single_link_state() {
3137            let temporary = tempfile::tempdir().expect("temporary directory");
3138            let staged = temporary.path().join("endpoint.tmp");
3139            let published = temporary.path().join("endpoint.json");
3140            drop(create_file(&staged, true).expect("protected staging file"));
3141            fs::hard_link(&staged, &published).expect("publish final hard link");
3142
3143            let error = open_file_for_removal(&staged)
3144                .expect_err("single-link removal open must reject publication pair");
3145            assert_eq!(error.kind(), PrivateDirectoryErrorKind::MultipleLinks);
3146            fs::remove_file(&staged).expect("remove staging link");
3147            fs::remove_file(&published).expect("remove final link");
3148        }
3149
3150        #[test]
3151        fn regular_file_link_count_tracks_added_hard_link_without_acl_policy() {
3152            let temporary = tempfile::tempdir().expect("temporary directory");
3153            let original = temporary.path().join("ordinary.txt");
3154            let linked = temporary.path().join("linked.txt");
3155            fs::write(&original, b"ordinary\n").expect("ordinary inherited-ACL file");
3156            let handle = File::open(&original).expect("open ordinary file");
3157
3158            assert_eq!(
3159                regular_file_link_count(handle.as_handle()).expect("single link count"),
3160                1
3161            );
3162            fs::hard_link(&original, &linked).expect("add hard link");
3163            assert_eq!(
3164                regular_file_link_count(handle.as_handle()).expect("two-link count"),
3165                2
3166            );
3167
3168            drop(handle);
3169            fs::remove_file(&linked).expect("remove added link");
3170            fs::remove_file(&original).expect("remove original link");
3171        }
3172
3173        #[test]
3174        fn inherited_regular_file_acl_is_not_private_policy() {
3175            let temporary = tempfile::tempdir().expect("temporary directory");
3176            let path = temporary.path().join("ordinary.json");
3177            fs::write(&path, b"{}\n").expect("ordinary file");
3178            let file = File::open(&path).expect("open ordinary file");
3179
3180            assert!(verify_file(file.as_handle(), PrivateFileLinkState::Single).is_err());
3181        }
3182
3183        #[test]
3184        fn staging_file_publication_transitions_from_one_link_to_two_and_back() {
3185            use std::io::Write as _;
3186
3187            let temporary = tempfile::tempdir().expect("temporary directory");
3188            let staged = temporary.path().join("endpoint.tmp");
3189            let published = temporary.path().join("endpoint.json");
3190            let mut handle = create_file(&staged, true).expect("protected staging file");
3191            handle.write_all(b"{}\n").expect("write staging file");
3192            handle.sync_all().expect("sync staging file");
3193            verify_file(handle.as_handle(), PrivateFileLinkState::Single)
3194                .expect("single staging link");
3195
3196            fs::hard_link(&staged, &published).expect("create final hard link");
3197            verify_file(handle.as_handle(), PrivateFileLinkState::PublicationPair)
3198                .expect("staging and final links");
3199            remove_file(handle, PrivateFileLinkState::PublicationPair)
3200                .expect("remove exact staging link");
3201
3202            assert!(!staged.exists());
3203            let final_handle = open_file(&published).expect("open final private file");
3204            verify_file(final_handle.as_handle(), PrivateFileLinkState::Single)
3205                .expect("single final link");
3206            drop(final_handle);
3207            fs::remove_file(&published).expect("remove final file");
3208        }
3209
3210        #[test]
3211        fn sealed_staging_handle_is_path_free_and_denies_new_writers() {
3212            use std::{fs::OpenOptions, io::Write as _, os::windows::fs::OpenOptionsExt as _};
3213
3214            let temporary = tempfile::tempdir().expect("temporary directory");
3215            let original = temporary.path().join("endpoint.tmp");
3216            let renamed = temporary.path().join("renamed.tmp");
3217            let mut writer = create_file(&original, true).expect("protected staging file");
3218            writer.write_all(b"sealed\n").expect("write staging file");
3219            writer.sync_all().expect("sync staging file");
3220            fs::rename(&original, &renamed).expect("rename retained staging file");
3221
3222            let sealed = seal_staging_file(writer).expect("path-free staging seal");
3223            verify_file(sealed.as_handle(), PrivateFileLinkState::Single)
3224                .expect("sealed private-file policy");
3225            assert!(
3226                OpenOptions::new()
3227                    .write(true)
3228                    .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
3229                    .open(&renamed)
3230                    .is_err()
3231            );
3232
3233            remove_file(sealed, PrivateFileLinkState::Single).expect("remove sealed staging file");
3234            assert!(!renamed.exists());
3235        }
3236
3237        #[test]
3238        fn sealed_staging_handle_supports_strict_dual_handle_publication() {
3239            use std::io::Write as _;
3240
3241            let temporary = tempfile::tempdir().expect("temporary directory");
3242            let staged = temporary.path().join("endpoint.tmp");
3243            let published = temporary.path().join("endpoint.json");
3244            let mut writer = create_file(&staged, true).expect("protected staging file");
3245            writer.write_all(b"sealed\n").expect("write staging file");
3246            writer.sync_all().expect("sync staging file");
3247            let sealed = seal_staging_file(writer).expect("sealed staging handle");
3248
3249            fs::hard_link(&staged, &published).expect("publish final hard link");
3250            verify_file(sealed.as_handle(), PrivateFileLinkState::PublicationPair)
3251                .expect("staging publication pair");
3252            let final_handle =
3253                open_file_for_removal_in_state(&published, PrivateFileLinkState::PublicationPair)
3254                    .expect("strict final publication handle");
3255
3256            remove_file(final_handle, PrivateFileLinkState::PublicationPair)
3257                .expect("roll back exact final link");
3258            remove_file(sealed, PrivateFileLinkState::Single).expect("remove exact staging link");
3259            assert!(!staged.exists());
3260            assert!(!published.exists());
3261        }
3262
3263        #[test]
3264        fn competing_writer_makes_seal_fail_with_confirmed_exact_cleanup() {
3265            use std::{cell::RefCell, fs::OpenOptions, os::windows::fs::OpenOptionsExt as _};
3266
3267            let temporary = tempfile::tempdir().expect("temporary directory");
3268            let staged = temporary.path().join("endpoint.tmp");
3269            let writer = create_file(&staged, true).expect("protected staging file");
3270            let competing = RefCell::new(None);
3271
3272            let error = seal_staging_file_with_transition(writer, || {
3273                let writer = OpenOptions::new()
3274                    .write(true)
3275                    .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
3276                    .open(&staged)
3277                    .expect("competing writer during transition");
3278                competing.replace(Some(writer));
3279            })
3280            .expect_err("competing writer must prevent sealed reopen");
3281
3282            assert_eq!(
3283                error.kind(),
3284                PrivateDirectoryErrorKind::WindowsApi(PrivateDirectoryOperation::OpenFile)
3285            );
3286            assert_eq!(
3287                error.cleanup_status(),
3288                PrivateDirectoryCleanupStatus::Confirmed
3289            );
3290            drop(competing.take());
3291            assert!(!staged.exists());
3292        }
3293
3294        #[test]
3295        fn closed_mutating_writer_requires_post_seal_content_revalidation() {
3296            use std::{
3297                fs::OpenOptions,
3298                io::{Read as _, Write as _},
3299                os::windows::fs::OpenOptionsExt as _,
3300            };
3301
3302            let temporary = tempfile::tempdir().expect("temporary directory");
3303            let staged = temporary.path().join("endpoint.tmp");
3304            let mut writer = create_file(&staged, true).expect("protected staging file");
3305            writer.write_all(b"trusted").expect("write trusted bytes");
3306            writer.sync_all().expect("sync trusted bytes");
3307
3308            let mut sealed = seal_staging_file_with_transition(writer, || {
3309                let mut competing = OpenOptions::new()
3310                    .write(true)
3311                    .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
3312                    .open(&staged)
3313                    .expect("short-lived competing writer");
3314                competing.set_len(0).expect("truncate staging file");
3315                competing
3316                    .write_all(b"mutated")
3317                    .expect("mutate staging file");
3318                competing.sync_all().expect("sync mutation");
3319            })
3320            .expect("closed writer does not block sealed reopen");
3321            let mut bytes = Vec::new();
3322            sealed
3323                .read_to_end(&mut bytes)
3324                .expect("read sealed staging bytes");
3325            assert_eq!(bytes, b"mutated");
3326
3327            remove_file(sealed, PrivateFileLinkState::Single).expect("remove mutated staging file");
3328            assert!(!staged.exists());
3329        }
3330
3331        #[test]
3332        fn private_tree_removal_accepts_ordinary_inherited_descendants() {
3333            let temporary = tempfile::tempdir().expect("temporary directory");
3334            let root = temporary.path().join("private");
3335            let handle = create(&root).expect("strict private root");
3336            let nested = root.join("Products/Applications/App.app");
3337            fs::create_dir_all(&nested).expect("ordinary nested directories");
3338            fs::write(nested.join("App"), b"ordinary inherited child")
3339                .expect("ordinary nested file");
3340
3341            remove_directory_tree(handle).expect("exact recursive removal");
3342            assert!(!root.exists());
3343        }
3344
3345        #[test]
3346        fn private_tree_removal_rejects_hard_linked_descendant() {
3347            let temporary = tempfile::tempdir().expect("temporary directory");
3348            let root = temporary.path().join("private");
3349            let handle = create(&root).expect("strict private root");
3350            let child = root.join("child.txt");
3351            let linked = temporary.path().join("linked.txt");
3352            fs::write(&child, b"preserve").expect("ordinary nested file");
3353            fs::hard_link(&child, &linked).expect("additional hard link");
3354
3355            let error = remove_directory_tree(handle).expect_err("hard link must fail closed");
3356            assert_eq!(error.kind(), PrivateDirectoryErrorKind::MultipleLinks);
3357            assert_eq!(
3358                error.cleanup_status(),
3359                PrivateDirectoryCleanupStatus::Uncertain
3360            );
3361            assert_eq!(fs::read(&child).expect("original survives"), b"preserve");
3362            assert_eq!(
3363                fs::read(&linked).expect("linked copy survives"),
3364                b"preserve"
3365            );
3366
3367            fs::remove_file(&linked).expect("remove added link");
3368            fs::remove_file(&child).expect("remove original link");
3369            fs::remove_dir(&root).expect("remove root");
3370        }
3371
3372        #[test]
3373        fn private_tree_guard_blocks_root_replacement_at_removal_boundary() {
3374            use std::cell::RefCell;
3375
3376            let temporary = tempfile::tempdir().expect("temporary directory");
3377            let root = temporary.path().join("private");
3378            let moved = temporary.path().join("moved");
3379            let replacement = temporary.path().join("replacement");
3380            let replacement_marker = replacement.join("preserve.txt");
3381            let handle = create(&root).expect("strict private root");
3382            fs::write(root.join("owned.txt"), b"owned").expect("owned nested file");
3383            fs::create_dir(&replacement).expect("replacement candidate");
3384            fs::write(&replacement_marker, b"preserve").expect("replacement marker");
3385            let attempts = RefCell::new(None);
3386
3387            remove_directory_tree_with_callback(handle, |_| {
3388                let move_owned = fs::rename(&root, &moved);
3389                let install_replacement = fs::rename(&replacement, &root);
3390                attempts.replace(Some((move_owned, install_replacement)));
3391            })
3392            .expect("remove retained original tree");
3393
3394            let (move_owned, install_replacement) = attempts
3395                .into_inner()
3396                .expect("replacement attempts recorded");
3397            assert!(move_owned.is_err());
3398            assert!(install_replacement.is_err());
3399            assert!(!root.exists());
3400            assert!(!moved.exists());
3401            assert_eq!(
3402                fs::read(&replacement_marker).expect("replacement survives"),
3403                b"preserve"
3404            );
3405        }
3406
3407        #[test]
3408        fn private_tree_cleanup_failure_is_reported_uncertain() {
3409            use std::{fs::OpenOptions, os::windows::fs::OpenOptionsExt as _};
3410
3411            let temporary = tempfile::tempdir().expect("temporary directory");
3412            let root = temporary.path().join("private");
3413            let handle = create(&root).expect("strict private root");
3414            let child = root.join("busy.txt");
3415            fs::write(&child, b"busy").expect("ordinary nested file");
3416            let busy = OpenOptions::new()
3417                .read(true)
3418                .share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
3419                .open(&child)
3420                .expect("non-delete-sharing competing handle");
3421
3422            let error = remove_directory_tree(handle).expect_err("busy child blocks cleanup");
3423            assert_eq!(
3424                error.kind(),
3425                PrivateDirectoryErrorKind::WindowsApi(PrivateDirectoryOperation::OpenTreeEntry)
3426            );
3427            assert_eq!(
3428                error.cleanup_status(),
3429                PrivateDirectoryCleanupStatus::Uncertain
3430            );
3431            assert!(root.is_dir());
3432            assert_eq!(fs::read(&child).expect("busy child survives"), b"busy");
3433
3434            drop(busy);
3435            fs::remove_file(&child).expect("remove busy child");
3436            fs::remove_dir(&root).expect("remove root");
3437        }
3438
3439        #[test]
3440        fn private_tree_removal_rejects_nested_reparse_without_touching_target() {
3441            let temporary = tempfile::tempdir().expect("temporary directory");
3442            let root = temporary.path().join("private");
3443            let target = temporary.path().join("target");
3444            let marker = target.join("preserve.txt");
3445            let linked = root.join("linked");
3446            let handle = create(&root).expect("strict private root");
3447            fs::create_dir(&target).expect("external target");
3448            fs::write(&marker, b"preserve").expect("external marker");
3449            if symlink_dir(&target, &linked).is_err() {
3450                drop(handle);
3451                fs::remove_dir(&root).expect("remove unused root");
3452                return;
3453            }
3454
3455            let error = remove_directory_tree(handle).expect_err("reparse must fail closed");
3456            assert_eq!(error.kind(), PrivateDirectoryErrorKind::ReparsePoint);
3457            assert_eq!(
3458                error.cleanup_status(),
3459                PrivateDirectoryCleanupStatus::Uncertain
3460            );
3461            assert_eq!(fs::read(&marker).expect("target survives"), b"preserve");
3462
3463            fs::remove_dir(&linked).expect("remove directory symlink");
3464            assert_eq!(
3465                fs::symlink_metadata(&linked)
3466                    .expect_err("directory symlink is absent")
3467                    .kind(),
3468                std::io::ErrorKind::NotFound
3469            );
3470            assert!(target.is_dir());
3471            assert_eq!(
3472                fs::read(&marker).expect("target survives link cleanup"),
3473                b"preserve"
3474            );
3475            fs::remove_dir(&root).expect("remove root");
3476        }
3477
3478        #[test]
3479        fn private_objects_support_extended_length_absolute_paths() {
3480            let temporary = tempfile::tempdir().expect("temporary directory");
3481            let mut parent = temporary.path().to_path_buf();
3482            while parent.as_os_str().encode_wide().count() < 280 {
3483                parent.push("long-private-path-segment");
3484            }
3485            fs::create_dir_all(&parent).expect("long parent path");
3486            let directory_path = parent.join("private");
3487            let directory = create(&directory_path).expect("long private directory path");
3488            verify(directory.as_handle()).expect("long directory policy");
3489
3490            let file_path = directory_path.join("endpoint.json");
3491            let file = create_file(&file_path, false).expect("long private file path");
3492            verify_file(file.as_handle(), PrivateFileLinkState::Single).expect("long file policy");
3493            remove_file(file, PrivateFileLinkState::Single).expect("remove long private file");
3494            remove(directory).expect("remove long private directory");
3495        }
3496
3497        fn set_world_writable(path: &Path) {
3498            let wide = wide_path(path).expect("wide path");
3499            let handle = open_test_directory(&wide);
3500            let world = Sid::well_known(WinWorldSid).expect("world SID");
3501            let mut acl = build_acl(&[&world]).expect("world ACL");
3502            let mut descriptor = SECURITY_DESCRIPTOR::default();
3503            // SAFETY: the descriptor and ACL are initialized, live buffers and the handle was
3504            // opened with WRITE_DAC for this isolated test directory.
3505            unsafe {
3506                assert_ne!(
3507                    InitializeSecurityDescriptor(
3508                        (&raw mut descriptor).cast(),
3509                        SECURITY_DESCRIPTOR_REVISION,
3510                    ),
3511                    0
3512                );
3513                assert_ne!(
3514                    SetSecurityDescriptorDacl(
3515                        (&raw mut descriptor).cast(),
3516                        1,
3517                        acl.as_mut_ptr().cast(),
3518                        0,
3519                    ),
3520                    0
3521                );
3522                assert_eq!(
3523                    SetKernelObjectSecurity(
3524                        handle.as_raw_handle(),
3525                        DACL_SECURITY_INFORMATION | UNPROTECTED_DACL_SECURITY_INFORMATION,
3526                        (&raw mut descriptor).cast(),
3527                    ),
3528                    1
3529                );
3530            }
3531        }
3532
3533        fn open_test_directory(path: &[u16]) -> File {
3534            // SAFETY: `path` is NUL-terminated and every pointer/flag follows CreateFileW's
3535            // directory-handle contract.
3536            let handle = unsafe {
3537                CreateFileW(
3538                    path.as_ptr(),
3539                    READ_CONTROL | WRITE_DAC | FILE_READ_ATTRIBUTES,
3540                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
3541                    ptr::null(),
3542                    OPEN_EXISTING,
3543                    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
3544                    ptr::null_mut(),
3545                )
3546            };
3547            assert_ne!(handle, INVALID_HANDLE_VALUE);
3548            // SAFETY: the successful call transferred one owned handle.
3549            unsafe { File::from_raw_handle(handle) }
3550        }
3551    }
3552}
3553
3554/// Atomically create one Windows directory with the strict private ACL and return its retained
3555/// standard-library handle after handle-bound verification.
3556///
3557/// No pathname-based permission repair or cleanup is attempted. If validation fails after
3558/// creation, the exact opened object is marked for deletion through its retained handle.
3559///
3560/// # Security
3561///
3562/// `CreateDirectoryW` does not return a handle. A permissive parent therefore leaves a narrow
3563/// create-to-open window in which another principal could substitute a different directory that
3564/// already has the same owner and exact ACL. Callers must write no sensitive data before this
3565/// function returns and must retain the returned no-delete-sharing handle until all sensitive
3566/// pathname use and handle-bound cleanup are complete.
3567///
3568/// # Errors
3569///
3570/// Returns a typed, path-free failure if the path is invalid or occupied, Windows cannot create
3571/// or open the directory, the filesystem lacks persistent ACLs, handle verification fails, or
3572/// post-create cleanup cannot be confirmed.
3573#[cfg(windows)]
3574pub fn create_private_directory(
3575    path: &std::path::Path,
3576) -> Result<std::fs::File, PrivateDirectoryError> {
3577    platform::create(path)
3578}
3579
3580/// Open an existing Windows directory without following reparse points, verify the strict private
3581/// ACL policy, and return a retained handle that denies delete sharing.
3582///
3583/// # Errors
3584///
3585/// Returns a typed, path-free failure if the path cannot be opened or the object, filesystem,
3586/// owner, or protected DACL differs from the private-directory policy.
3587#[cfg(windows)]
3588pub fn open_private_directory(
3589    path: &std::path::Path,
3590) -> Result<std::fs::File, PrivateDirectoryError> {
3591    platform::open(path)
3592}
3593
3594/// Open a strict private directory as a multi-reader, no-delete-sharing guard.
3595///
3596/// The handle requests read access plus the minimal directory-write right required by
3597/// [`sync_private_directory_handle`], shares read/write access with equivalent guards, and omits
3598/// delete sharing. Multiple readers can therefore coexist while every retained guard blocks
3599/// directory rename, deletion, and pathname replacement. The handle has no DELETE access and
3600/// cannot be consumed by [`remove_private_directory_handle`] or
3601/// [`remove_private_directory_tree_handle`].
3602///
3603/// This sharing mode is intentionally incompatible with a live deletion-capable handle returned
3604/// by [`create_private_directory`] or [`open_private_directory`]. Callers transitioning from a
3605/// mutation/removal lifetime must release that handle only under a separate identity/lock protocol
3606/// and revalidate the path-to-handle binding.
3607///
3608/// # Errors
3609///
3610/// Returns a typed failure if the path cannot be opened or the retained object fails the strict
3611/// private directory, owner, protected-DACL, reparse, or ACL-filesystem policy.
3612#[cfg(windows)]
3613pub fn open_private_directory_read_guard(
3614    path: &std::path::Path,
3615) -> Result<std::fs::File, PrivateDirectoryError> {
3616    platform::open_read_guard(path)
3617}
3618
3619/// Atomically create one Windows regular file with the strict private ACL and return its verified
3620/// no-delete-sharing handle.
3621///
3622/// The file is created with `CREATE_NEW`; an existing filesystem entry is never replaced.
3623///
3624/// # Errors
3625///
3626/// Returns a typed, path-free failure if the path is invalid or occupied, Windows cannot create
3627/// the file, the filesystem lacks persistent ACLs, or handle-bound validation fails.
3628#[cfg(windows)]
3629pub fn create_private_file(path: &std::path::Path) -> Result<std::fs::File, PrivateDirectoryError> {
3630    platform::create_file(path, false)
3631}
3632
3633/// Atomically create a strict private lock file with peer-compatible Windows sharing.
3634///
3635/// The returned handle requests read/write access, shares read/write access with other lock peers,
3636/// and omits delete sharing. Peers can therefore coexist at the filesystem layer while every live
3637/// handle prevents pathname deletion or replacement; callers must use a byte-range/file-locking
3638/// primitive such as `fs2` for shared/exclusive arbitration. The path uses `CREATE_NEW` and an
3639/// existing entry is never replaced.
3640///
3641/// # Errors
3642///
3643/// Returns a typed error if atomic creation or strict private-file verification fails. Because this
3644/// deliberately non-delete-capable handle cannot remove itself exactly, a post-create policy
3645/// failure reports [`PrivateDirectoryCleanupStatus::Uncertain`]. Callers should clean the enclosing
3646/// private directory through its retained exact directory capability.
3647#[cfg(windows)]
3648pub fn create_private_lock_file(
3649    path: &std::path::Path,
3650) -> Result<std::fs::File, PrivateDirectoryError> {
3651    platform::create_lock_file(path)
3652}
3653
3654/// Open an existing strict private lock file with peer-compatible Windows sharing.
3655///
3656/// The returned read/write handle shares read/write access, omits delete sharing, rejects reparse
3657/// points and multiple links, and verifies the exact private owner/protected-DACL policy. It is
3658/// suitable for acquiring an `fs2` lock before opening no-delete ancestor directory guards.
3659///
3660/// # Errors
3661///
3662/// Returns a typed failure if the path cannot be opened or the retained object fails strict
3663/// private-file verification.
3664#[cfg(windows)]
3665pub fn open_private_lock_file(
3666    path: &std::path::Path,
3667) -> Result<std::fs::File, PrivateDirectoryError> {
3668    platform::open_lock_file(path)
3669}
3670
3671/// Atomically publish a completed private file under a new name in a retained private directory.
3672///
3673/// `staging` must be the strict single-link, no-delete-sharing handle returned by
3674/// [`create_private_file`]. `destination_directory` must be a retained handle returned by
3675/// [`create_private_directory`] or [`open_private_directory`], and `destination_name` must be one
3676/// ordinary Windows filename component. The rename is handle-relative, does not consult the
3677/// staging pathname, and never replaces an existing destination. Success is returned only after
3678/// the same retained file handle still satisfies the strict single-link policy and its filesystem
3679/// identity is bound to `destination_name` through retained-directory enumeration.
3680///
3681/// The returned handle continues to deny write and delete sharing. Callers should retain it and
3682/// the directory handle through final readback, synchronization, and durable metadata commit.
3683///
3684/// # Errors
3685///
3686/// Every error owns the exact input file handle. [`PrivatePublicationPhase::Unpublished`] means
3687/// the rename was not reported successful; [`PrivateDirectoryErrorKind::AlreadyExists`] in that
3688/// phase guarantees the existing destination was not replaced. A
3689/// [`PrivatePublicationPhase::CommitUncertain`] error means the rename succeeded but strict
3690/// postconditions could not be confirmed. Callers must recover through the returned handle and
3691/// must not delete either pathname speculatively.
3692#[cfg(windows)]
3693pub fn publish_private_file_handle_create_new(
3694    staging: std::fs::File,
3695    destination_directory: std::os::windows::io::BorrowedHandle<'_>,
3696    destination_name: &std::ffi::OsStr,
3697) -> Result<std::fs::File, PrivatePublicationError> {
3698    platform::publish_file_create_new(staging, destination_directory, destination_name)
3699}
3700
3701/// Collapse a verified legacy staging/final hard-link pair into one sealed final link.
3702///
3703/// `staging` must be an exact strict [`PrivateFileLinkState::PublicationPair`] removal handle.
3704/// The destination directory must remain retained without delete sharing, and
3705/// `destination_name` must be one safe filename component. This function opens the named final
3706/// link while the pair exists, binds it to the staging handle by filesystem identity, removes the
3707/// exact staging link with handle-bound POSIX disposition, and then uses `ReOpenFile` to acquire a
3708/// final handle that denies write and delete sharing. Success requires strict single-link policy
3709/// and a second retained-directory identity binding.
3710///
3711/// # Errors
3712///
3713/// Every failure owns an exact staging or final handle. [`PrivatePublicationPairPhase::PairIntact`]
3714/// means staging-link removal was not reported successful. Later phases mean the staging link was
3715/// removed; callers must inspect the phase and retain or consume the exact returned handle rather
3716/// than deleting either pathname. In [`PrivatePublicationPairPhase::FinalSingleSharedDelete`], a
3717/// competing same-user namespace operation may still be live. In
3718/// [`PrivatePublicationPairPhase::FinalSingleSealed`], the exact original final object is stable,
3719/// but its requested destination binding or strict policy was not confirmed.
3720#[cfg(windows)]
3721pub fn complete_private_publication_pair(
3722    staging: std::fs::File,
3723    destination_directory: std::os::windows::io::BorrowedHandle<'_>,
3724    destination_name: &std::ffi::OsStr,
3725) -> Result<std::fs::File, PrivatePublicationPairError> {
3726    platform::complete_publication_pair(staging, destination_directory, destination_name)
3727}
3728
3729/// Atomically create a private staging file whose handle permits delete sharing.
3730///
3731/// This variant exists for create-only hard-link publication. The strict parent-directory handles
3732/// must remain retained while this handle is live, and callers must validate the transition from
3733/// [`PrivateFileLinkState::Single`] to [`PrivateFileLinkState::PublicationPair`] before removing
3734/// the staging link by handle.
3735///
3736/// # Errors
3737///
3738/// Returns a typed, path-free failure under the same conditions as [`create_private_file`].
3739#[cfg(windows)]
3740pub fn create_private_staging_file(
3741    path: &std::path::Path,
3742) -> Result<std::fs::File, PrivateDirectoryError> {
3743    platform::create_file(path, true)
3744}
3745
3746/// Consume a writable private staging handle and return an exact read/delete handle that denies
3747/// write sharing, without reopening the file by pathname.
3748///
3749/// The transition uses handle-relative Windows reopen operations. A short-lived intermediate
3750/// handle admits the consumed writer, then a second reopen proves that no competing writer remains
3751/// before the intermediate handle is dropped. The returned handle is suitable for create-only
3752/// hard-link publication and strict [`PrivateFileLinkState::PublicationPair`] verification.
3753/// A competing same-user writer can open, mutate, and close during that transition; callers whose
3754/// integrity decision predates sealing must revalidate contents through the returned sealed handle
3755/// before publication.
3756///
3757/// # Errors
3758///
3759/// Returns a typed, path-free failure if the input is not a strict single-link private file, an
3760/// exact handle transition fails, a competing writer prevents the sealed reopen, or handle-bound
3761/// cleanup cannot be confirmed. Once transition begins, every failure attempts deletion through a
3762/// retained exact handle and reports its cleanup status.
3763#[cfg(windows)]
3764pub fn seal_private_staging_file(
3765    file: std::fs::File,
3766) -> Result<std::fs::File, PrivateDirectoryError> {
3767    platform::seal_staging_file(file)
3768}
3769
3770/// Open an existing private regular file without following reparse points.
3771///
3772/// The retained handle permits concurrent readers but denies write and delete sharing, keeping
3773/// both contents and pathname stable through the caller's validation and read.
3774///
3775/// # Errors
3776///
3777/// Returns a typed, path-free failure if the path cannot be opened or the file, filesystem,
3778/// owner, hard-link state, or protected DACL differs from the private-file policy.
3779#[cfg(windows)]
3780pub fn open_private_file(path: &std::path::Path) -> Result<std::fs::File, PrivateDirectoryError> {
3781    platform::open_file(path)
3782}
3783
3784/// Open an existing strict private file for a durability-only synchronization retry.
3785///
3786/// The retained handle requests read/write access so [`std::fs::File::sync_all`] can issue the
3787/// Windows flush, shares only reads, and therefore denies competing writers, deletion, rename, and
3788/// pathname replacement. It opens the final component without following reparse points and accepts
3789/// only a strict, single-link private file. The function never creates or replaces a path.
3790///
3791/// Callers should use the write access only for `sync_all`; writing through this handle would mutate
3792/// the already-published artifact whose durability is being retried.
3793///
3794/// # Errors
3795///
3796/// Returns a typed, path-free failure if the path cannot be opened or the file, filesystem, owner,
3797/// single-link state, or protected DACL differs from the strict private-file policy.
3798#[cfg(windows)]
3799pub fn open_private_file_for_sync(
3800    path: &std::path::Path,
3801) -> Result<std::fs::File, PrivateDirectoryError> {
3802    platform::open_file_for_sync(path)
3803}
3804
3805/// Open an existing private regular file for identity-bound removal after a same-volume rename.
3806///
3807/// The returned handle requests `DELETE` access and permits read and delete sharing. It therefore
3808/// remains bound to the verified file while another handle renames the file, and can subsequently
3809/// be consumed by [`remove_private_file_handle`] to remove that exact renamed object. Write sharing
3810/// remains denied while the handle is live.
3811///
3812/// # Errors
3813///
3814/// Returns a typed, path-free failure if the path cannot be opened or the file, filesystem,
3815/// owner, single-link state, or protected DACL differs from the private-file policy.
3816#[cfg(windows)]
3817pub fn open_private_file_for_removal(
3818    path: &std::path::Path,
3819) -> Result<std::fs::File, PrivateDirectoryError> {
3820    platform::open_file_for_removal(path)
3821}
3822
3823/// Open an existing private regular file for identity-bound removal in an explicit link state.
3824///
3825/// Like [`open_private_file_for_removal`], the returned handle requests `DELETE` access, permits
3826/// read and delete sharing, denies write sharing, and does not follow reparse points. The file must
3827/// satisfy the strict private owner, protected-DACL, ACL-filesystem, and requested hard-link-state
3828/// policy before the handle is returned. This permits crash recovery to reopen a staging path in
3829/// [`PrivateFileLinkState::PublicationPair`] and safely consume it with
3830/// [`remove_private_file_handle_in_state`].
3831///
3832/// # Errors
3833///
3834/// Returns a typed, path-free failure if the path cannot be opened or the file, filesystem,
3835/// owner, requested link state, or protected DACL differs from the private-file policy.
3836#[cfg(windows)]
3837pub fn open_private_file_for_removal_in_state(
3838    path: &std::path::Path,
3839    link_state: PrivateFileLinkState,
3840) -> Result<std::fs::File, PrivateDirectoryError> {
3841    platform::open_file_for_removal_in_state(path, link_state)
3842}
3843
3844/// Verify a retained Windows directory handle against the strict private-directory policy.
3845///
3846/// # Errors
3847///
3848/// Returns a typed, path-free failure if the handle is not a plain directory on an ACL-capable
3849/// filesystem or its owner, protected-DACL state, or exact ACE allowlist differs from policy.
3850#[cfg(windows)]
3851pub fn verify_private_directory_handle(
3852    handle: std::os::windows::io::BorrowedHandle<'_>,
3853) -> Result<(), PrivateDirectoryError> {
3854    platform::verify(handle)
3855}
3856
3857/// Synchronize cached metadata for one retained strict private directory.
3858///
3859/// This operation performs no pathname lookup. It verifies the exact retained directory before
3860/// and after issuing synchronous `NtFlushBuffersFileEx` normal mode, which requests cached data,
3861/// filesystem metadata, and the underlying storage cache to be flushed. Handles returned by
3862/// [`create_private_directory`], [`open_private_directory`], and
3863/// [`open_private_directory_read_guard`] include the minimal directory-write access required by
3864/// Windows while retaining their documented no-delete-sharing behavior.
3865///
3866/// A successful return means Windows reported completion of the flush request. Physical hardware
3867/// can still fail to honor cache-flush commands; this API cannot strengthen the storage device's
3868/// durability guarantees.
3869///
3870/// # Errors
3871///
3872/// Returns a typed, path-free failure if the retained object no longer satisfies strict private
3873/// directory policy, its handle lacks directory-write access, or Windows does not report successful
3874/// synchronous completion. The function never converts an unsupported or rejected flush into
3875/// success.
3876#[cfg(windows)]
3877pub fn sync_private_directory_handle(
3878    directory: std::os::windows::io::BorrowedHandle<'_>,
3879) -> Result<(), PrivateDirectoryError> {
3880    platform::sync_directory(directory)
3881}
3882
3883/// Verify a retained Windows regular-file handle against the strict private-file policy.
3884///
3885/// The file must be non-reparse, have exactly one hard link, be owned by the current process-token
3886/// user, and use the protected current-user/SYSTEM/Administrators DACL.
3887///
3888/// # Errors
3889///
3890/// Returns a typed, path-free failure if the handle or its ACL differs from policy.
3891#[cfg(windows)]
3892pub fn verify_private_file_handle(
3893    handle: std::os::windows::io::BorrowedHandle<'_>,
3894) -> Result<(), PrivateDirectoryError> {
3895    platform::verify_file(handle, PrivateFileLinkState::Single)
3896}
3897
3898/// Verify a retained Windows private-file handle in an explicit hard-link publication state.
3899///
3900/// # Errors
3901///
3902/// Returns a typed, path-free failure if the handle or its ACL/link state differs from policy.
3903#[cfg(windows)]
3904pub fn verify_private_file_handle_in_state(
3905    handle: std::os::windows::io::BorrowedHandle<'_>,
3906    link_state: PrivateFileLinkState,
3907) -> Result<(), PrivateDirectoryError> {
3908    platform::verify_file(handle, link_state)
3909}
3910
3911/// Return the hard-link count for an arbitrary retained Windows regular-file handle.
3912///
3913/// This policy-neutral query rejects directories and reparse points, but deliberately does not
3914/// inspect filesystem ACL support, owner identity, or DACL contents. Callers that require the
3915/// strict private-file policy must separately use [`verify_private_file_handle`] or
3916/// [`verify_private_file_handle_in_state`].
3917///
3918/// # Errors
3919///
3920/// Returns a typed, path-free failure if Windows cannot query the handle or if it identifies a
3921/// directory or reparse point rather than a regular file.
3922#[cfg(windows)]
3923pub fn regular_file_link_count(
3924    handle: std::os::windows::io::BorrowedHandle<'_>,
3925) -> Result<u32, PrivateDirectoryError> {
3926    platform::regular_file_link_count(handle)
3927}
3928
3929/// Verify and remove the exact empty private directory identified by a retained Windows handle.
3930///
3931/// The consumed handle must be the no-delete-sharing handle returned by
3932/// [`create_private_directory`]. No pathname lookup or recursive pathname deletion occurs.
3933///
3934/// # Errors
3935///
3936/// Returns a typed, path-free failure if the handle no longer satisfies private-directory policy
3937/// or Windows cannot mark the exact empty directory for deletion. Every error reports
3938/// [`PrivateDirectoryCleanupStatus::Uncertain`].
3939#[cfg(windows)]
3940pub fn remove_private_directory_handle(
3941    directory: std::fs::File,
3942) -> Result<(), PrivateDirectoryError> {
3943    platform::remove(directory)
3944}
3945
3946/// Recursively remove the strict private root identified by a retained Windows directory handle.
3947///
3948/// The root path is resolved from the handle itself while its no-delete-sharing contract remains
3949/// active. Every descendant is enumerated with a filesystem identifier, reopened without
3950/// following reparse points and without delete sharing, matched to that identifier, and retained
3951/// through exact handle disposition. Descendant directories may use ordinary inherited security
3952/// descriptors; reparse points, non-regular files, and multiply linked regular files are rejected.
3953/// Concurrently added entries are never traversed and make final directory disposition fail.
3954///
3955/// # Errors
3956///
3957/// Returns a typed failure if the root is not strict and private, a descendant cannot be bound or
3958/// violates the tree policy, or Windows cannot remove an exact retained object. Because recursive
3959/// cleanup may already have removed siblings, every failure reports
3960/// [`PrivateDirectoryCleanupStatus::Uncertain`].
3961#[cfg(windows)]
3962pub fn remove_private_directory_tree_handle(
3963    directory: std::fs::File,
3964) -> Result<(), PrivateDirectoryError> {
3965    platform::remove_directory_tree(directory)
3966}
3967
3968/// Verify and remove the exact private regular file identified by a retained Windows handle.
3969///
3970/// # Errors
3971///
3972/// Returns a typed, path-free failure if the handle no longer satisfies the private-file policy
3973/// or Windows cannot mark the exact file for deletion.
3974#[cfg(windows)]
3975pub fn remove_private_file_handle(file: std::fs::File) -> Result<(), PrivateDirectoryError> {
3976    platform::remove_file(file, PrivateFileLinkState::Single)
3977}
3978
3979/// Verify and remove the exact private regular-file link in an explicit publication state.
3980///
3981/// This is intended for the staging handle returned by [`create_private_staging_file`]. With
3982/// [`PrivateFileLinkState::PublicationPair`], Windows removes the staging link opened by that
3983/// handle while preserving the separately verified final hard link.
3984///
3985/// # Errors
3986///
3987/// Returns a typed, path-free failure if the handle no longer satisfies the requested private-file
3988/// policy or Windows cannot mark the exact link for deletion.
3989#[cfg(windows)]
3990pub fn remove_private_file_handle_in_state(
3991    file: std::fs::File,
3992    link_state: PrivateFileLinkState,
3993) -> Result<(), PrivateDirectoryError> {
3994    platform::remove_file(file, link_state)
3995}
3996
3997#[cfg(test)]
3998mod tests {
3999    use super::*;
4000
4001    const USER: &[u8] = &[1, 1, 0, 0, 0, 0, 0, 5, 21, 0, 0, 0];
4002    const SYSTEM: &[u8] = &[1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0];
4003    const ADMINISTRATORS: &[u8] = &[1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0];
4004
4005    #[test]
4006    fn exact_private_acl_is_accepted_independent_of_ace_order() {
4007        let raw = acl(&[
4008            ace(
4009                SYSTEM,
4010                ACCESS_ALLOWED_ACE_TYPE,
4011                DIRECTORY_INHERIT_FLAGS,
4012                FILE_ALL_ACCESS_MASK,
4013            ),
4014            ace(
4015                USER,
4016                ACCESS_ALLOWED_ACE_TYPE,
4017                DIRECTORY_INHERIT_FLAGS,
4018                FILE_ALL_ACCESS_MASK,
4019            ),
4020            ace(
4021                ADMINISTRATORS,
4022                ACCESS_ALLOWED_ACE_TYPE,
4023                DIRECTORY_INHERIT_FLAGS,
4024                FILE_ALL_ACCESS_MASK,
4025            ),
4026        ]);
4027        assert_eq!(validate_acl(&raw, &[USER, SYSTEM, ADMINISTRATORS]), Ok(()));
4028    }
4029
4030    #[test]
4031    fn regular_file_acl_requires_non_inheriting_aces() {
4032        let raw = acl(&[
4033            ace(
4034                USER,
4035                ACCESS_ALLOWED_ACE_TYPE,
4036                FILE_ACE_FLAGS,
4037                FILE_ALL_ACCESS_MASK,
4038            ),
4039            ace(
4040                SYSTEM,
4041                ACCESS_ALLOWED_ACE_TYPE,
4042                FILE_ACE_FLAGS,
4043                FILE_ALL_ACCESS_MASK,
4044            ),
4045            ace(
4046                ADMINISTRATORS,
4047                ACCESS_ALLOWED_ACE_TYPE,
4048                FILE_ACE_FLAGS,
4049                FILE_ALL_ACCESS_MASK,
4050            ),
4051        ]);
4052        assert_eq!(
4053            validate_acl_with_flags(&raw, &[USER, SYSTEM, ADMINISTRATORS], FILE_ACE_FLAGS),
4054            Ok(())
4055        );
4056        assert_eq!(
4057            validate_acl(&raw, &[USER, SYSTEM, ADMINISTRATORS]),
4058            Err(PrivateDirectoryAclViolation::UnexpectedAceFlags)
4059        );
4060    }
4061
4062    #[test]
4063    fn unknown_allow_ace_is_rejected() {
4064        let unknown = [1, 1, 0, 0, 0, 0, 0, 5, 11, 0, 0, 0];
4065        let raw = acl(&[
4066            ace(
4067                USER,
4068                ACCESS_ALLOWED_ACE_TYPE,
4069                DIRECTORY_INHERIT_FLAGS,
4070                FILE_ALL_ACCESS_MASK,
4071            ),
4072            ace(
4073                SYSTEM,
4074                ACCESS_ALLOWED_ACE_TYPE,
4075                DIRECTORY_INHERIT_FLAGS,
4076                FILE_ALL_ACCESS_MASK,
4077            ),
4078            ace(
4079                &unknown,
4080                ACCESS_ALLOWED_ACE_TYPE,
4081                DIRECTORY_INHERIT_FLAGS,
4082                FILE_ALL_ACCESS_MASK,
4083            ),
4084        ]);
4085        assert_eq!(
4086            validate_acl(&raw, &[USER, SYSTEM, ADMINISTRATORS]),
4087            Err(PrivateDirectoryAclViolation::UnknownPrincipal)
4088        );
4089    }
4090
4091    #[test]
4092    fn deny_object_and_inherited_aces_are_rejected() {
4093        for (ace_type, flags, expected) in [
4094            (
4095                1,
4096                DIRECTORY_INHERIT_FLAGS,
4097                PrivateDirectoryAclViolation::UnsupportedAceType,
4098            ),
4099            (
4100                ACCESS_ALLOWED_ACE_TYPE,
4101                DIRECTORY_INHERIT_FLAGS | 0x10,
4102                PrivateDirectoryAclViolation::UnexpectedAceFlags,
4103            ),
4104            (
4105                5,
4106                DIRECTORY_INHERIT_FLAGS,
4107                PrivateDirectoryAclViolation::UnsupportedAceType,
4108            ),
4109        ] {
4110            let raw = acl(&[
4111                ace(USER, ace_type, flags, FILE_ALL_ACCESS_MASK),
4112                ace(
4113                    SYSTEM,
4114                    ACCESS_ALLOWED_ACE_TYPE,
4115                    DIRECTORY_INHERIT_FLAGS,
4116                    FILE_ALL_ACCESS_MASK,
4117                ),
4118                ace(
4119                    ADMINISTRATORS,
4120                    ACCESS_ALLOWED_ACE_TYPE,
4121                    DIRECTORY_INHERIT_FLAGS,
4122                    FILE_ALL_ACCESS_MASK,
4123                ),
4124            ]);
4125            assert_eq!(
4126                validate_acl(&raw, &[USER, SYSTEM, ADMINISTRATORS]),
4127                Err(expected)
4128            );
4129        }
4130    }
4131
4132    #[test]
4133    fn broad_or_root_inherit_only_access_is_rejected() {
4134        for (flags, mask, expected) in [
4135            (
4136                DIRECTORY_INHERIT_FLAGS,
4137                FILE_ALL_ACCESS_MASK | 0x0040_0000,
4138                PrivateDirectoryAclViolation::UnexpectedAccessMask,
4139            ),
4140            (
4141                DIRECTORY_INHERIT_FLAGS | 0x08,
4142                FILE_ALL_ACCESS_MASK,
4143                PrivateDirectoryAclViolation::UnexpectedAceFlags,
4144            ),
4145        ] {
4146            let raw = acl(&[
4147                ace(USER, ACCESS_ALLOWED_ACE_TYPE, flags, mask),
4148                ace(
4149                    SYSTEM,
4150                    ACCESS_ALLOWED_ACE_TYPE,
4151                    DIRECTORY_INHERIT_FLAGS,
4152                    FILE_ALL_ACCESS_MASK,
4153                ),
4154                ace(
4155                    ADMINISTRATORS,
4156                    ACCESS_ALLOWED_ACE_TYPE,
4157                    DIRECTORY_INHERIT_FLAGS,
4158                    FILE_ALL_ACCESS_MASK,
4159                ),
4160            ]);
4161            assert_eq!(
4162                validate_acl(&raw, &[USER, SYSTEM, ADMINISTRATORS]),
4163                Err(expected)
4164            );
4165        }
4166    }
4167
4168    #[test]
4169    fn duplicate_missing_and_malformed_principals_are_rejected() {
4170        let duplicate = acl(&[
4171            ace(
4172                USER,
4173                ACCESS_ALLOWED_ACE_TYPE,
4174                DIRECTORY_INHERIT_FLAGS,
4175                FILE_ALL_ACCESS_MASK,
4176            ),
4177            ace(
4178                USER,
4179                ACCESS_ALLOWED_ACE_TYPE,
4180                DIRECTORY_INHERIT_FLAGS,
4181                FILE_ALL_ACCESS_MASK,
4182            ),
4183            ace(
4184                ADMINISTRATORS,
4185                ACCESS_ALLOWED_ACE_TYPE,
4186                DIRECTORY_INHERIT_FLAGS,
4187                FILE_ALL_ACCESS_MASK,
4188            ),
4189        ]);
4190        assert_eq!(
4191            validate_acl(&duplicate, &[USER, SYSTEM, ADMINISTRATORS]),
4192            Err(PrivateDirectoryAclViolation::DuplicatePrincipal)
4193        );
4194
4195        let malformed_sid = [1, 2, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0];
4196        let malformed = acl(&[
4197            ace(
4198                &malformed_sid,
4199                ACCESS_ALLOWED_ACE_TYPE,
4200                DIRECTORY_INHERIT_FLAGS,
4201                FILE_ALL_ACCESS_MASK,
4202            ),
4203            ace(
4204                SYSTEM,
4205                ACCESS_ALLOWED_ACE_TYPE,
4206                DIRECTORY_INHERIT_FLAGS,
4207                FILE_ALL_ACCESS_MASK,
4208            ),
4209            ace(
4210                ADMINISTRATORS,
4211                ACCESS_ALLOWED_ACE_TYPE,
4212                DIRECTORY_INHERIT_FLAGS,
4213                FILE_ALL_ACCESS_MASK,
4214            ),
4215        ]);
4216        assert_eq!(
4217            validate_acl(&malformed, &[USER, SYSTEM, ADMINISTRATORS]),
4218            Err(PrivateDirectoryAclViolation::InvalidSid)
4219        );
4220
4221        let missing = acl(&[
4222            ace(
4223                USER,
4224                ACCESS_ALLOWED_ACE_TYPE,
4225                DIRECTORY_INHERIT_FLAGS,
4226                FILE_ALL_ACCESS_MASK,
4227            ),
4228            ace(
4229                SYSTEM,
4230                ACCESS_ALLOWED_ACE_TYPE,
4231                DIRECTORY_INHERIT_FLAGS,
4232                FILE_ALL_ACCESS_MASK,
4233            ),
4234        ]);
4235        assert_eq!(
4236            validate_acl(&missing, &[USER, SYSTEM, ADMINISTRATORS]),
4237            Err(PrivateDirectoryAclViolation::WrongAceCount)
4238        );
4239
4240        let mut truncated = acl(&[
4241            ace(
4242                USER,
4243                ACCESS_ALLOWED_ACE_TYPE,
4244                DIRECTORY_INHERIT_FLAGS,
4245                FILE_ALL_ACCESS_MASK,
4246            ),
4247            ace(
4248                SYSTEM,
4249                ACCESS_ALLOWED_ACE_TYPE,
4250                DIRECTORY_INHERIT_FLAGS,
4251                FILE_ALL_ACCESS_MASK,
4252            ),
4253            ace(
4254                ADMINISTRATORS,
4255                ACCESS_ALLOWED_ACE_TYPE,
4256                DIRECTORY_INHERIT_FLAGS,
4257                FILE_ALL_ACCESS_MASK,
4258            ),
4259        ]);
4260        truncated.pop();
4261        assert_eq!(
4262            validate_acl(&truncated, &[USER, SYSTEM, ADMINISTRATORS]),
4263            Err(PrivateDirectoryAclViolation::Malformed)
4264        );
4265    }
4266
4267    #[test]
4268    fn duplicate_fixed_principal_uses_one_exact_ace() {
4269        let deduplicated = acl(&[
4270            ace(
4271                SYSTEM,
4272                ACCESS_ALLOWED_ACE_TYPE,
4273                DIRECTORY_INHERIT_FLAGS,
4274                FILE_ALL_ACCESS_MASK,
4275            ),
4276            ace(
4277                ADMINISTRATORS,
4278                ACCESS_ALLOWED_ACE_TYPE,
4279                DIRECTORY_INHERIT_FLAGS,
4280                FILE_ALL_ACCESS_MASK,
4281            ),
4282        ]);
4283        assert_eq!(
4284            validate_acl(&deduplicated, &[SYSTEM, ADMINISTRATORS]),
4285            Ok(())
4286        );
4287    }
4288
4289    fn acl(aces: &[Vec<u8>]) -> Vec<u8> {
4290        let length = ACL_HEADER_BYTES + aces.iter().map(Vec::len).sum::<usize>();
4291        let mut raw = vec![0; ACL_HEADER_BYTES];
4292        raw[0] = ACL_REVISION;
4293        raw[2..4].copy_from_slice(
4294            &u16::try_from(length)
4295                .expect("test ACL length")
4296                .to_le_bytes(),
4297        );
4298        raw[4..6].copy_from_slice(
4299            &u16::try_from(aces.len())
4300                .expect("test ACE count")
4301                .to_le_bytes(),
4302        );
4303        for ace in aces {
4304            raw.extend_from_slice(ace);
4305        }
4306        raw
4307    }
4308
4309    fn ace(sid: &[u8], ace_type: u8, flags: u8, mask: u32) -> Vec<u8> {
4310        let length = ACCESS_ALLOWED_ACE_PREFIX_BYTES + sid.len();
4311        let mut raw = Vec::with_capacity(length);
4312        raw.push(ace_type);
4313        raw.push(flags);
4314        raw.extend_from_slice(
4315            &u16::try_from(length)
4316                .expect("test ACE length")
4317                .to_le_bytes(),
4318        );
4319        raw.extend_from_slice(&mask.to_le_bytes());
4320        raw.extend_from_slice(sid);
4321        raw
4322    }
4323}