Skip to main content

starnix_modules_ext4/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![recursion_limit = "256"]
6
7use ext4_lib::parser::{Parser as ExtParser, XattrMap as ExtXattrMap};
8use ext4_lib::readers::VmoReader;
9use ext4_lib::structs::{EntryType, INode, ROOT_INODE_NUM};
10use once_cell::sync::OnceCell;
11use starnix_core::mm::ProtectionFlags;
12use starnix_core::mm::memory::MemoryObject;
13use starnix_core::task::CurrentTask;
14use starnix_core::vfs::{
15    CacheMode, DEFAULT_BYTES_PER_BLOCK, DirEntry, DirectoryEntryType, DirentSink, FileObject,
16    FileOps, FileSystem, FileSystemHandle, FileSystemOps, FileSystemOptions, FsNode, FsNodeFlags,
17    FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr, FsString, MemoryRegularFile, SeekTarget,
18    SymlinkTarget, XattrOp, XattrStorage, default_seek, fileops_impl_directory,
19    fileops_impl_noop_sync, fs_node_impl_dir_readonly, fs_node_impl_not_dir, fs_node_impl_symlink,
20    fs_node_impl_xattr_delegate,
21};
22use starnix_logging::{impossible_error, track_stub};
23
24use starnix_types::vfs::default_statfs;
25use starnix_uapi::auth::FsCred;
26use starnix_uapi::errors::Errno;
27use starnix_uapi::file_mode::FileMode;
28use starnix_uapi::mount_flags::FileSystemFlags;
29use starnix_uapi::open_flags::OpenFlags;
30use starnix_uapi::{EXT4_SUPER_MAGIC, errno, error, ino_t, off_t, statfs};
31use std::sync::Arc;
32use std::sync::atomic::Ordering;
33
34mod pager;
35
36use pager::{Pager, PagerExtent};
37
38pub struct ExtFilesystem {
39    parser: ExtParser,
40    pager: Arc<Pager>,
41}
42
43impl FileSystemOps for ExtFilesystem {
44    fn name(&self) -> &'static FsStr {
45        "ext4".into()
46    }
47
48    fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
49        Ok(default_statfs(EXT4_SUPER_MAGIC))
50    }
51}
52
53struct ExtNode {
54    inode_num: u32,
55    inode: INode,
56    xattrs: ExtXattrMap,
57}
58
59impl ExtFilesystem {
60    pub fn new_fs(
61        current_task: &CurrentTask,
62        options: FileSystemOptions,
63    ) -> Result<FileSystemHandle, Errno> {
64        let mut open_flags = OpenFlags::RDWR;
65        let mut prot_flags = ProtectionFlags::READ | ProtectionFlags::WRITE | ProtectionFlags::EXEC;
66        if options.flags.load(Ordering::Relaxed).contains(FileSystemFlags::RDONLY) {
67            open_flags = OpenFlags::RDONLY;
68            prot_flags ^= ProtectionFlags::WRITE;
69        }
70
71        let source_device = current_task.open_file(options.source.as_ref(), open_flags)?;
72
73        // Note that we *require* get_memory to work here for performance reasons.  Fallback to
74        // FIDL-based read/write API is not an option.
75        let memory = source_device.get_memory(current_task, None, prot_flags)?;
76        let pager_vmo = memory
77            .as_vmo()
78            .ok_or_else(|| errno!(EINVAL))?
79            .duplicate_handle(zx::Rights::SAME_RIGHTS)
80            .map_err(impossible_error)?;
81        let parser_vmo = Arc::new(
82            memory
83                .as_vmo()
84                .ok_or_else(|| errno!(EINVAL))?
85                .duplicate_handle(zx::Rights::SAME_RIGHTS)
86                .map_err(impossible_error)?,
87        );
88        let parser = ExtParser::new(Box::new(VmoReader::new(parser_vmo)));
89        let pager =
90            Arc::new(Pager::new(pager_vmo, parser.block_size().map_err(|e| errno!(EIO, e))?)?);
91        let fs = Self { parser, pager };
92        let root_node = ExtNode::new(&fs, ROOT_INODE_NUM)?;
93        let root_info = fs_node_info_from_ext_node(&root_node);
94        let ops = ExtDirectory { inner: Arc::new(root_node) };
95        let fs = FileSystem::new(
96            current_task.kernel(),
97            CacheMode::Cached(current_task.kernel().fs_cache_config()),
98            fs,
99            options,
100        )?;
101        fs.create_root_with_info(ROOT_INODE_NUM as ino_t, ops, root_info);
102        Ok(fs)
103    }
104}
105
106impl ExtNode {
107    fn new(fs: &ExtFilesystem, inode_num: u32) -> Result<ExtNode, Errno> {
108        let inode = fs.parser.inode(inode_num).map_err(|e| errno!(EIO, e))?;
109        let xattrs = fs.parser.inode_xattrs(inode_num).unwrap_or_default();
110        Ok(ExtNode { inode_num, inode, xattrs })
111    }
112}
113
114impl XattrStorage for ExtNode {
115    fn list_xattrs(&self) -> Result<Vec<FsString>, Errno> {
116        Ok(self.xattrs.keys().map(|k| k.clone().into()).collect())
117    }
118
119    fn get_xattr(&self, name: &FsStr) -> Result<Box<[u8]>, Errno> {
120        self.xattrs.get(&**name).map(|a| a.as_slice().into()).ok_or_else(|| errno!(ENODATA))
121    }
122
123    fn set_xattr(&self, _name: &FsStr, _value: &[u8], _op: XattrOp) -> Result<(), Errno> {
124        error!(ENOSYS)
125    }
126    fn remove_xattr(&self, _name: &FsStr) -> Result<(), Errno> {
127        error!(ENOSYS)
128    }
129}
130
131struct ExtDirectory {
132    inner: Arc<ExtNode>,
133}
134
135impl FsNodeOps for ExtDirectory {
136    fs_node_impl_dir_readonly!();
137    fs_node_impl_xattr_delegate!(self, self.inner);
138
139    fn create_file_ops(
140        &self,
141        _node: &FsNode,
142        _current_task: &CurrentTask,
143        _flags: OpenFlags,
144    ) -> Result<Box<dyn FileOps>, Errno> {
145        Ok(Box::new(ExtDirFileObject { inner: self.inner.clone() }))
146    }
147
148    fn lookup(
149        &self,
150        entry: &DirEntry,
151        _current_task: &CurrentTask,
152        name: &FsStr,
153    ) -> Result<FsNodeHandle, Errno> {
154        let fs = entry.node.fs();
155        let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
156        let dir_entries =
157            fs_ops.parser.entries_from_inode(&self.inner.inode).map_err(|e| errno!(EIO, e))?;
158        let entry = dir_entries
159            .iter()
160            .find(|e| e.name_bytes() == name)
161            .ok_or_else(|| errno!(ENOENT, name))?;
162        let ext_node = ExtNode::new(fs_ops, entry.e2d_ino.into())?;
163        let inode_num = ext_node.inode_num as ino_t;
164        fs.get_or_create_node(inode_num, || {
165            let entry_type = EntryType::from_u8(entry.e2d_type).map_err(|e| errno!(EIO, e))?;
166            let info = fs_node_info_from_ext_node(&ext_node);
167
168            let ops: Box<dyn FsNodeOps> = match entry_type {
169                EntryType::RegularFile => Box::new(ExtFile::new(ext_node, name.to_owned())),
170                EntryType::Directory => Box::new(ExtDirectory { inner: Arc::new(ext_node) }),
171                EntryType::SymLink => Box::new(ExtSymlink { inner: ext_node }),
172                EntryType::Unknown => {
173                    track_stub!(TODO("https://fxbug.dev/322873719"), "ext4 unknown entry type");
174                    Box::new(ExtFile::new(ext_node, name.to_owned()))
175                }
176                EntryType::CharacterDevice => {
177                    track_stub!(TODO("https://fxbug.dev/322874445"), "ext4 character device");
178                    Box::new(ExtFile::new(ext_node, name.to_owned()))
179                }
180                EntryType::BlockDevice => {
181                    track_stub!(TODO("https://fxbug.dev/322874062"), "ext4 block device");
182                    Box::new(ExtFile::new(ext_node, name.to_owned()))
183                }
184                EntryType::FIFO => {
185                    track_stub!(TODO("https://fxbug.dev/322874249"), "ext4 fifo");
186                    Box::new(ExtFile::new(ext_node, name.to_owned()))
187                }
188                EntryType::Socket => {
189                    track_stub!(TODO("https://fxbug.dev/322874081"), "ext4 socket");
190                    Box::new(ExtFile::new(ext_node, name.to_owned()))
191                }
192            };
193
194            let child = FsNode::new_uncached(inode_num, ops, &fs, info, FsNodeFlags::empty());
195            Ok(child)
196        })
197    }
198}
199
200fn fs_node_info_from_ext_node(ext_node: &ExtNode) -> FsNodeInfo {
201    let mode = FileMode::from_bits(ext_node.inode.e2di_mode.into());
202    let uid = get_uid_from_node(ext_node);
203    let gid = get_gid_from_node(ext_node);
204    let owner = FsCred { uid, gid };
205    let mut info = FsNodeInfo::new(mode, owner);
206    info.size = get_size_from_node(ext_node, &mode) as usize;
207    info.blocks = get_blocks_from_node(ext_node) as usize;
208    info.blksize = DEFAULT_BYTES_PER_BLOCK;
209    info.link_count = ext_node.inode.e2di_nlink.into();
210    info
211}
212
213fn merge_low_high_16(low: u32, high: u32) -> u32 {
214    low | (high << 16)
215}
216
217fn merge_low_high_32(low: u64, high: u64) -> u64 {
218    low | (high << 32)
219}
220
221fn get_uid_from_node(ext_node: &ExtNode) -> u32 {
222    let uid_lower: u32 = ext_node.inode.e2di_uid.into();
223    let uid_upper: u32 = ext_node.inode.e2di_uid_high.into();
224    merge_low_high_16(uid_lower, uid_upper)
225}
226
227fn get_gid_from_node(ext_node: &ExtNode) -> u32 {
228    let gid_lower: u32 = ext_node.inode.e2di_gid.into();
229    let gid_upper: u32 = ext_node.inode.e2di_gid_high.into();
230    merge_low_high_16(gid_lower, gid_upper)
231}
232
233fn get_size_from_node(ext_node: &ExtNode, mode: &FileMode) -> u64 {
234    if mode.is_reg() {
235        let size_lower: u64 = ext_node.inode.e2di_size.into();
236        let size_upper: u64 = ext_node.inode.e2di_size_high.into();
237        merge_low_high_32(size_lower, size_upper)
238    } else {
239        ext_node.inode.e2di_size.into()
240    }
241}
242
243fn get_blocks_from_node(ext_node: &ExtNode) -> u64 {
244    let blocks_lower: u64 = ext_node.inode.e2di_nblock.into();
245    let blocks_upper: u64 = ext_node.inode.e2di_nblock_high.into();
246    merge_low_high_32(blocks_lower, blocks_upper)
247}
248
249struct ExtFile {
250    inner: ExtNode,
251    name: FsString,
252
253    // The VMO here will be a child of the main VMO that the pager holds.  We want to keep it here
254    // so that whilst ExtFile remains resident, we hold a child reference to the main VMO which
255    // will prevent the pager from dropping the VMO (and any data we might have paged-in).
256    memory: OnceCell<Arc<MemoryObject>>,
257}
258
259impl ExtFile {
260    fn new(inner: ExtNode, name: FsString) -> Self {
261        ExtFile { inner, name, memory: OnceCell::new() }
262    }
263}
264
265impl FsNodeOps for ExtFile {
266    fs_node_impl_not_dir!();
267    fs_node_impl_xattr_delegate!(self, self.inner);
268
269    fn create_file_ops(
270        &self,
271        node: &FsNode,
272        _current_task: &CurrentTask,
273        _flags: OpenFlags,
274    ) -> Result<Box<dyn FileOps>, Errno> {
275        let fs = node.fs();
276        let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
277        let inode_num = self.inner.inode_num;
278        let memory = self.memory.get_or_try_init(|| {
279            let (file_size, extents) = fs_ops
280                .parser
281                .read_extents(self.inner.inode_num)
282                .map_err(|e| errno!(EINVAL, format!("failed to read extents: {e}")))?;
283            // The extents should be sorted which we rely on later.
284            let mut pager_extents = Vec::with_capacity(extents.len());
285            let mut last_block = 0;
286            for e in extents {
287                let pager_extent = PagerExtent::from(e);
288                if pager_extent.logical.start < last_block {
289                    return error!(EIO, "Bad extent");
290                }
291                last_block = pager_extent.logical.end;
292                pager_extents.push(pager_extent);
293            }
294            Ok(Arc::new(MemoryObject::from(
295                fs_ops
296                    .pager
297                    .register(self.name.as_ref(), inode_num, file_size, &pager_extents)
298                    .map_err(|e| errno!(EINVAL, e))?,
299            )))
300        })?;
301
302        // TODO(https://fxbug.dev/42080696) returned memory shouldn't be writeable
303        Ok(Box::new(MemoryRegularFile::new(memory.clone())))
304    }
305}
306
307impl From<ext4_lib::structs::Extent> for PagerExtent {
308    fn from(e: ext4_lib::structs::Extent) -> Self {
309        let block_count: u16 = e.e_len.into();
310        let start = e.e_blk.into();
311        Self { logical: start..start + block_count as u32, physical_block: e.target_block_num() }
312    }
313}
314
315struct ExtSymlink {
316    inner: ExtNode,
317}
318
319impl FsNodeOps for ExtSymlink {
320    fs_node_impl_symlink!();
321    fs_node_impl_xattr_delegate!(self, self.inner);
322
323    fn readlink(&self, node: &FsNode, _current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> {
324        let fs = node.fs();
325        let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
326        let data = fs_ops.parser.read_data(self.inner.inode_num).map_err(|e| errno!(EIO, e))?;
327        Ok(SymlinkTarget::Path(data.into()))
328    }
329}
330
331struct ExtDirFileObject {
332    inner: Arc<ExtNode>,
333}
334
335impl FileOps for ExtDirFileObject {
336    fileops_impl_directory!();
337    fileops_impl_noop_sync!();
338
339    fn seek(
340        &self,
341        _file: &FileObject,
342        _current_task: &CurrentTask,
343        current_offset: off_t,
344        target: SeekTarget,
345    ) -> Result<off_t, Errno> {
346        Ok(default_seek(current_offset, target, || error!(EINVAL))?)
347    }
348
349    fn readdir(
350        &self,
351        file: &FileObject,
352        _current_task: &CurrentTask,
353        sink: &mut dyn DirentSink,
354    ) -> Result<(), Errno> {
355        let fs = file.node().fs();
356        let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
357        let dir_entries =
358            fs_ops.parser.entries_from_inode(&self.inner.inode).map_err(|e| errno!(EIO, e))?;
359
360        if sink.offset() as usize >= dir_entries.len() {
361            return Ok(());
362        }
363
364        for entry in dir_entries[(sink.offset() as usize)..].iter() {
365            let inode_num = entry.e2d_ino.into();
366            let entry_type = directory_entry_type(
367                EntryType::from_u8(entry.e2d_type).map_err(|e| errno!(EIO, e))?,
368            );
369            sink.add(inode_num, sink.offset() + 1, entry_type, entry.name_bytes().into())?;
370        }
371        Ok(())
372    }
373}
374
375fn directory_entry_type(entry_type: EntryType) -> DirectoryEntryType {
376    match entry_type {
377        EntryType::Unknown => DirectoryEntryType::UNKNOWN,
378        EntryType::RegularFile => DirectoryEntryType::REG,
379        EntryType::Directory => DirectoryEntryType::DIR,
380        EntryType::CharacterDevice => DirectoryEntryType::CHR,
381        EntryType::BlockDevice => DirectoryEntryType::BLK,
382        EntryType::FIFO => DirectoryEntryType::FIFO,
383        EntryType::Socket => DirectoryEntryType::SOCK,
384        EntryType::SymLink => DirectoryEntryType::LNK,
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use ext4_lib::structs::INode;
392    use starnix_uapi::file_mode::mode;
393    use zerocopy::FromBytes;
394    use zerocopy::byteorder::little_endian::{U16 as LE16, U32 as LE32};
395
396    fn default_inode() -> INode {
397        let zero = vec![0; 160];
398        INode::read_from_bytes(&zero).expect("failed to read from bytes")
399    }
400
401    fn create_test_ext_node(inode: INode) -> ExtNode {
402        ExtNode { inode_num: 1, inode, xattrs: ExtXattrMap::default() }
403    }
404
405    #[test]
406    fn test_get_uid_from_node() {
407        let mut inode = default_inode();
408        inode.e2di_uid = LE16::new(1001);
409        inode.e2di_uid_high = LE16::new(1);
410        let node = create_test_ext_node(inode);
411        assert_eq!(get_uid_from_node(&node), (1 << 16) | 1001);
412    }
413
414    #[test]
415    fn test_get_gid_from_node() {
416        let mut inode = default_inode();
417        inode.e2di_gid = LE16::new(1002);
418        inode.e2di_gid_high = LE16::new(2);
419        let node = create_test_ext_node(inode);
420        assert_eq!(get_gid_from_node(&node), (2 << 16) | 1002);
421    }
422
423    #[test]
424    fn test_get_size_from_node() {
425        // Test with a regular file.
426        let mut inode = default_inode();
427        inode.e2di_size = LE32::new(0x12345678);
428        inode.e2di_size_high = LE32::new(0x9);
429        let node = create_test_ext_node(inode);
430        let mode = mode!(IFREG, 0o777);
431        assert_eq!(get_size_from_node(&node, &mode), (0x9 << 32) | 0x12345678);
432
433        // Test with a directory, where size_high should be ignored.
434        let mode = mode!(IFDIR, 0o777);
435        assert_eq!(get_size_from_node(&node, &mode), 0x12345678);
436    }
437
438    #[test]
439    fn test_get_blocks_from_node() {
440        let mut inode = default_inode();
441        inode.e2di_nblock = LE32::new(0xABCDE);
442        inode.e2di_nblock_high = LE16::new(0x3);
443        let node = create_test_ext_node(inode);
444        assert_eq!(get_blocks_from_node(&node), (0x3 << 32) | 0xABCDE);
445    }
446}