Skip to main content

ext4_lwext4/
file.rs

1//! File operations for ext4 filesystems.
2
3use crate::error::{check_errno, check_errno_with_path, Error, Result};
4use crate::fs::Ext4Fs;
5use crate::types::{Metadata, OpenFlags, SeekFrom};
6use ext4_lwext4_sys::{
7    ext4_fclose, ext4_file, ext4_fopen2, ext4_fread, ext4_fseek, ext4_fsize, ext4_ftell,
8    ext4_ftruncate, ext4_fwrite,
9};
10use std::io::{self, Read, Seek, Write};
11use std::os::raw::c_void;
12
13/// A file handle for reading and writing.
14///
15/// Files are automatically closed when dropped.
16pub struct File<'a> {
17    fs: &'a Ext4Fs,
18    inner: ext4_file,
19    path: String,
20}
21
22impl<'a> File<'a> {
23    /// Open a file with the specified flags.
24    pub(crate) fn open(fs: &'a Ext4Fs, path: &str, flags: OpenFlags) -> Result<Self> {
25        let full_path = fs.make_path(path)?;
26        let mut inner = ext4_file::default();
27
28        let ret = unsafe { ext4_fopen2(&mut inner, full_path.as_ptr(), flags.to_raw()) };
29        check_errno_with_path(ret, path)?;
30
31        Ok(Self {
32            fs,
33            inner,
34            path: path.to_string(),
35        })
36    }
37
38    /// Read data from the file.
39    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
40        let mut rcnt: usize = 0;
41        let ret = unsafe {
42            ext4_fread(
43                &mut self.inner,
44                buf.as_mut_ptr() as *mut c_void,
45                buf.len(),
46                &mut rcnt,
47            )
48        };
49        check_errno(ret)?;
50        Ok(rcnt)
51    }
52
53    /// Write data to the file.
54    pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
55        let mut wcnt: usize = 0;
56        let ret = unsafe {
57            ext4_fwrite(
58                &mut self.inner,
59                buf.as_ptr() as *const c_void,
60                buf.len(),
61                &mut wcnt,
62            )
63        };
64        check_errno(ret)?;
65        Ok(wcnt)
66    }
67
68    /// Seek to a position in the file.
69    pub fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
70        let (offset, origin) = pos.to_raw();
71        let ret = unsafe { ext4_fseek(&mut self.inner, offset, origin) };
72        check_errno(ret)?;
73        Ok(self.position())
74    }
75
76    /// Get the current position in the file.
77    pub fn position(&self) -> u64 {
78        unsafe { ext4_ftell(&self.inner as *const ext4_file as *mut ext4_file) }
79    }
80
81    /// Get the file size.
82    pub fn size(&self) -> u64 {
83        unsafe { ext4_fsize(&self.inner as *const ext4_file as *mut ext4_file) }
84    }
85
86    /// Truncate or extend the file to the specified size.
87    pub fn truncate(&mut self, size: u64) -> Result<()> {
88        let ret = unsafe { ext4_ftruncate(&mut self.inner, size) };
89        check_errno(ret)
90    }
91
92    /// Flush pending writes (no-op for ext4, writes go through cache).
93    pub fn flush(&mut self) -> Result<()> {
94        // lwext4 doesn't have per-file flush, it's all through the cache
95        Ok(())
96    }
97
98    /// Sync the file to disk by flushing the filesystem cache.
99    pub fn sync(&mut self) -> Result<()> {
100        self.fs.sync()
101    }
102
103    /// Get file metadata.
104    pub fn metadata(&self) -> Result<Metadata> {
105        self.fs.metadata(&self.path)
106    }
107
108    /// Read the entire file contents into a vector.
109    pub fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
110        let size = self.size() as usize;
111        buf.reserve(size);
112
113        // Seek to beginning
114        self.seek(SeekFrom::Start(0))?;
115
116        // Read in chunks
117        let mut total_read = 0;
118        let mut chunk = vec![0u8; 8192];
119
120        loop {
121            let n = self.read(&mut chunk)?;
122            if n == 0 {
123                break;
124            }
125            buf.extend_from_slice(&chunk[..n]);
126            total_read += n;
127        }
128
129        Ok(total_read)
130    }
131
132    /// Read the entire file contents into a string.
133    pub fn read_to_string(&mut self, buf: &mut String) -> Result<usize> {
134        let mut bytes = Vec::new();
135        let n = self.read_to_end(&mut bytes)?;
136        *buf = String::from_utf8(bytes)
137            .map_err(|_| Error::InvalidArgument("file contains invalid UTF-8".to_string()))?;
138        Ok(n)
139    }
140
141    /// Write all bytes to the file.
142    pub fn write_all(&mut self, buf: &[u8]) -> Result<()> {
143        let mut written = 0;
144        while written < buf.len() {
145            let n = self.write(&buf[written..])?;
146            if n == 0 {
147                return Err(Error::Io(io::Error::new(
148                    io::ErrorKind::WriteZero,
149                    "failed to write whole buffer",
150                )));
151            }
152            written += n;
153        }
154        Ok(())
155    }
156}
157
158impl Drop for File<'_> {
159    fn drop(&mut self) {
160        unsafe {
161            ext4_fclose(&mut self.inner);
162        }
163    }
164}
165
166// Implement std::io traits for compatibility
167
168impl Read for File<'_> {
169    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
170        File::read(self, buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
171    }
172}
173
174impl Write for File<'_> {
175    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
176        File::write(self, buf).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
177    }
178
179    fn flush(&mut self) -> io::Result<()> {
180        File::flush(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
181    }
182}
183
184impl Seek for File<'_> {
185    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
186        let pos = match pos {
187            io::SeekFrom::Start(n) => SeekFrom::Start(n),
188            io::SeekFrom::End(n) => SeekFrom::End(n),
189            io::SeekFrom::Current(n) => SeekFrom::Current(n),
190        };
191        File::seek(self, pos).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
192    }
193}