Skip to main content

dolang_vfs/server/
mod.rs

1use std::sync::{
2    Arc,
3    atomic::{AtomicUsize, Ordering},
4};
5
6#[cfg(unix)]
7use std::{io, path::Path, time::Duration};
8
9use std::{future::poll_fn, process::Stdio};
10
11use bytes::{Buf, BytesMut};
12#[cfg(unix)]
13use dolang_rpc::auth::AuthKey;
14use dolang_rpc::{
15    handle::{DefaultHandle, OsHandle},
16    server::CallContext,
17    session::{Cite, Gift, OpaqueGuard, OpaqueResource},
18};
19use dolang_winterop::security::SecDesc;
20use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, copy_buf};
21#[cfg(unix)]
22use std::os::unix::io::OwnedFd;
23#[cfg(windows)]
24use tokio::net::windows::named_pipe::NamedPipeClient;
25#[cfg(all(docsrs, not(windows)))]
26struct NamedPipeClient;
27use tokio::sync::{Mutex, watch};
28#[cfg(unix)]
29use tokio::{
30    net::{UnixListener, UnixStream, unix::SocketAddr},
31    sync::mpsc,
32    task::{JoinError, JoinSet},
33    time::timeout,
34};
35
36use crate::{
37    MAX_FILE_READ, STREAM_CHUNK_SIZE, SessionMode, Vfs,
38    directory::ReadDir,
39    error::{Error, ErrorKind, Result},
40    extension::{self, ExtContext},
41    file::XattrEntry,
42    file::{AccessFlags, CopyDest, CopyMode, File, FileLock, FileLockRequest, StreamEntry},
43    metadata::{FsMetadata, Metadata},
44    path,
45    process::{
46        Child, Command, PipeRecv, PipeSend, Process, ProcessInfo, ProcessStatus, Processes, Signal,
47        StartTime,
48    },
49    protocol::{
50        AccessRequest, AclRequest, CanonicalizeRequest, ChildExit, CopyRequest, CreateDirRequest,
51        ExtensionRequest, ExtensionResponse, FsMetadataRequest, GlobRequest, HardLinkRequest,
52        MetadataRequest, MoveRequest, OpenFlags, OpenHandle, OpenRequest, OpenVfsHandle,
53        PipeResponse, ProcessPage, QueryResponse, ReadDirPage, ReadLinkRequest, RemoveDirRequest,
54        RemoveRequest, RenameRequest, Request, RequestKind, ResponseKind, SecDescRequest,
55        SetAclRequest, SetXattrRequest, SpawnRequest, StdioRecvTarget, StdioSendTarget,
56        StreamsRequest, SymlinkKind, SymlinkRequest, UnixVfsRequest, UpdateMetadataRequest,
57        UpdateSecDescRequest, VfsProtocol, WellKnownPathRequest, WindowsAdminRequest,
58        XattrNamespaceRequest, XattrRequest, XattrsRequest, rpc_builder,
59    },
60    security::{Acl, AclKind},
61    session::{
62        ChildMarker, FileLockMarker, FileMarker, ProcessEnumMarker, ProcessMarker, ReadDirMarker,
63        StdioRecvMarker, StdioSendMarker, VfsMarker,
64    },
65};
66
67#[derive(Clone)]
68struct Connection {
69    server: Arc<ServerState>,
70    mode: SessionMode,
71    drain: Arc<Drain>,
72}
73
74/// Tracks outstanding stdio receivers so a stop request can drain them.
75///
76/// A stop request must not sever the connection while a peer is still relaying
77/// from a pipe receiver it obtained from this session: a stdio relay may
78/// outlive the lexical scope of the session it was created in, since pipe
79/// negotiation decides which side of a cross-domain pipeline ends up owning it.
80/// Instead, a stop marks the session as stopping (so no *new* endpoints can be
81/// created) and waits for the receivers already handed out to be closed.
82/// Senders do not delay shutdown: their destination is going away, and waiting
83/// for them can cycle with a relay waiting for transport EOF before closing.
84struct Drain {
85    /// Outstanding endpoint count in the upper bits, stopping flag in the LSB.
86    state: AtomicUsize,
87    done: watch::Sender<bool>,
88}
89
90impl Drain {
91    fn new() -> Arc<Self> {
92        Arc::new(Self {
93            state: AtomicUsize::new(0),
94            done: watch::channel(false).0,
95        })
96    }
97
98    /// Reserves `count` endpoints, or fails if the session is stopping.
99    ///
100    /// The check and the increment are separate non-atomic steps, which is
101    /// sound because every handler for a connection runs as a future on the
102    /// single serve task: no other handler can observe or modify the state
103    /// between them, since there is no await point in between. A stop landing
104    /// just after a successful reservation is equivalent to one landing just
105    /// before it, and either way the drain waits for those endpoints.
106    fn try_acquire(&self, count: usize) -> bool {
107        if self.state.load(Ordering::Acquire) & 1 != 0 {
108            return false;
109        }
110        self.state.fetch_add(count << 1, Ordering::Relaxed);
111        true
112    }
113
114    /// Returns `count` endpoints, completing the drain if it goes idle.
115    fn release(&self, count: usize) {
116        // The stopping flag plus exactly `count` endpoints means the drain has
117        // just gone idle after a stop was requested.
118        if self.state.fetch_sub(count << 1, Ordering::AcqRel) == (count << 1) | 1 {
119            self.done.send_replace(true);
120        }
121    }
122
123    /// Marks the session as stopping, completing the drain if it is already idle.
124    fn begin_stop(&self) {
125        if self.state.fetch_or(1, Ordering::AcqRel) == 0 {
126            self.done.send_replace(true);
127        }
128    }
129
130    /// Waits for a stop to be requested and all outstanding endpoints to close.
131    async fn wait(&self) {
132        let _ = self.done.subscribe().wait_for(|done| *done).await;
133    }
134}
135
136/// A reserved drain slot, returned when the endpoint holding it dies.
137///
138/// Accounting rides the endpoint's own lifetime rather than any particular
139/// message, so an endpoint reaches the drain exactly once however it ends:
140/// explicitly closed, consumed by a spawn, or released because the peer
141/// dropped the last opaque naming it.
142struct DrainSlot(Arc<Drain>);
143
144impl Drop for DrainSlot {
145    fn drop(&mut self) {
146        self.0.release(1);
147    }
148}
149
150struct RetainedVfs {
151    vfs: Vfs,
152}
153
154impl RetainedVfs {
155    fn plain(vfs: Vfs) -> Self {
156        Self { vfs }
157    }
158}
159
160impl OpaqueResource for RetainedVfs {
161    type Marker = VfsMarker;
162}
163
164/// Reads the whole of `len` bytes at `offset`, or as much as exists.
165///
166/// Deliberately buffers the entire answer instead of streaming it: the response
167/// header goes out first and is the only place a structured error can be
168/// reported, so the read has to have already succeeded by the time it is sent.
169/// `len` is bounded by [`MAX_FILE_READ`] at the call site, which is what makes
170/// buffering it whole affordable.
171///
172/// One [`File::read_at`] may come up short for reasons other than the end
173/// of the file — a nested remote file clamps at one chunk, and a positional read
174/// is permitted to be short in general — so this loops rather than treating the
175/// first short read as the end.
176async fn read_file_range(file: &File, offset: u64, len: usize) -> Result<BytesMut> {
177    let mut buf = BytesMut::with_capacity(len);
178    while buf.len() < len {
179        let at = offset + buf.len() as u64;
180        if file.read_at(&mut buf, at).await? == 0 {
181            break;
182        }
183    }
184    // `read_at` fills the spare capacity, which the allocator may have rounded
185    // up past `len`. Delivering those extra bytes would be a protocol violation
186    // on the peer's side, so trim back to what was asked for.
187    buf.truncate(len);
188    Ok(buf)
189}
190
191/// Accumulates up to one chunk from `trailer`, or `None` at its end.
192///
193/// Bounds how much of a write is held in memory at once: the peer may submit a
194/// trailer far larger than a chunk, and the positional write it feeds wants an
195/// owned buffer.
196async fn next_trailer_chunk(
197    trailer: &mut dolang_rpc::trailer::TrailerRecv,
198) -> Result<Option<BytesMut>> {
199    let mut buf = BytesMut::with_capacity(STREAM_CHUNK_SIZE);
200    while buf.len() < STREAM_CHUNK_SIZE {
201        let want = STREAM_CHUNK_SIZE - buf.len();
202        let spare = &mut buf.spare_capacity_mut()[..want];
203        let read = poll_fn(|cx| trailer.poll_read_uninit(cx, spare)).await?;
204        if read == 0 {
205            break;
206        }
207        // SAFETY: the trailer initialized `read` bytes of the spare capacity.
208        unsafe { buf.set_len(buf.len() + read) };
209    }
210    Ok((!buf.is_empty()).then_some(buf))
211}
212
213struct RetainedFile(File);
214
215/// A lock the peer still holds, addressed by its own opaque handle.
216///
217/// Not wrapped in a mutex: releasing is the only thing ever done with one, and
218/// that unregisters the lock first, so the releasing task has it by value and
219/// no other task can still reach it.
220struct RetainedFileLock(FileLock);
221
222impl OpaqueResource for RetainedFileLock {
223    type Marker = FileLockMarker;
224}
225
226struct RetainedReadDir(Mutex<ReadDir>);
227
228impl OpaqueResource for RetainedReadDir {
229    type Marker = ReadDirMarker;
230}
231
232struct RetainedProcesses(Mutex<Processes>);
233
234impl OpaqueResource for RetainedProcesses {
235    type Marker = ProcessEnumMarker;
236}
237
238/// A process handle the peer still holds.
239///
240/// Unlike [`RetainedChild`] this needs no mutex: none of the operations on a
241/// [`Process`] mutate it, so concurrent calls on the same handle are safe. That
242/// matters more than it looks — a `wait` can be outstanding for the life of the
243/// process, and a mutex here would make `terminate` on the same handle
244/// unreachable until it returned.
245struct RetainedProcess(Process);
246
247impl OpaqueResource for RetainedProcess {
248    type Marker = ProcessMarker;
249}
250
251impl OpaqueResource for RetainedFile {
252    type Marker = FileMarker;
253}
254
255struct RetainedStdioSend {
256    stdio: Mutex<PipeSend>,
257}
258
259impl OpaqueResource for RetainedStdioSend {
260    type Marker = StdioSendMarker;
261}
262
263struct RetainedStdioRecv {
264    stdio: Mutex<PipeRecv>,
265    /// Returned to the drain when the endpoint dies, however it ends.
266    _slot: DrainSlot,
267}
268
269impl OpaqueResource for RetainedStdioRecv {
270    type Marker = StdioRecvMarker;
271}
272
273struct RetainedChild {
274    child: Mutex<Child>,
275    /// The files the child was given, whose final positions go back to the
276    /// peer with its exit.
277    files: Vec<File>,
278}
279
280/// Reports a child's exit along with where it left `files`.
281fn child_exit(status: ProcessStatus, files: &[File]) -> ChildExit {
282    let positions = files
283        .iter()
284        .map(|file| file.inner.position().load(Ordering::Relaxed))
285        .collect();
286    ChildExit { status, positions }
287}
288
289impl OpaqueResource for RetainedChild {
290    type Marker = ChildMarker;
291}
292
293struct ServerState {
294    vfs: Vfs,
295    #[cfg(unix)]
296    shutdown_tx: watch::Sender<()>,
297}
298
299/// How long a single connection may take to complete negotiation in
300/// single-session mode before it is dropped. Generous by handshake standards:
301/// the point is only to keep a peer that never speaks from occupying a slot
302/// indefinitely, not to police slow networks.
303#[cfg(unix)]
304const NEGOTIATE_TIMEOUT: Duration = Duration::from_secs(30);
305
306/// How many connections may be negotiating at once in single-session mode.
307/// Bounds what a peer that opens connections without finishing them can tie
308/// up; the listener resumes accepting as attempts drain.
309#[cfg(unix)]
310const MAX_PENDING_CONNECTIONS: usize = 8;
311
312/// Rough payload budget for one page of a process enumeration.
313///
314/// Process records vary by more than an order of magnitude — a kernel thread
315/// carries a short name and nothing else, a build tool's command line can run
316/// to kilobytes — so a fixed entry count would make the page size unpredictable
317/// in exactly the case that matters. Budgeting bytes instead keeps a page's
318/// cost bounded regardless of what is running on the target.
319const PROCESS_PAGE_BYTES: usize = 64 * 1024;
320
321/// Hard entry cap for one page, so a table of uniformly tiny records still
322/// yields to the caller at a sane interval instead of being sent whole.
323const PROCESS_PAGE_ENTRIES: usize = 512;
324
325/// Estimates a record's encoded size for [`PROCESS_PAGE_BYTES`].
326///
327/// Deliberately approximate: it exists to stop one page from being megabytes,
328/// and paying a serialization pass per entry to find out exactly would cost
329/// more than the imprecision does.
330fn process_info_size(info: &ProcessInfo) -> usize {
331    const FIXED: usize = 64;
332    let strings = info.name().len()
333        + info.exe().map_or(0, |path| path.as_str().len())
334        + info.cwd().map_or(0, |path| path.as_str().len())
335        + info
336            .command_line()
337            .map_or(0, |args| args.iter().map(|arg| arg.len() + 1).sum::<usize>())
338        // On a Windows target the command line is carried twice, split and
339        // whole, so it counts twice against the page budget.
340        + info
341            .windows_command_line()
342            .ok()
343            .flatten()
344            .map_or(0, str::len);
345    // Only one of these applies on any given target, and the accessor for the
346    // other reports that rather than returning nothing to measure.
347    let identity = info
348        .identity()
349        .ok()
350        .flatten()
351        .map_or(0, |identity| 16 + identity.groups().len() * 4);
352    // A token carries several SIDs plus one per group, each a variable-length
353    // structure; groups dominate.
354    let token = info
355        .token()
356        .ok()
357        .flatten()
358        .map_or(0, |token| 128 + token.groups().len() * 48);
359    FIXED + strings + identity + token
360}
361
362/// A connection that has completed negotiation, handed from a handler task
363/// back to [`Server::accept_one`].
364#[cfg(unix)]
365struct Negotiated {
366    rpc: dolang_rpc::server::Server<VfsProtocol>,
367    connection: Arc<Connection>,
368}
369
370/// VFS agent server.
371///
372/// Construct a connected server with [`new`](Self::new) or
373/// [`new_split`](Self::new_split) and call [`serve`](Self::serve). On Unix,
374/// `bind` constructs a listener that accepts sessions until a client requests
375/// shutdown.
376pub struct Server {
377    #[cfg(unix)]
378    listener: Option<UnixListener>,
379    rpc: Option<dolang_rpc::server::Server<VfsProtocol>>,
380    mode: SessionMode,
381    shared: Arc<ServerState>,
382    /// Key each accepted connection must prove knowledge of. Held here rather
383    /// than passed to [`bind`](Self::bind) because negotiation happens per
384    /// connection, long after the listener exists.
385    #[cfg(unix)]
386    key: Option<AuthKey>,
387}
388
389impl Server {
390    /// Creates an opaque-only VFS server over a bidirectional byte stream.
391    pub async fn new<T>(stream: T) -> Result<Self>
392    where
393        T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
394    {
395        let rpc = rpc_builder(None).server(stream).await?.bind();
396        Ok(Self {
397            #[cfg(unix)]
398            listener: None,
399            rpc: Some(rpc),
400            mode: SessionMode::Remote,
401            shared: Self::state()?,
402            #[cfg(unix)]
403            key: None,
404        })
405    }
406
407    /// Creates an opaque-only VFS server on separate reader and writer streams.
408    pub async fn new_split<R, W>(reader: R, writer: W) -> Result<Self>
409    where
410        R: AsyncRead + Send + 'static,
411        W: AsyncWrite + Send + 'static,
412    {
413        let rpc = rpc_builder(None).server_split(reader, writer).await?.bind();
414        Ok(Self {
415            #[cfg(unix)]
416            listener: None,
417            rpc: Some(rpc),
418            mode: SessionMode::Remote,
419            shared: Self::state()?,
420            #[cfg(unix)]
421            key: None,
422        })
423    }
424
425    fn state() -> Result<Arc<ServerState>> {
426        #[cfg(unix)]
427        let (shutdown_tx, _) = watch::channel(());
428        Ok(Arc::new(ServerState {
429            vfs: Vfs::direct()?,
430            #[cfg(unix)]
431            shutdown_tx,
432        }))
433    }
434
435    /// Binds a Unix-domain listener for VFS agent connections.
436    #[cfg(unix)]
437    pub async fn bind(path: impl AsRef<Path>) -> Result<Self> {
438        Self::from_listener(UnixListener::bind(path)?, None)
439    }
440
441    /// Binds a Unix-domain listener that requires mutual proof of a pre-shared
442    /// key from every connection.
443    ///
444    /// The socket's permissions cannot distinguish the intended client when
445    /// the peer's uid is not knowable in advance; `key` is what does. A
446    /// connection that fails the check is dropped during negotiation, before
447    /// it can issue any request.
448    #[cfg(unix)]
449    pub async fn bind_with_key(path: impl AsRef<Path>, key: Option<AuthKey>) -> Result<Self> {
450        Self::from_listener(UnixListener::bind(path)?, key)
451    }
452
453    /// Create a server from an existing `UnixListener`.
454    #[cfg(unix)]
455    fn from_listener(listener: UnixListener, key: Option<AuthKey>) -> Result<Self> {
456        Ok(Self {
457            listener: Some(listener),
458            rpc: None,
459            mode: SessionMode::Native,
460            shared: Self::state()?,
461            key,
462        })
463    }
464
465    /// Creates a VFS RPC server on the client end of a connected Windows named pipe.
466    #[cfg(any(windows, docsrs))]
467    #[cfg_attr(docsrs, doc(cfg(windows)))]
468    #[cfg_attr(all(docsrs, not(windows)), allow(private_interfaces))]
469    pub async fn from_named_pipe_client(pipe: NamedPipeClient) -> Result<Self> {
470        #[cfg(windows)]
471        {
472            let rpc = rpc_builder(None)
473                .server_named_pipe_client(pipe)
474                .await?
475                .bind();
476            Ok(Self {
477                #[cfg(unix)]
478                listener: None,
479                rpc: Some(rpc),
480                mode: SessionMode::Native,
481                shared: Self::state()?,
482            })
483        }
484        #[cfg(all(docsrs, not(windows)))]
485        {
486            let _ = pipe;
487            unreachable!()
488        }
489    }
490
491    #[cfg(unix)]
492    fn handle_accept(
493        &self,
494        res: io::Result<(UnixStream, SocketAddr)>,
495        handlers: &mut JoinSet<Result<()>>,
496    ) -> Result<()> {
497        let (stream, _) = res?;
498        let stream = stream.into_std()?;
499        let connection = Arc::new(Connection {
500            server: self.shared.clone(),
501            mode: SessionMode::Native,
502            drain: Drain::new(),
503        });
504        let key = self.key;
505        handlers.spawn(async move {
506            // Negotiation (a real handshake over the wire) happens here,
507            // inside the per-connection task, so a slow or misbehaving peer
508            // can't stall the accept loop from taking new connections. That
509            // includes authentication: an unauthenticated peer fails here and
510            // never reaches `serve_connection`.
511            let rpc = rpc_builder(key).server_unix(stream).await?.bind();
512            let handler = connection.clone();
513            serve_connection(rpc, handler).await
514        });
515        Ok(())
516    }
517
518    /// Accepts connections until a client requests server shutdown.
519    ///
520    /// Each connection runs in an independent handler task. Routine client
521    /// disconnects are ignored; unexpected handler failures are reported to
522    /// standard error.
523    #[cfg(unix)]
524    pub async fn accept(mut self) -> Result<()> {
525        let mut shutdown_rx = self.shared.shutdown_tx.subscribe();
526        let mut handlers = JoinSet::new();
527
528        loop {
529            tokio::select! {
530                res = self.listener.as_ref().unwrap().accept() => {
531                    if let Err(error) = self.handle_accept(res, &mut handlers) {
532                        eprintln!("VFS server failed to accept a connection: {error}");
533                    }
534                }
535                result = handlers.join_next(), if !handlers.is_empty() => {
536                    report_handler_exit(result.unwrap());
537                }
538                _ = shutdown_rx.changed() => {
539                    self.listener.take();
540                    break;
541                }
542            }
543        }
544
545        while let Some(result) = handlers.join_next().await {
546            report_handler_exit(result);
547        }
548        Ok(())
549    }
550
551    /// Accepts connections until one completes negotiation, then serves that
552    /// session alone.
553    ///
554    /// `established` runs once, as soon as some connection has negotiated
555    /// successfully — the point at which the listening socket has done its job
556    /// and the caller can unlink it. Nothing is accepted afterwards.
557    ///
558    /// Connections that fail to negotiate (including failing authentication)
559    /// are dropped and do *not* consume the single slot, so an impostor that
560    /// reaches the socket first cannot deny the intended client its session;
561    /// it can only waste an attempt. Negotiation has a timeout and a fixed
562    /// limit on in-flight attempts, so a peer that connects and then says
563    /// nothing cannot stall or crowd out the real one either.
564    #[cfg(unix)]
565    pub async fn accept_one<F>(mut self, established: F) -> Result<()>
566    where
567        F: FnOnce(),
568    {
569        let mut handlers = JoinSet::new();
570        // Capacity one, and only ever received from once: whichever connection
571        // negotiates first hands its session over and wins. A second one that
572        // finishes in the same instant finds the channel full and is dropped.
573        let (session_tx, mut session_rx) = mpsc::channel::<Negotiated>(1);
574
575        let session = loop {
576            tokio::select! {
577                res = self.listener.as_ref().unwrap().accept(),
578                    if handlers.len() < MAX_PENDING_CONNECTIONS =>
579                {
580                    if let Err(error) = self.handle_accept_one(res, &mut handlers, &session_tx) {
581                        eprintln!("VFS server failed to accept a connection: {error}");
582                    }
583                }
584                result = handlers.join_next(), if !handlers.is_empty() => {
585                    report_handler_exit(result.unwrap());
586                }
587                Some(session) = session_rx.recv() => break session,
588            }
589        };
590
591        // Stop listening before the session runs: the socket has done its job,
592        // and any connection still mid-handshake has already lost the race, so
593        // waiting for it would only delay the session (and, at shutdown, hold
594        // the process open for the length of a negotiation timeout).
595        self.listener.take();
596        handlers.abort_all();
597        while let Some(result) = handlers.join_next().await {
598            if matches!(&result, Err(error) if error.is_cancelled()) {
599                continue;
600            }
601            report_handler_exit(result);
602        }
603        established();
604
605        let Negotiated { rpc, connection } = session;
606        match serve_connection(rpc, connection).await {
607            Ok(()) => Ok(()),
608            Err(error) if orderly_disconnect(&error) => Ok(()),
609            Err(error) => Err(error),
610        }
611    }
612
613    /// Spawns a handler that negotiates and offers the resulting session to
614    /// [`accept_one`](Self::accept_one), which serves it.
615    ///
616    /// The session is handed back rather than served in place so that the
617    /// accept loop can abandon every other in-flight attempt the moment one
618    /// succeeds.
619    #[cfg(unix)]
620    fn handle_accept_one(
621        &self,
622        res: io::Result<(UnixStream, SocketAddr)>,
623        handlers: &mut JoinSet<Result<()>>,
624        session_tx: &mpsc::Sender<Negotiated>,
625    ) -> Result<()> {
626        let (stream, _) = res?;
627        let stream = stream.into_std()?;
628        let connection = Arc::new(Connection {
629            server: self.shared.clone(),
630            mode: SessionMode::Native,
631            drain: Drain::new(),
632        });
633        let key = self.key;
634        let session_tx = session_tx.clone();
635        handlers.spawn(async move {
636            let negotiated = timeout(NEGOTIATE_TIMEOUT, rpc_builder(key).server_unix(stream)).await;
637            let rpc = match negotiated {
638                Ok(result) => result?.bind(),
639                Err(_elapsed) => {
640                    // Not worth reporting: a peer that connects and then says
641                    // nothing is exactly what the timeout is for.
642                    return Ok(());
643                }
644            };
645            // A full channel or a closed receiver both mean another connection
646            // got there first; drop this one.
647            let _ = session_tx.try_send(Negotiated { rpc, connection });
648            Ok(())
649        });
650        Ok(())
651    }
652
653    /// Serves one connected VFS session until it closes or fails.
654    pub async fn serve(mut self) -> Result<()> {
655        let connection = Arc::new(Connection {
656            server: self.shared,
657            mode: self.mode,
658            drain: Drain::new(),
659        });
660        let rpc = self
661            .rpc
662            .take()
663            .expect("server does not own a connected session");
664        match serve_connection(rpc, connection).await {
665            Ok(()) => Ok(()),
666            Err(error) if orderly_disconnect(&error) => Ok(()),
667            Err(error) => Err(error),
668        }
669    }
670}
671
672#[cfg(unix)]
673fn report_handler_exit(result: std::result::Result<Result<()>, JoinError>) {
674    match result {
675        Ok(Ok(())) => {}
676        Ok(Err(error)) if orderly_disconnect(&error) => {}
677        Ok(Err(error)) => eprintln!("VFS connection handler failed: {error}"),
678        Err(error) if error.is_panic() => std::panic::resume_unwind(error.into_panic()),
679        Err(error) => eprintln!("VFS connection handler task failed: {error}"),
680    }
681}
682
683fn orderly_disconnect(error: &Error) -> bool {
684    matches!(
685        error.kind(),
686        ErrorKind::UnexpectedEof
687            | ErrorKind::BrokenPipe
688            | ErrorKind::ConnectionReset
689            | ErrorKind::NotConnected
690    )
691}
692
693async fn serve_connection(
694    rpc: dolang_rpc::server::Server<VfsProtocol>,
695    connection: Arc<Connection>,
696) -> Result<()> {
697    rpc.serve(async move |mut context, Request { vfs, kind }| {
698        let response = if matches!(kind, RequestKind::Stop) {
699            connection.handle_stop(&mut context, vfs).await
700        } else if let Err(error) = connection.select(&context, vfs.clone()) {
701            Err(error)
702        } else {
703            let connection = connection.select(&context, vfs).unwrap();
704            match kind {
705                RequestKind::Spawn(request) => {
706                    connection.handle_spawn_rpc(&mut context, request).await
707                }
708                RequestKind::ChildWait { child } => {
709                    connection.handle_child_wait(&mut context, child).await
710                }
711                RequestKind::ChildTerminate { child } => {
712                    connection.handle_child_terminate(&context, child).await
713                }
714                RequestKind::ChildClose { child } => connection.handle_child_close(&context, child),
715                RequestKind::FileRead { file, offset, len } => {
716                    connection
717                        .handle_file_read(context, file, offset, len)
718                        .await;
719                    return;
720                }
721                RequestKind::StdioRecvRead { stdio, len } => {
722                    connection.handle_stdio_recv_read(context, stdio, len).await;
723                    return;
724                }
725                RequestKind::Stop => unreachable!(),
726                request => connection.handle(&mut context, request).await,
727            }
728        };
729        context.respond(response);
730    })
731    .await?;
732    Ok(())
733}
734
735impl Connection {
736    fn select(
737        &self,
738        context: &CallContext<VfsProtocol>,
739        vfs: Option<Cite<VfsMarker>>,
740    ) -> Result<Self> {
741        let Some(vfs) = vfs else {
742            return Ok(self.clone());
743        };
744        let selected = context
745            .acquire::<RetainedVfs>(vfs.clone())
746            .map_err(|_| Self::invalid_opaque("VFS"))?;
747        Ok(Self {
748            server: Arc::new(ServerState {
749                vfs: selected.vfs.clone(),
750                #[cfg(unix)]
751                shutdown_tx: self.server.shutdown_tx.clone(),
752            }),
753            mode: self.mode,
754            drain: self.drain.clone(),
755        })
756    }
757
758    async fn handle_stop(
759        &self,
760        context: &mut CallContext<VfsProtocol>,
761        vfs: Option<Cite<VfsMarker>>,
762    ) -> Result<ResponseKind> {
763        let Some(vfs) = vfs else {
764            // Stop accepting immediately. Existing sessions have their own
765            // connection tasks and continue draining independently.
766            #[cfg(unix)]
767            let _ = self.server.shutdown_tx.send(());
768            // Reject new stdio endpoints, then keep serving reads, writes and
769            // closes on the ones already handed out until they are all closed.
770            // The rpc serve loop polls request handlers on the same task as it
771            // reads frames, so awaiting here does not stall the connection.
772            self.drain.begin_stop();
773            self.drain.wait().await;
774            context.shutdown();
775            return Ok(ResponseKind::Stop);
776        };
777        let retained = match context.unregister::<RetainedVfs>(vfs) {
778            Ok(Some(retained)) => retained,
779            Ok(None) => {
780                return Err(Error::new(ErrorKind::ResourceBusy, "opaque VFS is in use"));
781            }
782            Err(_) => return Err(Self::invalid_opaque("VFS")),
783        };
784        match retained.vfs.stop().await {
785            Ok(()) => Ok(ResponseKind::Stop),
786            Err(error) => Err(error),
787        }
788    }
789
790    fn unsupported(operation: &str) -> Error {
791        Error::new(
792            ErrorKind::Unsupported,
793            format!("{operation} is not supported by a remote VFS session"),
794        )
795    }
796
797    fn invalid_opaque(kind: &str) -> Error {
798        Error::new(ErrorKind::InvalidInput, format!("invalid opaque {kind}"))
799    }
800
801    async fn handle(
802        &self,
803        context: &mut CallContext<VfsProtocol>,
804        kind: RequestKind,
805    ) -> Result<ResponseKind> {
806        match kind {
807            RequestKind::Query => self.handle_query().await,
808            RequestKind::UserName { uid } => Ok(ResponseKind::UserName(
809                self.server.vfs.user_name(uid).await?,
810            )),
811            RequestKind::UserId { name } => {
812                Ok(ResponseKind::UserId(self.server.vfs.user_id(&name).await?))
813            }
814            RequestKind::GroupName { gid } => Ok(ResponseKind::GroupName(
815                self.server.vfs.group_name(gid).await?,
816            )),
817            RequestKind::GroupId { name } => Ok(ResponseKind::GroupId(
818                self.server.vfs.group_id(&name).await?,
819            )),
820            RequestKind::SidName { sid } => {
821                Ok(ResponseKind::SidName(self.server.vfs.sid_name(&sid).await?))
822            }
823            RequestKind::AccountName { name } => Ok(ResponseKind::AccountName(
824                self.server.vfs.account_name(&name).await?,
825            )),
826            RequestKind::ResolvePrincipalId { input, want } => {
827                Ok(ResponseKind::ResolvePrincipalId(
828                    self.server.vfs.resolve_principal_id(input, want).await?,
829                ))
830            }
831            RequestKind::Which { program, path, cwd } => {
832                self.handle_which(program, path, cwd).await
833            }
834            RequestKind::WellKnownPath(request) => self.handle_well_known_path(request).await,
835            RequestKind::Stop
836            | RequestKind::Spawn(_)
837            | RequestKind::ChildWait { .. }
838            | RequestKind::ChildTerminate { .. }
839            | RequestKind::ChildClose { .. } => unreachable!(),
840            RequestKind::ClearCache => {
841                self.server.vfs.clear_cache().await?;
842                Ok(ResponseKind::ClearCache)
843            }
844            RequestKind::Pipe { buf_size } => Ok(ResponseKind::Pipe(
845                self.handle_pipe(context, buf_size).await?,
846            )),
847            RequestKind::Open(request) => self.handle_open(context, request).await,
848            RequestKind::FileRead { .. } => unreachable!(),
849            RequestKind::FileWrite { file, offset } => Ok(ResponseKind::FileWrite(
850                self.handle_file_write(context, file, offset).await?,
851            )),
852            RequestKind::FileAppend { file } => Ok(ResponseKind::FileAppend(
853                self.handle_file_append(context, file).await?,
854            )),
855            RequestKind::FileSize { file } => Ok(ResponseKind::FileSize(
856                self.handle_file_size(context, file).await?,
857            )),
858            RequestKind::FileSetSize { file, size } => {
859                self.handle_file_set_size(context, file, size).await?;
860                Ok(ResponseKind::FileSetSize)
861            }
862            RequestKind::FileSync { file, data } => {
863                self.handle_file_sync(context, file, data).await?;
864                Ok(ResponseKind::FileSync)
865            }
866            RequestKind::FileCopyData {
867                src,
868                dst,
869                src_offset,
870                target,
871                len,
872                mode,
873            } => Ok(ResponseKind::FileCopyData(
874                self.handle_file_copy_data(context, src, dst, src_offset, target, len, mode)
875                    .await?,
876            )),
877            RequestKind::FileLock { file, request } => {
878                self.handle_file_lock(context, file, request).await
879            }
880            RequestKind::FileUnlock { lock } => self.handle_file_unlock(context, lock).await,
881            RequestKind::StdioSendClose { stdio } => {
882                self.close_stdio_send(context, stdio)?;
883                Ok(ResponseKind::StdioSendClose)
884            }
885            RequestKind::StdioSendWrite { stdio } => Ok(ResponseKind::StdioSendWrite(
886                self.handle_stdio_send_write(context, stdio).await?,
887            )),
888            RequestKind::StdioSendClone { stdio } => Ok(ResponseKind::StdioSendClone(
889                self.handle_stdio_send_clone(context, stdio).await?,
890            )),
891            RequestKind::StdioRecvClose { stdio } => {
892                self.close_stdio_recv(context, stdio)?;
893                Ok(ResponseKind::StdioRecvClose)
894            }
895            RequestKind::StdioRecvRead { .. } => unreachable!(),
896            RequestKind::StdioRecvClone { stdio } => Ok(ResponseKind::StdioRecvClone(
897                self.handle_stdio_recv_clone(context, stdio).await?,
898            )),
899            RequestKind::FileMetadata { file } => Ok(ResponseKind::FileMetadata(
900                self.handle_file_metadata(context, file).await?,
901            )),
902            RequestKind::FileFsMetadata { file } => Ok(ResponseKind::FileFsMetadata(
903                self.handle_file_fs_metadata(context, file).await?,
904            )),
905            RequestKind::FileAcl {
906                file,
907                kind,
908                default,
909            } => Ok(ResponseKind::FileAcl(
910                self.handle_file_acl(context, file, kind, default).await?,
911            )),
912            RequestKind::FileSetAcl {
913                file,
914                kind,
915                acl,
916                default,
917            } => {
918                self.handle_file_set_acl(context, file, kind, acl, default)
919                    .await?;
920                Ok(ResponseKind::FileSetAcl)
921            }
922            RequestKind::FileSecDesc { file, mask } => Ok(ResponseKind::FileSecDesc(
923                self.handle_file_sec_desc(context, file, mask).await?,
924            )),
925            RequestKind::FileUpdateSecDesc { file, sec_desc } => {
926                self.handle_file_update_sec_desc(context, file, sec_desc)
927                    .await?;
928                Ok(ResponseKind::FileUpdateSecDesc)
929            }
930            RequestKind::FileXattrs { file, namespace } => Ok(ResponseKind::FileXattrs(
931                self.handle_file_xattrs(context, file, namespace).await?,
932            )),
933            RequestKind::FileXattr {
934                file,
935                name,
936                namespace,
937            } => Ok(ResponseKind::FileXattr(
938                self.handle_file_xattr(context, file, name, namespace)
939                    .await?,
940            )),
941            RequestKind::FileStreams { file } => Ok(ResponseKind::FileStreams(
942                self.handle_file_streams(context, file).await?,
943            )),
944            RequestKind::FileSetXattr {
945                file,
946                name,
947                namespace,
948                value,
949            } => {
950                self.handle_file_set_xattr(context, file, name, namespace, value)
951                    .await?;
952                Ok(ResponseKind::FileSetXattr)
953            }
954            RequestKind::FileRemoveXattr {
955                file,
956                name,
957                namespace,
958            } => {
959                self.handle_file_remove_xattr(context, file, name, namespace)
960                    .await?;
961                Ok(ResponseKind::FileRemoveXattr)
962            }
963            RequestKind::FileClose { file } => self.handle_file_close(context, file).await,
964            RequestKind::UnixVfs(request) => self.handle_unix_vfs(context, request).await,
965            RequestKind::WindowsAdmin(request) => self.handle_windows_admin(context, request).await,
966            RequestKind::ReadDir { path } => self.handle_read_dir(context, path).await,
967            RequestKind::ReadDirNext { read_dir } => {
968                self.handle_read_dir_next(context, read_dir).await
969            }
970            RequestKind::ReadDirClose { read_dir } => self.handle_read_dir_close(context, read_dir),
971            RequestKind::ProcessEnumerate => self.handle_process_enumerate(context).await,
972            RequestKind::ProcessEnumerateNext { processes } => {
973                self.handle_process_enumerate_next(context, processes).await
974            }
975            RequestKind::ProcessEnumerateClose { processes } => {
976                self.handle_process_enumerate_close(context, processes)
977            }
978            RequestKind::ProcessDescribe { pid } => self.handle_process_describe(pid).await,
979            RequestKind::ProcessOpen { pid, start } => {
980                self.handle_process_open(context, pid, start).await
981            }
982            RequestKind::ProcessInfo { process } => {
983                self.handle_process_info(context, process).await
984            }
985            RequestKind::ProcessSignal { process, signal } => {
986                self.handle_process_signal(context, process, signal).await
987            }
988            RequestKind::ProcessTerminate { process } => {
989                self.handle_process_terminate(context, process).await
990            }
991            RequestKind::ProcessKill { process } => {
992                self.handle_process_kill(context, process).await
993            }
994            RequestKind::ProcessWait { process } => {
995                self.handle_process_wait(context, process).await
996            }
997            RequestKind::ProcessClose { process } => self.handle_process_close(context, process),
998            RequestKind::Remove(request) => self.handle_remove(request).await,
999            RequestKind::Metadata(request) => self.handle_metadata(request).await,
1000            RequestKind::FsMetadata(request) => self.handle_fs_metadata(request).await,
1001            RequestKind::Acl(request) => self.handle_acl(request).await,
1002            RequestKind::SetAcl(request) => self.handle_set_acl(request).await,
1003            RequestKind::SecDesc(request) => self.handle_sec_desc(request).await,
1004            RequestKind::UpdateSecDesc(request) => self.handle_update_sec_desc(request).await,
1005            RequestKind::CreateDir(request) => self.handle_create_dir(request).await,
1006            RequestKind::RemoveDir(request) => self.handle_remove_dir(request).await,
1007            RequestKind::Copy(request) => self.handle_copy(request).await,
1008            RequestKind::Rename(request) => self.handle_rename(request).await,
1009            RequestKind::Move(request) => self.handle_move(request).await,
1010            RequestKind::Symlink(request) => self.handle_symlink(request).await,
1011            RequestKind::HardLink(request) => self.handle_hard_link(request).await,
1012            RequestKind::SymlinkMetadata(request) => self.handle_symlink_metadata(request).await,
1013            RequestKind::UpdateMetadata(request) => self.handle_update_metadata(request).await,
1014            RequestKind::Canonicalize(request) => self.handle_canonicalize(request).await,
1015            RequestKind::ReadLink(request) => self.handle_read_link(request).await,
1016            RequestKind::Access(request) => self.handle_access(request).await,
1017            RequestKind::Glob(request) => self.handle_glob(request).await,
1018            RequestKind::Xattrs(request) => self.handle_xattrs(request).await,
1019            RequestKind::Xattr(request) => self.handle_xattr(request).await,
1020            RequestKind::SetXattr(request) => self.handle_set_xattr(request).await,
1021            RequestKind::RemoveXattr(request) => self.handle_remove_xattr(request).await,
1022            RequestKind::Streams(request) => self.handle_streams(request).await,
1023            RequestKind::Extension(request) => self.handle_extension(context, request).await,
1024        }
1025    }
1026
1027    async fn handle_extension(
1028        &self,
1029        context: &mut CallContext<VfsProtocol>,
1030        request: ExtensionRequest,
1031    ) -> Result<ResponseKind> {
1032        let ExtensionRequest {
1033            name,
1034            version,
1035            payload,
1036        } = request;
1037        let Some(ext) = extension::lookup(&name, version).filter(|extension| extension.available())
1038        else {
1039            return Err(Self::unsupported(&format!(
1040                "VFS extension {name} v{version}"
1041            )));
1042        };
1043        let mut ctx = ExtContext::remote(context, self.mode == SessionMode::Native);
1044        let payload = ext.dispatch(&mut ctx, payload).await;
1045        Ok(ResponseKind::Extension(ExtensionResponse {
1046            name,
1047            version,
1048            payload,
1049        }))
1050    }
1051
1052    async fn handle_which(
1053        &self,
1054        program: path::PathBuf,
1055        path: Option<String>,
1056        cwd: Option<path::PathBuf>,
1057    ) -> Result<ResponseKind> {
1058        let resolved = self
1059            .server
1060            .vfs
1061            .which(
1062                Into::into(&program),
1063                path.as_deref(),
1064                cwd.as_ref().map(Into::into),
1065            )
1066            .await?;
1067        Ok(ResponseKind::Which(resolved))
1068    }
1069
1070    async fn handle_well_known_path(&self, req: WellKnownPathRequest) -> Result<ResponseKind> {
1071        let path = self
1072            .server
1073            .vfs
1074            .well_known_path(req.key, req.app.as_deref(), &req.env)
1075            .await?;
1076        Ok(ResponseKind::WellKnownPath(path))
1077    }
1078
1079    async fn handle_spawn_rpc(
1080        &self,
1081        context: &mut CallContext<VfsProtocol>,
1082        req: SpawnRequest,
1083    ) -> Result<ResponseKind> {
1084        let mut cmd = self.server.vfs.command(Into::into(&req.program));
1085        for arg in &req.args {
1086            cmd.arg(arg);
1087        }
1088
1089        if let Some(cwd) = &req.cwd {
1090            cmd.current_dir(Into::into(cwd));
1091        }
1092
1093        for (k, v) in &req.env {
1094            match v {
1095                Some(val) => {
1096                    cmd.env(k, val);
1097                }
1098                None => {
1099                    cmd.env_remove(k);
1100                }
1101            };
1102        }
1103        cmd.process_control(req.process_control);
1104        cmd.termination_policy(req.termination_policy);
1105
1106        let files = self
1107            .configure_spawn_stdio(context, &mut cmd, req.stdin, req.stdout, req.stderr)
1108            .await?;
1109
1110        let child = cmd.spawn().await?;
1111        Ok(ResponseKind::Spawn(context.register(RetainedChild {
1112            child: Mutex::new(child),
1113            files,
1114        })))
1115    }
1116
1117    fn spawn_stdio_recv(
1118        &self,
1119        context: &CallContext<VfsProtocol>,
1120        target: StdioRecvTarget,
1121    ) -> Result<Option<SpawnStdio<PipeRecv>>> {
1122        match target {
1123            StdioRecvTarget::Null => Ok(None),
1124            StdioRecvTarget::Native(handle) => {
1125                if self.mode == SessionMode::Remote {
1126                    return Err(Self::unsupported("native process stdio"));
1127                }
1128                Ok(Some(SpawnStdio::Native(std::fs::File::from(
1129                    handle.into_inner(),
1130                ))))
1131            }
1132            StdioRecvTarget::Opaque(stdio) => {
1133                // Consuming the receiver releases its drain slot: once a child
1134                // owns it, the peer is no longer relaying through it.
1135                let stdio = context
1136                    .unregister::<RetainedStdioRecv>(stdio)
1137                    .map_err(|_| Self::invalid_opaque("stdio receive"))?;
1138                let Some(stdio) = stdio else {
1139                    return Err(Error::new(
1140                        ErrorKind::ResourceBusy,
1141                        "opaque stdio receive is in use",
1142                    ));
1143                };
1144                Ok(Some(SpawnStdio::Pipe(stdio.stdio.into_inner())))
1145            }
1146            StdioRecvTarget::File { file, pos } => self.spawn_stdio_file(context, file, pos),
1147        }
1148    }
1149
1150    /// Positions a retained file at `pos`, the peer's cursor, for a child.
1151    fn spawn_stdio_file<P>(
1152        &self,
1153        context: &CallContext<VfsProtocol>,
1154        file: Cite<FileMarker>,
1155        pos: u64,
1156    ) -> Result<Option<SpawnStdio<P>>> {
1157        let file = self.retained_file(context, file)?.0.clone();
1158        file.inner.position().store(pos, Ordering::Relaxed);
1159        Ok(Some(SpawnStdio::File(file)))
1160    }
1161
1162    fn spawn_stdio_send(
1163        &self,
1164        context: &CallContext<VfsProtocol>,
1165        target: StdioSendTarget,
1166    ) -> Result<Option<SpawnStdio<PipeSend>>> {
1167        match target {
1168            StdioSendTarget::Null | StdioSendTarget::Stdout => Ok(None),
1169            StdioSendTarget::Native(handle) => {
1170                if self.mode == SessionMode::Remote {
1171                    return Err(Self::unsupported("native process stdio"));
1172                }
1173                Ok(Some(SpawnStdio::Native(std::fs::File::from(
1174                    handle.into_inner(),
1175                ))))
1176            }
1177            StdioSendTarget::Opaque(stdio) => {
1178                let stdio = context
1179                    .unregister::<RetainedStdioSend>(stdio)
1180                    .map_err(|_| Self::invalid_opaque("stdio send"))?;
1181                let Some(stdio) = stdio else {
1182                    return Err(Error::new(
1183                        ErrorKind::ResourceBusy,
1184                        "opaque stdio send is in use",
1185                    ));
1186                };
1187                Ok(Some(SpawnStdio::Pipe(stdio.stdio.into_inner())))
1188            }
1189            StdioSendTarget::File { file, pos } => self.spawn_stdio_file(context, file, pos),
1190        }
1191    }
1192
1193    /// Configures the child's standard streams, returning the files among
1194    /// them in stdin, stdout, stderr order.
1195    async fn configure_spawn_stdio(
1196        &self,
1197        context: &CallContext<VfsProtocol>,
1198        command: &mut Command<'_>,
1199        stdin: StdioRecvTarget,
1200        stdout: StdioSendTarget,
1201        stderr: StdioSendTarget,
1202    ) -> Result<Vec<File>> {
1203        let stdin = self.spawn_stdio_recv(context, stdin);
1204        let stdout = self.spawn_stdio_send(context, stdout);
1205        let mut stderr_to_stdout = matches!(stderr, StdioSendTarget::Stdout);
1206        let stderr = self.spawn_stdio_send(context, stderr);
1207        let (stdin, stdout, mut stderr) = (stdin?, stdout?, stderr?);
1208        // A native stdout is used as is, so merging gives stderr a duplicate of
1209        // it, rather than leaving the command to use the server's own stdout.
1210        if stderr_to_stdout && let Some(SpawnStdio::Native(file)) = &stdout {
1211            stderr = Some(SpawnStdio::Native(file.try_clone()?));
1212            stderr_to_stdout = false;
1213        }
1214
1215        let mut files = Vec::new();
1216        match stdin {
1217            Some(SpawnStdio::Pipe(pipe)) => command.stdin(pipe)?,
1218            Some(SpawnStdio::Native(file)) => command.stdin_native(Stdio::from(file))?,
1219            Some(SpawnStdio::File(file)) => {
1220                files.push(file.clone());
1221                command.stdin(file)?
1222            }
1223            None => command.stdin_null(),
1224        };
1225        match stdout {
1226            Some(SpawnStdio::Pipe(pipe)) => command.stdout(pipe)?,
1227            Some(SpawnStdio::Native(file)) => command.stdout_native(Stdio::from(file))?,
1228            Some(SpawnStdio::File(file)) => {
1229                files.push(file.clone());
1230                command.stdout(file)?
1231            }
1232            None => command.stdout_null(),
1233        };
1234        if stderr_to_stdout {
1235            command.stderr_to_stdout()?;
1236        } else {
1237            match stderr {
1238                Some(SpawnStdio::Pipe(pipe)) => command.stderr(pipe)?,
1239                Some(SpawnStdio::Native(file)) => command.stderr_native(Stdio::from(file))?,
1240                Some(SpawnStdio::File(file)) => {
1241                    files.push(file.clone());
1242                    command.stderr(file)?
1243                }
1244                None => command.stderr_null(),
1245            };
1246        }
1247        Ok(files)
1248    }
1249
1250    fn take_child(
1251        &self,
1252        context: &CallContext<VfsProtocol>,
1253        child: Cite<ChildMarker>,
1254    ) -> Result<RetainedChild> {
1255        context
1256            .unregister::<RetainedChild>(child)
1257            .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid opaque child"))?
1258            .ok_or_else(|| Error::new(ErrorKind::ResourceBusy, "opaque child is in use"))
1259    }
1260
1261    async fn handle_child_wait(
1262        &self,
1263        context: &mut CallContext<VfsProtocol>,
1264        child: Cite<ChildMarker>,
1265    ) -> Result<ResponseKind> {
1266        let RetainedChild { child, files } = self.take_child(context, child)?;
1267        let mut child = child.into_inner();
1268        let status = match context.cancel_guard(async |_| child.wait().await).await {
1269            Ok(result) => result?,
1270            Err(_) => child
1271                .terminate()
1272                .await?
1273                .ok_or_else(|| Error::other("process was orphaned during cancelled wait"))?,
1274        };
1275        Ok(ResponseKind::ChildWait(child_exit(status, &files)))
1276    }
1277
1278    async fn handle_child_terminate(
1279        &self,
1280        context: &CallContext<VfsProtocol>,
1281        child: Cite<ChildMarker>,
1282    ) -> Result<ResponseKind> {
1283        let RetainedChild { child, files } = self.take_child(context, child)?;
1284        let status = child.into_inner().terminate().await?;
1285        Ok(ResponseKind::ChildTerminate(
1286            status.map(|status| child_exit(status, &files)),
1287        ))
1288    }
1289
1290    fn handle_child_close(
1291        &self,
1292        context: &CallContext<VfsProtocol>,
1293        child: Cite<ChildMarker>,
1294    ) -> Result<ResponseKind> {
1295        context
1296            .unregister::<RetainedChild>(child)
1297            .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid opaque child"))?;
1298        Ok(ResponseKind::ChildClose)
1299    }
1300
1301    async fn handle_query(&self) -> Result<ResponseKind> {
1302        let vfs = &self.server.vfs;
1303        Ok(ResponseKind::Query(QueryResponse {
1304            session: vfs.session(),
1305            pid: vfs.pid(),
1306            env: vfs.env().collect(),
1307            cwd: vfs.cwd().into(),
1308            current_exe: vfs.current_exe().into(),
1309            target: vfs.target().clone(),
1310            security: vfs.security().clone(),
1311            extensions: vfs.extensions().clone(),
1312        }))
1313    }
1314
1315    /// Gates endpoint creation, reserving a drain slot only for receivers.
1316    ///
1317    /// Only endpoint creation is gated this way. `Spawn` and `Open` stay
1318    /// available while stopping: they create no stdio endpoint of their own,
1319    /// and refusing a spawn could break the very in-flight pipeline stage the
1320    /// drain exists to protect.
1321    fn reserve_stdio(&self, receiver: bool) -> Result<()> {
1322        if self.drain.try_acquire(usize::from(receiver)) {
1323            Ok(())
1324        } else {
1325            Err(Error::new(
1326                ErrorKind::NotConnected,
1327                "VFS session is stopping",
1328            ))
1329        }
1330    }
1331
1332    async fn handle_pipe(
1333        &self,
1334        context: &CallContext<VfsProtocol>,
1335        buf_size: Option<usize>,
1336    ) -> Result<PipeResponse> {
1337        let (send, recv) = self.server.vfs.pipe(buf_size).await?;
1338        self.reserve_stdio(true)?;
1339        let recv_slot = DrainSlot(self.drain.clone());
1340        Ok(PipeResponse {
1341            send: context.register(RetainedStdioSend {
1342                stdio: Mutex::new(send),
1343            }),
1344            recv: context.register(RetainedStdioRecv {
1345                stdio: Mutex::new(recv),
1346                _slot: recv_slot,
1347            }),
1348        })
1349    }
1350
1351    fn retained_stdio_send(
1352        &self,
1353        context: &CallContext<VfsProtocol>,
1354        stdio: Cite<StdioSendMarker>,
1355    ) -> Result<OpaqueGuard<RetainedStdioSend>> {
1356        context
1357            .acquire::<RetainedStdioSend>(stdio)
1358            .map_err(|_| Self::invalid_opaque("stdio send"))
1359    }
1360
1361    fn retained_stdio_recv(
1362        &self,
1363        context: &CallContext<VfsProtocol>,
1364        stdio: Cite<StdioRecvMarker>,
1365    ) -> Result<OpaqueGuard<RetainedStdioRecv>> {
1366        context
1367            .acquire::<RetainedStdioRecv>(stdio)
1368            .map_err(|_| Self::invalid_opaque("stdio receive"))
1369    }
1370
1371    /// Empties an endpoint's contents, without waiting for the peer to stop
1372    /// naming it.
1373    ///
1374    /// A close that races the endpoint's own read or write leaves the contents
1375    /// alive until that operation finishes, and the registration alive until
1376    /// the peer drops its last reference. Neither is worth reporting: a peer
1377    /// closing while its own I/O is in flight has no flush guarantee to lose,
1378    /// and a receiver's drain slot is returned when it actually dies rather
1379    /// than when this request happens to be served.
1380    fn close_stdio_send(
1381        &self,
1382        context: &CallContext<VfsProtocol>,
1383        stdio: Cite<StdioSendMarker>,
1384    ) -> Result<()> {
1385        context
1386            .unregister::<RetainedStdioSend>(stdio)
1387            .map_err(|_| Self::invalid_opaque("stdio send"))?;
1388        Ok(())
1389    }
1390
1391    fn close_stdio_recv(
1392        &self,
1393        context: &CallContext<VfsProtocol>,
1394        stdio: Cite<StdioRecvMarker>,
1395    ) -> Result<()> {
1396        context
1397            .unregister::<RetainedStdioRecv>(stdio)
1398            .map_err(|_| Self::invalid_opaque("stdio receive"))?;
1399        Ok(())
1400    }
1401
1402    async fn handle_stdio_send_write(
1403        &self,
1404        context: &mut CallContext<VfsProtocol>,
1405        stdio: Cite<StdioSendMarker>,
1406    ) -> Result<usize> {
1407        let stdio = self.retained_stdio_send(context, stdio)?;
1408        let trailer = context.trailer().ok_or_else(|| {
1409            Error::new(
1410                ErrorKind::InvalidInput,
1411                "stdio write request is missing its data trailer",
1412            )
1413        })?;
1414        let mut trailer = BufReader::with_capacity(STREAM_CHUNK_SIZE, trailer);
1415        let len = copy_buf(&mut trailer, &mut *stdio.stdio.lock().await).await?;
1416        usize::try_from(len).map_err(|_| {
1417            Error::new(
1418                ErrorKind::InvalidData,
1419                "stdio trailer length does not fit in usize",
1420            )
1421        })
1422    }
1423
1424    async fn handle_stdio_send_clone(
1425        &self,
1426        context: &CallContext<VfsProtocol>,
1427        stdio: Cite<StdioSendMarker>,
1428    ) -> Result<Gift<StdioSendMarker>> {
1429        let stdio = self.retained_stdio_send(context, stdio)?;
1430        let clone = stdio.stdio.lock().await.try_clone().await?;
1431        self.reserve_stdio(false)?;
1432        Ok(context.register(RetainedStdioSend {
1433            stdio: Mutex::new(clone),
1434        }))
1435    }
1436
1437    async fn handle_stdio_recv_read(
1438        &self,
1439        context: CallContext<VfsProtocol>,
1440        stdio: Cite<StdioRecvMarker>,
1441        len: usize,
1442    ) {
1443        let stdio = match self.retained_stdio_recv(&context, stdio) {
1444            Ok(stdio) => stdio,
1445            Err(error) => {
1446                context.respond(Err(error));
1447                return;
1448            }
1449        };
1450        // Unlike `handle_file_read`, this streams: the source is a pipe of
1451        // unknown length, so there is nothing to hold and the answer cannot be
1452        // known before responding. A failure part way through therefore has to
1453        // report itself by abandoning the trailer, which for a byte stream is
1454        // the right shape anyway — it is a teardown, not an operation that
1455        // failed with a particular errno the peer could act on.
1456        let mut send = context.respond_with_trailer(Ok(ResponseKind::StdioRecvRead));
1457        let copied = {
1458            let mut guard = stdio.stdio.lock().await;
1459            let mut source = BufReader::with_capacity(
1460                len.clamp(1, STREAM_CHUNK_SIZE),
1461                (&mut *guard).take(len as u64),
1462            );
1463            copy_buf(&mut source, &mut send).await.is_ok()
1464        };
1465        // Released before the terminal fragment: the peer may hand the endpoint
1466        // to a child or close it as soon as the trailer ends, and both consume
1467        // the endpoint.
1468        drop(stdio);
1469        if copied {
1470            send.finish();
1471        }
1472    }
1473
1474    async fn handle_stdio_recv_clone(
1475        &self,
1476        context: &CallContext<VfsProtocol>,
1477        stdio: Cite<StdioRecvMarker>,
1478    ) -> Result<Gift<StdioRecvMarker>> {
1479        let stdio = self.retained_stdio_recv(context, stdio)?;
1480        let clone = stdio.stdio.lock().await.try_clone().await?;
1481        self.reserve_stdio(true)?;
1482        let slot = DrainSlot(self.drain.clone());
1483        Ok(context.register(RetainedStdioRecv {
1484            stdio: Mutex::new(clone),
1485            _slot: slot,
1486        }))
1487    }
1488
1489    async fn handle_open(
1490        &self,
1491        context: &CallContext<VfsProtocol>,
1492        req: OpenRequest,
1493    ) -> Result<ResponseKind> {
1494        let mut opts = self.server.vfs.open_options();
1495        opts.read(req.flags.contains(OpenFlags::READ))
1496            .write(req.flags.contains(OpenFlags::WRITE))
1497            .append(req.flags.contains(OpenFlags::APPEND))
1498            .create(req.flags.contains(OpenFlags::CREATE))
1499            .create_new(req.flags.contains(OpenFlags::CREATE_NEW))
1500            .truncate(req.flags.contains(OpenFlags::TRUNCATE))
1501            .no_follow(req.flags.contains(OpenFlags::NO_FOLLOW));
1502
1503        let file = opts.open(Into::into(&req.path)).await?;
1504        let handle = if self.mode == SessionMode::Remote {
1505            OpenHandle::Opaque(context.register(RetainedFile(file)))
1506        } else {
1507            let handle: DefaultHandle = file.try_into_std().await.unwrap().into();
1508            OpenHandle::Native(OsHandle::new(handle))
1509        };
1510        Ok(ResponseKind::Open(handle))
1511    }
1512
1513    fn retained_file(
1514        &self,
1515        context: &CallContext<VfsProtocol>,
1516        file: Cite<FileMarker>,
1517    ) -> Result<OpaqueGuard<RetainedFile>> {
1518        context
1519            .acquire::<RetainedFile>(file)
1520            .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid opaque file"))
1521    }
1522
1523    async fn handle_file_read(
1524        &self,
1525        context: CallContext<VfsProtocol>,
1526        file: Cite<FileMarker>,
1527        offset: u64,
1528        len: usize,
1529    ) {
1530        let file = match self.retained_file(&context, file) {
1531            Ok(file) => file,
1532            Err(error) => {
1533                context.respond(Err(error));
1534                return;
1535            }
1536        };
1537        // Read before responding. Once the response header is out the only way
1538        // left to report a failure is to abandon the trailer, which reaches the
1539        // peer as a bare `BrokenPipe` abort with the real error discarded — so
1540        // the read has to have already succeeded or failed by then. Clamping is
1541        // what makes holding the whole answer affordable, and it also stops the
1542        // peer from choosing the size of this allocation.
1543        let data = read_file_range(&file.0, offset, len.min(MAX_FILE_READ)).await;
1544        // Release the file before anything at all goes back. The peer is
1545        // entitled to close the file the moment it sees this read conclude, and
1546        // a reference still held here would make its `FileClose` fail as in
1547        // use. Buffering the answer first is what lets the guard be dropped
1548        // this early — earlier than when the read was streamed, where it had to
1549        // live until the last fragment.
1550        drop(file);
1551        let data = match data {
1552            Ok(data) => data,
1553            Err(error) => {
1554                context.respond(Err(error));
1555                return;
1556            }
1557        };
1558        let mut send = context.respond_with_trailer(Ok(ResponseKind::FileRead));
1559        if send.write_all(&data).await.is_ok() {
1560            send.finish();
1561        }
1562    }
1563
1564    async fn handle_file_write(
1565        &self,
1566        context: &mut CallContext<VfsProtocol>,
1567        file: Cite<FileMarker>,
1568        offset: u64,
1569    ) -> Result<usize> {
1570        let file = self.retained_file(context, file)?;
1571        let mut trailer = Self::write_trailer(context)?;
1572        let mut written = 0usize;
1573        while let Some(chunk) = next_trailer_chunk(&mut trailer).await? {
1574            let mut chunk = chunk.freeze();
1575            while !chunk.is_empty() {
1576                let n = file
1577                    .0
1578                    .write_at(chunk.clone(), offset + written as u64)
1579                    .await?;
1580                if n == 0 {
1581                    return Err(Error::new(
1582                        ErrorKind::WriteZero,
1583                        "file write made no progress",
1584                    ));
1585                }
1586                chunk.advance(n);
1587                written += n;
1588            }
1589        }
1590        Ok(written)
1591    }
1592
1593    async fn handle_file_append(
1594        &self,
1595        context: &mut CallContext<VfsProtocol>,
1596        file: Cite<FileMarker>,
1597    ) -> Result<(usize, u64)> {
1598        let file = self.retained_file(context, file)?;
1599        let mut trailer = Self::write_trailer(context)?;
1600        // The offset of an append is the description's business, not ours. The
1601        // peer cannot know where the data landed either, so report the
1602        // resulting position along with the count.
1603        let mut written = 0usize;
1604        let mut end = file.0.metadata().await?.len;
1605        while let Some(chunk) = next_trailer_chunk(&mut trailer).await? {
1606            let mut chunk = chunk.freeze();
1607            while !chunk.is_empty() {
1608                let (n, position) = file.0.append(chunk.clone()).await?;
1609                if n == 0 {
1610                    return Err(Error::new(
1611                        ErrorKind::WriteZero,
1612                        "file append made no progress",
1613                    ));
1614                }
1615                chunk.advance(n);
1616                written += n;
1617                end = position;
1618            }
1619        }
1620        Ok((written, end))
1621    }
1622
1623    fn write_trailer(
1624        context: &mut CallContext<VfsProtocol>,
1625    ) -> Result<dolang_rpc::trailer::TrailerRecv> {
1626        context.trailer().ok_or_else(|| {
1627            Error::new(
1628                ErrorKind::InvalidInput,
1629                "file write request is missing its data trailer",
1630            )
1631        })
1632    }
1633
1634    async fn handle_file_size(
1635        &self,
1636        context: &CallContext<VfsProtocol>,
1637        file: Cite<FileMarker>,
1638    ) -> Result<u64> {
1639        let file = self.retained_file(context, file)?;
1640        Ok(file.0.metadata().await?.len)
1641    }
1642
1643    async fn handle_file_set_size(
1644        &self,
1645        context: &CallContext<VfsProtocol>,
1646        file: Cite<FileMarker>,
1647        size: u64,
1648    ) -> Result<()> {
1649        let file = self.retained_file(context, file)?;
1650        file.0.set_size(size).await
1651    }
1652
1653    /// Copies bytes between two of this session's files.
1654    ///
1655    /// The only handler that holds two citations at once. Both are acquired
1656    /// for the duration, so a concurrent close of either endpoint reports the
1657    /// file as busy rather than pulling it out from under the bounded copy.
1658    /// Source
1659    /// first, always, so which citation a failure names does not depend on
1660    /// timing.
1661    ///
1662    /// Overlap and identity are not re-checked here: `File::copy_data` does
1663    /// that, and routing through it is what makes a remote copy behave
1664    /// identically to a local one. Cancellation needs no guard either — the
1665    /// serve loop drops this future, and the copy loop's every transfer is a
1666    /// drop point.
1667    #[allow(clippy::too_many_arguments)]
1668    async fn handle_file_copy_data(
1669        &self,
1670        context: &CallContext<VfsProtocol>,
1671        src: Cite<FileMarker>,
1672        dst: Cite<FileMarker>,
1673        src_offset: u64,
1674        target: CopyDest,
1675        len: Option<u64>,
1676        mode: CopyMode,
1677    ) -> Result<crate::file::CopyDataResult> {
1678        let src = self.retained_file(context, src)?;
1679        let dst = self.retained_file(context, dst)?;
1680        src.0.copy_data(&dst.0, src_offset, target, len, mode).await
1681    }
1682
1683    async fn handle_file_sync(
1684        &self,
1685        context: &CallContext<VfsProtocol>,
1686        file: Cite<FileMarker>,
1687        data: bool,
1688    ) -> Result<()> {
1689        let file = self.retained_file(context, file)?;
1690        file.0.sync(data).await
1691    }
1692
1693    async fn handle_file_metadata(
1694        &self,
1695        context: &CallContext<VfsProtocol>,
1696        file: Cite<FileMarker>,
1697    ) -> Result<Metadata> {
1698        let file = self.retained_file(context, file)?;
1699        file.0.metadata().await
1700    }
1701
1702    async fn handle_file_fs_metadata(
1703        &self,
1704        context: &CallContext<VfsProtocol>,
1705        file: Cite<FileMarker>,
1706    ) -> Result<FsMetadata> {
1707        let file = self.retained_file(context, file)?;
1708        file.0.fs_metadata().await
1709    }
1710
1711    async fn handle_file_sec_desc(
1712        &self,
1713        context: &CallContext<VfsProtocol>,
1714        file: Cite<FileMarker>,
1715        mask: dolang_winterop::security::SecInfo,
1716    ) -> Result<SecDesc> {
1717        let file = self.retained_file(context, file)?;
1718        file.0.sec_desc(mask).await
1719    }
1720
1721    async fn handle_file_acl(
1722        &self,
1723        context: &CallContext<VfsProtocol>,
1724        file: Cite<FileMarker>,
1725        kind: AclKind,
1726        default: bool,
1727    ) -> Result<Option<Acl>> {
1728        let file = self.retained_file(context, file)?;
1729        file.0.acl(kind, default).await
1730    }
1731
1732    async fn handle_file_set_acl(
1733        &self,
1734        context: &CallContext<VfsProtocol>,
1735        file: Cite<FileMarker>,
1736        kind: AclKind,
1737        acl: Option<Acl>,
1738        default: bool,
1739    ) -> Result<()> {
1740        let file = self.retained_file(context, file)?;
1741        file.0.set_acl(kind, acl.as_ref(), default).await
1742    }
1743
1744    async fn handle_file_update_sec_desc(
1745        &self,
1746        context: &CallContext<VfsProtocol>,
1747        file: Cite<FileMarker>,
1748        sec_desc: SecDesc,
1749    ) -> Result<()> {
1750        let file = self.retained_file(context, file)?;
1751        file.0.update_sec_desc(&sec_desc).await
1752    }
1753
1754    async fn handle_file_xattrs(
1755        &self,
1756        context: &CallContext<VfsProtocol>,
1757        file: Cite<FileMarker>,
1758        namespace: XattrNamespaceRequest,
1759    ) -> Result<Vec<XattrEntry>> {
1760        let file = self.retained_file(context, file)?;
1761        file.0.xattrs(namespace.as_borrowed()).await
1762    }
1763
1764    async fn handle_file_xattr(
1765        &self,
1766        context: &CallContext<VfsProtocol>,
1767        file: Cite<FileMarker>,
1768        name: String,
1769        namespace: Option<String>,
1770    ) -> Result<Vec<u8>> {
1771        let file = self.retained_file(context, file)?;
1772        file.0.xattr(&name, namespace.as_deref()).await
1773    }
1774
1775    async fn handle_file_streams(
1776        &self,
1777        context: &CallContext<VfsProtocol>,
1778        file: Cite<FileMarker>,
1779    ) -> Result<Vec<StreamEntry>> {
1780        let file = self.retained_file(context, file)?;
1781        file.0.streams().await
1782    }
1783
1784    async fn handle_file_set_xattr(
1785        &self,
1786        context: &CallContext<VfsProtocol>,
1787        file: Cite<FileMarker>,
1788        name: String,
1789        namespace: Option<String>,
1790        value: Vec<u8>,
1791    ) -> Result<()> {
1792        let file = self.retained_file(context, file)?;
1793        file.0.set_xattr(&name, namespace.as_deref(), &value).await
1794    }
1795
1796    async fn handle_file_remove_xattr(
1797        &self,
1798        context: &CallContext<VfsProtocol>,
1799        file: Cite<FileMarker>,
1800        name: String,
1801        namespace: Option<String>,
1802    ) -> Result<()> {
1803        let file = self.retained_file(context, file)?;
1804        file.0.remove_xattr(&name, namespace.as_deref()).await
1805    }
1806
1807    async fn handle_file_lock(
1808        &self,
1809        context: &mut CallContext<VfsProtocol>,
1810        file: Cite<FileMarker>,
1811        request: FileLockRequest,
1812    ) -> Result<ResponseKind> {
1813        let retained = self.retained_file(context, file)?;
1814        let acquired = context
1815            .cancel_guard(async |_context| {
1816                retained
1817                    .0
1818                    .lock(request.range, request.mode, request.behavior)
1819                    .await
1820            })
1821            .await;
1822        let acquired = acquired.map_err(|_| {
1823            Error::new(
1824                ErrorKind::Interrupted,
1825                "file lock acquisition was cancelled",
1826            )
1827        })??;
1828        drop(retained);
1829        // The lock gets an opaque handle of its own rather than an id in a
1830        // table hanging off the file. Nothing needs it to be reachable from the
1831        // file: closing a file releases every lock held on it in band, and a
1832        // lock dropped without an explicit release still releases itself.
1833        Ok(ResponseKind::FileLock(
1834            acquired.map(|lock| context.register(RetainedFileLock(lock))),
1835        ))
1836    }
1837
1838    async fn handle_file_unlock(
1839        &self,
1840        context: &CallContext<VfsProtocol>,
1841        lock: Cite<FileLockMarker>,
1842    ) -> Result<ResponseKind> {
1843        // Releasing consumes the lock, so a handle that is unknown or already
1844        // released is a no-op rather than an error, as it was when the peer
1845        // named locks by id.
1846        let Ok(Some(mut lock)) = context.unregister::<RetainedFileLock>(lock) else {
1847            return Ok(ResponseKind::FileUnlock);
1848        };
1849        lock.0.release().await?;
1850        Ok(ResponseKind::FileUnlock)
1851    }
1852
1853    async fn handle_file_close(
1854        &self,
1855        context: &CallContext<VfsProtocol>,
1856        file: Cite<FileMarker>,
1857    ) -> Result<ResponseKind> {
1858        // A clone closes the file for every operation still holding the
1859        // registration, so the registration can simply go.
1860        let retained = self.retained_file(context, file.clone())?.0.clone();
1861        let _ = context.unregister::<RetainedFile>(file);
1862        // `close` releases every lock still held on the file in band, so there
1863        // is nothing to unwind here first.
1864        retained.close().await?;
1865        Ok(ResponseKind::FileClose)
1866    }
1867
1868    async fn handle_read_dir(
1869        &self,
1870        context: &CallContext<VfsProtocol>,
1871        path: path::PathBuf,
1872    ) -> Result<ResponseKind> {
1873        let read_dir = self.server.vfs.read_dir(Into::into(&path)).await?;
1874        Ok(ResponseKind::ReadDir(
1875            context.register(RetainedReadDir(Mutex::new(read_dir))),
1876        ))
1877    }
1878
1879    async fn handle_read_dir_next(
1880        &self,
1881        context: &CallContext<VfsProtocol>,
1882        read_dir: Cite<ReadDirMarker>,
1883    ) -> Result<ResponseKind> {
1884        let retained = context
1885            .acquire::<RetainedReadDir>(read_dir.clone())
1886            .map_err(|_| Error::new(ErrorKind::InvalidInput, "invalid opaque directory"))?;
1887        let mut read_dir_guard = retained.0.lock().await;
1888        let mut entries = Vec::with_capacity(64);
1889        let mut done = false;
1890        while entries.len() < 64 {
1891            match read_dir_guard.next_entry().await? {
1892                Some(entry) => entries.push(entry),
1893                None => {
1894                    done = true;
1895                    break;
1896                }
1897            }
1898        }
1899        drop(read_dir_guard);
1900        drop(retained);
1901        if done {
1902            let _ = context.unregister::<RetainedReadDir>(read_dir);
1903        }
1904        Ok(ResponseKind::ReadDirNext(ReadDirPage { entries, done }))
1905    }
1906
1907    fn handle_read_dir_close(
1908        &self,
1909        context: &CallContext<VfsProtocol>,
1910        read_dir: Cite<ReadDirMarker>,
1911    ) -> Result<ResponseKind> {
1912        context
1913            .unregister::<RetainedReadDir>(read_dir)
1914            .map_err(|_| Self::invalid_opaque("directory"))?;
1915        Ok(ResponseKind::ReadDirClose)
1916    }
1917
1918    async fn handle_process_enumerate(
1919        &self,
1920        context: &CallContext<VfsProtocol>,
1921    ) -> Result<ResponseKind> {
1922        let processes = self.server.vfs.processes().await?;
1923        Ok(ResponseKind::ProcessEnumerate(
1924            context.register(RetainedProcesses(Mutex::new(processes))),
1925        ))
1926    }
1927
1928    async fn handle_process_enumerate_next(
1929        &self,
1930        context: &CallContext<VfsProtocol>,
1931        processes: Cite<ProcessEnumMarker>,
1932    ) -> Result<ResponseKind> {
1933        let retained = context
1934            .acquire::<RetainedProcesses>(processes.clone())
1935            .map_err(|_| Self::invalid_opaque("process enumeration"))?;
1936        let mut guard = retained.0.lock().await;
1937        let mut entries: Vec<ProcessInfo> = Vec::new();
1938        let mut budget = PROCESS_PAGE_BYTES;
1939        let mut done = false;
1940        while budget > 0 && entries.len() < PROCESS_PAGE_ENTRIES {
1941            match guard.next_entry().await? {
1942                Some(entry) => {
1943                    budget = budget.saturating_sub(process_info_size(&entry));
1944                    entries.push(entry);
1945                }
1946                None => {
1947                    done = true;
1948                    break;
1949                }
1950            }
1951        }
1952        drop(guard);
1953        drop(retained);
1954        if done {
1955            let _ = context.unregister::<RetainedProcesses>(processes);
1956        }
1957        Ok(ResponseKind::ProcessEnumerateNext(ProcessPage {
1958            entries,
1959            done,
1960        }))
1961    }
1962
1963    fn handle_process_enumerate_close(
1964        &self,
1965        context: &CallContext<VfsProtocol>,
1966        processes: Cite<ProcessEnumMarker>,
1967    ) -> Result<ResponseKind> {
1968        context
1969            .unregister::<RetainedProcesses>(processes)
1970            .map_err(|_| Self::invalid_opaque("process enumeration"))?;
1971        Ok(ResponseKind::ProcessEnumerateClose)
1972    }
1973
1974    async fn handle_process_describe(&self, pid: u32) -> Result<ResponseKind> {
1975        Ok(ResponseKind::ProcessDescribe(
1976            self.server.vfs.describe_process(pid).await?,
1977        ))
1978    }
1979
1980    async fn handle_process_open(
1981        &self,
1982        context: &CallContext<VfsProtocol>,
1983        pid: u32,
1984        start: Option<StartTime>,
1985    ) -> Result<ResponseKind> {
1986        let process = self.server.vfs.open_process_raw(pid, start).await?;
1987        Ok(ResponseKind::ProcessOpen(
1988            context.register(RetainedProcess(process)),
1989        ))
1990    }
1991
1992    async fn handle_process_info(
1993        &self,
1994        context: &CallContext<VfsProtocol>,
1995        process: Cite<ProcessMarker>,
1996    ) -> Result<ResponseKind> {
1997        let retained = context
1998            .acquire::<RetainedProcess>(process)
1999            .map_err(|_| Self::invalid_opaque("process"))?;
2000        Ok(ResponseKind::ProcessInfo(retained.0.info().await?))
2001    }
2002
2003    async fn handle_process_signal(
2004        &self,
2005        context: &CallContext<VfsProtocol>,
2006        process: Cite<ProcessMarker>,
2007        signal: Signal,
2008    ) -> Result<ResponseKind> {
2009        let retained = context
2010            .acquire::<RetainedProcess>(process)
2011            .map_err(|_| Self::invalid_opaque("process"))?;
2012        retained.0.signal(signal).await?;
2013        Ok(ResponseKind::ProcessSignal)
2014    }
2015
2016    async fn handle_process_terminate(
2017        &self,
2018        context: &CallContext<VfsProtocol>,
2019        process: Cite<ProcessMarker>,
2020    ) -> Result<ResponseKind> {
2021        let retained = context
2022            .acquire::<RetainedProcess>(process)
2023            .map_err(|_| Self::invalid_opaque("process"))?;
2024        retained.0.terminate().await?;
2025        Ok(ResponseKind::ProcessTerminate)
2026    }
2027
2028    async fn handle_process_kill(
2029        &self,
2030        context: &CallContext<VfsProtocol>,
2031        process: Cite<ProcessMarker>,
2032    ) -> Result<ResponseKind> {
2033        let retained = context
2034            .acquire::<RetainedProcess>(process)
2035            .map_err(|_| Self::invalid_opaque("process"))?;
2036        retained.0.kill().await?;
2037        Ok(ResponseKind::ProcessKill)
2038    }
2039
2040    async fn handle_process_wait(
2041        &self,
2042        context: &CallContext<VfsProtocol>,
2043        process: Cite<ProcessMarker>,
2044    ) -> Result<ResponseKind> {
2045        let retained = context
2046            .acquire::<RetainedProcess>(process)
2047            .map_err(|_| Self::invalid_opaque("process"))?;
2048        Ok(ResponseKind::ProcessWait(retained.0.wait().await?))
2049    }
2050
2051    fn handle_process_close(
2052        &self,
2053        context: &CallContext<VfsProtocol>,
2054        process: Cite<ProcessMarker>,
2055    ) -> Result<ResponseKind> {
2056        context
2057            .unregister::<RetainedProcess>(process)
2058            .map_err(|_| Self::invalid_opaque("process"))?;
2059        Ok(ResponseKind::ProcessClose)
2060    }
2061
2062    async fn handle_unix_vfs(
2063        &self,
2064        context: &CallContext<VfsProtocol>,
2065        req: UnixVfsRequest,
2066    ) -> Result<ResponseKind> {
2067        #[cfg(unix)]
2068        if self.mode == SessionMode::Native && self.server.vfs.is_direct() {
2069            let handle: OwnedFd = async {
2070                let path = req.path.to_native()?;
2071                let stream = UnixStream::connect(path).await?;
2072                Ok::<OwnedFd, Error>(stream.into_std()?.into())
2073            }
2074            .await?;
2075            return Ok(ResponseKind::UnixVfs(OpenVfsHandle::Native(OsHandle::new(
2076                handle,
2077            ))));
2078        }
2079
2080        let vfs = self
2081            .server
2082            .vfs
2083            .unix_socket(Into::into(&req.path), req.key.as_deref())
2084            .await?;
2085        Ok(ResponseKind::UnixVfs(OpenVfsHandle::Opaque(
2086            context.register(RetainedVfs::plain(vfs)),
2087        )))
2088    }
2089
2090    async fn handle_windows_admin(
2091        &self,
2092        context: &CallContext<VfsProtocol>,
2093        req: WindowsAdminRequest,
2094    ) -> Result<ResponseKind> {
2095        let vfs = self
2096            .server
2097            .vfs
2098            .windows_admin(Into::into(&req.cwd), req.env, req.elevate)
2099            .await?;
2100        Ok(ResponseKind::WindowsAdmin(
2101            context.register(RetainedVfs::plain(vfs)),
2102        ))
2103    }
2104
2105    async fn handle_remove(&self, req: RemoveRequest) -> Result<ResponseKind> {
2106        self.server
2107            .vfs
2108            .remove(Into::into(&req.path), req.all, req.ignore)
2109            .await?;
2110        Ok(ResponseKind::Remove)
2111    }
2112
2113    async fn handle_metadata(&self, req: MetadataRequest) -> Result<ResponseKind> {
2114        let metadata = self.server.vfs.metadata(Into::into(&req.path)).await?;
2115        Ok(ResponseKind::Metadata(metadata))
2116    }
2117
2118    async fn handle_fs_metadata(&self, req: FsMetadataRequest) -> Result<ResponseKind> {
2119        let metadata = self
2120            .server
2121            .vfs
2122            .fs_metadata(Into::into(&req.path), req.follow)
2123            .await?;
2124        Ok(ResponseKind::FsMetadata(metadata))
2125    }
2126
2127    async fn handle_sec_desc(&self, req: SecDescRequest) -> Result<ResponseKind> {
2128        let sec_desc = self
2129            .server
2130            .vfs
2131            .sec_desc(Into::into(&req.path), req.mask, req.follow)
2132            .await?;
2133        Ok(ResponseKind::SecDesc(sec_desc))
2134    }
2135
2136    async fn handle_acl(&self, req: AclRequest) -> Result<ResponseKind> {
2137        let acl = self
2138            .server
2139            .vfs
2140            .acl(Into::into(&req.path), req.kind, req.default, req.follow)
2141            .await?;
2142        Ok(ResponseKind::Acl(acl))
2143    }
2144
2145    async fn handle_set_acl(&self, req: SetAclRequest) -> Result<ResponseKind> {
2146        self.server
2147            .vfs
2148            .set_acl(
2149                Into::into(&req.path),
2150                req.kind,
2151                req.acl.as_ref(),
2152                req.default,
2153                req.follow,
2154            )
2155            .await?;
2156        Ok(ResponseKind::SetAcl)
2157    }
2158
2159    async fn handle_update_sec_desc(&self, req: UpdateSecDescRequest) -> Result<ResponseKind> {
2160        self.server
2161            .vfs
2162            .update_sec_desc(Into::into(&req.path), &req.sec_desc, req.follow)
2163            .await?;
2164        Ok(ResponseKind::UpdateSecDesc)
2165    }
2166
2167    async fn handle_create_dir(&self, req: CreateDirRequest) -> Result<ResponseKind> {
2168        self.server
2169            .vfs
2170            .create_dir(Into::into(&req.path), req.all)
2171            .await?;
2172        Ok(ResponseKind::CreateDir)
2173    }
2174
2175    async fn handle_remove_dir(&self, req: RemoveDirRequest) -> Result<ResponseKind> {
2176        self.server
2177            .vfs
2178            .remove_dir(Into::into(&req.path), req.all, req.ignore)
2179            .await?;
2180        Ok(ResponseKind::RemoveDir)
2181    }
2182
2183    async fn handle_copy(&self, req: CopyRequest) -> Result<ResponseKind> {
2184        self.server
2185            .vfs
2186            .copy(Into::into(&req.from), Into::into(&req.to), req.all)
2187            .await?;
2188        Ok(ResponseKind::Copy)
2189    }
2190
2191    async fn handle_rename(&self, req: RenameRequest) -> Result<ResponseKind> {
2192        self.server
2193            .vfs
2194            .rename(Into::into(&req.from), Into::into(&req.to), req.replace)
2195            .await?;
2196        Ok(ResponseKind::Rename)
2197    }
2198
2199    async fn handle_move(&self, req: MoveRequest) -> Result<ResponseKind> {
2200        self.server
2201            .vfs
2202            .move_(Into::into(&req.from), Into::into(&req.to), req.all)
2203            .await?;
2204        Ok(ResponseKind::Move)
2205    }
2206
2207    async fn handle_symlink(&self, req: SymlinkRequest) -> Result<ResponseKind> {
2208        match req.kind {
2209            SymlinkKind::Infer => {
2210                self.server
2211                    .vfs
2212                    .symlink(
2213                        Into::into(&req.cwd),
2214                        Into::into(&req.src),
2215                        Into::into(&req.dst),
2216                    )
2217                    .await
2218            }
2219            SymlinkKind::Dir => {
2220                self.server
2221                    .vfs
2222                    .symlink_dir(Into::into(&req.src), Into::into(&req.dst))
2223                    .await
2224            }
2225            SymlinkKind::File => {
2226                self.server
2227                    .vfs
2228                    .symlink_file(Into::into(&req.src), Into::into(&req.dst))
2229                    .await
2230            }
2231        }?;
2232        Ok(ResponseKind::Symlink)
2233    }
2234
2235    async fn handle_hard_link(&self, req: HardLinkRequest) -> Result<ResponseKind> {
2236        self.server
2237            .vfs
2238            .hard_link(Into::into(&req.src), Into::into(&req.dst))
2239            .await?;
2240        Ok(ResponseKind::HardLink)
2241    }
2242
2243    async fn handle_symlink_metadata(&self, req: MetadataRequest) -> Result<ResponseKind> {
2244        let metadata = self
2245            .server
2246            .vfs
2247            .symlink_metadata(Into::into(&req.path))
2248            .await?;
2249        Ok(ResponseKind::SymlinkMetadata(metadata))
2250    }
2251
2252    async fn handle_update_metadata(&self, req: UpdateMetadataRequest) -> Result<ResponseKind> {
2253        let paths: Vec<_> = req
2254            .paths
2255            .iter()
2256            .map(|path| path::Path::from(path).to_path_buf())
2257            .collect();
2258        self.server.vfs.update_metadata(&paths, req.patch).await?;
2259        Ok(ResponseKind::UpdateMetadata)
2260    }
2261
2262    async fn handle_canonicalize(&self, req: CanonicalizeRequest) -> Result<ResponseKind> {
2263        let path = self.server.vfs.canonicalize(Into::into(&req.path)).await?;
2264        Ok(ResponseKind::Canonicalize(path))
2265    }
2266
2267    async fn handle_read_link(&self, req: ReadLinkRequest) -> Result<ResponseKind> {
2268        let path = self.server.vfs.read_link(Into::into(&req.path)).await?;
2269        Ok(ResponseKind::ReadLink(path))
2270    }
2271
2272    async fn handle_access(&self, req: AccessRequest) -> Result<ResponseKind> {
2273        let mode = AccessFlags::from_bits(req.mode).unwrap_or(AccessFlags::empty());
2274        self.server.vfs.access(Into::into(&req.path), mode).await?;
2275        Ok(ResponseKind::Access)
2276    }
2277
2278    async fn handle_glob(&self, req: GlobRequest) -> Result<ResponseKind> {
2279        let paths = self
2280            .server
2281            .vfs
2282            .glob(
2283                req.pattern,
2284                Into::into(&req.root),
2285                req.follow_symlinks,
2286                req.max_depth,
2287            )
2288            .await?;
2289        Ok(ResponseKind::Glob(paths))
2290    }
2291
2292    async fn handle_xattrs(&self, req: XattrsRequest) -> Result<ResponseKind> {
2293        let xattrs = self
2294            .server
2295            .vfs
2296            .xattrs(
2297                Into::into(&req.path),
2298                req.namespace.as_borrowed(),
2299                req.follow,
2300            )
2301            .await?;
2302        Ok(ResponseKind::Xattrs(xattrs))
2303    }
2304
2305    async fn handle_xattr(&self, req: XattrRequest) -> Result<ResponseKind> {
2306        let xattr = self
2307            .server
2308            .vfs
2309            .xattr(
2310                Into::into(&req.path),
2311                &req.name,
2312                req.namespace.as_deref(),
2313                req.follow,
2314            )
2315            .await?;
2316        Ok(ResponseKind::Xattr(xattr))
2317    }
2318
2319    async fn handle_set_xattr(&self, req: SetXattrRequest) -> Result<ResponseKind> {
2320        self.server
2321            .vfs
2322            .set_xattr(
2323                Into::into(&req.path),
2324                &req.name,
2325                req.namespace.as_deref(),
2326                &req.value,
2327                req.follow,
2328            )
2329            .await?;
2330        Ok(ResponseKind::SetXattr)
2331    }
2332
2333    async fn handle_remove_xattr(&self, req: XattrRequest) -> Result<ResponseKind> {
2334        self.server
2335            .vfs
2336            .remove_xattr(
2337                Into::into(&req.path),
2338                &req.name,
2339                req.namespace.as_deref(),
2340                req.follow,
2341            )
2342            .await?;
2343        Ok(ResponseKind::RemoveXattr)
2344    }
2345
2346    async fn handle_streams(&self, req: StreamsRequest) -> Result<ResponseKind> {
2347        Ok(ResponseKind::Streams(
2348            self.server
2349                .vfs
2350                .streams(Into::into(&req.path), req.follow)
2351                .await?,
2352        ))
2353    }
2354}
2355
2356/// A child's standard stream as the peer named it.
2357enum SpawnStdio<P> {
2358    /// A pipe end the peer retained here.
2359    Pipe(P),
2360    /// A native handle the peer passed along, used as is.
2361    Native(std::fs::File),
2362    /// A file the peer retained here.
2363    File(File),
2364}
2365
2366#[cfg(test)]
2367mod tests {
2368    #[cfg(unix)]
2369    use super::report_handler_exit;
2370    use super::{Server, orderly_disconnect};
2371    use crate::{
2372        error::{Error, ErrorKind},
2373        path,
2374        protocol::{
2375            self, OpenFlags, OpenHandle, OpenRequest, Request, RequestKind, ResponseKind,
2376            VfsProtocol,
2377        },
2378    };
2379
2380    fn request(kind: RequestKind) -> Request {
2381        Request { vfs: None, kind }
2382    }
2383
2384    #[tokio::test]
2385    #[cfg(unix)]
2386    #[should_panic(expected = "connection handler panic")]
2387    async fn connection_handler_panics_are_propagated() {
2388        let mut handlers = tokio::task::JoinSet::new();
2389        handlers.spawn(async {
2390            panic!("connection handler panic");
2391            #[allow(unreachable_code)]
2392            Ok::<(), Error>(())
2393        });
2394        report_handler_exit(handlers.join_next().await.unwrap());
2395    }
2396
2397    #[test]
2398    fn orderly_connection_close_is_expected() {
2399        assert!(orderly_disconnect(&Error::new(
2400            ErrorKind::ConnectionReset,
2401            "closed"
2402        )));
2403        assert!(orderly_disconnect(&Error::new(
2404            ErrorKind::BrokenPipe,
2405            "closed"
2406        )));
2407        assert!(!orderly_disconnect(&Error::new(
2408            ErrorKind::InvalidData,
2409            "bad frame"
2410        )));
2411    }
2412
2413    #[tokio::test]
2414    async fn remote_server_replies_without_serializing_a_handle() {
2415        let (client_stream, server_stream) = crate::tests::duplex(4096);
2416        let server =
2417            tokio::spawn(async move { Server::new(server_stream).await.unwrap().serve().await });
2418        let client = protocol::rpc_builder(None)
2419            .client(client_stream)
2420            .await
2421            .unwrap()
2422            .bind::<VfsProtocol>();
2423
2424        let temp = crate::test_temp::tempdir().unwrap();
2425        let file_path = temp.path().join("file");
2426        std::fs::write(&file_path, []).unwrap();
2427        let response = client
2428            .call(request(RequestKind::Open(OpenRequest {
2429                path: path::PathBuf::from_native(file_path)
2430                    .unwrap()
2431                    .to_path()
2432                    .into(),
2433                flags: OpenFlags::READ,
2434            })))
2435            .await
2436            .unwrap()
2437            .into_response()
2438            .unwrap();
2439        let ResponseKind::Open(OpenHandle::Opaque(file)) = response else {
2440            panic!("remote open did not return an opaque file");
2441        };
2442        let ResponseKind::FileClose = client
2443            .call(request(RequestKind::FileClose { file: file.cite() }))
2444            .await
2445            .unwrap()
2446            .into_response()
2447            .unwrap()
2448        else {
2449            panic!("file close returned the wrong response");
2450        };
2451        let error = client
2452            .call(request(RequestKind::FileClose { file: file.cite() }))
2453            .await
2454            .unwrap()
2455            .into_response()
2456            .unwrap_err();
2457        assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
2458
2459        let _ = client.call(request(RequestKind::Stop)).await.unwrap();
2460        client.close().await;
2461        server.await.unwrap().unwrap();
2462    }
2463}