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 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}