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