ext2/lib.rs
1//! This crate was created with the purpose of being able to read and write on ext2 partitions, whether they are in block device or in the form of a simple disk image file.
2//!
3//! It does not require any Unix kernel module to function. Originally, it was designed to open ext2 images from within a Docker container without the need for privileged mode.
4//!
5//! This crate covers basic system calls:
6//!
7//! - **open :** Open a file.
8//! - **read_dir :** Returns a Vector over the entries within a directory.
9//! - **create_dir :** Creates a new, empty directory at the provided path.
10//! - **remove_dir :** Removes an empty directory.
11//! - **chmod :** Change the file permission bits of the specified file.
12//! - **chown :** Change the ownership of the file at path to be owned by the specified owner (user) and group.
13//! - **stat :** This function returns information about a file.
14//! - **remove_file :** Removes a file from the filesystem.
15//! - **utime :** Change the access and modification times of a file.
16//! - **rename :** Rename a file or directory to a new name, it cannot replace the original file if to already exists.
17//! - **link :** Make a new name for a file. It is also called “hard-link”.
18//! - **symlink :** Make a new name for a file. It is symbolic links.
19//!
20//! Additionally, the crate also has its own implementation of OpenOptions.
21//!
22//! *You have full permissions on the files, and all specified paths must be absolute. Currently, this crate only works on Unix-like operating systems.*
23//!
24//! **Disclaimer :** This crate is still in its early stages and should be used with caution on existing system partitions.
25//!
26//! this module contains a ext2 driver
27//! see [osdev](https://wiki.osdev.org/Ext2)
28//!
29//! **FUTURE ROAD MAP**
30//! - Fix some incoherencies
31//! - Use std::io::Error instead of IOError
32//! - Use ErrorKind instead of errno
33//! - Made compilation on others platforms than UNIX
34//! - no-std
35//! - Cache of directory entries
36//! - Change current directory
37//! - Set Permissions
38#![deny(missing_docs)]
39#![cfg_attr(feature = "unstable", feature(maybe_uninit_uninit_array))]
40#![cfg_attr(feature = "unstable", feature(maybe_uninit_array_assume_init))]
41#![cfg_attr(feature = "unstable", feature(io_error_more))]
42
43mod inner;
44
45use inner::{Ext2Filesystem, Inode};
46type IoResult<T> = std::result::Result<T, Errno>;
47
48use libc::{blksize_t, c_void, ino_t, off_t, time_t};
49use nix::dir::Entry;
50use nix::errno::Errno;
51use nix::sys::stat::{Mode, SFlag};
52use nix::unistd::{Gid, Uid};
53
54use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write};
55use std::mem::MaybeUninit;
56use std::mem::{size_of, transmute};
57use std::os::unix::ffi::OsStrExt;
58use std::path::{Component, Path};
59use std::sync::{Arc, Mutex};
60use std::time::SystemTime;
61
62/// This structure represents an entire ext2 filesystem.
63#[derive(Debug)]
64pub struct Ext2<T: Read + Seek + Write>(Arc<Mutex<Ext2Filesystem<T>>>);
65
66impl<T> Clone for Ext2<T>
67where
68 T: Read + Seek + Write,
69{
70 fn clone(&self) -> Self {
71 Ext2(self.0.clone())
72 }
73}
74
75/// Invocation of a new FileSystem instance: Take anything that implements Read + Seek + Write.
76/// ```rust,ignore
77/// let f = std::fs::OpenOptions::new()
78/// .read(true)
79/// .write(true)
80/// .open(MY_DISK_OBJECT)
81/// .expect("open filesystem failed");
82/// let ext2 = open_ext2_drive(f).unwrap();
83/// ```
84pub fn open_ext2_drive<T>(disk: T) -> Result<Ext2<T>>
85where
86 T: Read + Seek + Write,
87{
88 Ext2::new(disk)
89}
90
91impl<T> Ext2<T>
92where
93 T: Read + Seek + Write,
94{
95 /// Invocation of a new FileSystem instance: Take anything that implements Read + Seek + Write.
96 /// ```rust,ignore
97 /// let f = std::fs::OpenOptions::new()
98 /// .read(true)
99 /// .write(true)
100 /// .open(MY_DISK_OBJECT)
101 /// .expect("open filesystem failed");
102 /// let ext2 = open_ext2_drive(f).unwrap();
103 /// ```
104 pub fn new(disk: T) -> Result<Self> {
105 Ok(Self(Arc::new(Mutex::new(
106 Ext2Filesystem::new(disk).map_err(|e| Error::from_raw_os_error(e as i32))?,
107 ))))
108 }
109
110 /// Opens a file in write-only mode.
111 ///
112 /// This function will create a file if it does not exist,
113 /// and will truncate it if it does.
114 ///
115 /// Depending on the platform, this function may fail if the
116 /// full directory path does not exist.
117 /// See the [`OpenOptions::open`] function for more details.
118 pub fn create<P: AsRef<Path>>(&mut self, path: P) -> Result<File<T>> {
119 OpenOptions::new()
120 .write(true)
121 .create(true)
122 .truncate(true)
123 .open(path.as_ref(), self.clone())
124 }
125
126 /// Attempts to open a file in read-only mode.
127 ///
128 /// See the [`OpenOptions::open`] method for more details.
129 ///
130 /// # Errors
131 ///
132 /// This function will return an error if `path` does not already exist.
133 /// Other errors may also be returned according to [`OpenOptions::open`].
134 pub fn open<P: AsRef<Path>>(&mut self, path: P) -> Result<File<T>> {
135 OpenOptions::new()
136 .read(true)
137 .open(path.as_ref(), self.clone())
138 }
139
140 /// Returns a Vector over the entries within a directory.
141 ///
142 /// The collection will yield instances of <code>[std::io::Result]<[Entry]></code>.
143 /// New errors may be encountered after an iterator is initially constructed.
144 /// ```rust,ignore
145 /// let v = ext2.read_dir("/").unwrap();
146 /// for entry in v {
147 /// dbg!(entry);
148 /// }
149 /// ```
150 pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> Result<Vec<Entry>> {
151 let path = get_path(&path)?;
152 let ext2 = self.0.lock().unwrap();
153 let iter = _lookup_directory(&ext2, path)?;
154
155 let type_field = ext2.get_superblock().directory_entry_contain_type_field();
156 use inner::DirectoryEntryType::*;
157 Ok(iter
158 .enumerate()
159 .map(move |(i, entry)| {
160 let dirent = libc::dirent {
161 d_ino: entry.directory.header.inode as u64,
162 d_off: i as i64,
163 d_reclen: size_of::<libc::dirent>() as u16,
164 d_type: match type_field {
165 true => match entry.directory.header.type_indicator {
166 RegularFile => libc::DT_REG,
167 Directory => libc::DT_DIR,
168 CharacterDevice => libc::DT_CHR,
169 BlockDevice => libc::DT_BLK,
170 Fifo => libc::DT_FIFO,
171 Socket => libc::DT_SOCK,
172 SymbolicLink => libc::DT_LNK,
173 },
174 false => libc::DT_UNKNOWN,
175 },
176 d_name: entry.directory.filename.0,
177 };
178 unsafe { transmute::<_, Entry>(dirent) } // Inner field of type dirent is not public.
179 })
180 .collect())
181 }
182
183 /// Creates a new, empty directory at the provided path.
184 /// ```rust,ignore
185 /// ext2.create_dir("/bananes").unwrap();
186 /// ```
187 pub fn create_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
188 let path = get_path(&path)?;
189 let timestamp = SystemTime::now()
190 .duration_since(SystemTime::UNIX_EPOCH)
191 .unwrap()
192 .as_secs();
193 let parent = path.parent().ok_or_else(|| ErrorKind::AlreadyExists)?;
194 let filename: &str = path.file_name().unwrap().to_str().unwrap();
195 let mut ext2 = self.0.lock().unwrap();
196 let iter = _lookup_directory(&ext2, &parent)?;
197 let parent = iter.fold(Ok(None), |res, entry| {
198 if entry.directory.filename == filename.try_into().unwrap() {
199 return Err(ErrorKind::AlreadyExists);
200 }
201 res.map(|opt| {
202 opt.or({
203 if unsafe { entry.directory.get_filename() == "." } {
204 Some(entry)
205 } else {
206 None
207 }
208 })
209 })
210 })?;
211 let parent_inode_nbr = parent.unwrap().directory.header.inode;
212 ext2.create_dir(
213 parent_inode_nbr,
214 filename,
215 timestamp as u32,
216 def_mode() | Mode::S_IXUSR | Mode::S_IXOTH | Mode::S_IXGRP,
217 (
218 nix::unistd::geteuid().as_raw(),
219 nix::unistd::getegid().as_raw(),
220 ),
221 )?;
222 Ok(())
223 }
224
225 /// Removes an empty directory.
226 /// ```rust,ignore
227 /// ext2.remove_dir("/bananes").unwrap();
228 /// ```
229 pub fn remove_dir<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
230 let path = get_path(&path)?;
231 let mut ext2 = self.0.lock().unwrap();
232 let iter = _lookup_directory(&ext2, path)?;
233 let parent = iter.enumerate().fold(Ok(None), |res, (idx, entry)| {
234 if idx > 1 {
235 #[cfg(unstable)]
236 return Err(ErrorKind::DirectoryNotEmpty);
237 #[cfg(not(unstable))]
238 return Err(ErrorKind::PermissionDenied);
239 }
240 res.map(|opt| {
241 opt.or({
242 if unsafe { entry.directory.get_filename() == ".." } {
243 Some(entry)
244 } else {
245 None
246 }
247 })
248 })
249 })?;
250 match path.file_name() {
251 Some(filename) => Ok(ext2.rmdir(
252 parent.unwrap().directory.get_inode(),
253 filename.to_str().unwrap(),
254 )?),
255 None => Err(ErrorKind::AlreadyExists.into()),
256 }
257 }
258
259 /// Change the file permission bits of the specified file.
260 /// ```rust,ignore
261 /// let mode = Mode::S_IRWXU | Mode::S_IRWXG | Mode::S_IRWXO;
262 /// ext2.chmod("/bananes/toto.txt", mode).unwrap();
263 /// ```
264 pub fn chmod<P: AsRef<Path>>(&mut self, path: P, mode: Mode) -> Result<()> {
265 let path = get_path(&path)?;
266 let mut ext2 = self.0.lock().unwrap();
267
268 match _find_entry(&ext2, path)? {
269 Some(entry) => Ok(ext2.chmod(entry.directory.get_inode(), mode)?),
270 None => Err(ErrorKind::NotFound.into()),
271 }
272 }
273
274 /// Change the ownership of the file at `path` to be owned by the specified
275 /// `owner` (user) and `group` (see
276 /// [chown(2)](https://pubs.opengroup.org/onlinepubs/9699919799/functions/chown.html)).
277 /// ```rust,ignore
278 /// ext2.chown("/bananes/toto.txt", Uid::from_raw(0), Gid::from_raw(0)).unwrap();
279 /// ```
280 pub fn chown<P: AsRef<Path>>(&mut self, path: P, owner: Uid, group: Gid) -> Result<()> {
281 let path = get_path(&path)?;
282 let mut ext2 = self.0.lock().unwrap();
283
284 match _find_entry(&ext2, path)? {
285 Some(entry) => {
286 Ok(ext2.chown(entry.directory.get_inode(), owner.into(), group.into())?)
287 }
288 None => Err(ErrorKind::NotFound.into()),
289 }
290 }
291
292 /// This function returns information about a file,
293 /// ```rust,ignore
294 /// let s1 = ext2.stat("/bananes/toto.txt").unwrap();
295 /// ```
296 pub fn stat<P: AsRef<Path>>(&self, path: P) -> Result<libc::stat> {
297 let path = get_path(&path)?;
298 let ext2 = self.0.lock().unwrap();
299
300 match _find_entry(&ext2, path)? {
301 Some(entry) => Ok(_stat(&ext2, entry.directory.get_inode(), entry.inode)?),
302 None => Err(ErrorKind::NotFound.into()),
303 }
304 }
305
306 /// Removes a file from the filesystem.
307 ///
308 /// # Platform-specific behavior
309 ///
310 /// This function currently corresponds to the `unlink` function on Unix
311 /// and the `DeleteFile` function on Windows.
312 /// Note that, this may change in the future.
313 /// ```rust,ignore
314 /// ext2.remove_file("/bananes/toto.txt").unwrap();
315 /// ```
316 pub fn remove_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
317 let path = get_path(&path)?;
318 #[cfg(unstable)]
319 path.parent().ok_or_else(|| ErrorKind::IsADirectory)?;
320 #[cfg(not(unstable))]
321 path.parent().ok_or_else(|| ErrorKind::PermissionDenied)?;
322 let mut ext2 = self.0.lock().unwrap();
323
324 let parent = _find_entry(&ext2, path.parent().unwrap())?;
325 let parent_inode_nbr = parent.unwrap().directory.header.inode;
326 Ok(ext2.unlink(
327 parent_inode_nbr,
328 path.file_name().unwrap().to_str().unwrap(),
329 true,
330 )?)
331 }
332
333 /// Change the access and modification times of a file.
334 /// ```rust,ignore
335 /// ext2.utime("/bananes/toto.txt", Some(&libc::utimbuf {
336 /// actime: 42,
337 /// modtime: 42,
338 /// })).unwrap();
339 /// ```
340 pub fn utime<P: AsRef<Path>>(&mut self, path: P, time: Option<&libc::utimbuf>) -> Result<()> {
341 let timestamp = SystemTime::now()
342 .duration_since(SystemTime::UNIX_EPOCH)
343 .unwrap()
344 .as_secs();
345 let path = get_path(&path)?;
346 let mut ext2 = self.0.lock().unwrap();
347 match _find_entry(&ext2, path)? {
348 Some(entry) => Ok(ext2.utime(entry.directory.get_inode(), time, timestamp as u32)?),
349 None => Err(ErrorKind::NotFound.into()),
350 }
351 }
352
353 /// Rename a file or directory to a new name, it cannot replace the original file if
354 /// `to` already exists.
355 /// ```rust,ignore
356 /// ext2.rename("/bananes/toto.txt", "/tata.txt").unwrap();
357 /// ```
358 pub fn rename<P: AsRef<Path>>(&mut self, path: P, new_path: P) -> Result<()> {
359 let path = get_path(&path)?;
360 let new_path = get_path(&new_path)?;
361 match (path.parent(), new_path.parent()) {
362 (Some(parent), Some(new_parent)) => {
363 let mut ext2 = self.0.lock().unwrap();
364 if let Ok(Some(_)) = _find_entry(&ext2, new_path) {
365 return Err(ErrorKind::AlreadyExists.into());
366 }
367 let child = _find_entry(&ext2, parent)?;
368 match child {
369 Some(child) => {
370 let new_parent = _find_entry(&ext2, new_parent)?;
371 Ok(ext2.rename(
372 child.directory.get_inode(),
373 path.file_name().unwrap().to_str().unwrap(),
374 new_parent.unwrap().directory.get_inode(),
375 new_path.file_name().unwrap().to_str().unwrap(),
376 )?)
377 }
378 None => Err(ErrorKind::NotFound.into()),
379 }
380 }
381 _ => Err(ErrorKind::Unsupported.into()),
382 }
383 }
384
385 /// Make a new name for a file. It is also called "hard-link".
386 /// ```rust,ignore
387 /// ext2.link("/bananes/toto.txt", "/tata.txt").unwrap();
388 /// ```
389 pub fn link<P: AsRef<Path>>(&mut self, target_path: P, link_path: P) -> Result<()> {
390 let target_path = get_path(&target_path)?;
391 let link_path = get_path(&link_path)?;
392 match link_path.parent() {
393 Some(link_parent) => {
394 let mut ext2 = self.0.lock().unwrap();
395 if let Ok(Some(_)) = _find_entry(&ext2, link_path) {
396 return Err(ErrorKind::AlreadyExists.into());
397 }
398 let target_entry = _find_entry(&ext2, target_path)?;
399 match target_entry {
400 Some(target_entry) => {
401 let parent_link = _find_entry(&ext2, link_parent)?;
402 ext2.link(
403 parent_link.unwrap().directory.get_inode(),
404 target_entry.directory.get_inode(),
405 link_path.file_name().unwrap().to_str().unwrap(),
406 )?;
407 Ok(())
408 }
409 None => Err(ErrorKind::NotFound.into()),
410 }
411 }
412 _ => Err(ErrorKind::Unsupported.into()),
413 }
414 }
415
416 /// Make a new name for a file. It is symbolic links.
417 /// ```rust,ignore
418 /// ext2.symlink("/bananes/toto.txt", "/tata.txt").unwrap();
419 /// ```
420 pub fn symlink<P: AsRef<Path>>(&mut self, target_path: P, link_path: P) -> Result<()> {
421 let link_path = get_path(&link_path)?;
422 let timestamp = SystemTime::now()
423 .duration_since(SystemTime::UNIX_EPOCH)
424 .unwrap()
425 .as_secs();
426 match link_path.parent() {
427 Some(link_parent) => {
428 let mut ext2 = self.0.lock().unwrap();
429 if let Ok(Some(_)) = _find_entry(&ext2, link_path) {
430 return Err(ErrorKind::AlreadyExists.into());
431 }
432 let parent_link_entry = _find_entry(&ext2, link_parent)?;
433 ext2.symlink(
434 parent_link_entry.unwrap().directory.get_inode(),
435 target_path.as_ref().to_str().unwrap(),
436 link_path.file_name().unwrap().to_str().unwrap(),
437 timestamp as u32,
438 )?;
439 Ok(())
440 }
441 _ => Err(ErrorKind::Unsupported.into()),
442 }
443 }
444}
445
446fn def_mode() -> Mode {
447 Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH
448}
449
450fn get_path<'a, P: AsRef<Path>>(path: &'a P) -> Result<&'a Path> {
451 let path = path.as_ref();
452 for component in path.components() {
453 if component == Component::ParentDir {
454 return Err(ErrorKind::Unsupported.into());
455 }
456 }
457 match path.is_absolute() {
458 true => Ok(path),
459 false => Err(ErrorKind::Unsupported.into()),
460 }
461}
462
463fn _find_entry<T>(ext2: &Ext2Filesystem<T>, path: &Path) -> Result<Option<inner::Entry>>
464where
465 T: Read + Seek + Write,
466{
467 Ok(match path.parent() {
468 Some(parent) => {
469 let mut iter = _lookup_directory(ext2, parent)?;
470 iter.find(|entry| unsafe { entry.directory.get_filename() } == path.file_name().unwrap().to_str().unwrap())
471 }
472 // rootdir
473 None => {
474 let mut iter = _lookup_directory(ext2, path)?;
475 iter.find(|entry| unsafe { entry.directory.get_filename() } == ".")
476 }
477 })
478}
479
480fn _lookup_directory<'a, T>(
481 ext2: &'a Ext2Filesystem<T>,
482 path: &Path,
483) -> Result<impl Iterator<Item = inner::Entry> + 'a>
484where
485 T: Read + Seek + Write,
486{
487 debug_assert_eq!(path.is_absolute(), true);
488 let mut iter = ext2.lookup_directory(2).expect("Root mut be a directory");
489 for directory in path.components() {
490 if directory == Component::RootDir {
491 continue;
492 } else {
493 let elem = iter.find(|entry| {
494 let filelen = directory.as_os_str().len();
495 unsafe {
496 match libc::memcmp(
497 entry.directory.filename.0.as_ptr() as *const c_void,
498 directory.as_os_str().as_bytes().as_ptr() as *const c_void,
499 filelen,
500 ) {
501 0 => true,
502 _ => false,
503 }
504 }
505 });
506 match elem {
507 None => return Err(ErrorKind::NotFound.into()),
508 Some(entry) => {
509 let inode = entry.directory.get_inode();
510 iter = ext2.lookup_directory(inode)?;
511 }
512 }
513 }
514 }
515 Ok(iter)
516}
517fn _stat<T>(ext2: &Ext2Filesystem<T>, inode_nbr: u32, inode: Inode) -> Result<libc::stat>
518where
519 T: Read + Seek + Write,
520{
521 let mut stat = MaybeUninit::<libc::stat>::zeroed();
522 let ptr = stat.as_mut_ptr();
523 unsafe {
524 (*ptr).st_dev = 0; // Device ID
525 (*ptr).st_ino = inode_nbr as ino_t;
526 (*ptr).st_mode = inode.type_and_perm.0 as u32; // Mode of file (see below).
527 (*ptr).st_nlink = inode.nbr_hard_links as u64; // Number of hard links to the file.
528 (*ptr).st_uid = inode.user_id as u32; // User ID of file.
529 (*ptr).st_gid = inode.group_id as u32; // Group ID of file.
530 (*ptr).st_rdev = 0; // Device ID (if file is character or block special).
531 (*ptr).st_size = inode.low_size as off_t; // For regular files, the file size in bytes.
532 (*ptr).st_atime = inode.last_access_time as time_t;
533 (*ptr).st_mtime = inode.last_modification_time as time_t;
534 (*ptr).st_ctime = inode.creation_time as time_t;
535 (*ptr).st_blksize = ext2.get_block_size() as blksize_t;
536 (*ptr).st_blocks = inode.nbr_disk_sectors as i64; // Number of blocks allocated for this object.
537 (*ptr).st_atime_nsec = 0;
538 (*ptr).st_mtime_nsec = 0;
539 (*ptr).st_ctime_nsec = 0;
540 Ok(stat.assume_init())
541 }
542}
543
544/// Options and flags which can be used to configure how a file is opened.
545///
546/// This builder exposes the ability to configure how a [`File`] is opened and
547/// what operations are permitted on the open file. The [`OpenOptions::open`] and
548/// [`OpenOptions::create`] methods are aliases for commonly used options using this
549/// builder.
550///
551/// Generally speaking, when using `OpenOptions`, you'll first call
552/// [`OpenOptions::new`], then chain calls to methods to set each option, then
553/// call [`OpenOptions::open`], passing the path of the file you're trying to
554/// open. This will give you a [`std::io::Result`] with a [`File`] inside that you
555/// can further operate on.
556#[derive(Debug, Copy, Clone)]
557pub struct OpenOptions {
558 read: bool,
559 write: bool,
560 create: bool,
561 append: bool,
562 truncate: bool,
563}
564
565impl OpenOptions {
566 /// Creates a blank new set of options ready for configuration.
567 ///
568 /// All options are initially set to `false`.
569 ///
570 /// # Examples
571 ///
572 /// ```rust,ignore
573 /// use ext2::OpenOptions;
574 ///
575 /// let f = std::fs::OpenOptions::new()
576 /// .read(true)
577 /// .write(true)
578 /// .open(MY_DISK_OBJECT)
579 /// .expect("open filesystem failed");
580 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
581 ///
582 /// let mut options = OpenOptions::new();
583 /// let file = options.read(true).open("/foo.txt", ext2);
584 /// ```
585 pub fn new() -> Self {
586 OpenOptions {
587 read: false,
588 write: false,
589 create: false,
590 append: false,
591 truncate: false,
592 }
593 }
594
595 /// Sets the option for read access.
596 ///
597 /// This option, when true, will indicate that the file should be
598 /// `read`-able if opened.
599 ///
600 /// # Examples
601 ///
602 /// ```rust,ignore
603 /// use ext2::OpenOptions;
604 ///
605 /// let f = std::fs::OpenOptions::new()
606 /// .read(true)
607 /// .write(true)
608 /// .open(MY_DISK_OBJECT)
609 /// .expect("open filesystem failed");
610 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
611 ///
612 /// let file = OpenOptions::new().read(true).open("/foo.txt", ext2);
613 /// ```
614 pub fn read(&mut self, read: bool) -> &mut Self {
615 self.read = read;
616 self
617 }
618
619 /// Sets the option for write access.
620 ///
621 /// This option, when true, will indicate that the file should be
622 /// `write`-able if opened.
623 ///
624 /// If the file already exists, any write calls on it will overwrite its
625 /// contents, without truncating it.
626 ///
627 /// # Examples
628 ///
629 /// ```rust,ignore
630 /// use ext2::OpenOptions;
631 ///
632 /// let f = std::fs::OpenOptions::new()
633 /// .read(true)
634 /// .write(true)
635 /// .open(MY_DISK_OBJECT)
636 /// .expect("open filesystem failed");
637 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
638 ///
639 /// let file = OpenOptions::new().write(true).open("/foo.txt", ext2);
640 /// ```
641 pub fn write(&mut self, write: bool) -> &mut Self {
642 self.write = write;
643 self
644 }
645
646 /// Sets the option to create a new file, or open it if it already exists.
647 ///
648 /// In order for the file to be created, [`OpenOptions::write`] or
649 /// [`OpenOptions::append`] access must be used.
650 ///
651 /// # Examples
652 ///
653 /// ```rust,ignore
654 /// use ext2::OpenOptions;
655 ///
656 /// let f = std::fs::OpenOptions::new()
657 /// .read(true)
658 /// .write(true)
659 /// .open(MY_DISK_OBJECT)
660 /// .expect("open filesystem failed");
661 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
662 ///
663 /// let file = OpenOptions::new().write(true).create(true).open("/foo.txt", ext2);
664 /// ```
665 pub fn create(&mut self, create: bool) -> &mut Self {
666 self.create = create;
667 self
668 }
669
670 /// Sets the option for the append mode.
671 ///
672 /// This option, when true, means that writes will append to a file instead
673 /// of overwriting previous contents.
674 /// Note that setting `.write(true).append(true)` has the same effect as
675 /// setting only `.append(true)`.
676 ///
677 /// For most filesystems, the operating system guarantees that all writes are
678 /// atomic: no writes get mangled because another process writes at the same
679 /// time.
680 ///
681 /// One maybe obvious note when using append-mode: make sure that all data
682 /// that belongs together is written to the file in one operation. This
683 /// can be done by concatenating strings before passing them to [`write()`],
684 /// or using a buffered writer (with a buffer of adequate size),
685 /// and calling [`flush()`] when the message is complete.
686 ///
687 /// If a file is opened with both read and append access, beware that after
688 /// opening, and after every write, the position for reading may be set at the
689 /// end of the file. So, before writing, save the current position (using
690 /// <code>[seek]\([SeekFrom]::[Current]\(0))</code>), and restore it before the next read.
691 ///
692 /// ## Note
693 ///
694 /// This function doesn't create the file if it doesn't exist. Use the
695 /// [`OpenOptions::create`] method to do so.
696 ///
697 /// [`write()`]: Write::write "io::Write::write"
698 /// [`flush()`]: Write::flush "io::Write::flush"
699 /// [seek]: Seek::seek "io::Seek::seek"
700 /// [Current]: SeekFrom::Current "io::SeekFrom::Current"
701 ///
702 /// # Examples
703 ///
704 /// ```rust,ignore
705 /// use ext2::OpenOptions;
706 ///
707 /// let f = std::fs::OpenOptions::new()
708 /// .read(true)
709 /// .write(true)
710 /// .open(MY_DISK_OBJECT)
711 /// .expect("open filesystem failed");
712 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
713 ///
714 /// let file = OpenOptions::new().append(true).open("/foo.txt", ext2);
715 /// ```
716 pub fn append(&mut self, append: bool) -> &mut Self {
717 self.append = append;
718 self
719 }
720
721 /// Sets the option for truncating a previous file.
722 ///
723 /// If a file is successfully opened with this option set it will truncate
724 /// the file to 0 length if it already exists.
725 ///
726 /// The file must be opened with write access for truncate to work.
727 ///
728 /// # Examples
729 ///
730 /// ```rust,ignore
731 /// use ext2::OpenOptions;
732 ///
733 /// let f = std::fs::OpenOptions::new()
734 /// .read(true)
735 /// .write(true)
736 /// .open(MY_DISK_OBJECT)
737 /// .expect("open filesystem failed");
738 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
739 ///
740 /// let file = OpenOptions::new().write(true).truncate(true).open("/foo.txt", ext2);
741 /// ```
742 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
743 self.truncate = truncate;
744 self
745 }
746
747 /// Opens a file at `path` with the options specified by `self`.
748 ///
749 /// # Errors
750 ///
751 /// This function will return an error under a number of different
752 /// circumstances. Some of these error conditions are listed here, together
753 /// with their [`std::io::ErrorKind`]. The mapping to [`std::io::ErrorKind`]s is not
754 /// part of the compatibility contract of the function.
755 ///
756 /// * [`NotFound`]: The specified file does not exist and neither `create`
757 /// or `create_new` is set.
758 /// * [`NotFound`]: One of the directory components of the file path does
759 /// not exist.
760 /// * [`InvalidInput`]: Invalid combinations of open options (truncate
761 /// without write access, no access mode set, etc.).
762 ///
763 /// The following errors don't match any existing [`std::io::ErrorKind`] at the moment:
764 /// * One of the directory components of the specified file path
765 /// was not, in fact, a directory.
766 /// * Filesystem-level errors: full disk, write permission
767 /// requested on a read-only file system, exceeded disk quota, too many
768 /// open files, too long filename, too many symbolic links in the
769 /// specified path (Unix-like systems only), etc.
770 ///
771 /// # Examples
772 ///
773 /// ```rust,ignore
774 /// use ext2::OpenOptions;
775 ///
776 /// let f = std::fs::OpenOptions::new()
777 /// .read(true)
778 /// .write(true)
779 /// .open(MY_DISK_OBJECT)
780 /// .expect("open filesystem failed");
781 /// let ext2 = ext2::open_ext2_drive(f).unwrap();
782 ///
783 /// let file = OpenOptions::new().read(true).open("/foo.txt", ext2);
784 /// ```
785 ///
786 /// [`InvalidInput`]: std::io::ErrorKind::InvalidInput
787 /// [`NotFound`]: std::io::ErrorKind::NotFound
788 pub fn open<T, P: AsRef<Path>>(&mut self, path: P, ext2_clone: Ext2<T>) -> Result<File<T>>
789 where
790 T: Read + Seek + Write,
791 {
792 let path = get_path(&path)?;
793 #[cfg(unstable)]
794 path.parent().ok_or_else(|| ErrorKind::IsADirectory)?;
795 #[cfg(not(unstable))]
796 path.parent().ok_or_else(|| ErrorKind::PermissionDenied)?;
797 let mut ext2 = ext2_clone.0.lock().unwrap();
798
799 let file = _find_entry(&ext2, path)?;
800 match file {
801 Some(file) => {
802 if file.inode.is_a_directory() {
803 // TODO Must be a regular file
804 #[cfg(unstable)]
805 return Err(ErrorKind::IsADirectory.into());
806 #[cfg(not(unstable))]
807 Err(ErrorKind::PermissionDenied.into())
808 } else {
809 if self.truncate && self.write {
810 ext2.truncate(file.directory.get_inode(), 0)?;
811 }
812 let curr_offset = if self.append && self.write {
813 ext2.read_inode(file.directory.get_inode())?.get_size() as i64
814 } else {
815 0
816 };
817 drop(ext2);
818 Ok(File {
819 inode: file.directory.get_inode(),
820 curr_offset: curr_offset as u64,
821 ext2: ext2_clone,
822 options: *self,
823 })
824 }
825 }
826 None => {
827 if self.create && self.write {
828 let timestamp = SystemTime::now()
829 .duration_since(SystemTime::UNIX_EPOCH)
830 .unwrap()
831 .as_secs();
832 let parent = _find_entry(&ext2, path.parent().unwrap())?;
833 let entry = ext2.create(
834 path.file_name().unwrap().to_str().unwrap(),
835 parent.unwrap().directory.get_inode(),
836 timestamp as u32,
837 (def_mode().bits(), SFlag::S_IFREG).try_into().unwrap(),
838 (
839 nix::unistd::geteuid().as_raw(),
840 nix::unistd::getegid().as_raw(),
841 ),
842 )?;
843 drop(ext2);
844 Ok(File {
845 inode: entry.directory.get_inode(),
846 curr_offset: 0,
847 ext2: ext2_clone,
848 options: *self,
849 })
850 } else {
851 Err(ErrorKind::NotFound.into())
852 }
853 }
854 }
855 }
856}
857
858/// An object providing access to an open file on the EXT2 filesystem.
859///
860/// An instance of a `File` can be read and/or written depending on what options
861/// it was opened with. Files also implement [`Seek`] to alter the logical cursor
862/// that the file contains internally.
863///
864/// Files are automatically closed when they go out of scope. Errors detected
865/// on closing are ignored by the implementation of `Drop`.
866#[derive(Debug)]
867pub struct File<T>
868where
869 T: Read + Seek + Write,
870{
871 inode: u32,
872 curr_offset: u64,
873 ext2: Ext2<T>,
874 options: OpenOptions,
875}
876
877impl<T> File<T>
878where
879 T: Read + Seek + Write,
880{
881 /// **currently unimplemented!()** : Metadata information about a file.
882 ///
883 /// This structure is returned from the [`File::metadata`] function or method and represents known
884 /// metadata about a file such as its permissions, size, modification
885 /// times, etc.
886 ///
887 pub fn metadata() {
888 unimplemented!();
889 }
890}
891
892impl<T> Seek for File<T>
893where
894 T: Read + Seek + Write,
895{
896 // Required method
897 fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
898 let ext2 = self.ext2.0.lock().unwrap();
899 let file_len = ext2.read_inode(self.inode)?.get_size() as i64;
900 match pos {
901 SeekFrom::Start(u) => {
902 if u > file_len as u64 {
903 return Err(ErrorKind::UnexpectedEof.into());
904 }
905 self.curr_offset = u;
906 }
907 SeekFrom::End(i) => {
908 if i < 0 || i > file_len {
909 return Err(ErrorKind::UnexpectedEof.into());
910 }
911 self.curr_offset = (file_len - i) as u64;
912 }
913 SeekFrom::Current(i) => {
914 let new_curr_offset = self.curr_offset as i64 + i;
915 if new_curr_offset < 0 || new_curr_offset > file_len {
916 return Err(ErrorKind::UnexpectedEof.into());
917 }
918 self.curr_offset = new_curr_offset as u64;
919 }
920 }
921 Ok(self.curr_offset)
922 }
923}
924
925impl<T> Write for File<T>
926where
927 T: Read + Seek + Write,
928{
929 fn write(&mut self, buf: &[u8]) -> Result<usize> {
930 if !self.options.write {
931 return Err(ErrorKind::PermissionDenied.into());
932 }
933 let mut ext2 = self.ext2.0.lock().unwrap();
934 Ok(ext2
935 .write(self.inode, &mut self.curr_offset, buf)
936 .map(|s| s.0 as usize)?)
937 }
938
939 fn flush(&mut self) -> Result<()> {
940 Ok(())
941 }
942}
943
944impl<T> Read for File<T>
945where
946 T: Read + Seek + Write,
947{
948 fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
949 if !self.options.read {
950 return Err(ErrorKind::PermissionDenied.into());
951 }
952 let mut ext2 = self.ext2.0.lock().unwrap();
953 Ok(ext2
954 .read(self.inode, &mut self.curr_offset, buf)
955 .map(|s| s as usize)?)
956 }
957}