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}