#![allow(missing_docs)]
use std::ffi::{CStr, CString};
use std::io::{Error, ErrorKind, Result};
use super::{Context, Entry, FileSystem, GetxattrReply};
use crate::abi::fuse_abi::stat64;
pub const OPAQUE_XATTR_LEN: u32 = 16;
pub const OPAQUE_XATTR: &str = "user.fuseoverlayfs.opaque";
pub const UNPRIVILEGED_OPAQUE_XATTR: &str = "user.overlay.opaque";
pub const PRIVILEGED_OPAQUE_XATTR: &str = "trusted.overlay.opaque";
pub trait Layer: FileSystem {
fn root_inode(&self) -> Self::Inode;
fn create_whiteout(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> Result<Entry> {
let ino: u64 = parent.into();
match self.lookup(ctx, ino.into(), name) {
Ok(v) => {
if is_whiteout(v.attr) {
return Ok(v);
}
if v.inode != 0 {
self.forget(ctx, v.inode.into(), 1);
return Err(Error::from_raw_os_error(libc::EEXIST));
}
}
Err(e) => match e.raw_os_error() {
Some(raw_error) => {
if raw_error != libc::ENOENT {
return Err(e);
}
}
None => return Err(e),
},
}
let dev = libc::makedev(0, 0);
let mode = libc::S_IFCHR | 0o777;
self.mknod(ctx, ino.into(), name, mode, dev as u32, 0)
}
fn delete_whiteout(&self, ctx: &Context, parent: Self::Inode, name: &CStr) -> Result<()> {
let ino: u64 = parent.into();
match self.lookup(ctx, ino.into(), name) {
Ok(v) => {
if v.inode != 0 {
self.forget(ctx, v.inode.into(), 1);
}
if is_whiteout(v.attr) {
return self.unlink(ctx, ino.into(), name);
}
if v.inode != 0 {
return Err(Error::from_raw_os_error(libc::EINVAL));
}
}
Err(e) => match e.raw_os_error() {
Some(raw_error) => {
if raw_error != libc::ENOENT {
return Err(e);
}
}
None => return Err(e),
},
}
Ok(())
}
fn is_whiteout(&self, ctx: &Context, inode: Self::Inode) -> Result<bool> {
let (st, _) = self.getattr(ctx, inode, None)?;
Ok(is_whiteout(st))
}
fn set_opaque(&self, ctx: &Context, inode: Self::Inode) -> Result<()> {
let ino: u64 = inode.into();
let (st, _d) = self.getattr(ctx, ino.into(), None)?;
if !is_dir(st) {
return Err(Error::from_raw_os_error(libc::ENOTDIR));
}
self.setxattr(
ctx,
ino.into(),
to_cstring(OPAQUE_XATTR)?.as_c_str(),
b"y",
0,
)
}
fn is_opaque(&self, ctx: &Context, inode: Self::Inode) -> Result<bool> {
let ino: u64 = inode.into();
let (st, _d) = self.getattr(ctx, ino.into(), None)?;
if !is_dir(st) {
return Err(Error::from_raw_os_error(libc::ENOTDIR));
}
let check_attr = |inode: Self::Inode, attr_name: &str, attr_size: u32| -> Result<bool> {
let cname = CString::new(attr_name)?;
match self.getxattr(ctx, inode, cname.as_c_str(), attr_size) {
Ok(v) => {
if let GetxattrReply::Value(buf) = v {
if buf.len() == 1 && buf[0].eq_ignore_ascii_case(&b'y') {
return Ok(true);
}
}
Ok(false)
}
Err(e) => {
if let Some(raw_error) = e.raw_os_error() {
if raw_error == libc::ENODATA {
return Ok(false);
}
}
Err(e)
}
}
};
let is_opaque = check_attr(ino.into(), OPAQUE_XATTR, OPAQUE_XATTR_LEN)?;
if is_opaque {
return Ok(true);
}
let is_opaque = check_attr(ino.into(), PRIVILEGED_OPAQUE_XATTR, OPAQUE_XATTR_LEN)?;
if is_opaque {
return Ok(true);
}
let is_opaque = check_attr(ino.into(), UNPRIVILEGED_OPAQUE_XATTR, OPAQUE_XATTR_LEN)?;
if is_opaque {
return Ok(true);
}
Ok(false)
}
}
pub(crate) fn is_dir(st: stat64) -> bool {
st.st_mode & libc::S_IFMT == libc::S_IFDIR
}
pub(crate) fn is_chardev(st: stat64) -> bool {
st.st_mode & libc::S_IFMT == libc::S_IFCHR
}
pub(crate) fn is_whiteout(st: stat64) -> bool {
let major = libc::major(st.st_rdev);
let minor = libc::minor(st.st_rdev);
is_chardev(st) && major == 0 && minor == 0
}
pub(crate) fn to_cstring(name: &str) -> Result<CString> {
CString::new(name).map_err(|e| Error::new(ErrorKind::InvalidData, e))
}