//! Lazy workspaces on Windows: the workspace folder is a sync root of the //! Cloud Files API, the one OneDrive's files on demand use. It's built into //! Windows 10 (1709) and later: nothing to install, no admin rights. Every //! file of the tree is a placeholder in the folder from the start, at its //! size; the first time something reads one, Windows asks `on mount //! --serve`, which fetches it from the host. From then on it's an ordinary //! file, and edits stay in the folder. The workspace's own `.onset` sits in //! the folder as in any workspace, and the backing (ONSET_HOME/lazy/) //! keeps only the tree's index and the server's log. //! //! Each placeholder is made with its content's ID and its path, and //! Windows keeps it "in sync" until something changes it, so a save can //! tell a file that's still as it was made without reading (and so //! fetching) it. After a restart the placeholders are still there; ones not //! fetched yet open once the next `on` command (or `on mount`) has started //! the server again. use std::collections::BTreeMap; use std::ffi::{OsStr, c_void}; use std::os::windows::ffi::OsStrExt; use std::path::Path; use anyhow::{Context, Result, anyhow}; use onset_core::Id; use onset_core::tree::{self, EntryKind}; use windows_sys::Win32::Foundation::{CloseHandle, E_FAIL, HANDLE, HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, NTSTATUS, STATUS_CLOUD_FILE_UNSUCCESSFUL, STATUS_SUCCESS, SetHandleInformation}; use windows_sys::Win32::Storage::CloudFilters::*; use windows_sys::Win32::Storage::FileSystem::{FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL, FILE_BASIC_INFO, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE}; use windows_sys::Win32::System::Console::{GetStdHandle, STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LOAD_LIBRARY_SEARCH_SYSTEM32, LoadLibraryExW}; use windows_sys::Win32::System::Threading::{CreateEventW, EVENT_MODIFY_STATE, INFINITE, OpenEventW, SYNCHRONIZATION_SYNCHRONIZE, SetEvent, WaitForSingleObject}; use windows_sys::core::{GUID, HRESULT, PCWSTR}; use crate::app::App; use crate::db::Workspace; use crate::lazy::{MTIME_NS, backing, workspace_at}; use crate::objects::Store; use crate::remote::Remote; use crate::ws::{self, Index}; /// What Windows knows the sync roots by. const PROVIDER: &str = "Onset"; const PROVIDER_ID: GUID = GUID::from_u128(0x5f0c_2a7e_93d4_4b61_8a2e_0d7c_4f19_b3a6); /// 100-ns ticks from 1601, where Windows' file times start, to 1970. const TICKS_TO_1970: i64 = 116_444_736_000_000_000; /// How much of a file goes to Windows at once while it's fetched. const CHUNK: usize = 1 << 20; /// The Cloud Files API's functions, from cldapi.dll the first time a lazy /// workspace needs one, so `on` still starts on a Windows without it (10 /// before version 1709). There each answers ERROR_NOT_SUPPORTED. macro_rules! cloud_files { ($($name:ident($($arg:ident: $ty:ty),*);)*) => { #[allow(non_snake_case)] struct Api { $($name: unsafe extern "system" fn($($ty),*) -> HRESULT,)* } fn api() -> Option<&'static Api> { static API: std::sync::OnceLock> = std::sync::OnceLock::new(); API.get_or_init(|| { // SAFETY: a system DLL by name, and functions of its with these signatures. unsafe { let lib = LoadLibraryExW(wide("cldapi.dll").as_ptr(), std::ptr::null_mut(), LOAD_LIBRARY_SEARCH_SYSTEM32); if lib.is_null() { return None; } Some(Api { $($name: std::mem::transmute:: isize, unsafe extern "system" fn($($ty),*) -> HRESULT>( GetProcAddress(lib, concat!(stringify!($name), "\0").as_ptr())?, ),)* }) } }) .as_ref() } $( #[allow(non_snake_case)] unsafe fn $name($($arg: $ty),*) -> HRESULT { match api() { // SAFETY: the caller's, as for the function itself. Some(api) => unsafe { (api.$name)($($arg),*) }, None => from_win32(50), } } )* }; } cloud_files! { CfRegisterSyncRoot(path: PCWSTR, registration: *const CF_SYNC_REGISTRATION, policies: *const CF_SYNC_POLICIES, flags: CF_REGISTER_FLAGS); CfUnregisterSyncRoot(path: PCWSTR); CfGetSyncRootInfoByPath(path: PCWSTR, class: CF_SYNC_ROOT_INFO_CLASS, buffer: *mut c_void, length: u32, returned: *mut u32); CfCreatePlaceholders(base: PCWSTR, items: *mut CF_PLACEHOLDER_CREATE_INFO, count: u32, flags: CF_CREATE_FLAGS, done: *mut u32); CfGetPlaceholderInfo(file: HANDLE, class: CF_PLACEHOLDER_INFO_CLASS, buffer: *mut c_void, length: u32, returned: *mut u32); CfConnectSyncRoot(path: PCWSTR, callbacks: *const CF_CALLBACK_REGISTRATION, context: *const c_void, flags: CF_CONNECT_FLAGS, key: *mut CF_CONNECTION_KEY); CfDisconnectSyncRoot(key: CF_CONNECTION_KEY); CfExecute(op: *const CF_OPERATION_INFO, params: *mut CF_OPERATION_PARAMETERS); } /// A string or path as Windows' API takes it: UTF-16, NUL-terminated. Long /// paths get the `\\?\` prefix that lifts the 260-character limit. fn wide(s: impl AsRef) -> Vec { s.as_ref().encode_wide().chain(Some(0)).collect() } fn wide_path(path: &Path) -> Vec { if path.as_os_str().len() < 248 || path.to_string_lossy().starts_with(r"\\") { return wide(path); } let mut long = std::ffi::OsString::from(r"\\?\"); long.push(path.as_os_str()); wide(long) } /// The `len` bytes `offset` bytes into a buffer Windows filled, if they're inside it. fn tail(buf: &[u64], offset: usize, len: usize) -> Option<&[u8]> { // SAFETY: the buffer's bytes, as bytes. let bytes = unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len() * 8) }; bytes.get(offset..offset.checked_add(len)?) } /// An HRESULT as words: one that carries a Windows error reads as that error. fn os_error(hr: HRESULT) -> String { if (hr as u32) >> 16 == 0x8007 { std::io::Error::from_raw_os_error(hr & 0xFFFF).to_string() } else { format!("error {:#010x}", hr as u32) } } const fn from_win32(code: u32) -> HRESULT { (0x8007_0000 | code) as HRESULT } /// A time as Windows keeps file times. fn ticks(ns: i64) -> i64 { ns / 100 + TICKS_TO_1970 } /// What a placeholder is made with: its content's ID and its path. fn identity(id: &Id, path: &str) -> Vec { format!("{}\n{path}", id.hex()).into_bytes() } /// The named event a running server holds: it exists while the server /// does, and setting it stops the server. fn event_name(workspace: &str) -> Vec { wide(format!("Local\\onset-lazy-{workspace}")) } /// Whether `root` is a lazy workspace's folder: a sync root of Onset's for /// the workspace there, its placeholders in place (the server may be /// stopped). pub fn mounted(root: &Path) -> bool { let Ok(id) = ws::read_id(root) else { return false }; if !crate::lazy::is_lazy(&id) { return false; } // The info, its provider's name, and room for the workspace's ID after it. let mut buf = vec![0u64; 512]; let size = (buf.len() * 8) as u32; let mut got = 0u32; // SAFETY: a NUL-terminated path, and a buffer of `size` bytes to fill. if unsafe { CfGetSyncRootInfoByPath(wide_path(root).as_ptr(), CF_SYNC_ROOT_INFO_STANDARD, buf.as_mut_ptr().cast(), size, &mut got) } < 0 { return false; } // SAFETY: the buffer is aligned for, and holds, a CF_SYNC_ROOT_STANDARD_INFO. let info = unsafe { &*(buf.as_ptr() as *const CF_SYNC_ROOT_STANDARD_INFO) }; let name = String::from_utf16_lossy(&info.ProviderName[..info.ProviderName.iter().position(|c| *c == 0).unwrap_or(info.ProviderName.len())]); let identity = tail(&buf, std::mem::offset_of!(CF_SYNC_ROOT_STANDARD_INFO, SyncRootIdentity), info.SyncRootIdentityLength as usize); name == PROVIDER && identity == Some(id.as_bytes()) } /// Whether the server for the workspace at `root` is running. fn running(root: &Path) -> bool { let Ok(id) = ws::read_id(root) else { return false }; // SAFETY: a NUL-terminated name; the handle is closed at once. unsafe { let event = OpenEventW(SYNCHRONIZATION_SYNCHRONIZE, 0, event_name(&id).as_ptr()); if event.is_null() { return false; } CloseHandle(event); } true } /// Makes `root` a sync root of Onset's, for workspace `id`. fn register(root: &Path, id: &str) -> Result<()> { let name = wide(PROVIDER); let version = wide(crate::VERSION); let registration = CF_SYNC_REGISTRATION { StructSize: std::mem::size_of::() as u32, ProviderName: name.as_ptr(), ProviderVersion: version.as_ptr(), SyncRootIdentity: id.as_ptr().cast(), SyncRootIdentityLength: id.len() as u32, FileIdentity: std::ptr::null(), FileIdentityLength: 0, ProviderId: PROVIDER_ID, }; let policies = CF_SYNC_POLICIES { StructSize: std::mem::size_of::() as u32, // A file comes whole the first time it's read. Hydration: CF_HYDRATION_POLICY { Primary: CF_HYDRATION_POLICY_FULL, Modifier: CF_HYDRATION_POLICY_MODIFIER_NONE }, // Every folder's placeholders are made up front. Population: CF_POPULATION_POLICY { Primary: CF_POPULATION_POLICY_ALWAYS_FULL, Modifier: CF_POPULATION_POLICY_MODIFIER_NONE }, // Any change to a file, its times or attributes takes it out of sync. InSync: CF_INSYNC_POLICY_TRACK_ALL, HardLink: CF_HARDLINK_POLICY_NONE, PlaceholderManagement: CF_PLACEHOLDER_MANAGEMENT_POLICY_DEFAULT, }; let flags = CF_REGISTER_FLAG_DISABLE_ON_DEMAND_POPULATION_ON_ROOT | CF_REGISTER_FLAG_MARK_IN_SYNC_ON_ROOT; let path = wide_path(root); // SAFETY: the registration's strings and identity outlive the calls. let mut hr = unsafe { CfRegisterSyncRoot(path.as_ptr(), ®istration, &policies, flags) }; if hr < 0 { // Registered already (by Onset, as something else saw it): it's registered again. hr = unsafe { CfRegisterSyncRoot(path.as_ptr(), ®istration, &policies, flags | CF_REGISTER_FLAG_UPDATE) }; } if hr < 0 { return Err(anyhow!( "couldn't make {} a lazy workspace: {} (it needs a folder on an NTFS disk, outside OneDrive and other synced folders)", root.display(), os_error(hr) )); } Ok(()) } /// A file or folder a placeholder is made for. enum Item { Folder, /// Content, size, time shown (ns). File(Id, u64, i64), } /// Makes a placeholder for every file and folder of the tree `base` /// indexes that isn't in the folder yet, each at the size and time the /// index has for it. fn place(root: &Path, base: &Index) -> Result<()> { let split = |p: &str| -> (String, String) { match p.rsplit_once('/') { Some((dir, name)) => (dir.to_string(), name.to_string()), None => (String::new(), p.to_string()), } }; let mut dirs: BTreeMap> = BTreeMap::new(); for (path, e) in &base.entries { // Never Onset's or git's own folders, or a name this system can't have. if !tree::valid_path(path) || !path.split('/').all(crate::sys::fits) { return Err(anyhow!("{path} can't be written here: this system can't have a file by that name")); } let item = if e.entry.kind == EntryKind::Gitlink { Item::Folder } else { Item::File(e.entry.id, e.stat.size, e.stat.mtime_ns) }; let (dir, name) = split(path); dirs.entry(dir.clone()).or_default().insert(name, item); // Its folders, up to the root. let mut d = dir; while !d.is_empty() { let (up, name) = split(&d); let kids = dirs.entry(up.clone()).or_default(); if kids.contains_key(&name) { break; } kids.insert(name, Item::Folder); d = up; } } let empty = onset_core::object::Object::blob(Vec::new()).id(); // A folder before what's in it: "a" sorts before "a/b". for (dir, all) in &dirs { let at = if dir.is_empty() { root.to_path_buf() } else { root.join(dir) }; let full = |name: &str| if dir.is_empty() { name.to_string() } else { format!("{dir}/{name}") }; // Empty files are made as they are: there's nothing to fetch. let (empties, kids): (Vec<_>, Vec<_>) = all.iter().partition(|(_, item)| matches!(item, Item::File(id, 0, _) if *id == empty)); for (name, item) in empties { let file = at.join(name); if let Item::File(_, _, ns) = item && !file.exists() { std::fs::File::create(&file)?.set_modified(std::time::UNIX_EPOCH + std::time::Duration::from_nanos(*ns as u64))?; } } if kids.is_empty() { continue; } let names: Vec> = kids.iter().map(|(name, _)| wide(name)).collect(); let identities: Vec> = kids .iter() .map(|(name, item)| match *item { Item::File(id, ..) => identity(id, &full(name)), Item::Folder => format!("folder\n{}", full(name)).into_bytes(), }) .collect(); let mut infos: Vec = kids .iter() .enumerate() .map(|(i, (_, item))| { let (attributes, size, time, flags) = match *item { Item::Folder => (FILE_ATTRIBUTE_DIRECTORY, 0, ticks(MTIME_NS), CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC | CF_PLACEHOLDER_CREATE_FLAG_DISABLE_ON_DEMAND_POPULATION), Item::File(_, size, ns) => (FILE_ATTRIBUTE_NORMAL, *size as i64, ticks(*ns), CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC), }; CF_PLACEHOLDER_CREATE_INFO { RelativeFileName: names[i].as_ptr(), FsMetadata: CF_FS_METADATA { BasicInfo: FILE_BASIC_INFO { CreationTime: time, LastAccessTime: time, LastWriteTime: time, ChangeTime: time, FileAttributes: attributes }, FileSize: size, }, FileIdentity: identities[i].as_ptr().cast(), FileIdentityLength: identities[i].len() as u32, Flags: flags, // Until Windows says otherwise. Result: E_FAIL, CreateUsn: 0, } }) .collect(); let mut done = 0u32; // SAFETY: each entry's name and identity outlive the call. let hr = unsafe { CfCreatePlaceholders(wide_path(&at).as_ptr(), infos.as_mut_ptr(), infos.len() as u32, CF_CREATE_FLAG_NONE, &mut done) }; for ((name, _), info) in kids.iter().zip(&infos) { // Already there (a folder copied from another workspace, say): it stays. if info.Result < 0 && info.Result != from_win32(183) && info.Result != from_win32(80) { let why = if info.Result == E_FAIL && hr < 0 { os_error(hr) } else { os_error(info.Result) }; return Err(anyhow!("couldn't make {} in the lazy workspace: {why}", full(name))); } } } Ok(()) } /// Starts the server for a lazy workspace, if it isn't running: the folder /// becomes a sync root with every file's placeholder the first time, then /// `on mount --serve` runs in the background, and this is back once it /// answers. pub fn mount(w: &Workspace) -> Result<()> { if running(&w.path) { return Ok(()); } if api().is_none() { return Err(anyhow!("lazy workspaces need Windows 10 version 1709 or later, which has the Cloud Files API")); } let dir = backing(&w.id); if !ws::is_workspace(&w.path) { return Err(anyhow!("{} has lost its {} folder, so its files can't come back; on get --lazy makes a new one", w.path.display(), ws::DIR)); } if !mounted(&w.path) { register(&w.path, &w.id)?; } let placed = dir.join(ws::DIR).join("placed"); if !placed.exists() { let base = Index::load_from(&dir, "lazy-base").ok_or_else(|| anyhow!("{} has no lazy index", dir.display()))?; place(&w.path, &base)?; std::fs::write(&placed, "")?; } let log = dir.join(ws::DIR).join("mount.log"); let out = std::fs::File::create(&log)?; let mut command = std::process::Command::new(std::env::current_exe()?); command.arg("mount").arg("--serve").arg(&w.path).current_dir(&dir).stdin(std::process::Stdio::null()).stdout(out.try_clone()?).stderr(out); crate::sys::detach(&mut command); keep_std_handles(); let mut child = command.spawn()?; for _ in 0..200 { if running(&w.path) { return Ok(()); } if let Ok(Some(status)) = child.try_wait() { let said = std::fs::read_to_string(&log).unwrap_or_default(); return Err(anyhow!("the mount at {} stopped ({status}): {}", w.path.display(), said.trim())); } std::thread::sleep(std::time::Duration::from_millis(50)); } Err(anyhow!("the mount at {} didn't start; see {}", w.path.display(), log.display())) } /// Keeps this process's own input and output out of the server it starts. /// Windows hands a new process every handle that may be inherited, and a /// pipe this command writes to (an agent's shell, `$(...)`) would stay open /// in the server for as long as it runs, so whatever reads it would wait. /// Each child still gets the handles it's given: Rust passes it copies. fn keep_std_handles() { for which in [STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE] { // SAFETY: a handle of this process's own, or none. unsafe { let handle = GetStdHandle(which); if !handle.is_null() && handle != INVALID_HANDLE_VALUE { SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0); } } } } /// Stops a lazy workspace's server. Its placeholders stay; files not /// fetched yet can't be opened until it runs again. pub fn unmount(root: &Path) -> Result<()> { if !running(root) { return Ok(()); } let id = ws::read_id(root)?; // SAFETY: a NUL-terminated name; the handle is closed at once. unsafe { let event = OpenEventW(EVENT_MODIFY_STATE, 0, event_name(&id).as_ptr()); if !event.is_null() { SetEvent(event); CloseHandle(event); } } for _ in 0..200 { if !running(root) { return Ok(()); } std::thread::sleep(std::time::Duration::from_millis(50)); } Err(anyhow!("couldn't stop the mount at {} (is something still using it?)", root.display())) } /// Lets go of a lazy workspace's folder before it's deleted: it's no longer /// a sync root, and files not fetched yet go with that. pub fn forget(root: &Path) { // SAFETY: a NUL-terminated path. unsafe { CfUnregisterSyncRoot(wide_path(root).as_ptr()) }; } /// Whether file `path` of the workspace at `root` is a placeholder just as /// it was made for that path with `id`'s content: Windows says it's in sync /// (nothing has changed it), and it was made for that path and content. /// Opening it for its attributes never fetches it. Only lazy workspaces /// are asked; whether a folder is one is remembered for the process. pub fn untouched(root: &Path, path: &str, id: &Id) -> bool { use std::os::windows::fs::OpenOptionsExt; use std::os::windows::io::AsRawHandle; static LAZY: std::sync::Mutex> = std::sync::Mutex::new(BTreeMap::new()); let lazy = *LAZY .lock() .unwrap_or_else(|e| e.into_inner()) .entry(root.to_path_buf()) .or_insert_with(|| ws::read_id(root).is_ok_and(|id| crate::lazy::is_lazy(&id))); if !lazy { return false; } let Ok(file) = std::fs::OpenOptions::new().access_mode(FILE_READ_ATTRIBUTES).share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE).open(root.join(path)) else { return false; }; // The info and an identity of up to 4 KiB. let mut buf = vec![0u64; 640]; let size = (buf.len() * 8) as u32; let mut got = 0u32; // SAFETY: an open handle, and a buffer of `size` bytes to fill. if unsafe { CfGetPlaceholderInfo(file.as_raw_handle() as HANDLE, CF_PLACEHOLDER_INFO_BASIC, buf.as_mut_ptr().cast(), size, &mut got) } < 0 { return false; } // SAFETY: the buffer is aligned for, and holds, a CF_PLACEHOLDER_BASIC_INFO. let info = unsafe { &*(buf.as_ptr() as *const CF_PLACEHOLDER_BASIC_INFO) }; let made = tail(&buf, std::mem::offset_of!(CF_PLACEHOLDER_BASIC_INFO, FileIdentity), info.FileIdentityLength as usize); info.InSyncState == CF_IN_SYNC_STATE_IN_SYNC && made == Some(identity(id, path).as_slice()) } /// What the server needs to fetch a file. struct Server { tree: Id, store: Store, remote: Remote, } impl Server { /// A file's content from the store, fetched from the host first if it isn't here. fn content(&self, id: &Id, path: &str) -> Result> { if let Ok(bytes) = self.store.read_file(id) { return Ok(bytes); } let bundle = self.remote.post_for_bytes(&format!("/trees/{}/files", self.tree), &serde_json::json!({ "paths": [path] }))?; self.store.ingest(&bundle)?; self.store.read_file(id) } } /// `on mount --serve `: answers Windows' requests for the /// workspace's files until `on mount --stop`. pub fn serve(app: &App, root: &Path) -> Result<()> { let w = workspace_at(app, root)?.filter(|w| w.path == root).ok_or_else(|| anyhow!("{} isn't a lazy workspace on this machine", root.display()))?; let project = app.db.project(&w.project)?.ok_or_else(|| anyhow!("the workspace's project isn't known here"))?; let dir = backing(&w.id); let base = Index::load_from(&dir, "lazy-base").ok_or_else(|| anyhow!("{} has no lazy index", dir.display()))?; let tree = base.tree.ok_or_else(|| anyhow!("{} has no tree", dir.display()))?; // Windows calls back on threads of its own for as long as this runs. let server: &'static Server = Box::leak(Box::new(Server { tree, store: Store::open(&crate::paths::objects()).context("opening the object store")?, remote: Remote::for_project(&project) })); // Only fetches: moves and deletes need no say from the server. let callbacks = [ CF_CALLBACK_REGISTRATION { Type: CF_CALLBACK_TYPE_FETCH_DATA, Callback: Some(fetch_data) }, CF_CALLBACK_REGISTRATION { Type: CF_CALLBACK_TYPE_NONE, Callback: None }, ]; let mut key: CF_CONNECTION_KEY = 0; // SAFETY: a NUL-terminated path, a callback table ending in NONE, and a // context that lives as long as the process. let hr = unsafe { CfConnectSyncRoot(wide_path(root).as_ptr(), callbacks.as_ptr(), server as *const Server as *const c_void, CF_CONNECT_FLAG_NONE, &mut key) }; if hr == from_win32(378) { // Another server has it already. return Ok(()); } if hr < 0 { return Err(anyhow!("couldn't serve {}: {}", root.display(), os_error(hr))); } // SAFETY: a NUL-terminated name; the event is waited on, then closed. unsafe { let stop = CreateEventW(std::ptr::null(), 1, 0, event_name(&w.id).as_ptr()); if stop.is_null() { CfDisconnectSyncRoot(key); return Err(anyhow!("couldn't serve {}: {}", root.display(), std::io::Error::last_os_error())); } WaitForSingleObject(stop, INFINITE); CfDisconnectSyncRoot(key); CloseHandle(stop); } Ok(()) } /// Windows wants a placeholder's content: the whole file goes to it, from /// the start of the range it asked for. unsafe extern "system" fn fetch_data(info: *const CF_CALLBACK_INFO, params: *const CF_CALLBACK_PARAMETERS) { // A panic can't cross into Windows; the fetch fails instead. let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { // SAFETY: Windows passes valid info and parameters for the call's length. let (info, fetch) = unsafe { (&*info, (*params).Anonymous.FetchData) }; // Transfers start on a 4 KiB boundary; failing one names the rest of the file. let start = fetch.RequiredFileOffset & !4095; let rest = info.FileSize - start; // SAFETY: Windows' context is the server, and its identity is the placeholder's. let server = unsafe { &*(info.CallbackContext as *const Server) }; let made: &[u8] = if info.FileIdentity.is_null() { &[] } else { unsafe { std::slice::from_raw_parts(info.FileIdentity as *const u8, info.FileIdentityLength as usize) } }; let found = std::str::from_utf8(made).ok().and_then(|t| t.split_once('\n')).and_then(|(id, path)| Some((Id::parse(id)?, path))); let Some((id, path)) = found else { eprintln!("on mount: a placeholder Onset didn't make was asked for"); return transfer(info, start, None, rest); }; match server.content(&id, path) { Ok(bytes) if bytes.len() as i64 == info.FileSize => { if bytes.is_empty() { return transfer(info, 0, Some(&bytes), 0); } let mut at = start; while at < info.FileSize { let end = (at + CHUNK as i64).min(info.FileSize); let hr = transfer(info, at, Some(&bytes[at as usize..end as usize]), end - at); if hr < 0 { eprintln!("on mount: couldn't hand over {path}: {}", os_error(hr)); return hr; } at = end; } 0 } Ok(bytes) => { eprintln!("on mount: {path} is {} bytes, not the {} its placeholder says", bytes.len(), info.FileSize); transfer(info, start, None, rest) } Err(e) => { eprintln!("on mount: couldn't fetch {path}: {e:#}"); transfer(info, start, None, rest) } } })); } /// Gives Windows `len` bytes of a placeholder from `at`, or (with no bytes) /// fails its fetch. fn transfer(info: &CF_CALLBACK_INFO, at: i64, bytes: Option<&[u8]>, len: i64) -> HRESULT { let op = CF_OPERATION_INFO { StructSize: std::mem::size_of::() as u32, Type: CF_OPERATION_TYPE_TRANSFER_DATA, ConnectionKey: info.ConnectionKey, TransferKey: info.TransferKey, CorrelationVector: info.CorrelationVector, SyncStatus: std::ptr::null(), RequestKey: info.RequestKey, }; let status: NTSTATUS = if bytes.is_some() { STATUS_SUCCESS } else { STATUS_CLOUD_FILE_UNSUCCESSFUL }; let mut params = CF_OPERATION_PARAMETERS { ParamSize: (std::mem::offset_of!(CF_OPERATION_PARAMETERS, Anonymous) + std::mem::size_of::()) as u32, ..Default::default() }; params.Anonymous.TransferData = CF_OPERATION_PARAMETERS_0_0 { Flags: CF_OPERATION_TRANSFER_DATA_FLAG_NONE, CompletionStatus: status, Buffer: bytes.map_or(std::ptr::null(), |b| b.as_ptr().cast()), Offset: at, Length: len, }; // SAFETY: the operation's keys are the callback's, and `bytes` outlives the call. unsafe { CfExecute(&op, &mut params) } }