ad_editor/fsys/
mod.rs

1//! An Acme style filesystem interface for ad
2//!
3//!
4//! ## Mount Point
5//! <https://www.pathname.com/fhs/pub/fhs-2.3.html#VARLIBLTEDITORGTEDITORBACKUPFILESAN>
6//!
7//! `/var/lib/ad` feels like it would be the "correct" place to mount the filesystem but
8//! this would need to be created with something like:
9//! ```sh
10//! $ sudo mkdir /var/lib/ad
11//! $ sudo chown $(whoami) /var/lib/ad
12//! ```
13//!
14//! So instead we mount to `$HOME/.ad/mnt/`
15//!
16//! ## Filesystem contents
17//! ```text
18//! $HOME/.ad/mnt/
19//!   ctl
20//!   minibuffer
21//!   scratch
22//!   log
23//!   buffers/
24//!     current
25//!     index
26//!     [n]/
27//!       filename
28//!       dot
29//!       addr
30//!       body
31//!       event
32//! ```
33use crate::{editor::Action, input::Event, ui::SCRATCH_ID};
34use ninep::{
35    Result,
36    fs::{FileMeta, IoUnit, Mode, Perm, Stat},
37    sync::server::{ClientId, ReadOutcome, Serve9p, Server, socket_path},
38};
39use std::{
40    collections::HashMap,
41    env,
42    fs::{create_dir_all, remove_file},
43    mem::take,
44    path::{Path, PathBuf},
45    process::Command,
46    sync::{
47        Arc, Mutex,
48        mpsc::{Receiver, Sender, channel},
49    },
50    thread::{JoinHandle, spawn},
51    time::SystemTime,
52};
53use tracing::{error, trace};
54
55mod buffer;
56mod event;
57mod log;
58mod message;
59
60pub(crate) use event::InputFilter;
61pub(crate) use log::LogEvent;
62pub(crate) use message::{Message, Req};
63
64use buffer::{BufferNodes, QidCheck};
65use log::spawn_log_listener;
66
67const DEFAULT_SOCKET_NAME: &str = "ad";
68const MOUNT_DIR: &str = ".ad/mnt";
69const IO_UNIT: u32 = 8168;
70
71// Fixed qids inside of '$HOME/.ad/mnt/buffers':
72///   0. $HOME/.ad/mnt  -> The directory we mount to
73const MOUNT_ROOT_QID: u64 = 0;
74///   1.   /ctl         -> control file for issuing commands
75const CONTROL_FILE_QID: u64 = 1;
76const CONTROL_FILE: &str = "ctl";
77///   2.   /log         -> read only log of events in the editor
78const LOG_FILE_QID: u64 = 2;
79const LOG_FILE: &str = "log";
80///   3    /minibuffer  -> control file for selecting text using the minibuffer
81const MINIBUFFER_QID: u64 = 3;
82const MINIBUFFER: &str = "minibuffer";
83///   4    /scratch     -> control file for reading and appending to the scratch buffer
84const SCRATCH_QID: u64 = 4;
85const SCRATCH: &str = "scratch";
86///   4    /buffers/    -> parent directory for buffers
87const BUFFERS_QID: u64 = 5;
88const BUFFERS_DIR: &str = "buffers";
89//    5      /index     -> a listing of all of the currently open buffers
90const INDEX_BUFFER_QID: u64 = 6;
91const INDEX_BUFFER: &str = "index";
92//    6      /current   -> the fsys filename of the current buffer
93const CURRENT_BUFFER_QID: u64 = 7;
94const CURRENT_BUFFER: &str = "current";
95
96/// The number of qids required to serve both the directory and contents
97/// of a buffer node (used to generate qid values for buffers):
98///
99///   1. $id            -> The buffer directory
100///   2.   filename     -> The current filename for the buffer
101///   3.   dot          -> The text currently held in dot
102///   4.   addr         -> The address value of dot
103///   5.   xdot         -> The text currently held in xdot (a virtual dot not affecting real dot)
104///   6.   xaddr        -> The address value of xdot
105///   7.   body         -> The full body of the buffer
106///   8.   event        -> Control file for intercepting input events for the buffer
107///   9.   output       -> Write only output connected to stdout/err of commands run within the buffer
108///   10.  filetype     -> ad's view of what filetype is configured for the buffer
109const QID_OFFSET: u64 = 10;
110
111const TOP_LEVEL_QIDS: [u64; 8] = [
112    MOUNT_ROOT_QID,
113    CONTROL_FILE_QID,
114    MINIBUFFER_QID,
115    SCRATCH_QID,
116    LOG_FILE_QID,
117    BUFFERS_QID,
118    INDEX_BUFFER_QID,
119    CURRENT_BUFFER_QID,
120];
121
122const E_UNKNOWN_FILE: &str = "unknown file";
123const E_NOT_ALLOWED: &str = "not allowed";
124
125enum InternalRead {
126    Immediate(Vec<u8>),
127    Blocked(Receiver<Vec<u8>>),
128    Unknown,
129}
130
131#[derive(Debug, Default)]
132struct Cids {
133    cids: Vec<ClientId>,
134    read_locked: Option<ClientId>,
135}
136
137/// A join handle for the filesystem thread
138#[derive(Debug)]
139pub struct FsHandle {
140    path: PathBuf,
141    inner: JoinHandle<()>,
142}
143
144impl FsHandle {
145    /// Remove our socket (will cause the 9p server to exit)
146    pub fn remove_socket(&self) {
147        _ = remove_file(&self.path);
148    }
149
150    /// Join on the filesystem thread
151    pub fn join(self) {
152        _ = self.inner.join();
153    }
154}
155
156#[derive(Debug)]
157enum MiniBufferContent {
158    Buffering(Vec<u8>),
159    Data(Vec<u8>),
160    Pending(Sender<Sender<Vec<u8>>>, Receiver<Vec<u8>>),
161}
162
163pub fn default_socket_path_for_pid() -> PathBuf {
164    socket_path(format!("{DEFAULT_SOCKET_NAME}-{}", crate::pid()))
165}
166
167/// Mutable state for the ad filesystem.
168///
169/// The parent [AdFs] holds onto this state inside of an Arc<Mutex<>> which means that all
170/// incoming requests will be processed sequentially.
171#[derive(Debug)]
172struct State {
173    tx: Sender<Event>,
174    buffer_nodes: BufferNodes,
175    minibuffer_content: MiniBufferContent,
176    minibuffer_prompt: Option<String>,
177    /// map of qids to client IDs with that qid open
178    open_cids: HashMap<u64, Cids>,
179    // Root level files and directories
180    mount_dir_stat: Stat,
181    control_file_stat: Stat,
182    minibuffer_stat: Stat,
183    scratch_stat: Stat,
184    log_file_stat: Stat,
185    mount_path: String,
186    auto_mount: bool,
187}
188
189impl Drop for State {
190    fn drop(&mut self) {
191        if self.auto_mount {
192            let res = Command::new("fusermount")
193                .args(["-u", &self.mount_path])
194                .spawn();
195
196            if let Ok(mut child) = res {
197                _ = child.wait();
198            }
199        }
200    }
201}
202
203impl State {
204    fn add_open_cid(&mut self, qid: u64, cid: ClientId) {
205        self.open_cids.entry(qid).or_default().cids.push(cid);
206    }
207
208    fn remove_open_cid(&mut self, qid: u64, cid: ClientId) {
209        self.open_cids.entry(qid).and_modify(|cids| {
210            cids.cids.retain(|&id| id != cid);
211            if cids.read_locked == Some(cid) {
212                cids.read_locked = None;
213            }
214        });
215    }
216
217    fn lock_qid_for_reading(&mut self, qid: u64, cid: ClientId) -> Result<()> {
218        trace!("locking qid for reading qid={qid} cid={cid:?}");
219        match self.open_cids.get_mut(&qid) {
220            Some(cids) => cids.read_locked = Some(cid),
221            None => return Err(E_UNKNOWN_FILE.to_string()),
222        }
223
224        Ok(())
225    }
226
227    fn readlocked_cid(&self, qid: u64) -> Option<ClientId> {
228        self.open_cids.get(&qid).and_then(|cids| cids.read_locked)
229    }
230
231    fn set_active_buffer(&mut self, s: String) -> Result<usize> {
232        let id: usize = match s.trim().parse() {
233            Ok(n) => n,
234            Err(_) => {
235                trace!("invalid buffer id submitted to buffers/current: {s}");
236                return Ok(0);
237            }
238        };
239
240        if let Err(e) = self.tx.send(Event::Action(Action::FocusBuffer { id })) {
241            error!("unable to send event to main loop: {e}");
242            return Ok(0);
243        }
244
245        Ok(s.len())
246    }
247
248    fn scratch_read(&self, offset: usize, count: usize) -> ReadOutcome {
249        let req = Req::ReadBufferBody { id: SCRATCH_ID };
250        match Message::send(req, &self.tx) {
251            Ok(s) => ReadOutcome::Immediate(apply_offset(s.as_bytes(), offset, count)),
252            Err(e) => {
253                error!("fsys failed to read file content: {e}");
254                ReadOutcome::Immediate(Vec::new())
255            }
256        }
257    }
258
259    fn scratch_write(&mut self, s: String) -> Result<usize> {
260        let n_bytes = s.len();
261        let req = Req::AppendBufferBody { id: SCRATCH_ID, s };
262
263        match Message::send(req, &self.tx) {
264            Ok(_) => Ok(n_bytes),
265            Err(e) => Err(format!(
266                "unable to write to scratch buffer (n_bytes={n_bytes}): {e}",
267            )),
268        }
269    }
270
271    fn minibuffer_read(&mut self, offset: usize, count: usize) -> ReadOutcome {
272        match &mut self.minibuffer_content {
273            MiniBufferContent::Buffering(lines_bytes) => {
274                let lines = match String::from_utf8(take(lines_bytes)) {
275                    Ok(s) => s,
276                    Err(e) => {
277                        error!("invalid minibuffer data: {e}");
278                        self.minibuffer_content = MiniBufferContent::Buffering(Vec::new());
279                        return ReadOutcome::Immediate(Vec::new());
280                    }
281                };
282                let prompt = self.minibuffer_prompt.take();
283
284                let (data_tx, data_rx) = channel();
285                let (fsys_tx, fsys_rx) = channel();
286                let (sub_tx, sub_rx) = channel();
287
288                self.minibuffer_stat.n_bytes = 0;
289                self.minibuffer_stat.last_modified = SystemTime::now();
290                spawn_minibuffer_listener(data_rx, fsys_tx, sub_rx);
291
292                let (tx, rx) = channel();
293                _ = sub_tx.send(tx);
294                self.minibuffer_content = MiniBufferContent::Pending(sub_tx, fsys_rx);
295
296                match Message::send(
297                    Req::MinibufferSelect {
298                        prompt,
299                        lines,
300                        tx: data_tx,
301                    },
302                    &self.tx,
303                ) {
304                    Ok(_) => ReadOutcome::Blocked(rx),
305                    Err(e) => {
306                        error!("unable to open minibuffer: {e}");
307                        self.minibuffer_content = MiniBufferContent::Buffering(Vec::new());
308                        ReadOutcome::Immediate(Vec::new())
309                    }
310                }
311            }
312
313            MiniBufferContent::Data(data) => {
314                ReadOutcome::Immediate(apply_offset(data, offset, count))
315            }
316
317            MiniBufferContent::Pending(sub_tx, fsys_rx) => match fsys_rx.try_recv() {
318                Ok(data) => {
319                    self.minibuffer_stat.n_bytes = data.len() as u64;
320                    self.minibuffer_content = MiniBufferContent::Data(data.clone());
321                    ReadOutcome::Immediate(apply_offset(&data, offset, count))
322                }
323                _ => {
324                    let (tx, rx) = channel();
325                    _ = sub_tx.send(tx);
326                    ReadOutcome::Blocked(rx)
327                }
328            },
329        }
330    }
331
332    /// Writing data to the minibuffer causes fsys to buffer the writes internally until the client
333    /// is done. When a client then attempts to read back the selection the full buffer is sent to
334    /// the editor for rendering and the reads block until the user makes a selection.
335    fn minibuffer_write(&mut self, lines: String) -> Result<usize> {
336        let n_bytes = lines.len();
337        match &mut self.minibuffer_content {
338            MiniBufferContent::Buffering(buffer) => buffer.extend_from_slice(lines.as_bytes()),
339            _ => self.minibuffer_content = MiniBufferContent::Buffering(lines.into_bytes()),
340        }
341
342        Ok(n_bytes)
343    }
344}
345
346/// The filesystem interface for ad
347#[derive(Debug)]
348pub(crate) struct AdFs {
349    state: Arc<Mutex<State>>,
350}
351
352impl AdFs {
353    /// Construct a new filesystem interface using channels held by the editor.
354    pub fn new(tx: Sender<Event>, brx: Receiver<LogEvent>, auto_mount: bool) -> Self {
355        let home = env::var("HOME").expect("$HOME to be set");
356        let mount_path = format!("{home}/{MOUNT_DIR}");
357
358        if !Path::new(&mount_path).exists() {
359            create_dir_all(&mount_path).expect("to be able to create our mount point");
360        }
361
362        let (log_tx, log_rx) = channel();
363        let (listener_tx, listener_rx) = channel();
364        spawn_log_listener(brx, listener_tx, log_rx);
365
366        let buffer_nodes = BufferNodes::new(tx.clone(), listener_rx, log_tx);
367
368        Self {
369            state: Arc::new(Mutex::new(State {
370                tx,
371                buffer_nodes,
372                open_cids: HashMap::new(),
373                minibuffer_content: MiniBufferContent::Data(Vec::new()),
374                minibuffer_prompt: None,
375                mount_dir_stat: empty_dir_stat(MOUNT_ROOT_QID, "/"),
376                control_file_stat: empty_file_stat(CONTROL_FILE_QID, CONTROL_FILE),
377                minibuffer_stat: empty_file_stat(MINIBUFFER_QID, MINIBUFFER),
378                scratch_stat: empty_file_stat(SCRATCH_QID, SCRATCH),
379                log_file_stat: empty_file_stat(LOG_FILE_QID, LOG_FILE),
380                mount_path,
381                auto_mount,
382            })),
383        }
384    }
385
386    /// Spawn a thread for running this filesystem and return a handle to it
387    pub fn run_threaded(self, custom_socket_path: Option<PathBuf>) -> FsHandle {
388        let s = self.state.lock().unwrap();
389        let auto_mount = s.auto_mount;
390        let mount_path = PathBuf::from(s.mount_path.clone());
391        let socket_path = custom_socket_path.unwrap_or_else(default_socket_path_for_pid);
392        drop(s);
393
394        let s = Server::new(self);
395        let handle = FsHandle {
396            path: socket_path.clone(),
397            inner: s.serve_socket_with_custom_path(socket_path.clone()),
398        };
399
400        if auto_mount {
401            let res = Command::new("9pfuse")
402                .args([socket_path, mount_path])
403                .spawn();
404
405            if let Ok(mut child) = res {
406                _ = child.wait();
407            }
408        }
409
410        handle
411    }
412}
413
414/// Spawn a listener to wait for a reply from the editor for our minibuffer selection
415fn spawn_minibuffer_listener(
416    data_rx: Receiver<String>,
417    fsys_tx: Sender<Vec<u8>>,
418    sub_rx: Receiver<Sender<Vec<u8>>>,
419) {
420    spawn(move || {
421        let data = match data_rx.recv() {
422            Ok(s) => s.into_bytes(),
423            Err(e) => {
424                error!("unable to read minibuffer output: {e}");
425                Vec::new()
426            }
427        };
428
429        // Reply to fsys first so the data is ready for incoming reads
430        _ = fsys_tx.send(data.clone());
431
432        // Any client currently blocked on a read then gets their own reply
433        for tx in sub_rx.try_iter() {
434            _ = tx.send(data.clone());
435        }
436    });
437}
438
439impl Serve9p for AdFs {
440    fn stat(&self, cid: ClientId, qid: u64, uname: &str) -> Result<Stat> {
441        trace!(?cid, %qid, %uname, "handling stat request");
442        let mut s = self.state.lock().unwrap();
443        s.buffer_nodes.update();
444
445        match qid {
446            MOUNT_ROOT_QID => Ok(s.mount_dir_stat.clone()),
447            CONTROL_FILE_QID => Ok(s.control_file_stat.clone()),
448            MINIBUFFER_QID => Ok(s.minibuffer_stat.clone()),
449            SCRATCH_QID => Ok(s.scratch_stat.clone()),
450            LOG_FILE_QID => Ok(s.log_file_stat.clone()),
451            BUFFERS_QID => Ok(s.buffer_nodes.stat().clone()),
452            qid => match s.buffer_nodes.get_stat_for_qid(qid) {
453                Some(stat) => Ok(stat.clone()),
454                None => Err(E_UNKNOWN_FILE.to_string()),
455            },
456        }
457    }
458
459    fn write_stat(&self, cid: ClientId, qid: u64, stat: Stat, uname: &str) -> Result<()> {
460        trace!(?cid, %qid, %uname, "handling write stat request");
461        let mut s = self.state.lock().unwrap();
462        s.buffer_nodes.update();
463
464        if stat.n_bytes == 0 {
465            trace!(%qid, %uname, "stat n_bytes=0, truncating file");
466            match qid {
467                MOUNT_ROOT_QID | CONTROL_FILE_QID | MINIBUFFER_QID | LOG_FILE_QID => (),
468                qid => s.buffer_nodes.truncate(qid),
469            }
470        }
471
472        Ok(())
473    }
474
475    fn walk(&self, cid: ClientId, parent_qid: u64, child: &str, uname: &str) -> Result<FileMeta> {
476        trace!(?cid, %parent_qid, %child, %uname, "handling walk request");
477        let mut s = self.state.lock().unwrap();
478        s.buffer_nodes.update();
479
480        match parent_qid {
481            MOUNT_ROOT_QID => match child {
482                CONTROL_FILE => Ok(s.control_file_stat.fm.clone()),
483                MINIBUFFER => Ok(s.minibuffer_stat.fm.clone()),
484                SCRATCH => Ok(s.scratch_stat.fm.clone()),
485                LOG_FILE => Ok(s.log_file_stat.fm.clone()),
486                BUFFERS_DIR => Ok(s.buffer_nodes.stat().fm.clone()),
487                _ => match s.buffer_nodes.lookup_file_stat(parent_qid, child) {
488                    Some(stat) => Ok(stat.fm.clone()),
489                    None => Err(format!("{E_UNKNOWN_FILE}: {parent_qid} {child}")),
490                },
491            },
492
493            qid if qid == BUFFERS_QID || s.buffer_nodes.is_known_buffer_qid(qid) => {
494                match s.buffer_nodes.lookup_file_stat(qid, child) {
495                    Some(stat) => Ok(stat.fm.clone()),
496                    None => Err(format!("{E_UNKNOWN_FILE}: {parent_qid} {child}")),
497                }
498            }
499
500            _ => Err(format!("{E_UNKNOWN_FILE}: {parent_qid} {child}")),
501        }
502    }
503
504    fn open(&self, cid: ClientId, qid: u64, mode: Mode, uname: &str) -> Result<IoUnit> {
505        trace!(?cid, %qid, %uname, ?mode, "handling open request");
506        let mut s = self.state.lock().unwrap();
507        s.buffer_nodes.update();
508
509        if qid == LOG_FILE_QID {
510            s.buffer_nodes.log.add_client(cid);
511        } else if !TOP_LEVEL_QIDS.contains(&qid)
512            && let QidCheck::Unknown = s.buffer_nodes.check_if_known_qid(qid)
513        {
514            return Err(format!("{E_UNKNOWN_FILE}: {qid}"));
515        }
516
517        s.add_open_cid(qid, cid);
518
519        Ok(IO_UNIT)
520    }
521
522    fn clunk(&self, cid: ClientId, qid: u64) {
523        trace!(?cid, %qid, "handling clunk request");
524        let mut s = self.state.lock().unwrap();
525
526        if qid == LOG_FILE_QID {
527            s.buffer_nodes.log.remove_client(cid);
528        } else if let QidCheck::EventFile { buf_qid } = s.buffer_nodes.check_if_known_qid(qid)
529            && s.readlocked_cid(qid) == Some(cid)
530        {
531            s.buffer_nodes.clear_input_filter(buf_qid);
532        }
533        s.remove_open_cid(qid, cid); // also handles clearing the read lock
534    }
535
536    fn read(
537        &self,
538        cid: ClientId,
539        qid: u64,
540        offset: usize,
541        count: usize,
542        uname: &str,
543    ) -> Result<ReadOutcome> {
544        trace!(?cid, %qid, %offset, %count, %uname, "handling read request");
545        let mut s = self.state.lock().unwrap();
546        s.buffer_nodes.update();
547
548        if qid == CONTROL_FILE_QID {
549            return Ok(ReadOutcome::Immediate(Vec::new()));
550        } else if qid == MINIBUFFER_QID {
551            return Ok(s.minibuffer_read(offset, count));
552        } else if qid == SCRATCH_QID {
553            return Ok(s.scratch_read(offset, count));
554        } else if qid == LOG_FILE_QID {
555            return Ok(s.buffer_nodes.log.events_since_last_read(cid));
556        }
557
558        if let QidCheck::EventFile { buf_qid } = s.buffer_nodes.check_if_known_qid(qid) {
559            match s.readlocked_cid(qid) {
560                Some(id) if id == cid => (),
561                Some(_) => return Ok(ReadOutcome::Immediate(Vec::new())),
562                None => {
563                    trace!("attaching filter qid={qid} cid={cid:?}");
564                    s.buffer_nodes.attach_input_filter(buf_qid)?;
565                    s.lock_qid_for_reading(qid, cid)?;
566                }
567            }
568        }
569
570        match s.buffer_nodes.get_file_content(qid, offset, count) {
571            InternalRead::Unknown => Err(format!("{E_UNKNOWN_FILE}: {qid}")),
572            InternalRead::Immediate(content) => Ok(ReadOutcome::Immediate(content)),
573            InternalRead::Blocked(tx) => Ok(ReadOutcome::Blocked(tx)),
574        }
575    }
576
577    fn read_dir(&self, cid: ClientId, qid: u64, uname: &str) -> Result<Vec<Stat>> {
578        trace!(?cid, %qid, %uname, "handling read dir request");
579        let mut s = self.state.lock().unwrap();
580        s.buffer_nodes.update();
581
582        match qid {
583            MOUNT_ROOT_QID => Ok(vec![
584                s.log_file_stat.clone(),
585                s.minibuffer_stat.clone(),
586                s.scratch_stat.clone(),
587                s.control_file_stat.clone(),
588                s.buffer_nodes.stat().clone(),
589            ]),
590            BUFFERS_QID => Ok(s.buffer_nodes.top_level_stats()),
591            qid => s
592                .buffer_nodes
593                .buffer_level_stats(qid)
594                .ok_or_else(|| E_UNKNOWN_FILE.to_string()),
595        }
596    }
597
598    fn write(
599        &self,
600        cid: ClientId,
601        qid: u64,
602        offset: usize,
603        data: Vec<u8>,
604        uname: &str,
605    ) -> Result<usize> {
606        trace!(?cid, %qid, %offset, n_bytes=%data.len(), %uname, "handling write request");
607        let mut s = self.state.lock().unwrap();
608        s.buffer_nodes.update();
609
610        let n_bytes = data.len();
611        let str = match String::from_utf8(data.to_vec()) {
612            Ok(s) => s,
613            Err(e) => return Err(format!("Invalid data: {e}")),
614        };
615
616        match qid {
617            CONTROL_FILE_QID => match str.strip_prefix("minibuffer-prompt ") {
618                Some(prompt) => {
619                    s.minibuffer_prompt = Some(prompt.to_string());
620                    Ok(n_bytes)
621                }
622                None => {
623                    s.control_file_stat.last_modified = SystemTime::now();
624                    match Message::send(Req::ControlMessage { msg: str }, &s.tx) {
625                        Ok(_) => Ok(n_bytes),
626                        Err(e) => Err(format!("unable to execute control message: {e}")),
627                    }
628                }
629            },
630
631            MINIBUFFER_QID => s.minibuffer_write(str),
632            SCRATCH_QID => s.scratch_write(str),
633            CURRENT_BUFFER_QID => s.set_active_buffer(str),
634
635            LOG_FILE_QID | INDEX_BUFFER_QID => Err(E_NOT_ALLOWED.to_string()),
636
637            qid => s.buffer_nodes.write(qid, str, offset),
638        }
639    }
640
641    // TODO: allow remove of a buffer to close the buffer
642    fn remove(&self, cid: ClientId, qid: u64, uname: &str) -> Result<()> {
643        trace!(?cid, %qid, %uname, "handling remove request");
644        Err("remove not allowed".to_string())
645    }
646
647    fn create(
648        &self,
649        cid: ClientId,
650        parent: u64,
651        name: &str,
652        perm: Perm,
653        mode: Mode,
654        uname: &str,
655    ) -> Result<(FileMeta, IoUnit)> {
656        trace!(?cid, %parent, %name, ?perm, ?mode, %uname, "handling create request");
657        Err("create not allowed".to_string())
658    }
659}
660
661fn apply_offset(data: &[u8], offset: usize, count: usize) -> Vec<u8> {
662    data.iter()
663        .skip(offset)
664        .take(count)
665        .copied()
666        .collect::<Vec<u8>>()
667}
668
669fn empty_dir_stat(qid: u64, name: &str) -> Stat {
670    Stat {
671        fm: FileMeta::dir(name, qid),
672        perms: Perm::OWNER_READ | Perm::OWNER_EXEC,
673        n_bytes: 0,
674        last_accesses: SystemTime::now(),
675        last_modified: SystemTime::now(),
676        owner: "ad".into(),
677        group: "ad".into(),
678        last_modified_by: "ad".into(),
679    }
680}
681
682fn empty_file_stat(qid: u64, name: &str) -> Stat {
683    Stat {
684        fm: FileMeta::file(name, qid),
685        perms: Perm::OWNER_READ | Perm::OWNER_WRITE,
686        n_bytes: 0,
687        last_accesses: SystemTime::now(),
688        last_modified: SystemTime::now(),
689        owner: "ad".into(),
690        group: "ad".into(),
691        last_modified_by: "ad".into(),
692    }
693}