use std::{
fmt::Debug,
fs::File,
mem::{self, MaybeUninit},
ops::{Bound, Range, RangeBounds},
os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle},
path::Path,
ptr,
};
use widestring::U16CString;
use windows::{
core::{self, PCWSTR},
Win32::{
Foundation::{
CloseHandle, BOOL, ERROR_NOT_A_CLOUD_FILE, E_HANDLE, HANDLE, INVALID_HANDLE_VALUE,
},
Storage::CloudFilters::{
self, CfCloseHandle, CfConvertToPlaceholder, CfGetPlaceholderInfo,
CfGetPlaceholderRangeInfo, CfGetWin32HandleFromProtectedHandle, CfHydratePlaceholder,
CfOpenFileWithOplock, CfReferenceProtectedHandle, CfReleaseProtectedHandle,
CfRevertPlaceholder, CfSetInSyncState, CfSetPinState, CfUpdatePlaceholder,
CF_CONVERT_FLAGS, CF_FILE_RANGE, CF_OPEN_FILE_FLAGS, CF_PIN_STATE,
CF_PLACEHOLDER_RANGE_INFO_CLASS, CF_PLACEHOLDER_STANDARD_INFO, CF_SET_PIN_FLAGS,
CF_UPDATE_FLAGS,
},
},
};
use crate::{metadata::Metadata, usn::Usn};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlaceholderHandleType {
CfApi,
Win32,
}
#[derive(Debug)]
pub struct OwnedPlaceholderHandle {
handle_type: PlaceholderHandleType,
handle: HANDLE,
}
impl OwnedPlaceholderHandle {
pub unsafe fn from_cfapi(handle: HANDLE) -> Self {
Self {
handle_type: PlaceholderHandleType::CfApi,
handle,
}
}
pub unsafe fn from_win32(handle: HANDLE) -> Self {
Self {
handle_type: PlaceholderHandleType::Win32,
handle,
}
}
pub const fn handle(&self) -> HANDLE {
self.handle
}
pub const fn handle_type(&self) -> PlaceholderHandleType {
self.handle_type
}
}
impl Drop for OwnedPlaceholderHandle {
fn drop(&mut self) {
match self.handle_type {
PlaceholderHandleType::CfApi => unsafe { CfCloseHandle(self.handle) },
PlaceholderHandleType::Win32 => unsafe {
_ = CloseHandle(self.handle);
},
}
}
}
pub struct ArcWin32Handle {
win32_handle: HANDLE,
protected_handle: HANDLE,
}
impl ArcWin32Handle {
pub fn handle(&self) -> HANDLE {
self.win32_handle
}
}
impl Clone for ArcWin32Handle {
fn clone(&self) -> Self {
if self.protected_handle != INVALID_HANDLE_VALUE {
unsafe { CfReferenceProtectedHandle(self.protected_handle) };
}
Self {
win32_handle: self.win32_handle,
protected_handle: self.protected_handle,
}
}
}
impl AsRawHandle for ArcWin32Handle {
fn as_raw_handle(&self) -> RawHandle {
unsafe { mem::transmute(self.win32_handle) }
}
}
impl Drop for ArcWin32Handle {
fn drop(&mut self) {
if self.protected_handle != INVALID_HANDLE_VALUE {
unsafe { CfReleaseProtectedHandle(self.protected_handle) };
}
}
}
unsafe impl Send for ArcWin32Handle {}
unsafe impl Sync for ArcWin32Handle {}
pub struct OpenOptions {
flags: CF_OPEN_FILE_FLAGS,
}
impl OpenOptions {
pub fn new() -> Self {
Self::default()
}
pub fn exclusive(mut self) -> Self {
self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_EXCLUSIVE;
self
}
pub fn write_access(mut self) -> Self {
self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_WRITE_ACCESS;
self
}
pub fn delete_access(mut self) -> Self {
self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_DELETE_ACCESS;
self
}
pub fn foreground(mut self) -> Self {
self.flags |= CloudFilters::CF_OPEN_FILE_FLAG_FOREGROUND;
self
}
pub fn open(self, path: impl AsRef<Path>) -> core::Result<Placeholder> {
let u16_path = U16CString::from_os_str(path.as_ref()).unwrap();
let handle = unsafe { CfOpenFileWithOplock(PCWSTR(u16_path.as_ptr()), self.flags) }?;
Ok(Placeholder {
handle: unsafe { OwnedPlaceholderHandle::from_cfapi(handle) },
})
}
}
impl Default for OpenOptions {
fn default() -> Self {
Self {
flags: CloudFilters::CF_OPEN_FILE_FLAG_NONE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PinState {
Unspecified,
Pinned,
Unpinned,
Excluded,
Inherit,
}
impl From<PinState> for CF_PIN_STATE {
fn from(state: PinState) -> Self {
match state {
PinState::Unspecified => CloudFilters::CF_PIN_STATE_UNSPECIFIED,
PinState::Pinned => CloudFilters::CF_PIN_STATE_PINNED,
PinState::Unpinned => CloudFilters::CF_PIN_STATE_UNPINNED,
PinState::Excluded => CloudFilters::CF_PIN_STATE_EXCLUDED,
PinState::Inherit => CloudFilters::CF_PIN_STATE_INHERIT,
}
}
}
impl From<CF_PIN_STATE> for PinState {
fn from(state: CF_PIN_STATE) -> Self {
match state {
CloudFilters::CF_PIN_STATE_UNSPECIFIED => PinState::Unspecified,
CloudFilters::CF_PIN_STATE_PINNED => PinState::Pinned,
CloudFilters::CF_PIN_STATE_UNPINNED => PinState::Unpinned,
CloudFilters::CF_PIN_STATE_EXCLUDED => PinState::Excluded,
CloudFilters::CF_PIN_STATE_INHERIT => PinState::Inherit,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct PinOptions(CF_SET_PIN_FLAGS);
impl PinOptions {
pub fn recurse(&mut self) -> &mut Self {
self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE;
self
}
pub fn recurse_children(&mut self) -> &mut Self {
self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE_ONLY;
self
}
pub fn stop_on_error(&mut self) -> &mut Self {
self.0 |= CloudFilters::CF_SET_PIN_FLAG_RECURSE_STOP_ON_ERROR;
self
}
}
impl Default for PinOptions {
fn default() -> Self {
Self(CloudFilters::CF_SET_PIN_FLAG_NONE)
}
}
#[derive(Debug, Clone)]
pub struct ConvertOptions {
flags: CF_CONVERT_FLAGS,
blob: Vec<u8>,
}
impl ConvertOptions {
pub fn mark_in_sync(mut self) -> Self {
self.flags |= CloudFilters::CF_CONVERT_FLAG_MARK_IN_SYNC;
self
}
pub fn dehydrate(mut self) -> Self {
self.flags |= CloudFilters::CF_CONVERT_FLAG_DEHYDRATE;
self
}
pub fn has_children(mut self) -> Self {
self.flags |= CloudFilters::CF_CONVERT_FLAG_ENABLE_ON_DEMAND_POPULATION;
self
}
pub fn block_dehydration(mut self) -> Self {
self.flags |= CloudFilters::CF_CONVERT_FLAG_ALWAYS_FULL;
self
}
pub fn force(mut self) -> Self {
self.flags |= CloudFilters::CF_CONVERT_FLAG_FORCE_CONVERT_TO_CLOUD_FILE;
self
}
pub fn blob(mut self, blob: Vec<u8>) -> Self {
assert!(
blob.len() <= CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH as usize,
"blob size must not exceed {} bytes, got {} bytes",
CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH,
blob.len()
);
self.blob = blob;
self
}
}
impl Default for ConvertOptions {
fn default() -> Self {
Self {
flags: CloudFilters::CF_CONVERT_FLAG_NONE,
blob: Vec::new(),
}
}
}
#[derive(Clone)]
pub struct PlaceholderInfo {
data: Vec<u8>,
info: *const CF_PLACEHOLDER_STANDARD_INFO,
}
impl PlaceholderInfo {
pub fn on_disk_data_size(&self) -> i64 {
unsafe { &*self.info }.OnDiskDataSize
}
pub fn validated_data_size(&self) -> i64 {
unsafe { &*self.info }.ValidatedDataSize
}
pub fn modified_data_size(&self) -> i64 {
unsafe { &*self.info }.ModifiedDataSize
}
pub fn properties_size(&self) -> i64 {
unsafe { &*self.info }.PropertiesSize
}
pub fn pin_state(&self) -> PinState {
unsafe { &*self.info }.PinState.into()
}
pub fn is_in_sync(&self) -> bool {
unsafe { &*self.info }.InSyncState == CloudFilters::CF_IN_SYNC_STATE_IN_SYNC
}
pub fn file_id(&self) -> i64 {
unsafe { &*self.info }.FileId
}
pub fn sync_root_file_id(&self) -> i64 {
unsafe { &*self.info }.SyncRootFileId
}
pub fn blob(&self) -> &[u8] {
&self.data[mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>()..]
}
}
impl std::fmt::Debug for PlaceholderInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PlaceholderInfo")
.field("on_disk_data_size", &self.on_disk_data_size())
.field("validated_data_size", &self.validated_data_size())
.field("modified_data_size", &self.modified_data_size())
.field("properties_size", &self.properties_size())
.field("pin_state", &self.pin_state())
.field("is_in_sync", &self.is_in_sync())
.field("file_id", &self.file_id())
.field("sync_root_file_id", &self.sync_root_file_id())
.finish()
}
}
#[derive(Debug, Clone)]
pub struct UpdateOptions<'a> {
metadata: Option<Metadata>,
dehydrate_ranges: Vec<CF_FILE_RANGE>,
flags: CF_UPDATE_FLAGS,
blob: &'a [u8],
}
impl<'a> UpdateOptions<'a> {
pub fn metadata(mut self, metadata: Metadata) -> Self {
self.flags &= !(CloudFilters::CF_UPDATE_FLAG_PASSTHROUGH_FS_METADATA);
self.metadata = Some(metadata);
self
}
pub fn metadata_all(mut self, metadata: Metadata) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_PASSTHROUGH_FS_METADATA;
self.metadata = Some(metadata);
self
}
pub fn dehydrate_ranges(mut self, ranges: impl IntoIterator<Item = Range<u64>>) -> Self {
self.dehydrate_ranges
.extend(ranges.into_iter().map(|r| CF_FILE_RANGE {
StartingOffset: r.start as _,
Length: (r.end - r.start) as _,
}));
self
}
pub fn update_if_in_sync(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_VERIFY_IN_SYNC;
self
}
pub fn mark_in_sync(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_MARK_IN_SYNC;
self
}
pub fn mark_not_in_sync(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_CLEAR_IN_SYNC;
self
}
pub fn dehydrate(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_DEHYDRATE;
self
}
pub fn has_no_children(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_DISABLE_ON_DEMAND_POPULATION;
self
}
pub fn has_children(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_ENABLE_ON_DEMAND_POPULATION;
self
}
pub fn remove_blob(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_REMOVE_FILE_IDENTITY;
self
}
pub fn remove_properties(mut self) -> Self {
self.flags |= CloudFilters::CF_UPDATE_FLAG_REMOVE_PROPERTY;
self
}
pub fn blob(mut self, blob: &'a [u8]) -> Self {
assert!(
blob.len() <= CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH as usize,
"blob size must not exceed {} bytes, got {} bytes",
CloudFilters::CF_PLACEHOLDER_MAX_FILE_IDENTITY_LENGTH,
blob.len()
);
self.blob = blob;
self
}
}
impl Default for UpdateOptions<'_> {
fn default() -> Self {
Self {
metadata: None,
dehydrate_ranges: Vec::new(),
flags: CloudFilters::CF_UPDATE_FLAG_NONE,
blob: &[],
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ReadType {
Any,
Validated,
Modified,
}
impl From<ReadType> for CF_PLACEHOLDER_RANGE_INFO_CLASS {
fn from(read_type: ReadType) -> Self {
match read_type {
ReadType::Any => CloudFilters::CF_PLACEHOLDER_RANGE_INFO_ONDISK,
ReadType::Validated => CloudFilters::CF_PLACEHOLDER_RANGE_INFO_VALIDATED,
ReadType::Modified => CloudFilters::CF_PLACEHOLDER_RANGE_INFO_MODIFIED,
}
}
}
#[derive(Debug)]
pub struct Placeholder {
handle: OwnedPlaceholderHandle,
}
impl Placeholder {
pub unsafe fn from_raw_handle(handle: OwnedPlaceholderHandle) -> Self {
Self { handle }
}
pub fn options() -> OpenOptions {
OpenOptions::default()
}
pub fn open(path: impl AsRef<Path>) -> core::Result<Self> {
OpenOptions::new().open(path)
}
pub fn mark_in_sync<'a>(
&mut self,
in_sync: bool,
usn: impl Into<Option<&'a mut Usn>>,
) -> core::Result<&mut Self> {
unsafe {
CfSetInSyncState(
self.handle.handle,
match in_sync {
true => CloudFilters::CF_IN_SYNC_STATE_IN_SYNC,
false => CloudFilters::CF_IN_SYNC_STATE_NOT_IN_SYNC,
},
CloudFilters::CF_SET_IN_SYNC_FLAG_NONE,
usn.into().map(|x| ptr::read(x) as *mut _),
)
}?;
Ok(self)
}
pub fn mark_pin(&mut self, state: PinState, options: PinOptions) -> core::Result<&mut Self> {
unsafe { CfSetPinState(self.handle.handle, state.into(), options.0, None) }?;
Ok(self)
}
pub fn convert_to_placeholder<'a>(
&mut self,
options: ConvertOptions,
usn: impl Into<Option<&'a mut Usn>>,
) -> core::Result<&mut Self> {
unsafe {
CfConvertToPlaceholder(
self.handle.handle,
(!options.blob.is_empty()).then_some(options.blob.as_ptr() as *const _),
options.blob.len() as _,
options.flags,
usn.into().map(|x| ptr::read(x) as *mut _),
None,
)
}?;
Ok(self)
}
pub fn info(&self) -> core::Result<Option<PlaceholderInfo>> {
let mut info_size = 0;
let mut data = vec![0u8; mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>() + 4096];
let r = unsafe {
CfGetPlaceholderInfo(
self.handle.handle,
CloudFilters::CF_PLACEHOLDER_INFO_STANDARD,
data.as_mut_ptr() as *mut _,
data.len() as _,
Some(&mut info_size),
)
};
match r {
Ok(()) => {
unsafe { data.set_len(info_size as _) };
data.shrink_to_fit();
Ok(Some(PlaceholderInfo {
info: &unsafe {
data[..=mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>()]
.align_to::<CF_PLACEHOLDER_STANDARD_INFO>()
}
.1[0] as *const _,
data,
}))
}
Err(e) if e.code() == ERROR_NOT_A_CLOUD_FILE.to_hresult() => Ok(None),
Err(e) => Err(e),
}
}
pub fn fixed_size_info(&self, blob_size: usize) -> core::Result<Option<PlaceholderInfo>> {
let mut data = vec![0; mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>() + blob_size];
let r = unsafe {
CfGetPlaceholderInfo(
self.handle.handle,
CloudFilters::CF_PLACEHOLDER_INFO_STANDARD,
data.as_mut_ptr() as *mut _,
data.len() as u32,
None,
)
};
match r {
Ok(()) => Ok(Some(PlaceholderInfo {
info: &unsafe {
data[..=mem::size_of::<CF_PLACEHOLDER_STANDARD_INFO>()]
.align_to::<CF_PLACEHOLDER_STANDARD_INFO>()
}
.1[0] as *const _,
data,
})),
Err(e) if e.code() == ERROR_NOT_A_CLOUD_FILE.to_hresult() => Ok(None),
Err(e) => Err(e),
}
}
pub fn update<'a>(
&mut self,
options: UpdateOptions,
usn: impl Into<Option<&'a mut Usn>>,
) -> core::Result<&mut Self> {
unsafe {
CfUpdatePlaceholder(
self.handle.handle,
options.metadata.map(|x| &x.0 as *const _),
(!options.blob.is_empty()).then_some(options.blob.as_ptr() as *const _),
options.blob.len() as _,
(options.dehydrate_ranges.is_empty()).then_some(&options.dehydrate_ranges),
options.flags,
usn.into().map(|u| u as *mut _),
None,
)
}?;
Ok(self)
}
pub fn retrieve_data(
&self,
read_type: ReadType,
offset: u64,
buffer: &mut [u8],
) -> core::Result<u32> {
let mut length = MaybeUninit::zeroed();
unsafe {
CfGetPlaceholderRangeInfo(
self.handle.handle,
read_type.into(),
offset as i64,
buffer.len() as i64,
buffer as *mut _ as *mut _,
buffer.len() as u32,
Some(length.as_mut_ptr()),
)
.map(|_| length.assume_init())
}
}
pub fn win32_handle(&self) -> core::Result<ArcWin32Handle> {
let (handle, win32_handle) = match self.handle.handle_type {
PlaceholderHandleType::CfApi => {
let win32_handle = unsafe {
CfReferenceProtectedHandle(self.handle.handle).ok()?;
CfGetWin32HandleFromProtectedHandle(self.handle.handle)
};
BOOL::from(!win32_handle.is_invalid()).ok()?;
(self.handle.handle, win32_handle)
}
PlaceholderHandleType::Win32 => Err(core::Error::from(E_HANDLE))?,
};
Ok(ArcWin32Handle {
win32_handle,
protected_handle: handle,
})
}
pub fn inner_handle(&self) -> &OwnedPlaceholderHandle {
&self.handle
}
pub fn hydrate(&mut self, range: impl RangeBounds<u64>) -> core::Result<()> {
unsafe {
CfHydratePlaceholder(
self.handle.handle,
match range.start_bound() {
Bound::Included(x) => (*x).try_into().unwrap(),
Bound::Excluded(x) => (x + 1).try_into().unwrap(),
Bound::Unbounded => 0,
},
match range.end_bound() {
Bound::Included(x) => (*x).try_into().unwrap(),
Bound::Excluded(x) => (x - 1).try_into().unwrap(),
Bound::Unbounded => -1,
},
CloudFilters::CF_HYDRATE_FLAG_NONE,
None,
)
}
}
}
impl From<File> for Placeholder {
fn from(file: File) -> Self {
Self {
handle: unsafe {
OwnedPlaceholderHandle::from_win32(HANDLE(file.into_raw_handle() as _))
},
}
}
}
impl TryFrom<Placeholder> for File {
type Error = core::Error;
#[allow(clippy::missing_transmute_annotations)]
fn try_from(placeholder: Placeholder) -> core::Result<Self> {
match placeholder.handle.handle_type {
PlaceholderHandleType::Win32 => {
let file =
unsafe { File::from_raw_handle(mem::transmute(placeholder.handle.handle)) };
Ok(file)
}
PlaceholderHandleType::CfApi => unsafe {
CfRevertPlaceholder(
placeholder.handle.handle,
CloudFilters::CF_REVERT_FLAG_NONE,
None,
)
}
.map(|_| unsafe { File::from_raw_handle(mem::transmute(placeholder.handle.handle)) }),
}
}
}