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