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