use std::ffi::{OsStr, OsString};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WhiteoutFormat {
CharDev,
OciWhiteout,
}
impl Default for WhiteoutFormat {
fn default() -> Self {
if cfg!(target_os = "macos") {
WhiteoutFormat::OciWhiteout
} else {
WhiteoutFormat::CharDev
}
}
}
pub const OCI_WHITEOUT_PREFIX: &str = ".wh.";
pub const OCI_OPAQUE_MARKER: &str = ".wh..wh..opq";
pub fn is_oci_whiteout_name(name: &OsStr) -> bool {
let bytes = name.as_encoded_bytes();
bytes.starts_with(OCI_WHITEOUT_PREFIX.as_bytes()) && bytes != OCI_OPAQUE_MARKER.as_bytes()
}
pub fn is_oci_opaque_marker(name: &OsStr) -> bool {
name.as_encoded_bytes() == OCI_OPAQUE_MARKER.as_bytes()
}
pub fn oci_whiteout_target(raw: &OsStr) -> Option<&OsStr> {
if !is_oci_whiteout_name(raw) {
return None;
}
let bytes = raw.as_encoded_bytes();
let target = &bytes[OCI_WHITEOUT_PREFIX.len()..];
Some(unsafe { OsStr::from_encoded_bytes_unchecked(target) })
}
pub fn oci_whiteout_name(base: &OsStr) -> OsString {
use std::os::unix::ffi::OsStringExt;
let mut bytes = OCI_WHITEOUT_PREFIX.as_bytes().to_vec();
bytes.extend_from_slice(base.as_encoded_bytes());
OsString::from_vec(bytes)
}
pub fn is_user_creatable_name(format: WhiteoutFormat, name: &OsStr) -> bool {
match format {
WhiteoutFormat::CharDev => true,
WhiteoutFormat::OciWhiteout => {
let bytes = name.as_encoded_bytes();
!bytes.starts_with(OCI_WHITEOUT_PREFIX.as_bytes())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_whiteout_names() {
assert!(is_oci_whiteout_name(OsStr::new(".wh.foo")));
assert!(is_oci_whiteout_name(OsStr::new(".wh..hidden")));
assert!(!is_oci_whiteout_name(OsStr::new("foo")));
assert!(!is_oci_whiteout_name(OsStr::new(OCI_OPAQUE_MARKER)));
}
#[test]
fn detects_opaque_marker() {
assert!(is_oci_opaque_marker(OsStr::new(OCI_OPAQUE_MARKER)));
assert!(!is_oci_opaque_marker(OsStr::new(".wh.foo")));
}
#[test]
fn target_extraction() {
assert_eq!(
oci_whiteout_target(OsStr::new(".wh.foo")),
Some(OsStr::new("foo"))
);
assert_eq!(oci_whiteout_target(OsStr::new("foo")), None);
assert_eq!(oci_whiteout_target(OsStr::new(OCI_OPAQUE_MARKER)), None);
}
#[test]
fn name_construction_roundtrips() {
let made = oci_whiteout_name(OsStr::new("payload"));
assert_eq!(made, OsString::from(".wh.payload"));
assert_eq!(oci_whiteout_target(&made), Some(OsStr::new("payload")));
}
#[test]
fn user_creation_rejected_in_oci() {
assert!(!is_user_creatable_name(
WhiteoutFormat::OciWhiteout,
OsStr::new(".wh.evil"),
));
assert!(is_user_creatable_name(
WhiteoutFormat::OciWhiteout,
OsStr::new("normal"),
));
assert!(is_user_creatable_name(
WhiteoutFormat::CharDev,
OsStr::new(".wh.allowed"),
));
}
#[test]
fn default_per_platform() {
let want = if cfg!(target_os = "macos") {
WhiteoutFormat::OciWhiteout
} else {
WhiteoutFormat::CharDev
};
assert_eq!(WhiteoutFormat::default(), want);
}
}