Skip to main content

dolang_vfs/
lib.rs

1#![deny(warnings)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3//! Filesystem and process operations over either a local or remote target.
4//!
5//! [`Vfs`] performs operations in either the current process's environment
6//! or through a `dolang-vfs` agent.
7//!
8//! Paths passed through [`Vfs`] are [`path::Path`] values. Their syntax
9//! belongs to the target VFS rather than necessarily to the host running this
10//! code, which lets a Unix host describe Windows paths and vice versa.
11
12use dolang_winterop::security::{SecDesc, Sid};
13use extension::VfsExtension;
14use std::collections::HashMap;
15use tokio::io::{AsyncRead, AsyncWrite};
16/// Remote VFS client implementation.
17mod client;
18/// Local-process VFS implementation.
19mod direct;
20/// Directory iteration types.
21pub mod directory;
22/// Error types returned by VFS operations.
23pub mod error;
24pub mod extension;
25pub mod file;
26mod macos_acl;
27pub mod metadata;
28mod nfs4_acl;
29pub mod path;
30mod posix_acl;
31/// Process status, control, and standard-I/O types.
32pub mod process;
33mod protocol;
34pub mod security;
35/// RPC server implementation.
36pub mod server;
37mod session;
38pub mod target;
39#[cfg(windows)]
40mod windows;
41
42/// Buffer size used when pumping bulk byte streams (stdio relays, file and
43/// stdio trailer transfers).
44///
45/// Each remote read or write turns into one round trip, so the buffer size
46/// sets how much data one round trip carries. `tokio::io::copy`'s built-in
47/// 8 KiB buffer makes that ratio disastrous for streaming; the copies here use
48/// `copy_buf` over a buffer of this size instead. It matches the default
49/// `dolang-rpc` maximum fragment size and the runtime's
50/// `BYTE_STREAM_CHUNK_SIZE`, so a full buffer maps onto a single wire
51/// fragment, and it also amortizes syscalls on purely local transfers.
52const STREAM_CHUNK_SIZE: usize = 512 * 1024;
53
54/// Largest range one `FileRead` request may ask for.
55///
56/// The server reads the whole requested range into memory *before* it responds,
57/// so that a filesystem error becomes a structured failure in the response
58/// rather than an aborted trailer with the `ErrorKind` lost. That makes the
59/// requested length an allocation the peer controls, so it has to be bounded.
60/// One chunk keeps a full reply inside a single wire fragment, matching what
61/// the trailer pool already budgets per transfer.
62///
63/// Reads larger than this are not an error: both the request length and the
64/// reply are clamped, and the caller sees an ordinary short read. Callers that
65/// must have every byte loop, exactly as they already must around a short read
66/// at any other layer.
67const MAX_FILE_READ: usize = STREAM_CHUNK_SIZE;
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub(crate) enum SessionMode {
71    Native,
72    Remote,
73}
74
75/// A filesystem and process-execution backend backed by either a remote client
76/// or the local process.
77///
78/// Path arguments always use the target's syntax; consult [`Vfs::target`] when
79/// selecting one for a remote VFS.
80#[derive(Clone)]
81enum VfsInner {
82    Client(client::Client),
83    Direct(direct::Direct),
84}
85
86#[derive(Clone)]
87pub struct Vfs {
88    inner: VfsInner,
89}
90
91impl Vfs {
92    fn from_client(client: client::Client) -> Self {
93        Self {
94            inner: VfsInner::Client(client),
95        }
96    }
97
98    fn from_direct(direct: direct::Direct) -> Self {
99        Self {
100            inner: VfsInner::Direct(direct),
101        }
102    }
103
104    /// Creates a VFS that accesses the local process directly.
105    pub fn direct() -> error::Result<Self> {
106        direct::Direct::new().map(Self::from_direct)
107    }
108
109    /// Starts an opaque-only VFS over a bidirectional byte stream.
110    ///
111    /// This transport cannot transfer native handles, so files, subprocesses,
112    /// and stdio endpoints are represented by remote references and relays.
113    pub async fn new<T>(stream: T) -> error::Result<Self>
114    where
115        T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
116    {
117        client::Client::new(stream).await.map(Self::from_client)
118    }
119
120    /// Starts an opaque-only VFS on separate reader and writer streams.
121    ///
122    /// This has the same opaque-only behavior as [`new`](Self::new).
123    pub async fn new_split<R, W>(reader: R, writer: W) -> error::Result<Self>
124    where
125        R: AsyncRead + Send + 'static,
126        W: AsyncWrite + Send + 'static,
127    {
128        client::Client::new_split(reader, writer)
129            .await
130            .map(Self::from_client)
131    }
132
133    /// Connects to an agent daemon at a Unix-domain socket path.
134    ///
135    /// This transport supports native file-descriptor transfer.
136    #[cfg(unix)]
137    pub async fn connect(path: impl AsRef<std::path::Path>) -> error::Result<Self> {
138        client::Client::connect(path).await.map(Self::from_client)
139    }
140
141    /// Connects to an agent daemon at a Unix-domain socket path, proving
142    /// knowledge of a pre-shared key.
143    ///
144    /// A socket that must be world-connectable cannot identify its peer from
145    /// credentials alone, so `key` distinguishes the intended agent and
146    /// client. Both ends must agree on the key.
147    #[cfg(unix)]
148    pub async fn connect_with_key(
149        path: impl AsRef<std::path::Path>,
150        key: Option<dolang_rpc::auth::AuthKey>,
151    ) -> error::Result<Self> {
152        client::Client::connect_with_key(path, key)
153            .await
154            .map(Self::from_client)
155    }
156
157    /// Connects using an existing Unix-domain stream.
158    ///
159    /// This transport supports native file-descriptor transfer.
160    #[cfg(unix)]
161    pub async fn from_stream(stream: tokio::net::UnixStream) -> error::Result<Self> {
162        client::Client::from_stream(stream)
163            .await
164            .map(Self::from_client)
165    }
166
167    /// Starts a VFS on an already-connected Unix-domain socket file
168    /// descriptor.
169    ///
170    /// This transport supports native file-descriptor transfer.
171    #[cfg(unix)]
172    pub async fn from_owned_fd(value: std::os::fd::OwnedFd) -> error::Result<Self> {
173        client::Client::from_owned_fd(value)
174            .await
175            .map(Self::from_client)
176    }
177
178    /// Starts a VFS on an already-connected Unix-domain socket file
179    /// descriptor, proving knowledge of a pre-shared key.
180    #[cfg(unix)]
181    pub async fn from_owned_fd_with_key(
182        value: std::os::fd::OwnedFd,
183        key: Option<dolang_rpc::auth::AuthKey>,
184    ) -> error::Result<Self> {
185        client::Client::from_owned_fd_with_key(value, key)
186            .await
187            .map(Self::from_client)
188    }
189
190    /// Starts a VFS on the server end of a connected Windows named pipe.
191    ///
192    /// # Safety
193    ///
194    /// `server_process` must identify the trusted process at the other end of
195    /// the pipe. That process can transfer handles which this process adopts.
196    #[cfg(windows)]
197    pub async unsafe fn from_named_pipe_server(
198        pipe: tokio::net::windows::named_pipe::NamedPipeServer,
199        server_process: std::os::windows::io::OwnedHandle,
200    ) -> error::Result<Self> {
201        unsafe { client::Client::from_named_pipe_server(pipe, server_process) }
202            .await
203            .map(Self::from_client)
204    }
205
206    /// Returns whether this VFS accesses the local process directly.
207    pub fn is_direct(&self) -> bool {
208        matches!(&self.inner, VfsInner::Direct(_))
209    }
210
211    /// Stops a remote backend and ends the connection to it. Direct backends
212    /// require no shutdown.
213    ///
214    /// The peer finishes shutting down once its incoming transport ends, so
215    /// this consumes the VFS and closes the connection itself; a bare request
216    /// would leave the peer waiting on a client with nothing left to say.
217    /// When the request fails the connection is aborted instead, since a peer
218    /// that could not answer may never close its own output. Nested sessions
219    /// stop the whole chain: the peer closes its connection to the far end as
220    /// it services the request.
221    ///
222    /// Clones share one session, so any that outlive this call can no longer
223    /// issue requests.
224    pub async fn stop(self) -> error::Result<()> {
225        match self.inner {
226            VfsInner::Client(client) => client.stop().await,
227            VfsInner::Direct(_) => Ok(()),
228        }
229    }
230
231    /// Gracefully closes a remote backend. Direct backends require no shutdown.
232    ///
233    /// This waits for the peer to close its outgoing transport and has no
234    /// built-in timeout. Windows named pipes instead close the shared pipe
235    /// after outgoing writes drain because they cannot half-close. Use
236    /// [`Vfs::abort`] when the peer is uncooperative.
237    pub async fn close(self) {
238        match self.inner {
239            VfsInner::Client(client) => client.close().await,
240            VfsInner::Direct(_) => {}
241        }
242    }
243
244    /// Abruptly closes a remote backend. Direct backends require no shutdown.
245    pub async fn abort(self) {
246        match self.inner {
247            VfsInner::Client(client) => client.abort().await,
248            VfsInner::Direct(_) => {}
249        }
250    }
251
252    /// Calls a registered VFS extension, dispatching directly in-process or
253    /// over RPC depending on which backend this `Vfs` wraps.
254    pub async fn call_extension<T: VfsExtension>(
255        &self,
256        request: T::Request,
257    ) -> error::Result<T::Response> {
258        match &self.inner {
259            VfsInner::Client(client) => client.call_extension::<T>(request).await,
260            VfsInner::Direct(direct) => direct.call_extension::<T>(request).await,
261        }
262    }
263
264    /// Iterates the target's initial process environment.
265    pub fn env(&self) -> Box<dyn Iterator<Item = (String, String)> + '_> {
266        match &self.inner {
267            VfsInner::Client(client) => client.env(),
268            VfsInner::Direct(direct) => direct.env(),
269        }
270    }
271
272    /// Returns the target's initial working directory.
273    pub fn cwd(&self) -> path::Path<'_> {
274        match &self.inner {
275            VfsInner::Client(vfs) => vfs.cwd(),
276            VfsInner::Direct(vfs) => vfs.cwd(),
277        }
278    }
279
280    /// Returns the target process executable.
281    pub fn current_exe(&self) -> path::Path<'_> {
282        match &self.inner {
283            VfsInner::Client(vfs) => vfs.current_exe(),
284            VfsInner::Direct(vfs) => vfs.current_exe(),
285        }
286    }
287
288    /// Returns target platform information.
289    pub fn target(&self) -> &target::TargetInfo {
290        match &self.inner {
291            VfsInner::Client(vfs) => vfs.target(),
292            VfsInner::Direct(vfs) => vfs.target(),
293        }
294    }
295
296    /// Returns the target's initial security context.
297    pub fn security(&self) -> &security::SecurityInfo {
298        match &self.inner {
299            VfsInner::Client(vfs) => vfs.security(),
300            VfsInner::Direct(vfs) => vfs.security(),
301        }
302    }
303
304    /// Returns the identity of this target session.
305    ///
306    /// Generated when the target's context was captured, so it distinguishes
307    /// this session from any other — including an earlier session against the
308    /// same machine. Values that are only meaningful against one target, such
309    /// as [`process::ProcessInfo`], carry it so they cannot be quietly
310    /// interpreted against a different one.
311    pub fn session(&self) -> uuid::Uuid {
312        match &self.inner {
313            VfsInner::Client(vfs) => vfs.session(),
314            VfsInner::Direct(vfs) => vfs.session(),
315        }
316    }
317
318    /// Returns the process ID of the target process itself.
319    ///
320    /// The process serving this VFS — the local interpreter for a direct
321    /// target, the remote agent for a client — and for a chained target, the
322    /// one at the far end that actually performs the work. Like any other PID
323    /// from this target, it is only meaningful against
324    /// [`session`](Self::session).
325    pub fn pid(&self) -> u32 {
326        match &self.inner {
327            VfsInner::Client(vfs) => vfs.pid(),
328            VfsInner::Direct(vfs) => vfs.pid(),
329        }
330    }
331
332    /// Enumerates the target's process table.
333    ///
334    /// Entries are produced lazily, and a process that exits partway through is
335    /// skipped rather than reported. A record carries everything the target
336    /// would report about that process — the same record
337    /// [`describe_process`](Self::describe_process) would produce for it.
338    pub async fn processes(&self) -> error::Result<process::Processes> {
339        match &self.inner {
340            VfsInner::Client(client) => client.processes().await,
341            VfsInner::Direct(direct) => direct.processes().await,
342        }
343    }
344
345    /// Describes the process that currently owns `pid`.
346    ///
347    /// The same record [`processes`](Self::processes) would produce for it,
348    /// without enumerating the table to reach it and without holding the
349    /// process open. Nothing pins the PID, so the record describes whatever
350    /// owned it at the moment it was read — for an identity that can be
351    /// checked, open a handle instead.
352    ///
353    /// Unlike [`open_process`](Self::open_process), this needs no rights over
354    /// the process: on a Windows target the kernel's own processes cannot be
355    /// opened by anyone, and this is the only route to what is known about
356    /// them.
357    pub async fn describe_process(&self, pid: u32) -> error::Result<process::ProcessInfo> {
358        match &self.inner {
359            VfsInner::Client(client) => client.describe_process(pid).await,
360            VfsInner::Direct(direct) => direct.describe_process(pid).await,
361        }
362    }
363
364    /// Opens a handle to the process a snapshot describes.
365    ///
366    /// Fails if the PID has been recycled since the snapshot was taken, or if
367    /// the snapshot came from a different target session.
368    pub async fn open_process_info(
369        &self,
370        info: &process::ProcessInfo,
371    ) -> error::Result<process::Process> {
372        if info.session() != self.session() {
373            return Err(error::Error::new(
374                error::ErrorKind::InvalidInput,
375                "process record was captured from a different target",
376            ));
377        }
378        self.open_process_raw(info.pid(), Some(info.start_time()))
379            .await
380    }
381
382    /// Opens a handle to whatever process currently owns `pid`.
383    ///
384    /// The escape hatch for a PID that did not come from
385    /// [`processes`](Self::processes) — read out of a pidfile, say. Nothing
386    /// here can tell whether the PID still names what the caller meant; prefer
387    /// [`open_process_info`](Self::open_process_info) when a snapshot is
388    /// available.
389    pub async fn open_process(&self, pid: u32) -> error::Result<process::Process> {
390        self.open_process_raw(pid, None).await
391    }
392
393    pub(crate) async fn open_process_raw(
394        &self,
395        pid: u32,
396        start: Option<process::StartTime>,
397    ) -> error::Result<process::Process> {
398        match &self.inner {
399            VfsInner::Client(client) => client.open_process(pid, start).await,
400            VfsInner::Direct(direct) => direct.open_process(pid, start).await,
401        }
402    }
403
404    /// Returns supported VFS extension protocol versions.
405    pub fn extensions(&self) -> &extension::ExtensionSet {
406        match &self.inner {
407            VfsInner::Client(vfs) => vfs.extensions(),
408            VfsInner::Direct(vfs) => vfs.extensions(),
409        }
410    }
411
412    /// Creates a file-open options builder.
413    pub fn open_options(&self) -> file::OpenOptions<'_> {
414        match &self.inner {
415            VfsInner::Client(client) => file::OpenOptions::client(client.open_options()),
416            VfsInner::Direct(direct) => file::OpenOptions::direct(direct.open_options()),
417        }
418    }
419
420    /// Creates a command builder for `program`.
421    pub fn command(&self, program: path::Path<'_>) -> process::Command<'_> {
422        process::Command::new(self, program)
423    }
424
425    /// Connects to a VFS agent over a Unix-domain socket.
426    ///
427    /// `key` is an optional pre-shared key that both ends must prove knowledge
428    /// of during negotiation. It is what identifies the intended agent when
429    /// the socket's permissions cannot; the concrete client accepts the same
430    /// key when connecting.
431    pub async fn unix_socket(
432        &self,
433        path: path::Path<'_>,
434        key: Option<&[u8]>,
435    ) -> error::Result<Vfs> {
436        match &self.inner {
437            VfsInner::Client(client) => client.unix_socket(path, key).await,
438            VfsInner::Direct(direct) => direct.unix_socket(path, key).await,
439        }
440    }
441
442    /// Starts a Windows administrative VFS session.
443    pub async fn windows_admin(
444        &self,
445        cwd: path::Path<'_>,
446        env: HashMap<String, Option<String>>,
447        elevate: bool,
448    ) -> error::Result<Vfs> {
449        match &self.inner {
450            VfsInner::Client(client) => client.windows_admin(cwd, env, elevate).await,
451            VfsInner::Direct(direct) => direct.windows_admin(cwd, env, elevate).await,
452        }
453    }
454
455    /// Creates a connected writable and readable pipe endpoint.
456    ///
457    /// `buf_size` is a best-effort kernel buffer size hint. Backends that
458    /// cannot honor the hint use their default buffer size.
459    pub async fn pipe(
460        &self,
461        buf_size: Option<usize>,
462    ) -> error::Result<(process::StdioSend, process::StdioRecv)> {
463        match &self.inner {
464            VfsInner::Client(client) => client.pipe(buf_size).await,
465            VfsInner::Direct(direct) => direct.pipe(buf_size).await,
466        }
467    }
468
469    /// Resolves a Unix user ID to a name.
470    pub async fn user_name(&self, uid: u32) -> error::Result<String> {
471        match &self.inner {
472            VfsInner::Client(client) => client.user_name(uid).await,
473            VfsInner::Direct(direct) => direct.user_name(uid).await,
474        }
475    }
476
477    /// Resolves a Unix user name to an ID.
478    pub async fn user_id(&self, name: &str) -> error::Result<u32> {
479        match &self.inner {
480            VfsInner::Client(client) => client.user_id(name).await,
481            VfsInner::Direct(direct) => direct.user_id(name).await,
482        }
483    }
484
485    /// Resolves a Unix group ID to a name.
486    pub async fn group_name(&self, gid: u32) -> error::Result<String> {
487        match &self.inner {
488            VfsInner::Client(client) => client.group_name(gid).await,
489            VfsInner::Direct(direct) => direct.group_name(gid).await,
490        }
491    }
492
493    /// Resolves a Unix group name to an ID.
494    pub async fn group_id(&self, name: &str) -> error::Result<u32> {
495        match &self.inner {
496            VfsInner::Client(client) => client.group_id(name).await,
497            VfsInner::Direct(direct) => direct.group_id(name).await,
498        }
499    }
500
501    /// Resolves a Windows SID to its account name.
502    pub async fn sid_name(&self, sid: &Sid) -> error::Result<security::SidName> {
503        match &self.inner {
504            VfsInner::Client(client) => client.sid_name(sid).await,
505            VfsInner::Direct(direct) => direct.sid_name(sid).await,
506        }
507    }
508
509    /// Resolves a Windows account name to its SID.
510    pub async fn account_name(&self, name: &str) -> error::Result<security::SidName> {
511        match &self.inner {
512            VfsInner::Client(client) => client.account_name(name).await,
513            VfsInner::Direct(direct) => direct.account_name(name).await,
514        }
515    }
516
517    /// Converts a principal ID from one representation to another (e.g. a
518    /// Unix uid/gid to/from a macOS principal UUID).
519    pub async fn resolve_principal_id(
520        &self,
521        input: security::PrincipalId,
522        want: security::PrincipalIdKind,
523    ) -> error::Result<security::PrincipalId> {
524        match &self.inner {
525            VfsInner::Client(client) => client.resolve_principal_id(input, want).await,
526            VfsInner::Direct(direct) => direct.resolve_principal_id(input, want).await,
527        }
528    }
529
530    /// Opens a directory iterator.
531    pub async fn read_dir(&self, path: path::Path<'_>) -> error::Result<directory::ReadDir> {
532        match &self.inner {
533            VfsInner::Client(client) => client.read_dir(path).await,
534            VfsInner::Direct(direct) => direct.read_dir(path).await,
535        }
536    }
537
538    /// Finds an executable using a target search path.
539    pub async fn which(
540        &self,
541        program: path::Path<'_>,
542        path: Option<&str>,
543        cwd: Option<path::Path<'_>>,
544    ) -> error::Result<Option<path::PathBuf>> {
545        match &self.inner {
546            VfsInner::Client(client) => client.which(program, path, cwd).await,
547            VfsInner::Direct(direct) => direct.which(program, path, cwd).await,
548        }
549    }
550
551    /// Resolves a target-specific well-known path.
552    pub async fn well_known_path(
553        &self,
554        key: path::WellKnownPath,
555        app: Option<&str>,
556        env: &HashMap<String, Option<String>>,
557    ) -> error::Result<path::PathBuf> {
558        match &self.inner {
559            VfsInner::Client(client) => client.well_known_path(key, app, env).await,
560            VfsInner::Direct(direct) => direct.well_known_path(key, app, env).await,
561        }
562    }
563
564    /// Clears target-side cached state.
565    pub async fn clear_cache(&self) -> error::Result<()> {
566        match &self.inner {
567            VfsInner::Client(client) => client.clear_cache().await,
568            VfsInner::Direct(direct) => direct.clear_cache().await,
569        }
570    }
571
572    /// Lists extended attributes for a path.
573    pub async fn xattrs(
574        &self,
575        path: path::Path<'_>,
576        namespace: file::XattrNamespace<'_>,
577        follow: bool,
578    ) -> error::Result<Vec<file::XattrEntry>> {
579        match &self.inner {
580            VfsInner::Client(client) => client.xattrs(path, namespace, follow).await,
581            VfsInner::Direct(direct) => direct.xattrs(path, namespace, follow).await,
582        }
583    }
584
585    /// Lists alternate data streams for a path.
586    pub async fn streams(
587        &self,
588        path: path::Path<'_>,
589        follow: bool,
590    ) -> error::Result<Vec<file::StreamEntry>> {
591        match &self.inner {
592            VfsInner::Client(client) => client.streams(path, follow).await,
593            VfsInner::Direct(direct) => direct.streams(path, follow).await,
594        }
595    }
596
597    /// Reads an extended attribute for a path.
598    pub async fn xattr(
599        &self,
600        path: path::Path<'_>,
601        name: &str,
602        namespace: Option<&str>,
603        follow: bool,
604    ) -> error::Result<Vec<u8>> {
605        match &self.inner {
606            VfsInner::Client(client) => client.xattr(path, name, namespace, follow).await,
607            VfsInner::Direct(direct) => direct.xattr(path, name, namespace, follow).await,
608        }
609    }
610
611    /// Creates or replaces an extended attribute for a path.
612    pub async fn set_xattr(
613        &self,
614        path: path::Path<'_>,
615        name: &str,
616        namespace: Option<&str>,
617        value: &[u8],
618        follow: bool,
619    ) -> error::Result<()> {
620        match &self.inner {
621            VfsInner::Client(client) => {
622                client.set_xattr(path, name, namespace, value, follow).await
623            }
624            VfsInner::Direct(direct) => {
625                direct.set_xattr(path, name, namespace, value, follow).await
626            }
627        }
628    }
629
630    /// Removes an extended attribute from a path.
631    pub async fn remove_xattr(
632        &self,
633        path: path::Path<'_>,
634        name: &str,
635        namespace: Option<&str>,
636        follow: bool,
637    ) -> error::Result<()> {
638        match &self.inner {
639            VfsInner::Client(client) => client.remove_xattr(path, name, namespace, follow).await,
640            VfsInner::Direct(direct) => direct.remove_xattr(path, name, namespace, follow).await,
641        }
642    }
643
644    /// Removes a file or symlink.
645    pub async fn remove(&self, path: path::Path<'_>, all: bool, ignore: bool) -> error::Result<()> {
646        match &self.inner {
647            VfsInner::Client(client) => client.remove(path, all, ignore).await,
648            VfsInner::Direct(direct) => direct.remove(path, all, ignore).await,
649        }
650    }
651
652    /// Returns metadata without following the final symlink.
653    pub async fn metadata(&self, path: path::Path<'_>) -> error::Result<metadata::Metadata> {
654        match &self.inner {
655            VfsInner::Client(client) => client.metadata(path).await,
656            VfsInner::Direct(direct) => direct.metadata(path).await,
657        }
658    }
659
660    /// Returns filesystem metadata for a path.
661    pub async fn fs_metadata(
662        &self,
663        path: path::Path<'_>,
664        follow: bool,
665    ) -> error::Result<metadata::FsMetadata> {
666        match &self.inner {
667            VfsInner::Client(client) => client.fs_metadata(path, follow).await,
668            VfsInner::Direct(direct) => direct.fs_metadata(path, follow).await,
669        }
670    }
671
672    /// Returns the ACL of the requested `kind` for a path. See
673    /// [`file::File::acl`] for `default`'s meaning.
674    pub async fn acl(
675        &self,
676        path: path::Path<'_>,
677        kind: security::AclKind,
678        default: bool,
679        follow: bool,
680    ) -> error::Result<Option<security::Acl>> {
681        match &self.inner {
682            VfsInner::Client(client) => client.acl(path, kind, default, follow).await,
683            VfsInner::Direct(direct) => direct.acl(path, kind, default, follow).await,
684        }
685    }
686
687    /// Sets or removes the ACL for a path. See [`file::File::set_acl`] for
688    /// `default`'s meaning.
689    pub async fn set_acl(
690        &self,
691        path: path::Path<'_>,
692        kind: security::AclKind,
693        acl: Option<&security::Acl>,
694        default: bool,
695        follow: bool,
696    ) -> error::Result<()> {
697        match &self.inner {
698            VfsInner::Client(client) => client.set_acl(path, kind, acl, default, follow).await,
699            VfsInner::Direct(direct) => direct.set_acl(path, kind, acl, default, follow).await,
700        }
701    }
702
703    /// Returns the Windows security descriptor for a path.
704    pub async fn sec_desc(
705        &self,
706        path: path::Path<'_>,
707        mask: dolang_winterop::security::SecInfo,
708        follow: bool,
709    ) -> error::Result<SecDesc> {
710        match &self.inner {
711            VfsInner::Client(client) => client.sec_desc(path, mask, follow).await,
712            VfsInner::Direct(direct) => direct.sec_desc(path, mask, follow).await,
713        }
714    }
715
716    /// Replaces the Windows security descriptor for a path.
717    pub async fn update_sec_desc(
718        &self,
719        path: path::Path<'_>,
720        sec_desc: &SecDesc,
721        follow: bool,
722    ) -> error::Result<()> {
723        match &self.inner {
724            VfsInner::Client(client) => client.update_sec_desc(path, sec_desc, follow).await,
725            VfsInner::Direct(direct) => direct.update_sec_desc(path, sec_desc, follow).await,
726        }
727    }
728
729    /// Creates a directory, optionally including missing parents.
730    pub async fn create_dir(&self, path: path::Path<'_>, all: bool) -> error::Result<()> {
731        match &self.inner {
732            VfsInner::Client(client) => client.create_dir(path, all).await,
733            VfsInner::Direct(direct) => direct.create_dir(path, all).await,
734        }
735    }
736
737    /// Removes a directory.
738    pub async fn remove_dir(
739        &self,
740        path: path::Path<'_>,
741        all: bool,
742        ignore: bool,
743    ) -> error::Result<()> {
744        match &self.inner {
745            VfsInner::Client(client) => client.remove_dir(path, all, ignore).await,
746            VfsInner::Direct(direct) => direct.remove_dir(path, all, ignore).await,
747        }
748    }
749
750    /// Copies a path, optionally including directory contents.
751    pub async fn copy(
752        &self,
753        from: path::Path<'_>,
754        to: path::Path<'_>,
755        all: bool,
756    ) -> error::Result<()> {
757        match &self.inner {
758            VfsInner::Client(client) => client.copy(from, to, all).await,
759            VfsInner::Direct(direct) => direct.copy(from, to, all).await,
760        }
761    }
762
763    /// Renames a path.
764    pub async fn rename(
765        &self,
766        from: path::Path<'_>,
767        to: path::Path<'_>,
768        replace: bool,
769    ) -> error::Result<()> {
770        match &self.inner {
771            VfsInner::Client(client) => client.rename(from, to, replace).await,
772            VfsInner::Direct(direct) => direct.rename(from, to, replace).await,
773        }
774    }
775
776    /// Moves a path, optionally including directory contents.
777    pub async fn move_(
778        &self,
779        from: path::Path<'_>,
780        to: path::Path<'_>,
781        all: bool,
782    ) -> error::Result<()> {
783        match &self.inner {
784            VfsInner::Client(client) => client.move_(from, to, all).await,
785            VfsInner::Direct(direct) => direct.move_(from, to, all).await,
786        }
787    }
788
789    /// Creates a symbolic link using `cwd` to interpret relative source paths.
790    pub async fn symlink(
791        &self,
792        cwd: path::Path<'_>,
793        src: path::Path<'_>,
794        dst: path::Path<'_>,
795    ) -> error::Result<()> {
796        match &self.inner {
797            VfsInner::Client(client) => client.symlink(cwd, src, dst).await,
798            VfsInner::Direct(direct) => direct.symlink(cwd, src, dst).await,
799        }
800    }
801
802    /// Creates a hard link.
803    pub async fn hard_link(&self, src: path::Path<'_>, dst: path::Path<'_>) -> error::Result<()> {
804        match &self.inner {
805            VfsInner::Client(client) => client.hard_link(src, dst).await,
806            VfsInner::Direct(direct) => direct.hard_link(src, dst).await,
807        }
808    }
809
810    /// Creates a symbolic link to a directory.
811    pub async fn symlink_dir(&self, src: path::Path<'_>, dst: path::Path<'_>) -> error::Result<()> {
812        match &self.inner {
813            VfsInner::Client(client) => client.symlink_dir(src, dst).await,
814            VfsInner::Direct(direct) => direct.symlink_dir(src, dst).await,
815        }
816    }
817
818    /// Creates a symbolic link to a file.
819    pub async fn symlink_file(
820        &self,
821        src: path::Path<'_>,
822        dst: path::Path<'_>,
823    ) -> error::Result<()> {
824        match &self.inner {
825            VfsInner::Client(client) => client.symlink_file(src, dst).await,
826            VfsInner::Direct(direct) => direct.symlink_file(src, dst).await,
827        }
828    }
829
830    /// Returns metadata without following the final symlink.
831    pub async fn symlink_metadata(
832        &self,
833        path: path::Path<'_>,
834    ) -> error::Result<metadata::Metadata> {
835        match &self.inner {
836            VfsInner::Client(client) => client.symlink_metadata(path).await,
837            VfsInner::Direct(direct) => direct.symlink_metadata(path).await,
838        }
839    }
840
841    /// Applies a metadata patch to every path.
842    pub async fn update_metadata(
843        &self,
844        paths: &[path::PathBuf],
845        patch: metadata::MetadataPatch,
846    ) -> error::Result<()> {
847        match &self.inner {
848            VfsInner::Client(client) => client.update_metadata(paths, patch).await,
849            VfsInner::Direct(direct) => direct.update_metadata(paths, patch).await,
850        }
851    }
852
853    /// Resolves a path to its canonical absolute form.
854    pub async fn canonicalize(&self, path: path::Path<'_>) -> error::Result<path::PathBuf> {
855        match &self.inner {
856            VfsInner::Client(client) => client.canonicalize(path).await,
857            VfsInner::Direct(direct) => direct.canonicalize(path).await,
858        }
859    }
860
861    /// Returns the destination of a symbolic link.
862    pub async fn read_link(&self, path: path::Path<'_>) -> error::Result<path::PathBuf> {
863        match &self.inner {
864            VfsInner::Client(client) => client.read_link(path).await,
865            VfsInner::Direct(direct) => direct.read_link(path).await,
866        }
867    }
868
869    /// Checks whether the process can access a path with the requested permissions.
870    pub async fn access(&self, path: path::Path<'_>, mode: file::AccessFlags) -> error::Result<()> {
871        match &self.inner {
872            VfsInner::Client(client) => client.access(path, mode).await,
873            VfsInner::Direct(direct) => direct.access(path, mode).await,
874        }
875    }
876
877    /// Expands a glob pattern beneath `root`.
878    pub async fn glob(
879        &self,
880        pattern: impl Into<String>,
881        root: path::Path<'_>,
882        follow_symlinks: bool,
883        max_depth: Option<usize>,
884    ) -> error::Result<Vec<path::PathBuf>> {
885        let pattern = pattern.into();
886
887        match &self.inner {
888            VfsInner::Client(client) => {
889                client.glob(pattern, root, follow_symlinks, max_depth).await
890            }
891            VfsInner::Direct(direct) => {
892                direct.glob(pattern, root, follow_symlinks, max_depth).await
893            }
894        }
895    }
896}