vfat-rs 0.1.1

A no_std-compatible FAT32/VFAT filesystem implementation in Rust for custom kernels
Documentation
use crate::io::{SeekFrom, Write};
use core::fmt::Formatter;
use core::{cmp, fmt};

use log::{debug, info};

use crate::api::Metadata;
use crate::{ClusterId, PathBuf, Result, VfatFS, VfatMetadataTrait};

/// A File representation in a VfatFilesystem.
//#[derive(Clone)]
pub struct File {
    pub(crate) vfat_filesystem: VfatFS,
    pub(crate) metadata: Metadata,
    // Current Seek position
    /// Current seek offset in bytes from the start of the file.
    pub offset: usize,
}
impl fmt::Debug for File {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "VfatFile: metadata: {:?}, offset: {:?}.",
            self.metadata, self.offset
        )
    }
}

impl File {
    /// Create a new [`File`] handle.
    pub fn new(vfat_filesystem: VfatFS, metadata: Metadata) -> Self {
        File {
            vfat_filesystem,
            metadata,
            offset: 0,
        }
    }
    /// Returns a reference to this file's [`Metadata`].
    pub fn metadata(&self) -> &Metadata {
        &self.metadata
    }

    fn update_file_size(&mut self, amount_written: usize) -> Result<()> {
        if self.offset + amount_written <= self.metadata.size as usize {
            return Ok(());
        }
        info!(
            "Offset: {}, written: {}, old size: {}",
            self.offset, amount_written, self.metadata.size
        );
        self.metadata.size = (self.offset + amount_written) as u32;
        info!("New file size: {}", self.metadata.size);
        info!(
            "I'm going to update file size on the fs... Parent path: {:?}",
            self.metadata.parent()
        );
        self.update_metadata()
    }

    fn update_metadata(&mut self) -> Result<()> {
        debug!("Going to update metadata on disk...");
        self.vfat_filesystem
            .get_from_absolute_path_unlocked(self.metadata.parent().clone())?
            .into_directory_unchecked()
            .update_entry(self.metadata.clone())
    }
    fn full_path(&self) -> &PathBuf {
        self.metadata.full_path()
    }

    /// Write `buf` to this file at the current offset. Returns the number of bytes written.
    pub fn write(&mut self, buf: &[u8]) -> Result<usize> {
        let lock = self.vfat_filesystem.fs_lock.clone();
        let _guard = lock.write();
        self.write_unlocked(buf)
    }

    fn write_unlocked(&mut self, buf: &[u8]) -> Result<usize> {
        if buf.is_empty() {
            return Ok(0);
        }
        debug!("{:?}: requested write", self.full_path(),);
        if self.metadata.has_no_cluster_allocated() {
            debug!("{:?}: has no cluster allocated.", self.full_path());
            self.metadata.cluster = self.vfat_filesystem.allocate_cluster_new_entry()?;
            debug!(
                "{:?}: allocated Cluster('{}'), updating metadata...",
                self.full_path(),
                self.metadata.cluster
            );
            self.update_metadata()?;
        }
        let mut ccw = self
            .vfat_filesystem
            .cluster_chain_writer(self.metadata.cluster);

        ccw.seek(self.offset)?;
        info!(
            "{:?}: Writing with initial cluster: {}, offset: {}",
            self.full_path(),
            self.metadata.cluster,
            self.offset
        );
        let amount_written = ccw.write(buf)?;
        info!(
            "{:?}: Write: Amount written: {}",
            self.full_path(),
            amount_written
        );
        self.update_file_size(amount_written)?;
        self.offset += amount_written;

        Ok(amount_written)
    }

    /// Flush any buffered data to the underlying block device.
    pub fn flush(&mut self) -> Result<()> {
        let lock = self.vfat_filesystem.fs_lock.clone();
        let _guard = lock.write();
        // TODO, should flush only data wrt this file..
        self.vfat_filesystem.device.flush()
    }

    /// Seek to a position in this file. Returns the new offset from the start.
    pub fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
        let lock = self.vfat_filesystem.fs_lock.clone();
        let _guard = lock.write();
        match pos {
            SeekFrom::Start(val) => {
                self.offset = val as usize;
            }
            SeekFrom::End(val) => {
                if self.metadata.size as i64 + val < 0 {
                    return Err(crate::io::Error::new(
                        crate::io::ErrorKind::InvalidInput,
                        "Invalid argument - offset cannot be less then zero.",
                    )
                    .into());
                }
                debug!(
                    "Seek from end, size: {}, movement: {}",
                    self.metadata.size, val
                );
                self.offset = (self.metadata.size as i64 + val) as usize;
            }
            SeekFrom::Current(val) => {
                if self.offset as i64 + val < 0 {
                    return Err(crate::io::Error::new(
                        crate::io::ErrorKind::InvalidInput,
                        "Invalid argument - offset cannot be less then zero.",
                    )
                    .into());
                }
                self.offset = (self.offset as i64 + val) as usize
            }
        }
        Ok(self.offset as u64)
    }
    /// Read from this file at the current offset into `buf`. Returns the number of bytes read.
    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
        let lock = self.vfat_filesystem.fs_lock.clone();
        let _guard = lock.read();
        self.read_unlocked(buf)
    }

    fn read_unlocked(&mut self, mut buf: &mut [u8]) -> Result<usize> {
        // TODO: if cluster is deleted, it should fail.
        // it should read at most the buf size or the missing file data.
        let amount_to_read = cmp::min(buf.len(), self.metadata.size().saturating_sub(self.offset));
        if amount_to_read == 0
            || self.metadata.cluster == ClusterId::new(0)
            || self.offset > self.metadata.size as usize
        {
            info!(
                "Amount to read: {}, cluster: {}, offset: {}, size: {}",
                amount_to_read, self.metadata.cluster, self.offset, self.metadata.size
            );
            return Ok(0);
        }
        let mut ccr = self
            .vfat_filesystem
            .cluster_chain_reader(self.metadata.cluster);
        info!("Going to seek to:{}", self.offset);
        ccr.seek(self.offset)?;

        info!(
            "File: Clusterid: {} amount to read: {}, file size: {}",
            self.metadata.cluster, amount_to_read, self.metadata.size
        );
        buf = &mut buf[..amount_to_read];
        let amount_read = ccr.read(buf)?;
        self.offset += amount_read;
        Ok(amount_read)
    }

    fn _sync(&mut self) -> Result<()> {
        self.flush()
    }

    /// Truncate the file to `new_size` bytes.
    ///
    /// If `new_size` is greater than or equal to the current size, this is a no-op.
    /// If `new_size` is 0, the entire cluster chain is freed.
    /// Otherwise, excess clusters are freed and the metadata is updated.
    pub fn truncate(&mut self, new_size: u32) -> Result<()> {
        let lock = self.vfat_filesystem.fs_lock.clone();
        let _guard = lock.write();

        if new_size >= self.metadata.size {
            return Ok(());
        }

        if new_size == 0 {
            if !self.metadata.has_no_cluster_allocated() {
                self.vfat_filesystem
                    .delete_fat_cluster_chain(self.metadata.cluster)?;
                self.metadata.cluster = ClusterId::new(0);
            }
        } else {
            let bpc = self.vfat_filesystem.bytes_per_cluster();
            let keep_count = (new_size as u64).div_ceil(bpc as u64);
            self.vfat_filesystem
                .truncate_cluster_chain(self.metadata.cluster, keep_count as u32)?;
        }

        self.metadata.size = new_size;
        if self.offset > new_size as usize {
            self.offset = new_size as usize;
        }
        self.update_metadata()
    }
}

impl Write for File {
    fn write(&mut self, buf: &[u8]) -> crate::io::Result<usize> {
        Ok(self.write(buf)?)
    }

    fn flush(&mut self) -> crate::io::Result<()> {
        Ok(self.flush()?)
    }
}

impl VfatMetadataTrait for File {
    fn metadata(&self) -> &Metadata {
        &self.metadata
    }
}