cloud_filter/
placeholder.rs

1use std::{
2    fmt::Debug,
3    fs::File,
4    mem::{self, MaybeUninit},
5    ops::{Bound, Range, RangeBounds},
6    os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle},
7    path::Path,
8    ptr,
9};
10
11use widestring::U16CString;
12use windows::{
13    core::{self, PCWSTR},
14    Win32::{
15        Foundation::{
16            CloseHandle, BOOL, ERROR_NOT_A_CLOUD_FILE, E_HANDLE, HANDLE, INVALID_HANDLE_VALUE,
17        },
18        Storage::CloudFilters::{
19            self, CfCloseHandle, CfConvertToPlaceholder, CfGetPlaceholderInfo,
20            CfGetPlaceholderRangeInfo, CfGetWin32HandleFromProtectedHandle, CfHydratePlaceholder,
21            CfOpenFileWithOplock, CfReferenceProtectedHandle, CfReleaseProtectedHandle,
22            CfRevertPlaceholder, CfSetInSyncState, CfSetPinState, CfUpdatePlaceholder,
23            CF_CONVERT_FLAGS, CF_FILE_RANGE, CF_OPEN_FILE_FLAGS, CF_PIN_STATE,
24            CF_PLACEHOLDER_RANGE_INFO_CLASS, CF_PLACEHOLDER_STANDARD_INFO, CF_SET_PIN_FLAGS,
25            CF_UPDATE_FLAGS,
26        },
27    },
28};
29
30use crate::{metadata::Metadata, usn::Usn};
31
32/// The type of handle that the placeholder file/directory owns.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum PlaceholderHandleType {
35    /// A handle that was opened with [CfOpenFileWithOplock].
36    CfApi,
37    /// A handle that was opened with [CreateFileW][windows::Win32::Storage::FileSystem::CreateFileW] etc.
38    Win32,
39}
40
41/// An owned handle to a placeholder file/directory.
42///
43/// This closes the handle on drop.
44#[derive(Debug)]
45pub struct OwnedPlaceholderHandle {
46    handle_type: PlaceholderHandleType,
47    handle: HANDLE,
48}
49
50impl OwnedPlaceholderHandle {
51    /// Create a new [OwnedPlaceholderHandle] from a handle returned by [CfOpenFileWithOplock].
52    ///
53    /// # Safety
54    ///
55    /// The handle must be valid and owned by the caller.
56    pub unsafe fn from_cfapi(handle: HANDLE) -> Self {
57        Self {
58            handle_type: PlaceholderHandleType::CfApi,
59            handle,
60        }
61    }
62
63    /// Create a new [OwnedPlaceholderHandle] from a handle returned by
64    /// [CreateFile][windows::Win32::Storage::FileSystem::CreateFileW] etc.
65    ///
66    /// # Safety
67    ///
68    /// The handle must be valid and owned by the caller.
69    pub unsafe fn from_win32(handle: HANDLE) -> Self {
70        Self {
71            handle_type: PlaceholderHandleType::Win32,
72            handle,
73        }
74    }
75
76    pub const fn handle(&self) -> HANDLE {
77        self.handle
78    }
79
80    pub const fn handle_type(&self) -> PlaceholderHandleType {
81        self.handle_type
82    }
83}
84
85impl Drop for OwnedPlaceholderHandle {
86    fn drop(&mut self) {
87        match self.handle_type {
88            PlaceholderHandleType::CfApi => unsafe { CfCloseHandle(self.handle) },
89            PlaceholderHandleType::Win32 => unsafe {
90                _ = CloseHandle(self.handle);
91            },
92        }
93    }
94}
95
96/// Holds a Win32 handle from the protected handle.
97///
98/// The reference count will increase when the [ArcWin32Handle] is cloned
99/// and decrease when the [ArcWin32Handle] is dropped.
100pub struct ArcWin32Handle {
101    win32_handle: HANDLE,
102    protected_handle: HANDLE,
103}
104
105impl ArcWin32Handle {
106    /// Win32 handle from the protected handle.
107    pub fn handle(&self) -> HANDLE {
108        self.win32_handle
109    }
110}
111
112impl Clone for ArcWin32Handle {
113    fn clone(&self) -> Self {
114        if self.protected_handle != INVALID_HANDLE_VALUE {
115            unsafe { CfReferenceProtectedHandle(self.protected_handle) };
116        }
117
118        Self {
119            win32_handle: self.win32_handle,
120            protected_handle: self.protected_handle,
121        }
122    }
123}
124
125impl AsRawHandle for ArcWin32Handle {
126    fn as_raw_handle(&self) -> RawHandle {
127        unsafe { mem::transmute(self.win32_handle) }
128    }
129}
130
131impl Drop for ArcWin32Handle {
132    fn drop(&mut self) {
133        if self.protected_handle != INVALID_HANDLE_VALUE {
134            unsafe { CfReleaseProtectedHandle(self.protected_handle) };
135        }
136    }
137}
138
139/// Safety: reference counted by syscall
140unsafe impl Send for ArcWin32Handle {}
141/// Safety: reference counted by syscall
142unsafe impl Sync for ArcWin32Handle {}
143
144/// Options for opening a placeholder file/directory.
145pub struct OpenOptions {
146    flags: CF_OPEN_FILE_FLAGS,
147}
148
149impl OpenOptions {
150    pub fn new() -> Self {
151        Self::default()
152    }
153
154    pub fn exclusive(mut self) -> Self {
155        self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_EXCLUSIVE;
156        self
157    }
158
159    pub fn write_access(mut self) -> Self {
160        self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_WRITE_ACCESS;
161        self
162    }
163
164    pub fn delete_access(mut self) -> Self {
165        self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_DELETE_ACCESS;
166        self
167    }
168
169    pub fn foreground(mut self) -> Self {
170        self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_FOREGROUND;
171        self
172    }
173
174    /// Open the placeholder file/directory using `CfOpenFileWithOplock`.
175    pub fn open(self, path: impl AsRef<Path>) -> core::Result<Placeholder> {
176        let u16_path = U16CString::from_os_str(path.as_ref()).unwrap();
177        let handle = unsafe { CfOpenFileWithOplock(PCWSTR(u16_path.as_ptr()), self.flags) }?;
178        Ok(Placeholder {
179            handle: unsafe { OwnedPlaceholderHandle::from_cfapi(handle) },
180        })
181    }
182}
183
184impl Default for OpenOptions {
185    fn default() -> Self {
186        Self {
187            flags: CloudFilters::CF_OPEN_FILE_FLAG_NONE,
188        }
189    }
190}
191
192/// The pin state of a placeholder.
193///
194/// [Read more
195/// here](https://docs.microsoft.com/en-us/windows/win32/api/cfapi/ne-cfapi-cf_pin_state#remarks)
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum PinState {
198    /// The platform could decide freely.
199    Unspecified,
200    /// [SyncFilter::fetch_data][crate::filter::SyncFilter::fetch_data] will be called to hydrate the rest
201    /// of the placeholder's data. Any dehydration requests will fail automatically.
202    Pinned,
203    /// [SyncFilter::dehydrate][crate::filter::SyncFilter::dehydrate] will be called to dehydrate the rest
204    /// of the placeholder's data.
205    Unpinned,
206    /// The placeholder will never sync to the cloud.
207    Excluded,
208    /// The placeholder will inherit the parent placeholder's pin state.
209    Inherit,
210}
211
212impl From<PinState> for CF_PIN_STATE {
213    fn from(state: PinState) -> Self {
214        match state {
215            PinState::Unspecified => CloudFilters::CF_PIN_STATE_UNSPECIFIED,
216            PinState::Pinned => CloudFilters::CF_PIN_STATE_PINNED,
217            PinState::Unpinned => CloudFilters::CF_PIN_STATE_UNPINNED,
218            PinState::Excluded => CloudFilters::CF_PIN_STATE_EXCLUDED,
219            PinState::Inherit => CloudFilters::CF_PIN_STATE_INHERIT,
220        }
221    }
222}
223
224impl From<CF_PIN_STATE> for PinState {
225    fn from(state: CF_PIN_STATE) -> Self {
226        match state {
227            CloudFilters::CF_PIN_STATE_UNSPECIFIED => PinState::Unspecified,
228            CloudFilters::CF_PIN_STATE_PINNED => PinState::Pinned,
229            CloudFilters::CF_PIN_STATE_UNPINNED => PinState::Unpinned,
230            CloudFilters::CF_PIN_STATE_EXCLUDED => PinState::Excluded,
231            CloudFilters::CF_PIN_STATE_INHERIT => PinState::Inherit,
232            _ => unreachable!(),
233        }
234    }
235}
236
237/// The placeholder pin flags.
238#[derive(Debug, Clone, Copy)]
239pub struct PinOptions(CF_SET_PIN_FLAGS);
240
241impl PinOptions {
242    /// Applies the pin state to all descendants of the placeholder (if the placeholder is a
243    /// directory).
244    pub fn recurse(&mut self) -> &mut Self {
245        self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE;
246        self
247    }
248
249    /// Applies the pin state to all descendants of the placeholder excluding the current one (if
250    /// the placeholder is a directory).
251    pub fn recurse_children(&mut self) -> &mut Self {
252        self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE_ONLY;
253        self
254    }
255
256    /// Stop applying the pin state when the first error is encountered. Otherwise, skip over it
257    /// and keep applying.
258    pub fn stop_on_error(&mut self) -> &mut Self {
259        self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE_STOP_ON_ERROR;
260        self
261    }
262}
263
264impl Default for PinOptions {
265    fn default() -> Self {
266        Self(CloudFilters::CF_SET_PIN_FLAG_NONE)
267    }
268}
269
270/// File to placeholder file conversion parameters.
271#[derive(Debug, Clone)]
272pub struct ConvertOptions {
273    flags: CF_CONVERT_FLAGS,
274    blob: Vec<u8>,
275}
276
277impl ConvertOptions {
278    /// Marks a placeholder as in sync.
279    ///
280    /// See also
281    /// [SetInSyncState](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfsetinsyncstate),
282    /// [What does "In-Sync" Mean?](https://www.userfilesystem.com/programming/faq/#nav_whatdoesin-syncmean)
283    pub fn mark_in_sync(mut self) -> Self {
284        self.flags |= CloudFilters::CF_CONVERT_FLAG_MARK_IN_SYNC;
285        self
286    }
287
288    /// Dehydrate the placeholder after conversion.
289    ///
290    /// This flag is only applicable to files.
291    pub fn dehydrate(mut self) -> Self {
292        self.flags |= CloudFilters::CF_CONVERT_FLAG_DEHYDRATE;
293        self
294    }
295
296    /// Marks the placeholder as "partially full," such that
297    /// [SyncFilter::fetch_placeholders][crate::filter::SyncFilter::fetch_placeholders]
298    /// will be invoked when this directory is next accessed so that the remaining placeholders are inserted.
299    ///
300    /// Only applicable to placeholder directories.
301    pub fn has_children(mut self) -> Self {
302        self.flags |= CloudFilters::CF_CONVERT_FLAG_ENABLE_ON_DEMAND_POPULATION;
303        self
304    }
305
306    /// Blocks this placeholder from being dehydrated.
307    ///
308    /// This flag does not work on directories.
309    pub fn block_dehydration(mut self) -> Self {
310        self.flags |= CloudFilters::CF_CONVERT_FLAG_ALWAYS_FULL;
311        self
312    }
313
314    /// Forces the conversion of a non-cloud placeholder file to a cloud placeholder file.
315    ///
316    /// Placeholder files are built into the NTFS file system and thus, a placeholder not associated
317    /// with the sync root is possible.
318    pub fn force(mut self) -> Self {
319        self.flags |= CloudFilters::CF_CONVERT_FLAG_FORCE_CONVERT_TO_CLOUD_FILE;
320        self
321    }
322
323    /// A buffer of bytes stored with the file that could be accessed through a
324    /// [Request::file_blob][crate::filter::Request::file_blob] or [Placeholder::info].
325    ///
326    /// The buffer must not exceed
327    /// [4KiB](https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Storage/CloudFilters/constant.CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH.html).
328    pub fn blob(mut self, blob: Vec<u8>) -> Self {
329        assert!(
330            blob.len() <= CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH as usize,
331            "blob size must not exceed {} bytes, got {} bytes",
332            CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH,
333            blob.len()
334        );
335        self.blob = blob;
336        self
337    }
338}
339
340impl Default for ConvertOptions {
341    fn default() -> Self {
342        Self {
343            flags: CloudFilters::CF_CONVERT_FLAG_NONE,
344            blob: Vec::new(),
345        }
346    }
347}
348
349#[derive(Clone)]
350pub struct PlaceholderInfo {
351    data: Vec<u8>,
352    info: *const CF_PLACEHOLDER_STANDARD_INFO,
353}
354
355impl PlaceholderInfo {
356    pub fn on_disk_data_size(&self) -> i64 {
357        unsafe { &*self.info }.OnDiskDataSize
358    }
359
360    pub fn validated_data_size(&self) -> i64 {
361        unsafe { &*self.info }.ValidatedDataSize
362    }
363
364    pub fn modified_data_size(&self) -> i64 {
365        unsafe { &*self.info }.ModifiedDataSize
366    }
367
368    pub fn properties_size(&self) -> i64 {
369        unsafe { &*self.info }.PropertiesSize
370    }
371
372    pub fn pin_state(&self) -> PinState {
373        unsafe { &*self.info }.PinState.into()
374    }
375
376    pub fn is_in_sync(&self) -> bool {
377        unsafe { &*self.info }.InSyncState == CloudFilters::CF_IN_SYNC_STATE_IN_SYNC
378    }
379
380    pub fn file_id(&self) -> i64 {
381        unsafe { &*self.info }.FileId
382    }
383
384    pub fn sync_root_file_id(&self) -> i64 {
385        unsafe { &*self.info }.SyncRootFileId
386    }
387
388    pub fn blob(&self) -> &[u8] {
389        &self.data[mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>()..]
390    }
391}
392
393impl std::fmt::Debug for PlaceholderInfo {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        f.debug_struct("PlaceholderInfo")
396            .field("on_disk_data_size", &self.on_disk_data_size())
397            .field("validated_data_size", &self.validated_data_size())
398            .field("modified_data_size", &self.modified_data_size())
399            .field("properties_size", &self.properties_size())
400            .field("pin_state", &self.pin_state())
401            .field("is_in_sync", &self.is_in_sync())
402            .field("file_id", &self.file_id())
403            .field("sync_root_file_id", &self.sync_root_file_id())
404            .finish()
405    }
406}
407
408/// Placeholder update parameters.
409#[derive(Debug, Clone)]
410pub struct UpdateOptions<'a> {
411    metadata: Option<Metadata>,
412    dehydrate_ranges: Vec<CF_FILE_RANGE>,
413    flags: CF_UPDATE_FLAGS,
414    blob: &'a [u8],
415}
416
417impl<'a> UpdateOptions<'a> {
418    /// [Metadata] contains file system metadata about the placeholder to be updated.
419    ///
420    /// File size will be truncates to 0 if not specified, otherwise to the specified size.
421    pub fn metadata(mut self, metadata: Metadata) -> Self {
422        self.flags &= !(CloudFilters::CF_UPDATE_FLAG_PASSTHROUGH_FS_METADATA);
423        self.metadata = Some(metadata);
424        self
425    }
426
427    /// Fields in [Metadata] will be updated.
428    pub fn metadata_all(mut self, metadata: Metadata) -> Self {
429        self.flags |= CloudFilters::CF_UPDATE_FLAG_PASSTHROUGH_FS_METADATA;
430        self.metadata = Some(metadata);
431        self
432    }
433
434    /// Extended ranges to be dehydrated.
435    ///
436    /// All the offsets and lengths should be `PAGE_SIZE` aligned.
437    /// Passing a single range with Offset `0` and Length `CF_EOF` will invalidate the entire file.
438    /// This has the same effect as passing the flag `CF_UPDATE_FLAG_DEHYDRATE` instead
439    pub fn dehydrate_ranges(mut self, ranges: impl IntoIterator<Item = Range<u64>>) -> Self {
440        self.dehydrate_ranges
441            .extend(ranges.into_iter().map(|r| CF_FILE_RANGE {
442                StartingOffset: r.start as _,
443                Length: (r.end - r.start) as _,
444            }));
445        self
446    }
447
448    /// The update will fail if the `IN_SYNC` attribute is not currently set on the placeholder.
449    pub fn update_if_in_sync(mut self) -> Self {
450        self.flags |= CloudFilters::CF_UPDATE_FLAG_VERIFY_IN_SYNC;
451        self
452    }
453
454    /// Marks a placeholder as in sync.
455    ///
456    /// See also
457    /// [SetInSyncState](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfsetinsyncstate),
458    /// [What does "In-Sync" Mean?](https://www.userfilesystem.com/programming/faq/#nav_whatdoesin-syncmean)
459    pub fn mark_in_sync(mut self) -> Self {
460        self.flags |= CloudFilters::CF_UPDATE_FLAG_MARK_IN_SYNC;
461        self
462    }
463
464    /// Marks a placeholder as not in sync. `Sync Pending` will be shown in explorer.
465    ///
466    /// See also
467    /// [SetInSyncState](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfsetinsyncstate),
468    /// [What does "In-Sync" Mean?](https://www.userfilesystem.com/programming/faq/#nav_whatdoesin-syncmean)
469    pub fn mark_not_in_sync(mut self) -> Self {
470        self.flags |= CloudFilters::CF_UPDATE_FLAG_CLEAR_IN_SYNC;
471        self
472    }
473
474    /// The platform dehydrates the file after updating the placeholder successfully.
475    pub fn dehydrate(mut self) -> Self {
476        self.flags |= CloudFilters::CF_UPDATE_FLAG_DEHYDRATE;
477        self
478    }
479
480    /// Disables on-demand population for directories.
481    pub fn has_no_children(mut self) -> Self {
482        self.flags |= CloudFilters::CF_UPDATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
483        self
484    }
485
486    /// Enable on-demand population for directories.
487    pub fn has_children(mut self) -> Self {
488        self.flags |= CloudFilters::CF_UPDATE_FLAG_ENABLE_ON_DEMAND_POPULATION;
489        self
490    }
491
492    /// Remove the file identity from the placeholder.
493    /// [UpdateOptions::blob()](crate::placeholder::UpdateOptions::blob) will be ignored.
494    pub fn remove_blob(mut self) -> Self {
495        self.flags |= CloudFilters::CF_UPDATE_FLAG_REMOVE_FILE_IDENTITY;
496        self
497    }
498
499    /// The platform removes all existing extrinsic properties on the placeholder.
500    pub fn remove_properties(mut self) -> Self {
501        self.flags |= CloudFilters::CF_UPDATE_FLAG_REMOVE_PROPERTY;
502        self
503    }
504
505    pub fn blob(mut self, blob: &'a [u8]) -> Self {
506        assert!(
507            blob.len() <= CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH as usize,
508            "blob size must not exceed {} bytes, got {} bytes",
509            CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH,
510            blob.len()
511        );
512        self.blob = blob;
513        self
514    }
515}
516
517impl Default for UpdateOptions<'_> {
518    fn default() -> Self {
519        Self {
520            metadata: None,
521            dehydrate_ranges: Vec::new(),
522            flags: CloudFilters::CF_UPDATE_FLAG_NONE,
523            blob: &[],
524        }
525    }
526}
527
528/// The type of data to read from a placeholder.
529#[derive(Debug, Copy, Clone, PartialEq, Eq)]
530pub enum ReadType {
531    /// Any data that is saved to the disk.
532    Any,
533    /// Data that has been synced to the cloud.
534    Validated,
535    /// Data that has not synced to the cloud.
536    Modified,
537}
538
539impl From<ReadType> for CF_PLACEHOLDER_RANGE_INFO_CLASS {
540    fn from(read_type: ReadType) -> Self {
541        match read_type {
542            ReadType::Any => CloudFilters::CF_PLACEHOLDER_RANGE_INFO_ONDISK,
543            ReadType::Validated => CloudFilters::CF_PLACEHOLDER_RANGE_INFO_VALIDATED,
544            ReadType::Modified => CloudFilters::CF_PLACEHOLDER_RANGE_INFO_MODIFIED,
545        }
546    }
547}
548
549// #[derive(Clone, Copy)]
550// pub struct PlaceholderState(CF_PLACEHOLDER_STATE);
551
552// impl PlaceholderState {
553//     /// The placeholder is both a directory as well as the sync root.
554//     pub fn sync_root(&self) -> bool {
555//         (self.0 & CloudFilters::CF_PLACEHOLDER_STATE_SYNC_ROOT).0 != 0
556//     }
557
558//     /// There exists an essential property in the property store of the file or directory.
559//     pub fn essential_prop_present(&self) -> bool {
560//         (self.0 & CloudFilters::CF_PLACEHOLDER_STATE_ESSENTIAL_PROP_PRESENT).0 != 0
561//     }
562
563//     /// The placeholder is in sync.
564//     pub fn in_sync(&self) -> bool {
565//         (self.0 & CloudFilters::CF_PLACEHOLDER_STATE_IN_SYNC).0 != 0
566//     }
567
568//     /// The placeholder content is not ready to be consumed by the user application,
569//     /// though it may or may not be fully present locally.
570//     ///
571//     /// An example is a placeholder file whose content has been fully downloaded to the local disk,
572//     /// but is yet to be validated by a sync provider that
573//     /// has registered the sync root with the hydration modifier
574//     /// [HydrationPolicy::require_validation][crate::root::HydrationPolicy::require_validation].
575//     pub fn partial(&self) -> bool {
576//         (self.0 & CloudFilters::CF_PLACEHOLDER_STATE_PARTIAL).0 != 0
577//     }
578
579//     /// The placeholder content is not fully present locally.
580//     ///
581//     /// When this is set, [PlaceholderState::partial] also be `true`.
582//     pub fn partial_on_disk(&self) -> bool {
583//         (self.0 & CloudFilters::CF_PLACEHOLDER_STATE_PARTIALLY_ON_DISK).0 != 0
584//     }
585// }
586
587// impl Debug for PlaceholderState {
588//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589//         f.debug_struct("PlaceholderState")
590//             .field("sync_root", &self.sync_root())
591//             .field("essential_prop_present", &self.essential_prop_present())
592//             .field("in_sync", &self.in_sync())
593//             .field("partial", &self.partial())
594//             .field("partial_on_disk", &self.partial_on_disk())
595//             .finish()
596//     }
597// }
598
599/// A struct to perform various operations on a placeholder(or regular) file/directory.
600#[derive(Debug)]
601pub struct Placeholder {
602    handle: OwnedPlaceholderHandle,
603}
604
605impl Placeholder {
606    /// Create a placeholder from a raw handle.
607    ///
608    /// # Safety
609    ///
610    /// The passed handle must be a valid protected handle or win32 handle.
611    pub unsafe fn from_raw_handle(handle: OwnedPlaceholderHandle) -> Self {
612        Self { handle }
613    }
614
615    /// Open options for opening [Placeholder]s.
616    pub fn options() -> OpenOptions {
617        OpenOptions::default()
618    }
619
620    /// Open the placeholder file/directory with `CF_OPEN_FILE_FLAG_NONE`.
621    pub fn open(path: impl AsRef<Path>) -> core::Result<Self> {
622        OpenOptions::new().open(path)
623    }
624
625    /// Marks a placeholder as in sync or not.
626    ///
627    /// If the passed [Usn] is outdated, the call will fail,
628    /// otherwise the [Usn] will be updated.
629    ///
630    /// See also
631    /// [SetInSyncState](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfsetinsyncstate),
632    /// [What does "In-Sync" Mean?](https://www.userfilesystem.com/programming/faq/#nav_whatdoesin-syncmean)
633    pub fn mark_in_sync<'a>(
634        &mut self,
635        in_sync: bool,
636        usn: impl Into<Option<&'a mut Usn>>,
637    ) -> core::Result<&mut Self> {
638        unsafe {
639            CfSetInSyncState(
640                self.handle.handle,
641                match in_sync {
642                    true => CloudFilters::CF_IN_SYNC_STATE_IN_SYNC,
643                    false => CloudFilters::CF_IN_SYNC_STATE_NOT_IN_SYNC,
644                },
645                CloudFilters::CF_SET_IN_SYNC_FLAG_NONE,
646                usn.into().map(|x| ptr::read(x) as *mut _),
647            )
648        }?;
649
650        Ok(self)
651    }
652
653    /// Sets the pin state of the placeholder.
654    ///
655    /// See also
656    /// [CfSetPinState](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfsetpinstate),
657    /// [What does "Pinned" Mean?](https://www.userfilesystem.com/programming/faq/#nav_howdoesthealwayskeeponthisdevicemenuworks)
658    pub fn mark_pin(&mut self, state: PinState, options: PinOptions) -> core::Result<&mut Self> {
659        unsafe { CfSetPinState(self.handle.handle, state.into(), options.0, None) }?;
660        Ok(self)
661    }
662
663    /// Converts a file to a placeholder file.
664    ///
665    /// If the passed [Usn] is outdated, the call will fail,
666    /// otherwise the [Usn] will be updated.
667    ///
668    /// See also [CfConvertToPlaceholder](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfconverttoplaceholder).
669    pub fn convert_to_placeholder<'a>(
670        &mut self,
671        options: ConvertOptions,
672        usn: impl Into<Option<&'a mut Usn>>,
673    ) -> core::Result<&mut Self> {
674        unsafe {
675            CfConvertToPlaceholder(
676                self.handle.handle,
677                (!options.blob.is_empty()).then_some(options.blob.as_ptr() as *const _),
678                options.blob.len() as _,
679                options.flags,
680                usn.into().map(|x| ptr::read(x) as *mut _),
681                None,
682            )
683        }?;
684
685        Ok(self)
686    }
687
688    /// Gets various characteristics of the placeholder.
689    ///
690    /// Returns [None] if the handle not points to a placeholder.
691    ///
692    /// If the placeholder blob size is known, use [fixed_size_info](Self::fixed_size_info) instead.
693    pub fn info(&self) -> core::Result<Option<PlaceholderInfo>> {
694        let mut info_size = 0;
695        let mut data = vec![0u8; mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>() + 4096];
696        let r = unsafe {
697            CfGetPlaceholderInfo(
698                self.handle.handle,
699                CloudFilters::CF_PLACEHOLDER_INFO_STANDARD,
700                data.as_mut_ptr() as *mut _,
701                data.len() as _,
702                Some(&mut info_size),
703            )
704        };
705
706        match r {
707            Ok(()) => {
708                unsafe { data.set_len(info_size as _) };
709                data.shrink_to_fit();
710
711                Ok(Some(PlaceholderInfo {
712                    info: &unsafe {
713                        data[..=mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>()]
714                            .align_to::<CF_PLACEHOLDER_STANDARD_INFO>()
715                    }
716                    .1[0] as *const _,
717                    data,
718                }))
719            }
720            Err(e) if e.code() == ERROR_NOT_A_CLOUD_FILE.to_hresult() => Ok(None),
721            Err(e) => Err(e),
722        }
723    }
724
725    /// Gets various characteristics of the placeholder.
726    ///
727    /// If the `blob_size` not matches the actual size of the blob,
728    /// the call will returns `HRESULT_FROM_WIN32(ERROR_MORE_DATA)`.
729    /// Returns [None] if the handle not points to a placeholder.
730    pub fn fixed_size_info(&self, blob_size: usize) -> core::Result<Option<PlaceholderInfo>> {
731        let mut data = vec![0; mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>() + blob_size];
732
733        let r = unsafe {
734            CfGetPlaceholderInfo(
735                self.handle.handle,
736                CloudFilters::CF_PLACEHOLDER_INFO_STANDARD,
737                data.as_mut_ptr() as *mut _,
738                data.len() as u32,
739                None,
740            )
741        };
742
743        match r {
744            Ok(()) => Ok(Some(PlaceholderInfo {
745                info: &unsafe {
746                    data[..=mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>()]
747                        .align_to::<CF_PLACEHOLDER_STANDARD_INFO>()
748                }
749                .1[0] as *const _,
750                data,
751            })),
752            Err(e) if e.code() == ERROR_NOT_A_CLOUD_FILE.to_hresult() => Ok(None),
753            Err(e) => Err(e),
754        }
755    }
756
757    /// Updates various characteristics of a placeholder.
758    ///
759    /// See also [CfUpdatePlaceholder](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfupdateplaceholder).
760    pub fn update<'a>(
761        &mut self,
762        options: UpdateOptions,
763        usn: impl Into<Option<&'a mut Usn>>,
764    ) -> core::Result<&mut Self> {
765        unsafe {
766            CfUpdatePlaceholder(
767                self.handle.handle,
768                options.metadata.map(|x| &x.0 as *const _),
769                (!options.blob.is_empty()).then_some(options.blob.as_ptr() as *const _),
770                options.blob.len() as _,
771                (options.dehydrate_ranges.is_empty()).then_some(&options.dehydrate_ranges),
772                options.flags,
773                usn.into().map(|u| u as *mut _),
774                None,
775            )
776        }?;
777
778        Ok(self)
779    }
780
781    /// Retrieves data from a placeholder.
782    pub fn retrieve_data(
783        &self,
784        read_type: ReadType,
785        offset: u64,
786        buffer: &mut [u8],
787    ) -> core::Result<u32> {
788        let mut length = MaybeUninit::zeroed();
789        unsafe {
790            CfGetPlaceholderRangeInfo(
791                self.handle.handle,
792                read_type.into(),
793                offset as i64,
794                buffer.len() as i64,
795                buffer as *mut _ as *mut _,
796                buffer.len() as u32,
797                Some(length.as_mut_ptr()),
798            )
799            .map(|_| length.assume_init())
800        }
801    }
802
803    // FIXME: This function is not work at all, the CF_PLACEHOLDER_STATE always be 0 or 1
804    // pub fn state(&self) -> core::Result<Option<PlaceholderState>> {
805    //     let mut info = MaybeUninit::<FILE_ATTRIBUTE_TAG_INFO>::zeroed();
806    //     let win32_handle = self.win32_handle()?;
807    //     let state = unsafe {
808    //         GetFileInformationByHandleEx(
809    //             win32_handle.win32_handle,
810    //             FileSystem::FileAttributeTagInfo,
811    //             info.as_mut_ptr() as *mut _,
812    //             mem::size_of::<FILE_ATTRIBUTE_TAG_INFO>() as u32,
813    //         )
814    //         .ok()
815    //         .inspect_err(|e| println!("GetFileInformationByHandleEx: {e:#?}"))?;
816
817    //         CfGetPlaceholderStateFromFileInfo(
818    //             info.assume_init_ref() as *const _ as *const _,
819    //             FileSystem::FileAttributeTagInfo,
820    //         )
821    //     };
822
823    //     match state {
824    //         CloudFilters::CF_PLACEHOLDER_STATE_INVALID => Err(core::Error::from_win32()),
825    //         CloudFilters::CF_PLACEHOLDER_STATE_NO_STATES => Ok(None),
826    //         s => Ok(Some(PlaceholderState(s))),
827    //     }
828    // }
829
830    /// Returns the Win32 handle from protected handle.
831    ///
832    /// Returns `Err(E_HANDLE)` if the [OwnedPlaceholderHandle::handle_type] is not [PlaceholderHandleType::CfApi].
833    pub fn win32_handle(&self) -> core::Result<ArcWin32Handle> {
834        let (handle, win32_handle) = match self.handle.handle_type {
835            PlaceholderHandleType::CfApi => {
836                let win32_handle = unsafe {
837                    CfReferenceProtectedHandle(self.handle.handle).ok()?;
838                    CfGetWin32HandleFromProtectedHandle(self.handle.handle)
839                };
840                BOOL::from(!win32_handle.is_invalid()).ok()?;
841                (self.handle.handle, win32_handle)
842            }
843            PlaceholderHandleType::Win32 => Err(core::Error::from(E_HANDLE))?,
844        };
845
846        Ok(ArcWin32Handle {
847            win32_handle,
848            protected_handle: handle,
849        })
850    }
851
852    /// Returns the owned placeholder handle.
853    pub fn inner_handle(&self) -> &OwnedPlaceholderHandle {
854        &self.handle
855    }
856
857    /// Hydrates a placeholder file by ensuring that the specified byte range is present on-disk
858    /// in the placeholder. This is valid for files only.
859    ///
860    /// # Panics
861    ///
862    /// Panics if the start bound is greater than [i64::MAX] or
863    /// the end bound sub start bound is greater than [i64::MAX].
864    ///
865    /// See also [CfHydratePlaceholder](https://learn.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfhydrateplaceholder)
866    /// and [discussion](https://docs.microsoft.com/en-us/windows/win32/api/cfapi/nf-cfapi-cfhydrateplaceholder#remarks).
867    pub fn hydrate(&mut self, range: impl RangeBounds<u64>) -> core::Result<()> {
868        unsafe {
869            CfHydratePlaceholder(
870                self.handle.handle,
871                match range.start_bound() {
872                    Bound::Included(x) => (*x).try_into().unwrap(),
873                    Bound::Excluded(x) => (x + 1).try_into().unwrap(),
874                    Bound::Unbounded => 0,
875                },
876                match range.end_bound() {
877                    Bound::Included(x) => (*x).try_into().unwrap(),
878                    Bound::Excluded(x) => (x - 1).try_into().unwrap(),
879                    Bound::Unbounded => -1,
880                },
881                CloudFilters::CF_HYDRATE_FLAG_NONE,
882                None,
883            )
884        }
885    }
886}
887
888impl From<File> for Placeholder {
889    fn from(file: File) -> Self {
890        Self {
891            handle: unsafe {
892                OwnedPlaceholderHandle::from_win32(HANDLE(file.into_raw_handle() as _))
893            },
894        }
895    }
896}
897
898impl TryFrom<Placeholder> for File {
899    type Error = core::Error;
900
901    #[allow(clippy::missing_transmute_annotations)]
902    fn try_from(placeholder: Placeholder) -> core::Result<Self> {
903        match placeholder.handle.handle_type {
904            PlaceholderHandleType::Win32 => {
905                let file =
906                    unsafe { File::from_raw_handle(mem::transmute(placeholder.handle.handle)) };
907                Ok(file)
908            }
909            PlaceholderHandleType::CfApi => unsafe {
910                CfRevertPlaceholder(
911                    placeholder.handle.handle,
912                    CloudFilters::CF_REVERT_FLAG_NONE,
913                    None,
914                )
915            }
916            .map(|_| unsafe { File::from_raw_handle(mem::transmute(placeholder.handle.handle)) }),
917        }
918    }
919}