1use crate::blockdev::{BlockDevice, BlockDeviceWrapper};
4use crate::dir::Dir;
5use crate::error::{check_errno, check_errno_with_path, Error, Result};
6use crate::file::File;
7use crate::types::{FileType, FsStats, Metadata, OpenFlags};
8use ext4_lwext4_sys::{
9 ext4_atime_get, ext4_cache_flush, ext4_ctime_get, ext4_device_register, ext4_device_unregister,
10 ext4_dir_mk, ext4_dir_rm, ext4_flink, ext4_fremove, ext4_frename, ext4_fsymlink,
11 ext4_inode_exist, ext4_journal_start, ext4_journal_stop, ext4_mode_get, ext4_mode_set,
12 ext4_mount, ext4_mount_point_stats, ext4_mount_stats, ext4_mtime_get, ext4_owner_get,
13 ext4_owner_set, ext4_readlink, ext4_recover, ext4_umount,
14};
15use std::ffi::{c_char, CStr, CString};
16use std::pin::Pin;
17use std::sync::atomic::{AtomicU64, Ordering};
18
19static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(0);
21
22pub struct Ext4Fs {
49 #[allow(dead_code)]
51 wrapper: Pin<Box<BlockDeviceWrapper>>,
52 device_name: CString,
54 mount_point: CString,
56 read_only: bool,
58 journal_active: bool,
60}
61
62impl Ext4Fs {
63 pub fn mount<B: BlockDevice + 'static>(device: B, read_only: bool) -> Result<Self> {
72 let id = DEVICE_COUNTER.fetch_add(1, Ordering::SeqCst);
74 let device_name = CString::new(format!("ext4dev{}", id)).unwrap();
75 let mount_point = CString::new(format!("/mp{}/", id)).unwrap();
76
77 let wrapper = BlockDeviceWrapper::new(device);
79
80 let ret = unsafe { ext4_device_register(wrapper.as_bdev_ptr(), device_name.as_ptr()) };
82 check_errno(ret)?;
83
84 let ret = unsafe { ext4_mount(device_name.as_ptr(), mount_point.as_ptr(), read_only) };
86 if ret != 0 {
87 unsafe { ext4_device_unregister(device_name.as_ptr()) };
89 return Err(Error::from(ret));
90 }
91
92 let ret = unsafe { ext4_recover(mount_point.as_ptr()) };
94 if ret != 0 {
95 }
97
98 let journal_active = if !read_only {
100 let ret = unsafe { ext4_journal_start(mount_point.as_ptr()) };
101 ret == 0
102 } else {
103 false
104 };
105
106 Ok(Self {
107 wrapper,
108 device_name,
109 mount_point,
110 read_only,
111 journal_active,
112 })
113 }
114
115 pub fn umount(self) -> Result<()> {
119 if self.journal_active {
121 unsafe { ext4_journal_stop(self.mount_point.as_ptr()) };
122 }
123
124 unsafe { ext4_cache_flush(self.mount_point.as_ptr()) };
126
127 let ret = unsafe { ext4_umount(self.mount_point.as_ptr()) };
129 check_errno(ret)?;
130
131 let ret = unsafe { ext4_device_unregister(self.device_name.as_ptr()) };
133 check_errno(ret)?;
134
135 Ok(())
136 }
137
138 #[allow(dead_code)]
140 pub(crate) fn mount_point(&self) -> &CStr {
141 &self.mount_point
142 }
143
144 pub(crate) fn make_path(&self, path: &str) -> Result<CString> {
146 let path = path.strip_prefix('/').unwrap_or(path);
148 let mount_point = self.mount_point.to_str().map_err(|_| {
149 Error::InvalidArgument("invalid mount point".to_string())
150 })?;
151 let full_path = format!("{}{}", mount_point, path);
152 CString::new(full_path).map_err(Error::from)
153 }
154
155 pub fn is_read_only(&self) -> bool {
157 self.read_only
158 }
159
160 pub fn stat(&self) -> Result<FsStats> {
162 let mut stats = ext4_mount_stats::default();
163 let ret = unsafe { ext4_mount_point_stats(self.mount_point.as_ptr(), &mut stats) };
164 check_errno(ret)?;
165
166 let volume_name = unsafe {
168 let name_bytes = &stats.volume_name;
169 let len = name_bytes.iter().position(|&c| c == 0).unwrap_or(16);
170 let slice = std::slice::from_raw_parts(name_bytes.as_ptr() as *const u8, len);
171 String::from_utf8_lossy(slice).into_owned()
172 };
173
174 Ok(FsStats {
175 block_size: stats.block_size,
176 total_blocks: stats.blocks_count,
177 free_blocks: stats.free_blocks_count,
178 total_inodes: stats.inodes_count as u64,
179 free_inodes: stats.free_inodes_count as u64,
180 block_group_count: stats.block_group_count,
181 blocks_per_group: stats.blocks_per_group,
182 inodes_per_group: stats.inodes_per_group,
183 volume_name,
184 })
185 }
186
187 pub fn open(&self, path: &str, flags: OpenFlags) -> Result<File<'_>> {
193 File::open(self, path, flags)
194 }
195
196 pub fn open_dir(&self, path: &str) -> Result<Dir<'_>> {
198 Dir::open(self, path)
199 }
200
201 pub fn mkdir(&self, path: &str, mode: u32) -> Result<()> {
207 if self.read_only {
208 return Err(Error::ReadOnly);
209 }
210
211 let full_path = self.make_path(path)?;
212 let ret = unsafe { ext4_dir_mk(full_path.as_ptr()) };
213 check_errno_with_path(ret, path)?;
214
215 if mode != 0 {
217 self.set_permissions(path, mode)?;
218 }
219
220 Ok(())
221 }
222
223 pub fn remove(&self, path: &str) -> Result<()> {
225 if self.read_only {
226 return Err(Error::ReadOnly);
227 }
228
229 let full_path = self.make_path(path)?;
230 let ret = unsafe { ext4_fremove(full_path.as_ptr()) };
231 check_errno_with_path(ret, path)
232 }
233
234 pub fn rmdir(&self, path: &str) -> Result<()> {
236 if self.read_only {
237 return Err(Error::ReadOnly);
238 }
239
240 let full_path = self.make_path(path)?;
241 let ret = unsafe { ext4_dir_rm(full_path.as_ptr()) };
242 check_errno_with_path(ret, path)
243 }
244
245 pub fn rename(&self, from: &str, to: &str) -> Result<()> {
247 if self.read_only {
248 return Err(Error::ReadOnly);
249 }
250
251 let from_path = self.make_path(from)?;
252 let to_path = self.make_path(to)?;
253 let ret = unsafe { ext4_frename(from_path.as_ptr(), to_path.as_ptr()) };
254 check_errno_with_path(ret, from)
255 }
256
257 pub fn link(&self, src: &str, dst: &str) -> Result<()> {
259 if self.read_only {
260 return Err(Error::ReadOnly);
261 }
262
263 let src_path = self.make_path(src)?;
264 let dst_path = self.make_path(dst)?;
265 let ret = unsafe { ext4_flink(src_path.as_ptr(), dst_path.as_ptr()) };
266 check_errno_with_path(ret, src)
267 }
268
269 pub fn symlink(&self, target: &str, path: &str) -> Result<()> {
275 if self.read_only {
276 return Err(Error::ReadOnly);
277 }
278
279 let target_cstr = CString::new(target)?;
280 let path_full = self.make_path(path)?;
281 let ret = unsafe { ext4_fsymlink(target_cstr.as_ptr(), path_full.as_ptr()) };
282 check_errno_with_path(ret, path)
283 }
284
285 pub fn readlink(&self, path: &str) -> Result<String> {
287 let full_path = self.make_path(path)?;
288 let mut buf = vec![0u8; 4096];
289 let mut rcnt: usize = 0;
290
291 let ret = unsafe {
292 ext4_readlink(
293 full_path.as_ptr(),
294 buf.as_mut_ptr() as *mut c_char,
295 buf.len(),
296 &mut rcnt,
297 )
298 };
299 check_errno_with_path(ret, path)?;
300
301 buf.truncate(rcnt);
302 String::from_utf8(buf).map_err(|_| Error::InvalidArgument("invalid UTF-8 in symlink".to_string()))
303 }
304
305 pub fn exists(&self, path: &str) -> bool {
307 self.metadata(path).is_ok()
308 }
309
310 pub fn is_file(&self, path: &str) -> bool {
312 let full_path = match self.make_path(path) {
313 Ok(p) => p,
314 Err(_) => return false,
315 };
316 unsafe { ext4_inode_exist(full_path.as_ptr(), FileType::RegularFile.to_raw() as i32) == 0 }
317 }
318
319 pub fn is_dir(&self, path: &str) -> bool {
321 let full_path = match self.make_path(path) {
322 Ok(p) => p,
323 Err(_) => return false,
324 };
325 unsafe { ext4_inode_exist(full_path.as_ptr(), FileType::Directory.to_raw() as i32) == 0 }
326 }
327
328 pub fn metadata(&self, path: &str) -> Result<Metadata> {
330 let full_path = self.make_path(path)?;
331
332 let mut mode: u32 = 0;
334 let ret = unsafe { ext4_mode_get(full_path.as_ptr(), &mut mode) };
335 check_errno_with_path(ret, path)?;
336
337 let file_type = match mode & 0o170000 {
339 0o100000 => FileType::RegularFile,
340 0o040000 => FileType::Directory,
341 0o120000 => FileType::Symlink,
342 0o060000 => FileType::BlockDevice,
343 0o020000 => FileType::CharDevice,
344 0o010000 => FileType::Fifo,
345 0o140000 => FileType::Socket,
346 _ => FileType::Unknown,
347 };
348
349 let mut uid: u32 = 0;
351 let mut gid: u32 = 0;
352 unsafe { ext4_owner_get(full_path.as_ptr(), &mut uid, &mut gid) };
353
354 let mut atime: u32 = 0;
356 let mut mtime: u32 = 0;
357 let mut ctime: u32 = 0;
358 unsafe {
359 ext4_atime_get(full_path.as_ptr(), &mut atime);
360 ext4_mtime_get(full_path.as_ptr(), &mut mtime);
361 ext4_ctime_get(full_path.as_ptr(), &mut ctime);
362 }
363
364 let size = if file_type == FileType::RegularFile {
366 if let Ok(file) = File::open(self, path, OpenFlags::READ) {
367 file.size()
368 } else {
369 0
370 }
371 } else {
372 0
373 };
374
375 Ok(Metadata {
376 file_type,
377 size,
378 blocks: 0, mode: mode & 0o7777, uid,
381 gid,
382 atime: atime as u64,
383 mtime: mtime as u64,
384 ctime: ctime as u64,
385 nlink: 1, })
387 }
388
389 pub fn set_permissions(&self, path: &str, mode: u32) -> Result<()> {
391 if self.read_only {
392 return Err(Error::ReadOnly);
393 }
394
395 let full_path = self.make_path(path)?;
396 let ret = unsafe { ext4_mode_set(full_path.as_ptr(), mode) };
397 check_errno_with_path(ret, path)
398 }
399
400 pub fn set_owner(&self, path: &str, uid: u32, gid: u32) -> Result<()> {
402 if self.read_only {
403 return Err(Error::ReadOnly);
404 }
405
406 let full_path = self.make_path(path)?;
407 let ret = unsafe { ext4_owner_set(full_path.as_ptr(), uid, gid) };
408 check_errno_with_path(ret, path)
409 }
410
411 pub fn sync(&self) -> Result<()> {
413 let ret = unsafe { ext4_cache_flush(self.mount_point.as_ptr()) };
414 check_errno(ret)
415 }
416}
417
418impl Drop for Ext4Fs {
419 fn drop(&mut self) {
420 if self.journal_active {
422 unsafe { ext4_journal_stop(self.mount_point.as_ptr()) };
423 }
424 unsafe {
425 ext4_cache_flush(self.mount_point.as_ptr());
426 ext4_umount(self.mount_point.as_ptr());
427 ext4_device_unregister(self.device_name.as_ptr());
428 }
429 }
430}