use std::{
ffi::CStr,
io,
sync::{Arc, atomic::Ordering},
};
use super::{
MemFs, inode,
types::{InodeContent, ROOT_INODE},
};
use crate::{
Context,
backends::shared::{init_binary, name_validation, platform},
};
pub(crate) fn do_unlink(fs: &MemFs, _ctx: Context, parent: u64, name: &CStr) -> io::Result<()> {
name_validation::validate_memfs_name(name)?;
if parent == ROOT_INODE && init_binary::is_init_name(name.to_bytes()) {
return Err(platform::eacces());
}
let parent_node = inode::get_node(fs, parent)?;
let name_bytes = name.to_bytes().to_vec();
let now = inode::current_time();
let child_ino = match &parent_node.content {
InodeContent::Directory { children, .. } => {
let mut ch = children.write().unwrap();
ch.remove(&name_bytes).ok_or_else(platform::enoent)?
}
_ => return Err(platform::enotdir()),
};
let child_node = inode::get_node(fs, child_ino)?;
if child_node.kind == platform::MODE_DIR {
if let InodeContent::Directory { children, .. } = &parent_node.content {
children.write().unwrap().insert(name_bytes, child_ino);
}
return Err(platform::eisdir());
}
{
let mut meta = child_node.meta.write().unwrap();
meta.nlink = meta.nlink.saturating_sub(1);
meta.ctime = now;
}
{
let mut meta = parent_node.meta.write().unwrap();
meta.mtime = now;
meta.ctime = now;
}
inode::try_evict(fs, child_ino);
Ok(())
}
pub(crate) fn do_rmdir(fs: &MemFs, _ctx: Context, parent: u64, name: &CStr) -> io::Result<()> {
name_validation::validate_memfs_name(name)?;
if parent == ROOT_INODE && init_binary::is_init_name(name.to_bytes()) {
return Err(platform::eacces());
}
let parent_node = inode::get_node(fs, parent)?;
let name_bytes = name.to_bytes().to_vec();
let now = inode::current_time();
let child_ino = match &parent_node.content {
InodeContent::Directory { children, .. } => {
let ch = children.read().unwrap();
*ch.get(&name_bytes).ok_or_else(platform::enoent)?
}
_ => return Err(platform::enotdir()),
};
let child_node = inode::get_node(fs, child_ino)?;
if child_node.kind != platform::MODE_DIR {
return Err(platform::enotdir());
}
if let InodeContent::Directory { children, .. } = &child_node.content
&& !children.read().unwrap().is_empty()
{
return Err(platform::enotempty());
}
if let InodeContent::Directory { children, .. } = &parent_node.content {
children.write().unwrap().remove(&name_bytes);
}
{
let mut meta = child_node.meta.write().unwrap();
meta.nlink = 0;
meta.ctime = now;
}
{
let mut meta = parent_node.meta.write().unwrap();
meta.nlink = meta.nlink.saturating_sub(1);
meta.mtime = now;
meta.ctime = now;
}
inode::try_evict(fs, child_ino);
Ok(())
}
const RENAME_NOREPLACE: u32 = 1;
const RENAME_EXCHANGE: u32 = 2;
const KNOWN_RENAME_FLAGS: u32 = RENAME_NOREPLACE | RENAME_EXCHANGE;
pub(crate) fn do_rename(
fs: &MemFs,
_ctx: Context,
olddir: u64,
oldname: &CStr,
newdir: u64,
newname: &CStr,
flags: u32,
) -> io::Result<()> {
name_validation::validate_memfs_name(oldname)?;
name_validation::validate_memfs_name(newname)?;
if flags & !KNOWN_RENAME_FLAGS != 0 {
return Err(platform::einval());
}
if flags & RENAME_NOREPLACE != 0 && flags & RENAME_EXCHANGE != 0 {
return Err(platform::einval());
}
let old_bytes = oldname.to_bytes().to_vec();
let new_bytes = newname.to_bytes().to_vec();
if olddir == ROOT_INODE && init_binary::is_init_name(&old_bytes) {
return Err(platform::eacces());
}
if newdir == ROOT_INODE && init_binary::is_init_name(&new_bytes) {
return Err(platform::eacces());
}
if olddir == newdir && old_bytes == new_bytes {
return Ok(());
}
let old_parent = inode::get_node(fs, olddir)?;
let new_parent = if newdir == olddir {
Arc::clone(&old_parent)
} else {
inode::get_node(fs, newdir)?
};
let now = inode::current_time();
let source_ino = match &old_parent.content {
InodeContent::Directory { children, .. } => {
let ch = children.read().unwrap();
*ch.get(&old_bytes).ok_or_else(platform::enoent)?
}
_ => return Err(platform::enotdir()),
};
let source_node = inode::get_node(fs, source_ino)?;
let source_is_dir = source_node.kind == platform::MODE_DIR;
let dest_ino = match &new_parent.content {
InodeContent::Directory { children, .. } => {
let ch = children.read().unwrap();
ch.get(&new_bytes).copied()
}
_ => return Err(platform::enotdir()),
};
if flags & RENAME_EXCHANGE != 0 {
let dest = dest_ino.ok_or_else(platform::enoent)?;
let dest_node = inode::get_node(fs, dest)?;
if olddir == newdir {
if let InodeContent::Directory { children, .. } = &old_parent.content {
let mut ch = children.write().unwrap();
ch.insert(old_bytes, dest);
ch.insert(new_bytes, source_ino);
}
} else {
if let InodeContent::Directory { children, .. } = &old_parent.content {
children.write().unwrap().insert(old_bytes, dest);
}
if let InodeContent::Directory { children, .. } = &new_parent.content {
children.write().unwrap().insert(new_bytes, source_ino);
}
}
if source_is_dir
&& olddir != newdir
&& let InodeContent::Directory { parent, .. } = &source_node.content
{
parent.store(newdir, Ordering::Relaxed);
}
let dest_is_dir = dest_node.kind == platform::MODE_DIR;
if dest_is_dir
&& olddir != newdir
&& let InodeContent::Directory { parent, .. } = &dest_node.content
{
parent.store(olddir, Ordering::Relaxed);
}
{
let mut meta = source_node.meta.write().unwrap();
meta.ctime = now;
}
{
let mut meta = dest_node.meta.write().unwrap();
meta.ctime = now;
}
{
let mut meta = old_parent.meta.write().unwrap();
meta.mtime = now;
meta.ctime = now;
}
if olddir != newdir {
let mut meta = new_parent.meta.write().unwrap();
meta.mtime = now;
meta.ctime = now;
}
return Ok(());
}
if flags & RENAME_NOREPLACE != 0 && dest_ino.is_some() {
return Err(platform::eexist());
}
let mut evict_dest = None;
if let Some(dest) = dest_ino {
let dest_node = inode::get_node(fs, dest)?;
let dest_is_dir = dest_node.kind == platform::MODE_DIR;
if source_is_dir && !dest_is_dir {
return Err(platform::enotdir());
}
if !source_is_dir && dest_is_dir {
return Err(platform::eisdir());
}
if dest_is_dir
&& let InodeContent::Directory { children, .. } = &dest_node.content
&& !children.read().unwrap().is_empty()
{
return Err(platform::enotempty());
}
{
let mut meta = dest_node.meta.write().unwrap();
if dest_is_dir {
meta.nlink = 0;
} else {
meta.nlink = meta.nlink.saturating_sub(1);
}
meta.ctime = now;
}
if dest_is_dir {
let mut meta = new_parent.meta.write().unwrap();
meta.nlink = meta.nlink.saturating_sub(1);
}
evict_dest = Some(dest);
}
if olddir == newdir {
if let InodeContent::Directory { children, .. } = &old_parent.content {
let mut ch = children.write().unwrap();
ch.remove(&old_bytes);
ch.insert(new_bytes, source_ino);
}
} else {
if let InodeContent::Directory { children, .. } = &old_parent.content {
children.write().unwrap().remove(&old_bytes);
}
if let InodeContent::Directory { children, .. } = &new_parent.content {
children.write().unwrap().insert(new_bytes, source_ino);
}
}
if source_is_dir && olddir != newdir {
{
let mut meta = old_parent.meta.write().unwrap();
meta.nlink = meta.nlink.saturating_sub(1);
}
{
let mut meta = new_parent.meta.write().unwrap();
meta.nlink += 1;
}
if let InodeContent::Directory { parent, .. } = &source_node.content {
parent.store(newdir, Ordering::Relaxed);
}
}
{
let mut meta = source_node.meta.write().unwrap();
meta.ctime = now;
}
{
let mut meta = old_parent.meta.write().unwrap();
meta.mtime = now;
meta.ctime = now;
}
if olddir != newdir {
let mut meta = new_parent.meta.write().unwrap();
meta.mtime = now;
meta.ctime = now;
}
if let Some(dest) = evict_dest {
inode::try_evict(fs, dest);
}
Ok(())
}