acyclic-fs 0.1.5

Rust SDK for versioned workspaces and embedded native mounts
Documentation
use crate::kernel::NameEncoding;
use crate::model::FilesystemProfile;
use std::ffi::OsStr;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct HostNameEncodingError;

fn copy_bytes(source: &[u8], maximum_bytes: u32) -> Result<Vec<u8>, HostNameEncodingError> {
    let maximum_bytes = usize::try_from(maximum_bytes).map_err(|_| HostNameEncodingError)?;
    if source.len() > maximum_bytes {
        return Err(HostNameEncodingError);
    }
    let mut bytes = Vec::new();
    bytes
        .try_reserve_exact(source.len())
        .map_err(|_| HostNameEncodingError)?;
    bytes.extend_from_slice(source);
    Ok(bytes)
}

fn utf16_bytes<I>(units: I, maximum_bytes: u32) -> Result<Vec<u8>, HostNameEncodingError>
where
    I: Iterator<Item = u16> + Clone,
{
    let length = units
        .clone()
        .count()
        .checked_mul(2)
        .ok_or(HostNameEncodingError)?;
    let maximum_bytes = usize::try_from(maximum_bytes).map_err(|_| HostNameEncodingError)?;
    if length > maximum_bytes {
        return Err(HostNameEncodingError);
    }
    let mut bytes = Vec::new();
    bytes
        .try_reserve_exact(length)
        .map_err(|_| HostNameEncodingError)?;
    for unit in units {
        bytes.extend_from_slice(&unit.to_le_bytes());
    }
    Ok(bytes)
}

#[cfg(unix)]
pub(crate) fn host_name_bytes(
    name: &OsStr,
    profile: FilesystemProfile,
    maximum_bytes: u32,
) -> Result<(NameEncoding, Vec<u8>), HostNameEncodingError> {
    use std::os::unix::ffi::OsStrExt;
    let raw = name.as_bytes();
    match profile {
        FilesystemProfile::Posix => Ok((NameEncoding::PosixBytes, copy_bytes(raw, maximum_bytes)?)),
        FilesystemProfile::Windows => {
            let text = std::str::from_utf8(raw).map_err(|_| HostNameEncodingError)?;
            Ok((
                NameEncoding::WindowsUtf16Le,
                utf16_bytes(text.encode_utf16(), maximum_bytes)?,
            ))
        }
        FilesystemProfile::Portable | FilesystemProfile::Browser => {
            std::str::from_utf8(raw).map_err(|_| HostNameEncodingError)?;
            Ok((NameEncoding::Utf8, copy_bytes(raw, maximum_bytes)?))
        }
    }
}

#[cfg(windows)]
pub(crate) fn host_name_bytes(
    name: &OsStr,
    profile: FilesystemProfile,
    maximum_bytes: u32,
) -> Result<(NameEncoding, Vec<u8>), HostNameEncodingError> {
    use std::os::windows::ffi::OsStrExt;
    match profile {
        FilesystemProfile::Windows => Ok((
            NameEncoding::WindowsUtf16Le,
            utf16_bytes(name.encode_wide(), maximum_bytes)?,
        )),
        FilesystemProfile::Posix => Ok((
            NameEncoding::PosixBytes,
            copy_bytes(
                name.to_str().ok_or(HostNameEncodingError)?.as_bytes(),
                maximum_bytes,
            )?,
        )),
        FilesystemProfile::Portable | FilesystemProfile::Browser => Ok((
            NameEncoding::Utf8,
            copy_bytes(
                name.to_str().ok_or(HostNameEncodingError)?.as_bytes(),
                maximum_bytes,
            )?,
        )),
    }
}

#[cfg(not(any(unix, windows)))]
pub(crate) fn host_name_bytes(
    name: &OsStr,
    _profile: FilesystemProfile,
    maximum_bytes: u32,
) -> Result<(NameEncoding, Vec<u8>), HostNameEncodingError> {
    Ok((
        NameEncoding::Utf8,
        copy_bytes(
            name.to_str().ok_or(HostNameEncodingError)?.as_bytes(),
            maximum_bytes,
        )?,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn encoding_rejects_a_component_before_allocating_beyond_its_bound() {
        assert!(host_name_bytes(OsStr::new("ab"), FilesystemProfile::Portable, 1).is_err());
        assert!(host_name_bytes(OsStr::new("ab"), FilesystemProfile::Windows, 3).is_err());
    }
}