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}