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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum PlaceholderHandleType {
35 CfApi,
37 Win32,
39}
40
41#[derive(Debug)]
45pub struct OwnedPlaceholderHandle {
46 handle_type: PlaceholderHandleType,
47 handle: HANDLE,
48}
49
50impl OwnedPlaceholderHandle {
51 pub unsafe fn from_cfapi(handle: HANDLE) -> Self {
57 Self {
58 handle_type: PlaceholderHandleType::CfApi,
59 handle,
60 }
61 }
62
63 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
96pub struct ArcWin32Handle {
101 win32_handle: HANDLE,
102 protected_handle: HANDLE,
103}
104
105impl ArcWin32Handle {
106 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
139unsafe impl Send for ArcWin32Handle {}
141unsafe impl Sync for ArcWin32Handle {}
143
144pub 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum PinState {
198 Unspecified,
200 Pinned,
203 Unpinned,
206 Excluded,
208 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#[derive(Debug, Clone, Copy)]
239pub struct PinOptions(CF_SET_PIN_FLAGS);
240
241impl PinOptions {
242 pub fn recurse(&mut self) -> &mut Self {
245 self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE;
246 self
247 }
248
249 pub fn recurse_children(&mut self) -> &mut Self {
252 self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE_ONLY;
253 self
254 }
255
256 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#[derive(Debug, Clone)]
272pub struct ConvertOptions {
273 flags: CF_CONVERT_FLAGS,
274 blob: Vec<u8>,
275}
276
277impl ConvertOptions {
278 pub fn mark_in_sync(mut self) -> Self {
284 self.flags |= CloudFilters::CF_CONVERT_FLAG_MARK_IN_SYNC;
285 self
286 }
287
288 pub fn dehydrate(mut self) -> Self {
292 self.flags |= CloudFilters::CF_CONVERT_FLAG_DEHYDRATE;
293 self
294 }
295
296 pub fn has_children(mut self) -> Self {
302 self.flags |= CloudFilters::CF_CONVERT_FLAG_ENABLE_ON_DEMAND_POPULATION;
303 self
304 }
305
306 pub fn block_dehydration(mut self) -> Self {
310 self.flags |= CloudFilters::CF_CONVERT_FLAG_ALWAYS_FULL;
311 self
312 }
313
314 pub fn force(mut self) -> Self {
319 self.flags |= CloudFilters::CF_CONVERT_FLAG_FORCE_CONVERT_TO_CLOUD_FILE;
320 self
321 }
322
323 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#[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 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 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 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 pub fn update_if_in_sync(mut self) -> Self {
450 self.flags |= CloudFilters::CF_UPDATE_FLAG_VERIFY_IN_SYNC;
451 self
452 }
453
454 pub fn mark_in_sync(mut self) -> Self {
460 self.flags |= CloudFilters::CF_UPDATE_FLAG_MARK_IN_SYNC;
461 self
462 }
463
464 pub fn mark_not_in_sync(mut self) -> Self {
470 self.flags |= CloudFilters::CF_UPDATE_FLAG_CLEAR_IN_SYNC;
471 self
472 }
473
474 pub fn dehydrate(mut self) -> Self {
476 self.flags |= CloudFilters::CF_UPDATE_FLAG_DEHYDRATE;
477 self
478 }
479
480 pub fn has_no_children(mut self) -> Self {
482 self.flags |= CloudFilters::CF_UPDATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
483 self
484 }
485
486 pub fn has_children(mut self) -> Self {
488 self.flags |= CloudFilters::CF_UPDATE_FLAG_ENABLE_ON_DEMAND_POPULATION;
489 self
490 }
491
492 pub fn remove_blob(mut self) -> Self {
495 self.flags |= CloudFilters::CF_UPDATE_FLAG_REMOVE_FILE_IDENTITY;
496 self
497 }
498
499 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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
530pub enum ReadType {
531 Any,
533 Validated,
535 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(Debug)]
601pub struct Placeholder {
602 handle: OwnedPlaceholderHandle,
603}
604
605impl Placeholder {
606 pub unsafe fn from_raw_handle(handle: OwnedPlaceholderHandle) -> Self {
612 Self { handle }
613 }
614
615 pub fn options() -> OpenOptions {
617 OpenOptions::default()
618 }
619
620 pub fn open(path: impl AsRef<Path>) -> core::Result<Self> {
622 OpenOptions::new().open(path)
623 }
624
625 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 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 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 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 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 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 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 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 pub fn inner_handle(&self) -> &OwnedPlaceholderHandle {
854 &self.handle
855 }
856
857 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}