lwext4_rs/
lib.rs

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}