daicon_native/
file.rs

1use std::{
2    fs::{File, OpenOptions},
3    io::{ErrorKind, Read, Seek, SeekFrom, Write},
4};
5
6use anyhow::{Context as _, Error};
7use daicon::protocol::{FileAction, FileMessage, ReadResult, WriteLocation, WriteResult};
8use stewart::{Actor, Context, Options, Sender, State};
9use tracing::{event, instrument, Level};
10
11#[instrument("SystemFile", skip_all)]
12pub fn open_system_file(
13    ctx: &mut Context,
14    path: String,
15    truncate: bool,
16) -> Result<Sender<FileMessage>, Error> {
17    event!(Level::INFO, "opening");
18
19    let (mut ctx, sender) = ctx.create(Options::default())?;
20
21    let file = OpenOptions::new()
22        .read(true)
23        .write(true)
24        .truncate(truncate)
25        .create(true)
26        .open(path)
27        .context("failed to open system file for writing")?;
28
29    let actor = SystemFile { file };
30    ctx.start(actor)?;
31
32    Ok(sender)
33}
34
35struct SystemFile {
36    file: File,
37}
38
39impl Actor for SystemFile {
40    type Message = FileMessage;
41
42    #[instrument("SystemFile", skip_all)]
43    fn process(&mut self, ctx: &mut Context, state: &mut State<Self>) -> Result<(), Error> {
44        while let Some(message) = state.next() {
45            match message.action {
46                FileAction::Read(action) => {
47                    // TODO: Currently remaining bytes after EOF are kept zero, but maybe we want to
48                    // feedback a lack of remaining bytes.
49
50                    let mut data = vec![0u8; action.size as usize];
51
52                    self.file.seek(SeekFrom::Start(action.offset))?;
53                    read_exact_eof(&mut self.file, &mut data)?;
54
55                    // Reply result
56                    let result = ReadResult {
57                        id: message.id,
58                        offset: action.offset,
59                        data,
60                    };
61                    action.on_result.send(ctx, result);
62                }
63                FileAction::Write(action) => {
64                    // Seek to given location
65                    let from = match action.location {
66                        WriteLocation::Offset(offset) => SeekFrom::Start(offset),
67                        WriteLocation::Append => SeekFrom::End(0),
68                    };
69                    self.file.seek(from)?;
70                    let offset = self.file.stream_position()?;
71
72                    // Perform the write
73                    self.file.write_all(&action.data)?;
74
75                    // Reply result
76                    let result = WriteResult {
77                        id: message.id,
78                        offset,
79                    };
80                    action.on_result.send(ctx, result);
81                }
82            }
83        }
84
85        Ok(())
86    }
87}
88
89/// Copy of read_exact except allowing for EOF.
90fn read_exact_eof(file: &mut File, mut buf: &mut [u8]) -> Result<(), Error> {
91    while !buf.is_empty() {
92        match file.read(buf) {
93            Ok(0) => break,
94            Ok(n) => {
95                let tmp = buf;
96                buf = &mut tmp[n..];
97            }
98            Err(error) => match error.kind() {
99                ErrorKind::Interrupted => {}
100                ErrorKind::UnexpectedEof => break,
101                _ => return Err(error.into()),
102            },
103        }
104    }
105
106    Ok(())
107}