scfs 0.10.6

A convenient splitting and concatenating filesystem.
Documentation
use std::ffi::{OsStr, OsString};
use std::fs;

use fuser::{FileAttr, ReplyAttr, ReplyData, ReplyEntry, Request};
use libc::ENOENT;
use rusqlite::{params, Connection, Error};

use crate::{FileInfo, FileInfoRow, STMT_QUERY_BY_INO, STMT_QUERY_BY_PARENT_INO_AND_FILENAME, TTL};

pub(crate) trait Shared {
    fn file_db(&self) -> &Connection;

    fn get_file_info_from_ino(&self, ino: u64) -> Result<FileInfo, Error> {
        let ino = FileInfoRow::from(FileInfo::with_ino(ino)).ino;

        let mut stmt = self.file_db().prepare_cached(STMT_QUERY_BY_INO).unwrap();

        let file_info = stmt
            .query_map(params![ino], |row| Ok(FileInfo::from(row)))?
            .next()
            .unwrap();
        file_info
    }

    fn get_file_info_from_parent_ino_and_file_name(
        &self,
        parent_ino: u64,
        file_name: OsString,
    ) -> Result<FileInfo, Error> {
        let parent_ino = FileInfoRow::from(FileInfo::with_parent_ino(parent_ino)).parent_ino;

        let mut stmt = self
            .file_db()
            .prepare_cached(STMT_QUERY_BY_PARENT_INO_AND_FILENAME)
            .unwrap();

        let file_name = FileInfo::default()
            .file_name(file_name)
            .into_file_info_row()
            .file_name;

        let file_info = stmt
            .query_map(params![parent_ino, file_name], |row| {
                Ok(FileInfo::from(row))
            })
            .unwrap()
            .next();

        match file_info {
            Some(f) => Ok(f.unwrap()),
            None => Err(Error::QueryReturnedNoRows),
        }
    }

    fn get_attr_from_file_info(&self, file_info: &FileInfo) -> FileAttr;

    fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
        let file_info =
            self.get_file_info_from_parent_ino_and_file_name(parent, OsString::from(name));
        if let Ok(file_info) = file_info {
            let attr = self.get_attr_from_file_info(&file_info);
            reply.entry(&TTL, &attr, 0);
        } else {
            reply.error(ENOENT);
        }
    }

    fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
        let file_info = self.get_file_info_from_ino(ino);
        if let Ok(file_info) = file_info {
            let attr = self.get_attr_from_file_info(&file_info);
            reply.attr(&TTL, &attr)
        } else {
            reply.error(ENOENT)
        }
    }

    fn readlink(&mut self, _req: &Request, ino: u64, reply: ReplyData) {
        let path = self.get_file_info_from_ino(ino).unwrap().path;
        let target = fs::read_link(path).unwrap();
        let target = target.to_str().unwrap().as_bytes();
        reply.data(target);
    }
}

#[cfg(test)]
pub(crate) mod tests {
    use std::collections::hash_map::RandomState;
    use std::collections::HashMap;
    use std::fs;
    use std::fs::{DirEntry, File};
    use std::io::Write;
    use std::os::unix::fs::symlink;
    use std::path::Path;

    pub(crate) fn create_files_and_symlinks(
        path: &Path,
        files: &Vec<(String, Vec<u8>)>,
        symlinks: &Vec<(String, String)>,
    ) -> Result<(), std::io::Error> {
        for (file_name, data) in files {
            let path = path.join(file_name);
            fs::create_dir_all(path.parent().unwrap())?;
            let mut file = File::create(&path)?;
            file.write_all(&data)?;
        }

        for (link_name, target) in symlinks {
            symlink(&target, path.join(&link_name))?;
        }

        Ok(())
    }

    pub(crate) fn check_symlinks(
        symlink_map: &mut HashMap<String, String, RandomState>,
        symlinks_found: &Vec<&DirEntry>,
    ) -> Result<(), std::io::Error> {
        assert_eq!(symlinks_found.len(), symlink_map.len());

        for symlink in symlinks_found {
            let symlink = symlink.path();
            let link_name = symlink.file_name().unwrap().to_str().unwrap();
            let target = symlink_map.remove(link_name);
            assert_ne!(target, None);
            let target = target.unwrap();
            assert_eq!(fs::read_link(symlink)?.to_str().unwrap(), target);
        }

        assert!(symlink_map.is_empty());

        Ok(())
    }
}