easy_fuser 0.7.1

A flexible and idiomatic Fuse implementation for Rust
Documentation
use std::collections::HashSet;
use std::ffi::{OsStr, OsString};
use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;

use crate::types::*;
use crate::unix_fs;

use super::{TEMP_ID, TEMP_PREFIX, XATTR_BUFFER_LIMIT};

pub(super) fn overlaps(left: &Path, right: &Path) -> bool {
    left.starts_with(right) || right.starts_with(left)
}

pub(super) fn metadata_if_exists(path: &Path) -> FuseResult<Option<fs::Metadata>> {
    match fs::symlink_metadata(path) {
        Ok(metadata) => Ok(Some(metadata)),
        Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(None),
        Err(error) => Err(error.into()),
    }
}

pub(super) fn load_whiteouts(path: &Path) -> io::Result<HashSet<PathBuf>> {
    let data = match fs::read_to_string(path) {
        Ok(data) => data,
        Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(HashSet::new()),
        Err(error) => return Err(error),
    };
    data.lines()
        .map(|line| {
            let bytes = line.as_bytes();
            if bytes.len() % 2 != 0 {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    "invalid overlay whiteout",
                ));
            }
            let decoded = bytes
                .chunks_exact(2)
                .map(|pair| {
                    let digit = |byte: u8| match byte {
                        b'0'..=b'9' => Some(byte - b'0'),
                        b'a'..=b'f' => Some(byte - b'a' + 10),
                        b'A'..=b'F' => Some(byte - b'A' + 10),
                        _ => None,
                    };
                    match (digit(pair[0]), digit(pair[1])) {
                        (Some(high), Some(low)) => Ok(high << 4 | low),
                        _ => Err(io::Error::new(
                            io::ErrorKind::InvalidData,
                            "invalid overlay whiteout",
                        )),
                    }
                })
                .collect::<io::Result<Vec<_>>>()?;
            Ok(PathBuf::from(OsString::from_vec(decoded)))
        })
        .collect()
}

pub(super) fn save_whiteouts(path: &Path, whiteouts: &HashSet<PathBuf>) -> io::Result<()> {
    let mut paths = whiteouts.iter().collect::<Vec<_>>();
    paths.sort();
    let mut data = String::new();
    for path in paths {
        for byte in path.as_os_str().as_bytes() {
            use std::fmt::Write as _;
            write!(&mut data, "{byte:02x}").unwrap();
        }
        data.push('\n');
    }
    let temporary = unique_sibling(path, "whiteouts").map_err(|error| error.io_error())?;
    let mut file = OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(&temporary)?;
    file.write_all(data.as_bytes())?;
    file.sync_all()?;
    drop(file);
    if let Err(error) = fs::rename(&temporary, path) {
        let _ = fs::remove_file(&temporary);
        return Err(error);
    }
    Ok(())
}

pub(super) fn unique_sibling(path: &Path, label: &str) -> FuseResult<PathBuf> {
    let parent = path.parent().unwrap_or(Path::new(""));
    if path.file_name().is_none() {
        return Err(ErrorKind::InvalidArgument.to_error("path has no file name"));
    }
    loop {
        let id = TEMP_ID.fetch_add(1, Ordering::Relaxed);
        let temporary_name = format!("{TEMP_PREFIX}{label}.{}.{}", std::process::id(), id);
        let candidate = parent.join(temporary_name);
        if metadata_if_exists(&candidate)?.is_none() {
            return Ok(candidate);
        }
    }
}

pub(super) fn copy_metadata(
    source: &Path,
    destination: &Path,
    metadata: &fs::Metadata,
) -> FuseResult<()> {
    let owner = SetAttrRequest::new()
        .uid(metadata.uid())
        .gid(metadata.gid());
    if let Err(error) = unix_fs::setattr(destination, owner) {
        let raw_error = error.io_error().raw_os_error();
        if raw_error != Some(libc::EPERM) && raw_error != Some(libc::EACCES) {
            return Err(error);
        }
    }
    fs::set_permissions(
        destination,
        fs::Permissions::from_mode(metadata.permissions().mode()),
    )?;
    copy_xattrs(source, destination)?;
    if let (Ok(atime), Ok(mtime)) = (metadata.accessed(), metadata.modified()) {
        unix_fs::setattr(
            destination,
            SetAttrRequest::new()
                .atime(TimeOrNow::SpecificTime(atime))
                .mtime(TimeOrNow::SpecificTime(mtime)),
        )?;
    }
    Ok(())
}

fn copy_xattrs(source: &Path, destination: &Path) -> FuseResult<()> {
    let names = match read_xattr_names(source) {
        Ok(names) => names,
        Err(error)
            if error.io_error().raw_os_error() == Some(libc::ENOTSUP)
                || error.io_error().raw_os_error() == Some(libc::EOPNOTSUPP) =>
        {
            return Ok(());
        }
        Err(error) => return Err(error),
    };
    for name in names
        .split(|byte| *byte == 0)
        .filter(|name| !name.is_empty())
    {
        let name = OsStr::from_bytes(name);
        let value = read_xattr_value(source, name)?;
        unix_fs::setxattr(destination, name, &value, SetXAttrFlags::empty(), 0)?;
    }
    Ok(())
}

fn read_xattr_names(path: &Path) -> FuseResult<Vec<u8>> {
    read_xattr_with_growth(
        |size| unix_fs::listxattr(path, size),
        "too many extended attributes",
    )
}

fn read_xattr_value(path: &Path, name: &OsStr) -> FuseResult<Vec<u8>> {
    read_xattr_with_growth(
        |size| unix_fs::getxattr(path, name, size),
        "extended attribute is too large",
    )
}

fn read_xattr_with_growth(
    mut read: impl FnMut(u32) -> FuseResult<Vec<u8>>,
    too_large_message: &str,
) -> FuseResult<Vec<u8>> {
    let mut size = 256;
    loop {
        match read(size) {
            Ok(value) => return Ok(value),
            Err(error) if error.io_error().raw_os_error() == Some(libc::ERANGE) => {
                if size >= XATTR_BUFFER_LIMIT {
                    return Err(ErrorKind::ValueTooLarge.to_error(too_large_message));
                }
                size = (size * 2).min(XATTR_BUFFER_LIMIT);
            }
            Err(error) => return Err(error),
        }
    }
}