Skip to main content

xtask_todo_lib/devshell/workspace/
io.rs

1//! Shared workspace file reads: Mode P γ ([`GuestFsOps`]) vs Mode S ([`Vfs`]).
2//!
3//! Used by [`crate::devshell::command::dispatch`] and script / REPL `source` loading (design §9).
4
5#![allow(clippy::pedantic, clippy::nursery)]
6
7use std::cell::RefCell;
8use std::rc::Rc;
9
10use crate::devshell::vfs::{Vfs, VfsError};
11#[cfg(any(unix, feature = "beta-vm"))]
12use crate::devshell::vm::GuestFsOps;
13use crate::devshell::vm::{GuestFsError, SessionHolder};
14
15#[cfg(unix)]
16use crate::devshell::vm::GammaSession;
17#[cfg(any(unix, feature = "beta-vm"))]
18use crate::devshell::workspace::logical_path_to_guest;
19use crate::devshell::workspace::WorkspaceBackendError;
20
21/// Failure reading a logical path from the active workspace (guest or VFS).
22#[derive(Debug)]
23pub enum WorkspaceReadError {
24    Vfs(VfsError),
25    Guest(GuestFsError),
26    PathOutsideWorkspace,
27    Backend(WorkspaceBackendError),
28}
29
30impl std::fmt::Display for WorkspaceReadError {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::Vfs(e) => write!(f, "{e}"),
34            Self::Guest(e) => write!(f, "{e}"),
35            Self::PathOutsideWorkspace => f.write_str("path outside workspace"),
36            Self::Backend(e) => write!(f, "{e}"),
37        }
38    }
39}
40
41impl std::error::Error for WorkspaceReadError {
42    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
43        match self {
44            Self::Vfs(e) => Some(e),
45            Self::Guest(e) => Some(e),
46            Self::Backend(e) => Some(e),
47            Self::PathOutsideWorkspace => None,
48        }
49    }
50}
51
52/// Map a logical path to a guest absolute path (γ guest-primary).
53#[cfg(unix)]
54pub fn logical_to_guest_abs(
55    vfs: &Vfs,
56    g: &GammaSession,
57    logical_path: &str,
58) -> Result<String, WorkspaceBackendError> {
59    logical_path_to_guest(g.guest_mount(), vfs.cwd(), logical_path)
60}
61
62/// Read file bytes for a logical path: guest-primary (γ or β) first, else in-memory [`Vfs`].
63pub fn read_logical_file_bytes(
64    vfs: &mut Vfs,
65    vm_session: &mut SessionHolder,
66    path: &str,
67) -> Result<Vec<u8>, WorkspaceReadError> {
68    #[cfg(any(unix, feature = "beta-vm"))]
69    {
70        if let Some((ops, mount)) = vm_session.guest_primary_fs_ops_mut() {
71            let gp = match logical_path_to_guest(&mount, vfs.cwd(), path) {
72                Ok(p) => p,
73                Err(WorkspaceBackendError::PathOutsideWorkspace) => {
74                    return Err(WorkspaceReadError::PathOutsideWorkspace);
75                }
76                Err(e) => return Err(WorkspaceReadError::Backend(e)),
77            };
78            return GuestFsOps::read_file(ops, &gp).map_err(WorkspaceReadError::Guest);
79        }
80    }
81    #[cfg(not(any(unix, feature = "beta-vm")))]
82    let _ = vm_session;
83    vfs.read_file(path).map_err(WorkspaceReadError::Vfs)
84}
85
86/// Same as [`read_logical_file_bytes`] with shared [`Rc`] handles (script / REPL).
87pub fn read_logical_file_bytes_rc(
88    vfs: &Rc<RefCell<Vfs>>,
89    vm_session: &Rc<RefCell<SessionHolder>>,
90    path: &str,
91) -> Result<Vec<u8>, WorkspaceReadError> {
92    read_logical_file_bytes(&mut vfs.borrow_mut(), &mut vm_session.borrow_mut(), path)
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::devshell::vfs::Vfs;
99
100    #[test]
101    fn read_host_session_uses_vfs() {
102        let vfs = Rc::new(RefCell::new(Vfs::new()));
103        vfs.borrow_mut().mkdir("/a").unwrap();
104        vfs.borrow_mut().write_file("/a/x", b"hi").unwrap();
105        let vm = Rc::new(RefCell::new(SessionHolder::new_host()));
106        let got = read_logical_file_bytes_rc(&vfs, &vm, "/a/x").unwrap();
107        assert_eq!(got, b"hi");
108    }
109}