1use 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
71const MOUNT_ROOT_QID: u64 = 0;
74const CONTROL_FILE_QID: u64 = 1;
76const CONTROL_FILE: &str = "ctl";
77const LOG_FILE_QID: u64 = 2;
79const LOG_FILE: &str = "log";
80const MINIBUFFER_QID: u64 = 3;
82const MINIBUFFER: &str = "minibuffer";
83const SCRATCH_QID: u64 = 4;
85const SCRATCH: &str = "scratch";
86const BUFFERS_QID: u64 = 5;
88const BUFFERS_DIR: &str = "buffers";
89const INDEX_BUFFER_QID: u64 = 6;
91const INDEX_BUFFER: &str = "index";
92const CURRENT_BUFFER_QID: u64 = 7;
94const CURRENT_BUFFER: &str = "current";
95
96const 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#[derive(Debug)]
139pub struct FsHandle {
140 path: PathBuf,
141 inner: JoinHandle<()>,
142}
143
144impl FsHandle {
145 pub fn remove_socket(&self) {
147 _ = remove_file(&self.path);
148 }
149
150 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#[derive(Debug)]
172struct State {
173 tx: Sender<Event>,
174 buffer_nodes: BufferNodes,
175 minibuffer_content: MiniBufferContent,
176 minibuffer_prompt: Option<String>,
177 open_cids: HashMap<u64, Cids>,
179 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 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#[derive(Debug)]
348pub(crate) struct AdFs {
349 state: Arc<Mutex<State>>,
350}
351
352impl AdFs {
353 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 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
414fn 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 _ = fsys_tx.send(data.clone());
431
432 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); }
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 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}