Skip to main content

kernel/
file.rs

1use core::mem::{self, MaybeUninit};
2use core::slice;
3
4use alloc::sync::Arc;
5
6use crate::console::Console;
7use crate::fs::{BSIZE, FsError, Inode, Stat};
8use crate::log::Operation;
9use crate::param::{MAXOPBLOCKS, NDEV, NFILE};
10use crate::pipe::Pipe;
11use crate::proc;
12use crate::sleeplock::SleepLock;
13use crate::spinlock::SpinLock;
14use crate::syscall::SysError;
15use crate::vm::VA;
16
17#[derive(Debug, Clone)]
18pub enum FileType {
19    None,
20    Pipe { pipe: Arc<Pipe> },
21    Inode { inode: Inode },
22    Device { inode: Inode, major: u16 },
23}
24
25/// File metadata protected by table-wide spinlock
26#[derive(Debug, Clone)]
27pub struct FileMeta {
28    pub ref_count: usize,
29}
30
31/// Per-file mutable state protected by per-file sleeplock
32#[derive(Debug, Clone)]
33pub struct FileInner {
34    /// Index into the file table.
35    pub readable: bool,
36    pub writeable: bool,
37    pub r#type: FileType,
38    pub offset: u32,
39}
40
41pub static FILE_TABLE: FileTable = FileTable::new();
42
43/// Global file table
44#[derive(Debug)]
45pub struct FileTable {
46    /// Protects allocation and reference counts
47    pub meta: SpinLock<[FileMeta; NFILE]>,
48    /// Per-file locks for concurrent access to different files
49    pub inner: [SleepLock<FileInner>; NFILE],
50}
51
52impl FileTable {
53    const fn new() -> Self {
54        let meta = {
55            let mut array: [MaybeUninit<FileMeta>; NFILE] =
56                unsafe { MaybeUninit::uninit().assume_init() };
57
58            let mut i = 0;
59            while i < NFILE {
60                array[i] = MaybeUninit::new(FileMeta { ref_count: 0 });
61                i += 1;
62            }
63
64            SpinLock::new(
65                unsafe {
66                    mem::transmute::<[MaybeUninit<FileMeta>; NFILE], [FileMeta; NFILE]>(array)
67                },
68                "filetable",
69            )
70        };
71
72        let inner = {
73            let mut array: [MaybeUninit<SleepLock<FileInner>>; NFILE] =
74                unsafe { MaybeUninit::uninit().assume_init() };
75
76            let mut i = 0;
77            while i < NFILE {
78                array[i] = MaybeUninit::new(SleepLock::new(
79                    FileInner {
80                        readable: false,
81                        writeable: false,
82                        r#type: FileType::None,
83                        offset: 0,
84                    },
85                    "file",
86                ));
87                i += 1;
88            }
89
90            unsafe {
91                mem::transmute::<
92                    [MaybeUninit<SleepLock<FileInner>>; NFILE],
93                    [SleepLock<FileInner>; NFILE],
94                >(array)
95            }
96        };
97
98        Self { meta, inner }
99    }
100}
101
102/// File handle, just an index into the `FileTable`
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct File {
105    pub id: usize,
106}
107
108impl File {
109    /// Allocates a file structure.
110    pub fn alloc() -> Result<Self, FsError> {
111        let mut meta = FILE_TABLE.meta.lock();
112
113        for (i, meta) in meta.iter_mut().enumerate() {
114            if meta.ref_count == 0 {
115                meta.ref_count = 1;
116
117                return Ok(Self { id: i });
118            }
119        }
120
121        err!(FsError::OutOfFile);
122    }
123
124    /// Incremets the reference count for the file.
125    pub fn dup(&mut self) -> Self {
126        let meta = &mut FILE_TABLE.meta.lock()[self.id];
127
128        assert!(meta.ref_count >= 1, "filedup");
129
130        meta.ref_count += 1;
131
132        self.clone()
133    }
134
135    /// Decrements the reference count and closes the file if it reaches 0.
136    pub fn close(&mut self) {
137        let mut meta_guard = FILE_TABLE.meta.lock();
138        let meta = &mut meta_guard[self.id];
139
140        assert!(meta.ref_count >= 1, "fileclose");
141
142        meta.ref_count -= 1;
143        if meta.ref_count > 0 {
144            return;
145        }
146
147        let inner_copy = {
148            let mut inner = FILE_TABLE.inner[self.id].lock();
149            // copy inner before resetting fields
150            let copy = inner.clone();
151
152            meta.ref_count = 0;
153            inner.r#type = FileType::None;
154
155            drop(meta_guard);
156            copy
157        }; // drop both inner and meta locks
158
159        match inner_copy.r#type {
160            FileType::None => {}
161            FileType::Pipe { pipe } => {
162                pipe.close(inner_copy.writeable);
163            }
164            FileType::Inode { inode } | FileType::Device { inode, .. } => {
165                let _op = Operation::begin();
166                inode.put();
167            }
168        }
169    }
170
171    /// Gets metadata about file.
172    pub fn stat(&self, addr: VA) -> Result<(), SysError> {
173        let file_inner = FILE_TABLE.inner[self.id].lock();
174
175        match &file_inner.r#type {
176            FileType::Inode { inode } | FileType::Device { inode, .. } => {
177                let inode_inner = inode.lock();
178                let stat = inode.stat(&inode_inner);
179                inode.unlock(inode_inner);
180
181                let src = unsafe {
182                    slice::from_raw_parts(&stat as *const _ as *const u8, size_of::<Stat>())
183                };
184                if log!(proc::copy_to_user(src, addr)).is_err() {
185                    err!(SysError::BadAddress);
186                }
187
188                Ok(())
189            }
190            _ => Err(SysError::BadDescriptor),
191        }
192    }
193
194    /// Reads from file.
195    pub fn read(&self, addr: VA, n: usize) -> Result<usize, SysError> {
196        let mut file_inner = FILE_TABLE.inner[self.id].lock();
197
198        if !file_inner.readable {
199            err!(SysError::BadDescriptor);
200        }
201
202        match &mut file_inner.r#type {
203            FileType::None => panic!("fileread"),
204
205            FileType::Pipe { pipe } => pipe.read(addr, n),
206
207            FileType::Inode { inode } => {
208                let inode = inode.clone();
209                let mut inode_inner = inode.lock();
210
211                let dst = unsafe { slice::from_raw_parts_mut(addr.as_mut_ptr(), n) };
212                let read = log!(inode.read(&mut inode_inner, file_inner.offset, dst, true));
213
214                if let Ok(read) = read {
215                    file_inner.offset += read;
216                }
217
218                inode.unlock(inode_inner);
219
220                if let Ok(read) = read {
221                    Ok(read as usize)
222                } else {
223                    err!(SysError::IoError);
224                }
225            }
226
227            FileType::Device { inode: _, major } => match &DEVICES[*major as usize] {
228                Some(dev) => (dev.read)(addr, n),
229                None => err!(SysError::NoEntry),
230            },
231        }
232    }
233
234    /// Writes to a file.
235    pub fn write(&mut self, addr: VA, n: usize) -> Result<usize, SysError> {
236        let mut file_inner = FILE_TABLE.inner[self.id].lock();
237
238        if !file_inner.writeable {
239            err!(SysError::BadDescriptor);
240        }
241
242        match &mut file_inner.r#type {
243            FileType::None => panic!("filewrite"),
244
245            FileType::Pipe { pipe } => pipe.write(addr, n),
246
247            FileType::Inode { inode } => {
248                let inode = inode.clone();
249
250                // write a few block at a time to avoid exceeding the maximum log transaction size,
251                // including inode, indirect block, allocation blocks, and 2 block of slop for
252                // non-aligned writes.
253                let max = ((MAXOPBLOCKS - 1 - 1 - 2) / 2) * BSIZE;
254                let mut i = 0;
255
256                while i < n {
257                    let n1 = (n - i).min(max);
258
259                    let _op = Operation::begin();
260                    let mut inode_inner = inode.lock();
261
262                    let src =
263                        unsafe { slice::from_raw_parts((addr.as_usize() + i) as *const u8, n1) };
264                    let write = log!(inode.write(&mut inode_inner, file_inner.offset, src, true));
265
266                    if let Ok(w) = write {
267                        file_inner.offset += w;
268                    }
269
270                    inode.unlock(inode_inner);
271                    drop(_op);
272
273                    if write.is_err() {
274                        break;
275                    }
276
277                    i += write.unwrap() as usize;
278                }
279
280                if i == n {
281                    Ok(n)
282                } else {
283                    err!(SysError::IoError);
284                }
285            }
286
287            FileType::Device { inode: _, major } => match &DEVICES[*major as usize] {
288                Some(dev) => (dev.write)(addr, n),
289                None => err!(SysError::NoEntry),
290            },
291        }
292    }
293}
294
295/// File open flags
296pub struct OpenFlag;
297
298impl OpenFlag {
299    pub const READ_ONLY: usize = 0x000;
300    pub const WRITE_ONLY: usize = 0x001;
301    pub const READ_WRITE: usize = 0x002;
302    pub const CREATE: usize = 0x200;
303    pub const TRUNCATE: usize = 0x400;
304}
305
306/// Device interface
307#[derive(Debug, Clone, Copy)]
308pub struct Device {
309    pub read: fn(addr: VA, n: usize) -> Result<usize, SysError>,
310    pub write: fn(addr: VA, n: usize) -> Result<usize, SysError>,
311}
312
313/// Console device major number
314pub const CONSOLE: usize = 1;
315
316/// Device table
317pub static DEVICES: [Option<Device>; NDEV] = {
318    let mut devices = [None; NDEV];
319    devices[CONSOLE] = Some(Device {
320        read: Console::read,
321        write: Console::write,
322    });
323    devices
324};