1use std::{
2 ffi::{c_void, CString},
3 io::{ErrorKind, Read, Result, Seek, Write},
4 mem::MaybeUninit,
5 ptr::null_mut,
6 u64,
7};
8
9use libc::mode_t;
10use lwext4::{
11 ext4_block, ext4_blockdev, ext4_blockdev_iface, ext4_cache_flush, ext4_dir_iterator_fini,
12 ext4_dir_iterator_init, ext4_dir_iterator_next, ext4_extent_get_blocks, ext4_file,
13 ext4_fs_fini, ext4_fs_init_inode_dblk_idx, ext4_fs_insert_inode_dblk, ext4_fs_put_inode_ref,
14 ext4_fsblk_t, ext4_get_mount, ext4_inode_get_flags, ext4_inode_get_size, ext4_inode_ref,
15 ext4_mount, EOK, EXT4_INODE_FLAG_EXTENTS, SEEK_CUR, SEEK_END, SEEK_SET,
16};
17
18#[allow(unused, nonstandard_style)]
19mod lwext4;
20
21fn errno_to_result(errno: i32) -> Result<()> {
22 match errno {
23 0 => Ok(()),
24 _ => Err(std::io::Error::from_raw_os_error(errno)),
25 }
26}
27
28fn result_to_errno(result: Result<()>) -> i32 {
29 match result {
30 Ok(()) => 0,
31 Err(err) => err.raw_os_error().unwrap_or_default(),
32 }
33}
34
35fn result_u32_to_errno(result: Result<u32>) -> i32 {
36 match result {
37 Ok(_) => 0,
38 Err(err) => err.raw_os_error().unwrap_or_default(),
39 }
40}
41
42unsafe fn get_iface<'a>(bd: *mut ext4_blockdev) -> &'a mut dyn Ext4BlockdevIface {
43 let iface = (*(*bd).bdif).p_user.cast::<BdIface>().as_mut().unwrap();
44 &mut *iface.iface
45}
46
47unsafe extern "C" fn bd_open(bd: *mut ext4_blockdev) -> i32 {
48 let iface = get_iface(bd);
49 result_to_errno(iface.open())
50}
51
52unsafe extern "C" fn bd_close(bd: *mut ext4_blockdev) -> i32 {
53 let iface = get_iface(bd);
54 result_to_errno(iface.close())
55}
56
57unsafe extern "C" fn bd_lock(bd: *mut ext4_blockdev) -> i32 {
58 let iface = get_iface(bd);
59 result_to_errno(iface.lock())
60}
61
62unsafe extern "C" fn bd_unlock(bd: *mut ext4_blockdev) -> i32 {
63 let iface = get_iface(bd);
64 result_to_errno(iface.unlock())
65}
66
67unsafe extern "C" fn bd_bread(
68 bd: *mut ext4_blockdev,
69 buf: *mut c_void,
70 block: u64,
71 bcount: u32,
72) -> i32 {
73 let iface = get_iface(bd);
74 result_u32_to_errno(iface.read(buf.cast(), block, bcount))
75}
76
77unsafe extern "C" fn bd_bwrite(
78 bd: *mut ext4_blockdev,
79 buf: *const c_void,
80 block: u64,
81 bcount: u32,
82) -> i32 {
83 let iface = get_iface(bd);
84 result_u32_to_errno(iface.write(buf.cast(), block, bcount))
85}
86
87pub trait Ext4BlockdevIface {
88 fn phys_block_size(&mut self) -> u32;
89 fn phys_block_count(&mut self) -> u64;
90 fn open(&mut self) -> Result<()>;
91 fn close(&mut self) -> Result<()>;
92 fn read(&mut self, buf: *mut u8, block: u64, bcount: u32) -> Result<u32>;
93 fn write(&mut self, buf: *const u8, block: u64, bcount: u32) -> Result<u32>;
94 fn lock(&self) -> Result<()>;
95 fn unlock(&self) -> Result<()>;
96}
97
98struct BdIface {
99 iface: Box<dyn Ext4BlockdevIface>,
100}
101
102#[allow(dead_code, unused_variables)]
103pub struct Ext4Blockdev {
104 iface: Box<BdIface>,
105 raw: Box<ext4_blockdev>,
106 raw_iface: Box<ext4_blockdev_iface>,
107 buf: Box<[u8]>,
108 name: CString,
109}
110
111unsafe impl Send for Ext4Blockdev {}
112unsafe impl Sync for Ext4Blockdev {}
113
114impl Drop for Ext4Blockdev {
115 fn drop(&mut self) {
116 unsafe { lwext4::ext4_device_unregister(self.name.as_ptr()) };
117 }
118}
119
120impl Ext4Blockdev {
121 pub fn iface(&mut self) -> &mut Box<dyn Ext4BlockdevIface> {
122 &mut self.iface.iface
123 }
124
125 pub fn new(
126 iface: impl Ext4BlockdevIface + 'static,
127 bsize: u32,
128 bcount: u64,
129 name: &str,
130 ) -> Result<Self> {
131 let mut iface = Box::new(iface);
132 let mut buf = vec![0u8; iface.phys_block_size() as usize].into_boxed_slice();
133 let psz = iface.phys_block_size();
134 let pcount = iface.phys_block_count();
135 let mut iface = Box::new(BdIface { iface });
136 let name = CString::new(name).unwrap();
137 let mut raw_iface = Box::new(ext4_blockdev_iface {
138 open: Some(bd_open),
139 bread: Some(bd_bread),
140 bwrite: Some(bd_bwrite),
141 close: Some(bd_close),
142 lock: Some(bd_lock),
143 unlock: Some(bd_unlock),
144 ph_bsize: psz,
145 ph_bcnt: pcount,
146 ph_bbuf: buf.as_mut_ptr(),
147 ph_refctr: 0,
148 bread_ctr: 0,
149 bwrite_ctr: 0,
150 p_user: ((&mut *iface) as *mut BdIface).cast(),
151 });
152 let mut raw = Box::new(lwext4::ext4_blockdev {
153 bdif: raw_iface.as_mut() as *mut _,
154 part_offset: 0,
155 part_size: u64::MAX,
156 bc: null_mut(),
157 lg_bsize: bsize,
158 lg_bcnt: bcount,
159 cache_write_back: 0,
160 fs: null_mut(),
161 journal: null_mut(),
162 });
163 let _ =
164 errno_to_result(unsafe { lwext4::ext4_device_register(raw.as_mut(), name.as_ptr()) })?;
165 Ok(Self {
166 iface,
167 raw,
168 buf,
169 raw_iface,
170 name,
171 })
172 }
173}
174
175#[allow(dead_code, unused_variables)]
176pub struct Ext4Fs {
177 bd: Ext4Blockdev,
178 mnt_name: CString,
179}
180
181#[allow(dead_code, unused_variables)]
182pub struct Ext4File<'a> {
183 file: Box<ext4_file>,
184 name: CString,
185 fs: &'a mut Ext4Fs,
186}
187
188impl Ext4File<'_> {
189 pub fn len(&mut self) -> u64 {
190 unsafe { lwext4::ext4_fsize(self.file.as_mut()) }
191 }
192
193 pub fn truncate(&mut self, new_size: u64) -> Result<()> {
194 errno_to_result(unsafe { lwext4::ext4_ftruncate(self.file.as_mut(), new_size) })
195 }
196
197 pub fn ensure_backing(&mut self, offset: u64) -> Result<()> {
198 let mut inode = self.fs.get_inode(self.file.inode)?;
199 let block_size = self.fs.block_size()?;
200 inode.get_data_block((offset / block_size) as u32, true)?;
201 Ok(())
202 }
203
204 pub fn get_file_inode(&mut self) -> Result<Ext4InodeRef> {
205 self.fs.get_inode(self.file.inode)
206 }
207}
208
209impl Drop for Ext4File<'_> {
210 fn drop(&mut self) {
211 unsafe { lwext4::ext4_fclose(self.file.as_mut()) };
212 }
213}
214
215pub use lwext4::{O_APPEND, O_CREAT, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY};
216use twizzler_abi::simple_mutex::MutexImp;
217
218pub struct Ext4InodeRef {
219 inode: ext4_inode_ref,
220}
221
222pub enum FileKind {
223 Regular,
224 Directory,
225 Symlink,
226 Other,
227}
228
229impl Ext4InodeRef {
230 pub fn get_data_block(&mut self, block: u32, create: bool) -> Result<u64> {
231 let mut fblock = 0;
232 if create {
233 errno_to_result(unsafe {
234 ext4_fs_insert_inode_dblk(&mut self.inode, &mut fblock, block)
235 })?;
236 } else {
237 errno_to_result(unsafe {
238 ext4_fs_init_inode_dblk_idx(&mut self.inode, block, &mut fblock)
239 })?;
240 }
241 Ok(fblock)
242 }
243
244 pub fn get_data_blocks(
245 &mut self,
246 block: u32,
247 max_blocks: u32,
248 create: bool,
249 ) -> Result<(ext4_fsblk_t, u32)> {
250 let flags = unsafe { ext4_inode_get_flags(self.inode.inode) };
251 if flags & EXT4_INODE_FLAG_EXTENTS == 0 {
252 return Err(ErrorKind::Unsupported.into());
253 }
254 let mut count = 0;
255 let mut dblock = 0;
256 errno_to_result(unsafe {
257 ext4_extent_get_blocks(
258 &mut self.inode,
259 block,
260 max_blocks,
261 &mut dblock,
262 create,
263 &mut count,
264 )
265 })?;
266
267 Ok((dblock, count))
268 }
269
270 pub fn num(&self) -> u32 {
271 self.inode.index
272 }
273
274 pub fn size(&self) -> u64 {
275 unsafe { ext4_inode_get_size(&mut (*self.inode.fs).sb, self.inode.inode) }
276 }
277
278 pub fn kind(&self) -> FileKind {
279 if unsafe {
280 lwext4::ext4_inode_is_type(
281 &mut (*self.inode.fs).sb,
282 self.inode.inode,
283 lwext4::EXT4_INODE_MODE_FILE,
284 )
285 } {
286 return FileKind::Regular;
287 }
288 if unsafe {
289 lwext4::ext4_inode_is_type(
290 &mut (*self.inode.fs).sb,
291 self.inode.inode,
292 lwext4::EXT4_INODE_MODE_DIRECTORY,
293 )
294 } {
295 return FileKind::Directory;
296 }
297 if unsafe {
298 lwext4::ext4_inode_is_type(
299 &mut (*self.inode.fs).sb,
300 self.inode.inode,
301 lwext4::EXT4_INODE_MODE_SOFTLINK,
302 )
303 } {
304 return FileKind::Symlink;
305 }
306 FileKind::Other
307 }
308}
309
310impl Drop for Ext4InodeRef {
311 fn drop(&mut self) {
312 unsafe { ext4_fs_put_inode_ref(&mut self.inode) };
313 }
314}
315
316pub struct MpLock {
317 imp: MutexImp,
318}
319
320impl MpLock {
321 pub const fn new() -> Self {
322 Self {
323 imp: MutexImp::new(),
324 }
325 }
326
327 #[track_caller]
328 pub fn lock(&self) {
329 unsafe { self.imp.lock(core::panic::Location::caller()) };
330 }
331
332 pub fn unlock(&self) {
333 unsafe { self.imp.unlock() };
334 }
335}
336
337static MP_LOCK: MpLock = MpLock::new();
338
339unsafe extern "C" fn _mp_lock() {
340 MP_LOCK.lock();
341}
342
343unsafe extern "C" fn _mp_unlock() {
344 MP_LOCK.unlock();
345}
346
347static BC_LOCK: MpLock = MpLock::new();
348
349unsafe extern "C" fn _bc_lock() {
350 BC_LOCK.lock();
351}
352
353unsafe extern "C" fn _bc_unlock() {
354 BC_LOCK.unlock();
355}
356
357static BA_LOCK: MpLock = MpLock::new();
358
359unsafe extern "C" fn _ba_lock() {
360 BA_LOCK.lock();
361}
362
363unsafe extern "C" fn _ba_unlock() {
364 BA_LOCK.unlock();
365}
366
367static IA_LOCK: MpLock = MpLock::new();
368
369unsafe extern "C" fn _ia_lock() {
370 IA_LOCK.lock();
371}
372
373unsafe extern "C" fn _ia_unlock() {
374 IA_LOCK.unlock();
375}
376
377static LOCKS: lwext4::ext4_lock = lwext4::ext4_lock {
378 lock: Some(_mp_lock),
379 unlock: Some(_mp_unlock),
380};
381
382impl Ext4Fs {
383 pub fn bd(&mut self) -> &mut Ext4Blockdev {
384 &mut self.bd
385 }
386
387 pub fn mode(&self, inode: &Ext4InodeRef) -> u32 {
388 let mut sb = null_mut();
389 errno_to_result(unsafe { lwext4::ext4_get_sblock(self.mnt_name.as_ptr(), &mut sb) })
390 .unwrap();
391 unsafe { ext4_inode_get_mode(sb, inode.inode.inode) }
392 }
393
394 pub fn new(bd: Ext4Blockdev, mnt_name: CString, read_only: bool) -> Result<Self> {
395 let r = unsafe { ext4_mount(bd.name.as_ptr(), mnt_name.as_ptr(), read_only) };
396 errno_to_result(r)?;
397 unsafe { lwext4::ext4_recover(mnt_name.as_ptr()) };
398 errno_to_result(unsafe { lwext4::ext4_journal_start(mnt_name.as_ptr()) })?;
399 errno_to_result(unsafe { lwext4::ext4_mount_setup_locks(mnt_name.as_ptr(), &LOCKS) })?;
400
401 let fs = unsafe { lwext4::ext4_mountpoint_fs(mnt_name.as_ptr()) };
402 unsafe {
403 (*fs).bcache_lock = Some(_bc_lock);
404 (*fs).bcache_unlock = Some(_bc_unlock);
405
406 (*fs).inode_alloc_lock = Some(_ia_lock);
407 (*fs).inode_alloc_unlock = Some(_ia_unlock);
408
409 (*fs).block_alloc_lock = Some(_ba_lock);
410 (*fs).block_alloc_unlock = Some(_ba_unlock);
411 }
412
413 Ok(Self { bd, mnt_name })
414 }
415
416 pub fn dirents(&mut self, inode: &mut Ext4InodeRef) -> Result<DirIter<'_>> {
417 let mut this = DirIter {
418 it: unsafe { MaybeUninit::zeroed().assume_init() },
419 done: false,
420 fs: self,
421 };
422 errno_to_result(unsafe { ext4_dir_iterator_init(&mut this.it, &mut inode.inode, 0) })?;
423 Ok(this)
424 }
425
426 pub fn link(&mut self, name: &str, container: u32, new_inode: u32) -> Result<()> {
427 let name = CString::new(name).unwrap();
428 let mp = unsafe { ext4_get_mount(self.mnt_name.as_ptr()) };
429 let mut cont = self.get_inode(container)?;
430 let mut ch = self.get_inode(new_inode)?;
431 errno_to_result(unsafe {
432 lwext4::ext4_link(
433 mp,
434 &mut cont.inode,
435 &mut ch.inode,
436 name.as_ptr().cast(),
437 name.as_bytes().len() as u32,
438 false,
439 )
440 })
441 }
442
443 pub fn open_file(&mut self, name: &str, flags: u32) -> Result<Ext4File<'_>> {
444 let name = format!("{}{}", self.mnt_name.to_string_lossy(), name);
445 let name = CString::new(name).unwrap();
446
447 let mut file = Box::new(ext4_file {
448 mp: null_mut(),
449 inode: 0,
450 flags: 0,
451 fsize: 0,
452 fpos: 0,
453 });
454 errno_to_result(unsafe {
455 lwext4::ext4_fopen2(file.as_mut(), name.as_ptr(), flags as i32)
456 })?;
457 Ok(Ext4File {
458 file,
459 name,
460 fs: self,
461 })
462 }
463
464 pub fn open_file_from_container(
465 &mut self,
466 cont_ino: u32,
467 name: &str,
468 flags: u32,
469 mode: mode_t,
470 ) -> Result<Ext4File<'_>> {
471 let fs = unsafe { lwext4::ext4_mountpoint_fs(self.mnt_name.as_ptr()) };
472 let mut parent = self.get_inode(cont_ino)?;
473 if cont_ino == 0 || cont_ino == 2 {
474 return self.open_file(name, flags);
475 }
476
477 let mut search = ext4_dir_search_result {
478 block: ext4_block {
479 lb_id: 0,
480 buf: null_mut(),
481 data: null_mut(),
482 },
483 dentry: null_mut(),
484 };
485 let res = errno_to_result(unsafe {
486 ext4_dir_find_entry(
487 &mut search,
488 &mut parent.inode,
489 name.as_ptr().cast(),
490 name.len() as u32,
491 )
492 });
493
494 if res.is_err() && flags & O_CREAT != 0 {
495 let ft = if mode & libc::S_IFDIR != 0 {
496 return Err(ErrorKind::Unsupported.into());
497 } else if mode & libc::S_IFLNK != 0 {
498 lwext4::EXT4_INODE_MODE_SOFTLINK
499 } else {
500 lwext4::EXT4_INODE_MODE_FILE
501 };
502
503 let mut new_inode = MaybeUninit::uninit();
504 unsafe {
505 ext4_journal_start(self.mnt_name.as_ptr());
506 errno_to_result(ext4_fs_alloc_inode(fs, new_inode.as_mut_ptr(), ft as i32))?;
507 let mp = ext4_get_mount(self.mnt_name.as_ptr());
508 errno_to_result(lwext4::ext4_link(
509 mp,
510 &mut parent.inode,
511 new_inode.as_mut_ptr(),
512 name.as_ptr().cast(),
513 name.len() as u32,
514 false,
515 ))?;
516 ext4_journal_stop(self.mnt_name.as_ptr());
517 return self.open_file_from_inode(new_inode.assume_init().index, flags);
518 }
519 } else if res.is_err() {
520 return Err(res.err().unwrap());
521 }
522 return self.open_file_from_inode(unsafe { (*search.dentry).inode }, flags);
523 }
524
525 pub fn open_file_from_inode(&mut self, index: u32, flags: u32) -> Result<Ext4File<'_>> {
526 let name = format!("{}{}", self.mnt_name.to_string_lossy(), index);
527 let name = CString::new(name).unwrap();
528
529 let inode = self.get_inode(index)?;
530 let file = Box::new(ext4_file {
531 mp: unsafe { ext4_get_mount(self.mnt_name.as_ptr()) },
532 inode: inode.num(),
533 flags,
534 fsize: inode.size(),
535 fpos: 0,
536 });
537 Ok(Ext4File {
538 file,
539 name,
540 fs: self,
541 })
542 }
543
544 pub fn get_inode(&mut self, index: u32) -> Result<Ext4InodeRef> {
545 let fs = unsafe { lwext4::ext4_mountpoint_fs(self.mnt_name.as_ptr()) };
546
547 let mut inode = ext4_inode_ref {
548 block: ext4_block {
549 lb_id: 0,
550 buf: null_mut(),
551 data: null_mut(),
552 },
553 inode: null_mut(),
554 fs: null_mut(),
555 index,
556 dirty: false,
557 };
558 errno_to_result(unsafe { lwext4::ext4_fs_get_inode_ref(fs, index, &mut inode) })?;
559 Ok(Ext4InodeRef { inode })
560 }
561
562 pub fn block_size(&mut self) -> Result<u64> {
563 let mut sb = null_mut();
564 errno_to_result(unsafe { lwext4::ext4_get_sblock(self.mnt_name.as_ptr(), &mut sb) })?;
565 let block_size = 1024 << (unsafe { *sb }).log_block_size;
566 Ok(block_size)
567 }
568
569 pub fn remove_file(&mut self, name: &str) -> Result<()> {
570 let name = format!("{}{}", self.mnt_name.to_string_lossy(), name);
571 let path = CString::new(name).unwrap();
572 errno_to_result(unsafe { lwext4::ext4_fremove(path.as_ptr()) })
573 }
574
575 pub fn create_dir(&mut self, name: &str) -> Result<()> {
576 let name = format!("{}{}", self.mnt_name.to_string_lossy(), name);
577 let path = CString::new(name).unwrap();
578 errno_to_result(unsafe { lwext4::ext4_dir_mk(path.as_ptr()) })
579 }
580
581 pub fn flush(&mut self) -> Result<()> {
582 let fs = unsafe { lwext4::ext4_mountpoint_fs(self.mnt_name.as_ptr()) };
583 let e = unsafe { ext4_fs_fini(fs) };
584
585 errno_to_result(e)?;
586 unsafe {
587 _bc_lock();
588 errno_to_result(ext4_cache_flush(self.mnt_name.as_ptr()))?;
589 _bc_unlock();
590 }
591 Ok(())
592 }
593}
594
595impl Read for Ext4File<'_> {
596 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
597 let mut completed = 0;
598 let r = unsafe {
599 lwext4::ext4_fread(
600 self.file.as_mut(),
601 buf.as_mut_ptr().cast(),
602 buf.len(),
603 &mut completed,
604 )
605 };
606 errno_to_result(r)?;
607 Ok(completed)
608 }
609}
610
611impl Write for Ext4File<'_> {
612 fn write(&mut self, buf: &[u8]) -> Result<usize> {
613 let mut completed = 0;
614 let r = unsafe {
615 lwext4::ext4_fwrite(
616 self.file.as_mut(),
617 buf.as_ptr().cast(),
618 buf.len(),
619 &mut completed,
620 )
621 };
622 errno_to_result(r)?;
623 Ok(completed)
624 }
625
626 fn flush(&mut self) -> Result<()> {
627 Ok(())
628 }
629}
630
631impl Seek for Ext4File<'_> {
632 fn seek(&mut self, pos: std::io::SeekFrom) -> Result<u64> {
633 let (mode, off) = match pos {
634 std::io::SeekFrom::Start(off) => (SEEK_SET, off as i64),
635 std::io::SeekFrom::End(off) => (SEEK_END, off),
636 std::io::SeekFrom::Current(off) => (SEEK_CUR, off),
637 };
638 let r = unsafe { lwext4::ext4_fseek(self.file.as_mut(), off, mode) };
639 errno_to_result(r)?;
640 let pos = unsafe { lwext4::ext4_ftell(self.file.as_mut()) };
641 Ok(pos)
642 }
643}
644
645impl Drop for Ext4Fs {
646 fn drop(&mut self) {
647 unsafe { lwext4::ext4_journal_stop(self.mnt_name.as_ptr()) };
648 unsafe { lwext4::ext4_umount(self.mnt_name.as_ptr()) };
649 }
650}
651
652pub struct DirIter<'a> {
653 it: lwext4::ext4_dir_iter,
654 done: bool,
655 fs: &'a mut Ext4Fs,
656}
657
658impl<'a> Drop for DirIter<'a> {
659 fn drop(&mut self) {
660 unsafe { ext4_dir_iterator_fini(&mut self.it) };
661 }
662}
663
664impl<'a> Iterator for DirIter<'a> {
665 type Item = (Vec<u8>, Result<Ext4InodeRef>);
666
667 fn next(&mut self) -> Option<Self::Item> {
668 if self.done {
669 return None;
670 }
671 if self.it.curr.is_null() {
672 return None;
673 }
674 let item = unsafe { &*self.it.curr };
675 let name = unsafe { item.name.as_slice(item.name_len as usize) }.to_vec();
676 let next = self.fs.get_inode(item.inode);
677 if unsafe { ext4_dir_iterator_next(&mut self.it) } != EOK as i32 {
678 self.done = true;
679 }
680 Some((name, next))
681 }
682}
683
684use pager_dynamic::ExternalKind;
685
686use crate::lwext4::{
687 ext4_dir_find_entry, ext4_dir_search_result, ext4_fs_alloc_inode, ext4_inode_get_mode,
688 ext4_journal_start, ext4_journal_stop,
689};
690
691impl From<FileKind> for ExternalKind {
692 fn from(value: FileKind) -> Self {
693 match value {
694 FileKind::Regular => ExternalKind::Regular,
695 FileKind::Directory => ExternalKind::Directory,
696 FileKind::Symlink => ExternalKind::SymLink,
697 FileKind::Other => ExternalKind::Other,
698 }
699 }
700}