lwext4_rs/
lib.rs

1use std::{
2    ffi::{c_void, CString},
3    io::{Read, Result, Seek, Write},
4    mem::MaybeUninit,
5    ptr::null_mut,
6    sync::{Condvar, Mutex},
7    u64,
8};
9
10use lwext4::{
11    ext4_block, ext4_blockdev, ext4_blockdev_iface, ext4_dir_iterator_fini, ext4_dir_iterator_init,
12    ext4_dir_iterator_next, ext4_extent_get_blocks, ext4_file, ext4_fs_init_inode_dblk_idx,
13    ext4_fs_insert_inode_dblk, ext4_fs_put_inode_ref, ext4_fsblk_t, ext4_get_mount,
14    ext4_inode_get_size, ext4_inode_ref, ext4_mount, EOK, 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};
215
216pub struct Ext4InodeRef {
217    inode: ext4_inode_ref,
218}
219
220pub enum FileKind {
221    Regular,
222    Directory,
223    Symlink,
224    Other,
225}
226
227impl Ext4InodeRef {
228    pub fn get_data_block(&mut self, block: u32, create: bool) -> Result<u64> {
229        let mut fblock = 0;
230        if create {
231            errno_to_result(unsafe {
232                ext4_fs_insert_inode_dblk(&mut self.inode, &mut fblock, block)
233            })?;
234        } else {
235            errno_to_result(unsafe {
236                ext4_fs_init_inode_dblk_idx(&mut self.inode, block, &mut fblock)
237            })?;
238        }
239        Ok(fblock)
240    }
241
242    pub fn get_data_blocks(
243        &mut self,
244        block: u32,
245        max_blocks: u32,
246        create: bool,
247    ) -> Result<(ext4_fsblk_t, u32)> {
248        let mut count = 0;
249        let mut dblock = 0;
250        errno_to_result(unsafe {
251            ext4_extent_get_blocks(
252                &mut self.inode,
253                block,
254                max_blocks,
255                &mut dblock,
256                create,
257                &mut count,
258            )
259        })?;
260
261        Ok((dblock, count))
262    }
263
264    pub fn num(&self) -> u32 {
265        self.inode.index
266    }
267
268    pub fn size(&self) -> u64 {
269        unsafe { ext4_inode_get_size(&mut (*self.inode.fs).sb, self.inode.inode) }
270    }
271
272    pub fn kind(&self) -> FileKind {
273        if unsafe {
274            lwext4::ext4_inode_is_type(
275                &mut (*self.inode.fs).sb,
276                self.inode.inode,
277                lwext4::EXT4_INODE_MODE_FILE,
278            )
279        } {
280            return FileKind::Regular;
281        }
282        if unsafe {
283            lwext4::ext4_inode_is_type(
284                &mut (*self.inode.fs).sb,
285                self.inode.inode,
286                lwext4::EXT4_INODE_MODE_DIRECTORY,
287            )
288        } {
289            return FileKind::Directory;
290        }
291        if unsafe {
292            lwext4::ext4_inode_is_type(
293                &mut (*self.inode.fs).sb,
294                self.inode.inode,
295                lwext4::EXT4_INODE_MODE_SOFTLINK,
296            )
297        } {
298            return FileKind::Symlink;
299        }
300        FileKind::Other
301    }
302}
303
304impl Drop for Ext4InodeRef {
305    fn drop(&mut self) {
306        unsafe { ext4_fs_put_inode_ref(&mut self.inode) };
307    }
308}
309
310struct MpLock {
311    inner: Mutex<bool>,
312    cv: Condvar,
313}
314
315impl MpLock {
316    fn lock(&self) {
317        let mut inner = self.inner.lock().unwrap();
318        while *inner {
319            inner = self.cv.wait(inner).unwrap();
320        }
321        *inner = true;
322    }
323
324    fn unlock(&self) {
325        let mut inner = self.inner.lock().unwrap();
326        assert!(*inner);
327        *inner = false;
328        self.cv.notify_all();
329    }
330}
331
332static MP_LOCK: MpLock = MpLock {
333    inner: Mutex::new(false),
334    cv: Condvar::new(),
335};
336
337unsafe extern "C" fn _mp_lock() {
338    MP_LOCK.lock();
339}
340
341unsafe extern "C" fn _mp_unlock() {
342    MP_LOCK.unlock();
343}
344
345static BC_LOCK: MpLock = MpLock {
346    inner: Mutex::new(false),
347    cv: Condvar::new(),
348};
349
350unsafe extern "C" fn _bc_lock() {
351    BC_LOCK.lock();
352}
353
354unsafe extern "C" fn _bc_unlock() {
355    BC_LOCK.unlock();
356}
357
358static BA_LOCK: MpLock = MpLock {
359    inner: Mutex::new(false),
360    cv: Condvar::new(),
361};
362
363unsafe extern "C" fn _ba_lock() {
364    BA_LOCK.lock();
365}
366
367unsafe extern "C" fn _ba_unlock() {
368    BA_LOCK.unlock();
369}
370
371static IA_LOCK: MpLock = MpLock {
372    inner: Mutex::new(false),
373    cv: Condvar::new(),
374};
375
376unsafe extern "C" fn _ia_lock() {
377    IA_LOCK.lock();
378}
379
380unsafe extern "C" fn _ia_unlock() {
381    IA_LOCK.unlock();
382}
383
384static LOCKS: lwext4::ext4_lock = lwext4::ext4_lock {
385    lock: Some(_mp_lock),
386    unlock: Some(_mp_unlock),
387};
388
389impl Ext4Fs {
390    pub fn bd(&mut self) -> &mut Ext4Blockdev {
391        &mut self.bd
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 open_file(&mut self, name: &str, flags: u32) -> Result<Ext4File<'_>> {
427        let name = format!("{}{}", self.mnt_name.to_string_lossy(), name);
428        let name = CString::new(name).unwrap();
429
430        let mut file = Box::new(ext4_file {
431            mp: null_mut(),
432            inode: 0,
433            flags: 0,
434            fsize: 0,
435            fpos: 0,
436        });
437        errno_to_result(unsafe {
438            lwext4::ext4_fopen2(file.as_mut(), name.as_ptr(), flags as i32)
439        })?;
440        Ok(Ext4File {
441            file,
442            name,
443            fs: self,
444        })
445    }
446
447    pub fn open_file_from_inode(&mut self, index: u32, flags: u32) -> Result<Ext4File<'_>> {
448        let name = format!("{}#{}", self.mnt_name.to_string_lossy(), index);
449        let name = CString::new(name).unwrap();
450
451        let inode = self.get_inode(index)?;
452        let file = Box::new(ext4_file {
453            mp: unsafe { ext4_get_mount(self.mnt_name.as_ptr()) },
454            inode: inode.num(),
455            flags,
456            fsize: inode.size(),
457            fpos: 0,
458        });
459        Ok(Ext4File {
460            file,
461            name,
462            fs: self,
463        })
464    }
465
466    pub fn get_inode(&mut self, index: u32) -> Result<Ext4InodeRef> {
467        let fs = unsafe { lwext4::ext4_mountpoint_fs(self.mnt_name.as_ptr()) };
468
469        let mut inode = ext4_inode_ref {
470            block: ext4_block {
471                lb_id: 0,
472                buf: null_mut(),
473                data: null_mut(),
474            },
475            inode: null_mut(),
476            fs: null_mut(),
477            index,
478            dirty: false,
479        };
480        errno_to_result(unsafe { lwext4::ext4_fs_get_inode_ref(fs, index, &mut inode) })?;
481        Ok(Ext4InodeRef { inode })
482    }
483
484    pub fn block_size(&mut self) -> Result<u64> {
485        let mut sb = null_mut();
486        errno_to_result(unsafe { lwext4::ext4_get_sblock(self.mnt_name.as_ptr(), &mut sb) })?;
487        let block_size = 1024 << (unsafe { *sb }).log_block_size;
488        Ok(block_size)
489    }
490
491    pub fn remove_file(&mut self, name: &str) -> Result<()> {
492        let name = format!("{}{}", self.mnt_name.to_string_lossy(), name);
493        let path = CString::new(name).unwrap();
494        errno_to_result(unsafe { lwext4::ext4_fremove(path.as_ptr()) })
495    }
496
497    pub fn create_dir(&mut self, name: &str) -> Result<()> {
498        let name = format!("{}{}", self.mnt_name.to_string_lossy(), name);
499        let path = CString::new(name).unwrap();
500        errno_to_result(unsafe { lwext4::ext4_dir_mk(path.as_ptr()) })
501    }
502}
503
504impl Read for Ext4File<'_> {
505    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
506        let mut completed = 0;
507        let r = unsafe {
508            lwext4::ext4_fread(
509                self.file.as_mut(),
510                buf.as_mut_ptr().cast(),
511                buf.len(),
512                &mut completed,
513            )
514        };
515        errno_to_result(r)?;
516        Ok(completed)
517    }
518}
519
520impl Write for Ext4File<'_> {
521    fn write(&mut self, buf: &[u8]) -> Result<usize> {
522        let mut completed = 0;
523        let r = unsafe {
524            lwext4::ext4_fwrite(
525                self.file.as_mut(),
526                buf.as_ptr().cast(),
527                buf.len(),
528                &mut completed,
529            )
530        };
531        errno_to_result(r)?;
532        Ok(completed)
533    }
534
535    fn flush(&mut self) -> Result<()> {
536        Ok(())
537    }
538}
539
540impl Seek for Ext4File<'_> {
541    fn seek(&mut self, pos: std::io::SeekFrom) -> Result<u64> {
542        let (mode, off) = match pos {
543            std::io::SeekFrom::Start(off) => (SEEK_SET, off as i64),
544            std::io::SeekFrom::End(off) => (SEEK_END, off),
545            std::io::SeekFrom::Current(off) => (SEEK_CUR, off),
546        };
547        let r = unsafe { lwext4::ext4_fseek(self.file.as_mut(), off, mode) };
548        errno_to_result(r)?;
549        let pos = unsafe { lwext4::ext4_ftell(self.file.as_mut()) };
550        Ok(pos)
551    }
552}
553
554impl Drop for Ext4Fs {
555    fn drop(&mut self) {
556        unsafe { lwext4::ext4_journal_stop(self.mnt_name.as_ptr()) };
557        unsafe { lwext4::ext4_umount(self.mnt_name.as_ptr()) };
558    }
559}
560
561pub struct DirIter<'a> {
562    it: lwext4::ext4_dir_iter,
563    done: bool,
564    fs: &'a mut Ext4Fs,
565}
566
567impl<'a> Drop for DirIter<'a> {
568    fn drop(&mut self) {
569        unsafe { ext4_dir_iterator_fini(&mut self.it) };
570    }
571}
572
573impl<'a> Iterator for DirIter<'a> {
574    type Item = (Vec<u8>, Result<Ext4InodeRef>);
575
576    fn next(&mut self) -> Option<Self::Item> {
577        if self.done {
578            return None;
579        }
580        if self.it.curr.is_null() {
581            return None;
582        }
583        let item = unsafe { &*self.it.curr };
584        let name = unsafe { item.name.as_slice(item.name_len as usize) }.to_vec();
585        let next = self.fs.get_inode(item.inode);
586        if unsafe { ext4_dir_iterator_next(&mut self.it) } != EOK as i32 {
587            self.done = true;
588        }
589        Some((name, next))
590    }
591}