use std::{
ffi::CStr,
io,
os::fd::FromRawFd,
sync::{Arc, RwLock, atomic::Ordering},
};
use super::{PassthroughFs, inode};
use crate::{
Context, Entry, Extensions, OpenOptions,
backends::shared::{handle_table::HandleData, name_validation, platform, stat_override},
};
#[allow(clippy::too_many_arguments)]
pub(crate) fn do_create(
fs: &PassthroughFs,
ctx: Context,
parent: u64,
name: &CStr,
mode: u32,
kill_priv: bool,
flags: u32,
umask: u32,
_extensions: Extensions,
) -> io::Result<(Entry, Option<u64>, OpenOptions)> {
name_validation::validate_name(name)?;
if fs.is_reserved_init_name(parent, name.to_bytes()) {
return Err(platform::eacces());
}
let parent_fd = inode::get_inode_fd(fs, parent)?;
let file_mode = mode & !umask & 0o7777;
let mut open_flags = inode::translate_open_flags(flags as i32);
open_flags |= libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW;
let fd = unsafe {
libc::openat(
parent_fd.raw(),
name.as_ptr(),
open_flags,
(libc::S_IRUSR | libc::S_IWUSR) as libc::c_uint,
)
};
if fd < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
let full_mode = platform::MODE_REG | file_mode;
if let Err(e) = stat_override::set_override(fd, ctx.uid, ctx.gid, full_mode, 0) {
unsafe { libc::close(fd) };
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(e);
}
unsafe { libc::close(fd) };
let entry = inode::do_lookup(fs, parent, name)?;
let open_fd = inode::open_inode_fd(fs, entry.inode, open_flags & !libc::O_CREAT)?;
if kill_priv
&& (open_flags & libc::O_TRUNC != 0)
&& let Some(ovr) = stat_override::get_override(open_fd, fs.cfg.xattr, fs.cfg.strict)?
{
let new_mode = ovr.mode & !(platform::MODE_SETUID | platform::MODE_SETGID);
if new_mode != ovr.mode {
let _ = stat_override::set_override(open_fd, ovr.uid, ovr.gid, new_mode, ovr.rdev);
}
}
let file = unsafe { std::fs::File::from_raw_fd(open_fd) };
let handle = fs.next_handle.fetch_add(1, Ordering::Relaxed);
let data = Arc::new(HandleData {
file: RwLock::new(file),
});
fs.handles.write().unwrap().insert(handle, data);
Ok((entry, Some(handle), fs.cache_open_options()))
}
pub(crate) fn do_mkdir(
fs: &PassthroughFs,
ctx: Context,
parent: u64,
name: &CStr,
mode: u32,
umask: u32,
_extensions: Extensions,
) -> io::Result<Entry> {
name_validation::validate_name(name)?;
if fs.is_reserved_init_name(parent, name.to_bytes()) {
return Err(platform::eacces());
}
let parent_fd = inode::get_inode_fd(fs, parent)?;
let dir_mode = mode & !umask & 0o7777;
let ret = unsafe {
libc::mkdirat(
parent_fd.raw(),
name.as_ptr(),
(libc::S_IRWXU) as libc::mode_t,
)
};
if ret < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
let full_mode = platform::MODE_DIR | dir_mode;
if let Err(e) =
stat_override::set_override_at(parent_fd.raw(), name, ctx.uid, ctx.gid, full_mode, 0)
{
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), libc::AT_REMOVEDIR) };
return Err(e);
}
inode::do_lookup(fs, parent, name)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn do_mknod(
fs: &PassthroughFs,
ctx: Context,
parent: u64,
name: &CStr,
mode: u32,
rdev: u32,
umask: u32,
_extensions: Extensions,
) -> io::Result<Entry> {
name_validation::validate_name(name)?;
if fs.is_reserved_init_name(parent, name.to_bytes()) {
return Err(platform::eacces());
}
let parent_fd = inode::get_inode_fd(fs, parent)?;
let perm_mode = mode & !umask & 0o7777;
let file_type = mode & platform::MODE_TYPE_MASK;
let fd = unsafe {
libc::openat(
parent_fd.raw(),
name.as_ptr(),
libc::O_CREAT | libc::O_EXCL | libc::O_WRONLY | libc::O_CLOEXEC,
(libc::S_IRUSR | libc::S_IWUSR) as libc::c_uint,
)
};
if fd < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
let full_mode = file_type | perm_mode;
if let Err(e) = stat_override::set_override(fd, ctx.uid, ctx.gid, full_mode, rdev) {
unsafe { libc::close(fd) };
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(e);
}
unsafe { libc::close(fd) };
inode::do_lookup(fs, parent, name)
}
pub(crate) fn do_symlink(
fs: &PassthroughFs,
ctx: Context,
linkname: &CStr,
parent: u64,
name: &CStr,
_extensions: Extensions,
) -> io::Result<Entry> {
name_validation::validate_name(name)?;
if fs.is_reserved_init_name(parent, name.to_bytes()) {
return Err(platform::eacces());
}
let parent_fd = inode::get_inode_fd(fs, parent)?;
#[cfg(target_os = "linux")]
{
let fd = unsafe {
libc::openat(
parent_fd.raw(),
name.as_ptr(),
libc::O_CREAT | libc::O_EXCL | libc::O_WRONLY | libc::O_CLOEXEC,
(libc::S_IRUSR | libc::S_IWUSR) as libc::c_uint,
)
};
if fd < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
let target = linkname.to_bytes();
let written =
unsafe { libc::write(fd, target.as_ptr() as *const libc::c_void, target.len()) };
if written < 0 {
let err = io::Error::last_os_error();
unsafe { libc::close(fd) };
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(platform::linux_error(err));
}
if (written as usize) != target.len() {
unsafe { libc::close(fd) };
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(platform::eio());
}
let mode = platform::MODE_LNK | 0o777;
if let Err(e) = stat_override::set_override(fd, ctx.uid, ctx.gid, mode, 0) {
unsafe { libc::close(fd) };
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(e);
}
unsafe { libc::close(fd) };
}
#[cfg(target_os = "macos")]
{
let ret = unsafe { libc::symlinkat(linkname.as_ptr(), parent_fd.raw(), name.as_ptr()) };
if ret < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
let mode = platform::MODE_LNK | 0o777;
let fd = unsafe {
libc::openat(
parent_fd.raw(),
name.as_ptr(),
libc::O_RDONLY | libc::O_CLOEXEC | libc::O_SYMLINK,
)
};
if fd < 0 {
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(platform::linux_error(io::Error::last_os_error()));
}
let xattr_result = stat_override::set_override(fd, ctx.uid, ctx.gid, mode, 0);
unsafe { libc::close(fd) };
if let Err(err) = xattr_result {
unsafe { libc::unlinkat(parent_fd.raw(), name.as_ptr(), 0) };
return Err(err);
}
}
inode::do_lookup(fs, parent, name)
}
pub(crate) fn do_link(
fs: &PassthroughFs,
_ctx: Context,
inode: u64,
newparent: u64,
newname: &CStr,
) -> io::Result<Entry> {
name_validation::validate_name(newname)?;
if fs.is_reserved_init_name(newparent, newname.to_bytes()) {
return Err(platform::eacces());
}
if fs.is_virtual_init_inode(inode) {
return Err(platform::eacces());
}
#[cfg(target_os = "linux")]
{
let inode_fd = inode::get_inode_fd(fs, inode)?;
let newparent_fd = inode::get_inode_fd(fs, newparent)?;
let path = format!("/proc/self/fd/{}\0", inode_fd.raw());
let ret = unsafe {
libc::linkat(
libc::AT_FDCWD,
path.as_ptr() as *const libc::c_char,
newparent_fd.raw(),
newname.as_ptr(),
libc::AT_SYMLINK_FOLLOW,
)
};
if ret < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
}
#[cfg(target_os = "macos")]
{
let inodes = fs.inodes.read().unwrap();
let data = inodes.get(&inode).ok_or_else(platform::ebadf)?;
let src_path = format!("/.vol/{}/{}\0", data.dev, data.ino);
let newparent_fd = inode::get_inode_fd(fs, newparent)?;
let ret = unsafe {
libc::linkat(
libc::AT_FDCWD,
src_path.as_ptr() as *const libc::c_char,
newparent_fd.raw(),
newname.as_ptr(),
0,
)
};
if ret < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
}
inode::do_lookup(fs, newparent, newname)
}
pub(crate) fn do_readlink(fs: &PassthroughFs, _ctx: Context, ino: u64) -> io::Result<Vec<u8>> {
if fs.is_virtual_init_inode(ino) {
return Err(platform::einval());
}
#[cfg(target_os = "linux")]
{
let inode_fd = inode::get_inode_fd(fs, ino)?;
let st = platform::fstat(inode_fd.raw())?;
if st.st_mode & libc::S_IFMT == libc::S_IFLNK {
return platform::readlink_fd(inode_fd.raw());
}
match stat_override::get_override(inode_fd.raw(), fs.cfg.xattr, fs.cfg.strict)? {
Some(ovr) if ovr.mode & platform::MODE_TYPE_MASK == platform::MODE_LNK => {}
_ => return Err(platform::einval()),
}
let fd = inode::open_inode_fd(fs, ino, libc::O_RDONLY)?;
let mut buf = vec![0u8; libc::PATH_MAX as usize];
let ret = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
let read_err = (ret < 0).then(io::Error::last_os_error);
unsafe { libc::close(fd) };
if ret < 0 {
return Err(platform::linux_error(
read_err.unwrap_or_else(io::Error::last_os_error),
));
}
buf.truncate(ret as usize);
Ok(buf)
}
#[cfg(target_os = "macos")]
{
let st = inode::stat_inode(fs, ino)?;
if platform::mode_file_type(st.st_mode) != platform::MODE_LNK {
return Err(platform::einval());
}
let inodes = fs.inodes.read().unwrap();
let data = inodes.get(&ino).ok_or_else(platform::ebadf)?;
let path = format!("/.vol/{}/{}\0", data.dev, data.ino);
let mut buf = vec![0u8; libc::PATH_MAX as usize];
let ret = unsafe {
libc::readlinkat(
libc::AT_FDCWD,
path.as_ptr() as *const libc::c_char,
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
)
};
if ret < 0 {
return Err(platform::linux_error(io::Error::last_os_error()));
}
buf.truncate(ret as usize);
Ok(buf)
}
}