hyperware_process_lib/vfs/
mod.rs

1use crate::Request;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5pub mod directory;
6pub mod file;
7
8pub use directory::*;
9pub use file::*;
10
11/// IPC body format for requests sent to vfs runtime module.
12#[derive(Debug, Serialize, Deserialize)]
13pub struct VfsRequest {
14    /// path is always prepended by [`crate::PackageId`], the capabilities of the topmost folder are checked
15    /// "/your-package:publisher.os/drive_folder/another_folder_or_file"
16    pub path: String,
17    pub action: VfsAction,
18}
19
20#[derive(Debug, Serialize, Deserialize)]
21pub enum VfsAction {
22    CreateDrive,
23    CreateDir,
24    CreateDirAll,
25    CreateFile,
26    OpenFile { create: bool },
27    CloseFile,
28    Write,
29    WriteAll,
30    Append,
31    SyncAll,
32    Read,
33    ReadDir,
34    ReadToEnd,
35    ReadExact { length: u64 },
36    ReadToString,
37    Seek(SeekFrom),
38    RemoveFile,
39    RemoveDir,
40    RemoveDirAll,
41    Rename { new_path: String },
42    Metadata,
43    AddZip,
44    CopyFile { new_path: String },
45    Len,
46    SetLen(u64),
47    Hash,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51pub enum SeekFrom {
52    Start(u64),
53    End(i64),
54    Current(i64),
55}
56
57#[derive(Debug, Serialize, Deserialize, PartialEq)]
58pub enum FileType {
59    File,
60    Directory,
61    Symlink,
62    Other,
63}
64
65#[derive(Debug, Serialize, Deserialize)]
66pub struct FileMetadata {
67    pub file_type: FileType,
68    pub len: u64,
69}
70
71#[derive(Debug, Serialize, Deserialize, PartialEq)]
72pub struct DirEntry {
73    pub path: String,
74    pub file_type: FileType,
75}
76
77#[derive(Debug, Serialize, Deserialize)]
78pub enum VfsResponse {
79    Ok,
80    Err(VfsError),
81    Read,
82    SeekFrom { new_offset: u64 },
83    ReadDir(Vec<DirEntry>),
84    ReadToString(String),
85    Metadata(FileMetadata),
86    Len(u64),
87    Hash([u8; 32]),
88}
89
90#[derive(Clone, Debug, Error, Serialize, Deserialize)]
91pub enum VfsError {
92    #[error("no write capability for requested drive")]
93    NoWriteCap,
94    #[error("no read capability for requested drive")]
95    NoReadCap,
96    #[error("failed to generate capability for new drive")]
97    AddCapFailed,
98    #[error("request could not be deserialized to valid VfsRequest")]
99    MalformedRequest,
100    #[error("request type used requires a blob")]
101    NoBlob,
102    #[error("error parsing path: {path}: {error}")]
103    ParseError { error: String, path: String },
104    #[error("IO error: {0}")]
105    IOError(String),
106    #[error("non-file non-dir in zip")]
107    UnzipError,
108    /// Not actually issued by `vfs:distro:sys`, just this library
109    #[error("SendError")]
110    SendError(crate::SendErrorKind),
111}
112
113pub fn vfs_request<T>(path: T, action: VfsAction) -> Request
114where
115    T: Into<String>,
116{
117    Request::new().target(("our", "vfs", "distro", "sys")).body(
118        serde_json::to_vec(&VfsRequest {
119            path: path.into(),
120            action,
121        })
122        .expect("failed to serialize VfsRequest"),
123    )
124}
125
126/// Metadata of a path, returns file type and length.
127pub fn metadata(path: &str, timeout: Option<u64>) -> Result<FileMetadata, VfsError> {
128    let timeout = timeout.unwrap_or(5);
129
130    let message = vfs_request(path, VfsAction::Metadata)
131        .send_and_await_response(timeout)
132        .unwrap()
133        .map_err(|e| VfsError::SendError(e.kind))?;
134
135    match parse_response(message.body())? {
136        VfsResponse::Metadata(metadata) => Ok(metadata),
137        VfsResponse::Err(e) => Err(e),
138        _ => Err(VfsError::ParseError {
139            error: "unexpected response".to_string(),
140            path: path.to_string(),
141        }),
142    }
143}
144
145/// Removes a path, if it's either a directory or a file.
146pub fn remove_path(path: &str, timeout: Option<u64>) -> Result<(), VfsError> {
147    let meta = metadata(path, timeout)?;
148
149    match meta.file_type {
150        FileType::Directory => remove_dir(path, timeout),
151        FileType::File => remove_file(path, timeout),
152        _ => Err(VfsError::ParseError {
153            error: "path is not a file or directory".to_string(),
154            path: path.to_string(),
155        }),
156    }
157}
158
159pub fn parse_response(body: &[u8]) -> Result<VfsResponse, VfsError> {
160    serde_json::from_slice::<VfsResponse>(body).map_err(|_| VfsError::MalformedRequest)
161}