1#![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, DirectoryEntryType, DirentSink, FileObject, FileOps,
16 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 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 ops = ExtDirectory { inner: Arc::new(ExtNode::new(&fs, ROOT_INODE_NUM)?) };
93 let fs = FileSystem::new(
94 current_task.kernel(),
95 CacheMode::Cached(current_task.kernel().fs_cache_config()),
96 fs,
97 options,
98 )?;
99 fs.create_root(ROOT_INODE_NUM as ino_t, ops);
100 Ok(fs)
101 }
102}
103
104impl ExtNode {
105 fn new(fs: &ExtFilesystem, inode_num: u32) -> Result<ExtNode, Errno> {
106 let inode = fs.parser.inode(inode_num).map_err(|e| errno!(EIO, e))?;
107 let xattrs = fs.parser.inode_xattrs(inode_num).unwrap_or_default();
108 Ok(ExtNode { inode_num, inode, xattrs })
109 }
110}
111
112impl XattrStorage for ExtNode {
113 fn list_xattrs(&self) -> Result<Vec<FsString>, Errno> {
114 Ok(self.xattrs.keys().map(|k| k.clone().into()).collect())
115 }
116
117 fn get_xattr(&self, name: &FsStr) -> Result<FsString, Errno> {
118 self.xattrs.get(&**name).map(|a| a.clone().into()).ok_or_else(|| errno!(ENODATA))
119 }
120
121 fn set_xattr(&self, _name: &FsStr, _value: &FsStr, _op: XattrOp) -> Result<(), Errno> {
122 error!(ENOSYS)
123 }
124 fn remove_xattr(&self, _name: &FsStr) -> Result<(), Errno> {
125 error!(ENOSYS)
126 }
127}
128
129struct ExtDirectory {
130 inner: Arc<ExtNode>,
131}
132
133impl FsNodeOps for ExtDirectory {
134 fs_node_impl_dir_readonly!();
135 fs_node_impl_xattr_delegate!(self, self.inner);
136
137 fn create_file_ops(
138 &self,
139 _node: &FsNode,
140 _current_task: &CurrentTask,
141 _flags: OpenFlags,
142 ) -> Result<Box<dyn FileOps>, Errno> {
143 Ok(Box::new(ExtDirFileObject { inner: self.inner.clone() }))
144 }
145
146 fn lookup(
147 &self,
148 node: &FsNode,
149 _current_task: &CurrentTask,
150 name: &FsStr,
151 ) -> Result<FsNodeHandle, Errno> {
152 let fs = node.fs();
153 let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
154 let dir_entries =
155 fs_ops.parser.entries_from_inode(&self.inner.inode).map_err(|e| errno!(EIO, e))?;
156 let entry = dir_entries
157 .iter()
158 .find(|e| e.name_bytes() == name)
159 .ok_or_else(|| errno!(ENOENT, name))?;
160 let ext_node = ExtNode::new(fs_ops, entry.e2d_ino.into())?;
161 let inode_num = ext_node.inode_num as ino_t;
162 fs.get_or_create_node(inode_num, || {
163 let entry_type = EntryType::from_u8(entry.e2d_type).map_err(|e| errno!(EIO, e))?;
164 let mode = FileMode::from_bits(ext_node.inode.e2di_mode.into());
165
166 let uid = get_uid_from_node(&ext_node);
167 let gid = get_gid_from_node(&ext_node);
168 let owner = FsCred { uid, gid };
169
170 let size = get_size_from_node(&ext_node, &mode);
171 let blocks = get_blocks_from_node(&ext_node);
172 let nlink = ext_node.inode.e2di_nlink.into();
173
174 let ops: Box<dyn FsNodeOps> = match entry_type {
175 EntryType::RegularFile => Box::new(ExtFile::new(ext_node, name.to_owned())),
176 EntryType::Directory => Box::new(ExtDirectory { inner: Arc::new(ext_node) }),
177 EntryType::SymLink => Box::new(ExtSymlink { inner: ext_node }),
178 EntryType::Unknown => {
179 track_stub!(TODO("https://fxbug.dev/322873719"), "ext4 unknown entry type");
180 Box::new(ExtFile::new(ext_node, name.to_owned()))
181 }
182 EntryType::CharacterDevice => {
183 track_stub!(TODO("https://fxbug.dev/322874445"), "ext4 character device");
184 Box::new(ExtFile::new(ext_node, name.to_owned()))
185 }
186 EntryType::BlockDevice => {
187 track_stub!(TODO("https://fxbug.dev/322874062"), "ext4 block device");
188 Box::new(ExtFile::new(ext_node, name.to_owned()))
189 }
190 EntryType::FIFO => {
191 track_stub!(TODO("https://fxbug.dev/322874249"), "ext4 fifo");
192 Box::new(ExtFile::new(ext_node, name.to_owned()))
193 }
194 EntryType::Socket => {
195 track_stub!(TODO("https://fxbug.dev/322874081"), "ext4 socket");
196 Box::new(ExtFile::new(ext_node, name.to_owned()))
197 }
198 };
199
200 let child = FsNode::new_uncached(
201 inode_num,
202 ops,
203 &fs,
204 FsNodeInfo { mode, uid: owner.uid, gid: owner.gid, ..Default::default() },
205 FsNodeFlags::empty(),
206 );
207 child.update_info(|info| {
208 info.size = size as usize;
209 info.link_count = nlink;
210 info.blksize = DEFAULT_BYTES_PER_BLOCK;
211 info.blocks = blocks as usize;
212 });
213 Ok(child)
214 })
215 }
216}
217
218fn merge_low_high_16(low: u32, high: u32) -> u32 {
219 low | (high << 16)
220}
221
222fn merge_low_high_32(low: u64, high: u64) -> u64 {
223 low | (high << 32)
224}
225
226fn get_uid_from_node(ext_node: &ExtNode) -> u32 {
227 let uid_lower: u32 = ext_node.inode.e2di_uid.into();
228 let uid_upper: u32 = ext_node.inode.e2di_uid_high.into();
229 merge_low_high_16(uid_lower, uid_upper)
230}
231
232fn get_gid_from_node(ext_node: &ExtNode) -> u32 {
233 let gid_lower: u32 = ext_node.inode.e2di_gid.into();
234 let gid_upper: u32 = ext_node.inode.e2di_gid_high.into();
235 merge_low_high_16(gid_lower, gid_upper)
236}
237
238fn get_size_from_node(ext_node: &ExtNode, mode: &FileMode) -> u64 {
239 if mode.is_reg() {
240 let size_lower: u64 = ext_node.inode.e2di_size.into();
241 let size_upper: u64 = ext_node.inode.e2di_size_high.into();
242 merge_low_high_32(size_lower, size_upper)
243 } else {
244 ext_node.inode.e2di_size.into()
245 }
246}
247
248fn get_blocks_from_node(ext_node: &ExtNode) -> u64 {
249 let blocks_lower: u64 = ext_node.inode.e2di_nblock.into();
250 let blocks_upper: u64 = ext_node.inode.e2di_nblock_high.into();
251 merge_low_high_32(blocks_lower, blocks_upper)
252}
253
254struct ExtFile {
255 inner: ExtNode,
256 name: FsString,
257
258 memory: OnceCell<Arc<MemoryObject>>,
262}
263
264impl ExtFile {
265 fn new(inner: ExtNode, name: FsString) -> Self {
266 ExtFile { inner, name, memory: OnceCell::new() }
267 }
268}
269
270impl FsNodeOps for ExtFile {
271 fs_node_impl_not_dir!();
272 fs_node_impl_xattr_delegate!(self, self.inner);
273
274 fn create_file_ops(
275 &self,
276 node: &FsNode,
277 _current_task: &CurrentTask,
278 _flags: OpenFlags,
279 ) -> Result<Box<dyn FileOps>, Errno> {
280 let fs = node.fs();
281 let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
282 let inode_num = self.inner.inode_num;
283 let memory = self.memory.get_or_try_init(|| {
284 let (file_size, extents) = fs_ops
285 .parser
286 .read_extents(self.inner.inode_num)
287 .map_err(|e| errno!(EINVAL, format!("failed to read extents: {e}")))?;
288 let mut pager_extents = Vec::with_capacity(extents.len());
290 let mut last_block = 0;
291 for e in extents {
292 let pager_extent = PagerExtent::from(e);
293 if pager_extent.logical.start < last_block {
294 return error!(EIO, "Bad extent");
295 }
296 last_block = pager_extent.logical.end;
297 pager_extents.push(pager_extent);
298 }
299 Ok(Arc::new(MemoryObject::from(
300 fs_ops
301 .pager
302 .register(self.name.as_ref(), inode_num, file_size, &pager_extents)
303 .map_err(|e| errno!(EINVAL, e))?,
304 )))
305 })?;
306
307 Ok(Box::new(MemoryRegularFile::new(memory.clone())))
309 }
310}
311
312impl From<ext4_lib::structs::Extent> for PagerExtent {
313 fn from(e: ext4_lib::structs::Extent) -> Self {
314 let block_count: u16 = e.e_len.into();
315 let start = e.e_blk.into();
316 Self { logical: start..start + block_count as u32, physical_block: e.target_block_num() }
317 }
318}
319
320struct ExtSymlink {
321 inner: ExtNode,
322}
323
324impl FsNodeOps for ExtSymlink {
325 fs_node_impl_symlink!();
326 fs_node_impl_xattr_delegate!(self, self.inner);
327
328 fn readlink(&self, node: &FsNode, _current_task: &CurrentTask) -> Result<SymlinkTarget, Errno> {
329 let fs = node.fs();
330 let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
331 let data = fs_ops.parser.read_data(self.inner.inode_num).map_err(|e| errno!(EIO, e))?;
332 Ok(SymlinkTarget::Path(data.into()))
333 }
334}
335
336struct ExtDirFileObject {
337 inner: Arc<ExtNode>,
338}
339
340impl FileOps for ExtDirFileObject {
341 fileops_impl_directory!();
342 fileops_impl_noop_sync!();
343
344 fn seek(
345 &self,
346 _file: &FileObject,
347 _current_task: &CurrentTask,
348 current_offset: off_t,
349 target: SeekTarget,
350 ) -> Result<off_t, Errno> {
351 Ok(default_seek(current_offset, target, || error!(EINVAL))?)
352 }
353
354 fn readdir(
355 &self,
356 file: &FileObject,
357 _current_task: &CurrentTask,
358 sink: &mut dyn DirentSink,
359 ) -> Result<(), Errno> {
360 let fs = file.node().fs();
361 let fs_ops = fs.downcast_ops::<ExtFilesystem>().unwrap();
362 let dir_entries =
363 fs_ops.parser.entries_from_inode(&self.inner.inode).map_err(|e| errno!(EIO, e))?;
364
365 if sink.offset() as usize >= dir_entries.len() {
366 return Ok(());
367 }
368
369 for entry in dir_entries[(sink.offset() as usize)..].iter() {
370 let inode_num = entry.e2d_ino.into();
371 let entry_type = directory_entry_type(
372 EntryType::from_u8(entry.e2d_type).map_err(|e| errno!(EIO, e))?,
373 );
374 sink.add(inode_num, sink.offset() + 1, entry_type, entry.name_bytes().into())?;
375 }
376 Ok(())
377 }
378}
379
380fn directory_entry_type(entry_type: EntryType) -> DirectoryEntryType {
381 match entry_type {
382 EntryType::Unknown => DirectoryEntryType::UNKNOWN,
383 EntryType::RegularFile => DirectoryEntryType::REG,
384 EntryType::Directory => DirectoryEntryType::DIR,
385 EntryType::CharacterDevice => DirectoryEntryType::CHR,
386 EntryType::BlockDevice => DirectoryEntryType::BLK,
387 EntryType::FIFO => DirectoryEntryType::FIFO,
388 EntryType::Socket => DirectoryEntryType::SOCK,
389 EntryType::SymLink => DirectoryEntryType::LNK,
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use ext4_lib::structs::INode;
397 use starnix_uapi::file_mode::mode;
398 use zerocopy::FromBytes;
399 use zerocopy::byteorder::little_endian::{U16 as LE16, U32 as LE32};
400
401 fn default_inode() -> INode {
402 let zero = vec![0; 160];
403 INode::read_from_bytes(&zero).expect("failed to read from bytes")
404 }
405
406 fn create_test_ext_node(inode: INode) -> ExtNode {
407 ExtNode { inode_num: 1, inode, xattrs: ExtXattrMap::default() }
408 }
409
410 #[test]
411 fn test_get_uid_from_node() {
412 let mut inode = default_inode();
413 inode.e2di_uid = LE16::new(1001);
414 inode.e2di_uid_high = LE16::new(1);
415 let node = create_test_ext_node(inode);
416 assert_eq!(get_uid_from_node(&node), (1 << 16) | 1001);
417 }
418
419 #[test]
420 fn test_get_gid_from_node() {
421 let mut inode = default_inode();
422 inode.e2di_gid = LE16::new(1002);
423 inode.e2di_gid_high = LE16::new(2);
424 let node = create_test_ext_node(inode);
425 assert_eq!(get_gid_from_node(&node), (2 << 16) | 1002);
426 }
427
428 #[test]
429 fn test_get_size_from_node() {
430 let mut inode = default_inode();
432 inode.e2di_size = LE32::new(0x12345678);
433 inode.e2di_size_high = LE32::new(0x9);
434 let node = create_test_ext_node(inode);
435 let mode = mode!(IFREG, 0o777);
436 assert_eq!(get_size_from_node(&node, &mode), (0x9 << 32) | 0x12345678);
437
438 let mode = mode!(IFDIR, 0o777);
440 assert_eq!(get_size_from_node(&node, &mode), 0x12345678);
441 }
442
443 #[test]
444 fn test_get_blocks_from_node() {
445 let mut inode = default_inode();
446 inode.e2di_nblock = LE32::new(0xABCDE);
447 inode.e2di_nblock_high = LE16::new(0x3);
448 let node = create_test_ext_node(inode);
449 assert_eq!(get_blocks_from_node(&node), (0x3 << 32) | 0xABCDE);
450 }
451}