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