use crate::{
fsemul::{
host_filesystem::ResolvedLocation,
pcfs::sata::{
proto::{SataRemovePacketBody, SataRequest, SataResponse, SataResultCode},
server::PcfsServerState,
},
},
net::server::requestable::{Body, State},
};
use std::ffi::OsStr;
use tokio::fs::{remove_dir_all, remove_file};
use tracing::{debug, error};
const FS_ERROR: u32 = 0xFFF0_FFE0;
const PATH_NOT_EXIST_ERROR: u32 = 0xFFF0_FFE9;
pub async fn handle_removal(
State(state): State<PcfsServerState>,
Body(request): Body<SataRequest<SataRemovePacketBody>>,
) -> SataResponse<SataResultCode> {
let request_header = request.header().clone();
let packet = request.body();
let Ok(final_location) = state.host_filesystem().resolve_path(packet.path()) else {
debug!(
packet.path = packet.path(),
packet.typ = "PcfsSrvRemoveFile",
"Failed to resolve path!",
);
return SataResponse::new(
state.pid(),
request_header,
SataResultCode::error(PATH_NOT_EXIST_ERROR),
);
};
let ResolvedLocation::Filesystem(fs_location) = final_location else {
todo!("network shares not yet implemented!")
};
if !fs_location.resolved_path().exists() {
return SataResponse::new(
state.pid(),
request_header,
SataResultCode::error(PATH_NOT_EXIST_ERROR),
);
}
if state.disable_real_removal() {
let new_path = if fs_location.resolved_path().is_file() {
let mut new_filename = fs_location
.resolved_path()
.file_name()
.unwrap_or_default()
.to_owned();
new_filename.push(OsStr::new(".rm"));
let mut new_path = fs_location.resolved_path().clone();
new_path.pop();
new_path.push(new_filename);
new_path
} else {
let mut new_path = fs_location.resolved_path().clone();
let mut dir_name = new_path
.components()
.next_back()
.map(|c| c.as_os_str().to_os_string())
.unwrap_or_default();
dir_name.push(".rm");
new_path.pop();
new_path.push(dir_name);
new_path
};
if let Err(cause) = state
.host_filesystem()
.rename(fs_location.resolved_path(), &new_path)
{
error!(
?cause,
path = %fs_location.resolved_path().display(),
"Failed to remove/rename directory as requested by Pcfs."
);
return SataResponse::new(state.pid(), request_header, SataResultCode::error(FS_ERROR));
}
} else if fs_location.resolved_path().is_file() {
if let Err(cause) = remove_file(fs_location.resolved_path()).await {
error!(
?cause,
path = %fs_location.resolved_path().display(),
"Failed to remove file as requested by Pcfs.",
);
return SataResponse::new(state.pid(), request_header, SataResultCode::error(FS_ERROR));
}
} else if fs_location.resolved_path().is_dir() {
if let Err(cause) = remove_dir_all(fs_location.resolved_path()).await {
error!(
?cause,
path = %fs_location.resolved_path().display(),
"Failed to remove directory as requested by Pcfs."
);
return SataResponse::new(state.pid(), request_header, SataResultCode::error(FS_ERROR));
}
} else {
return SataResponse::new(state.pid(), request_header, SataResultCode::error(FS_ERROR));
}
SataResponse::new(state.pid(), request_header, SataResultCode::success())
}
#[cfg(test)]
mod unit_tests {
use super::*;
use crate::fsemul::{
host_filesystem::test_helpers::{create_temporary_host_filesystem, join_many},
pcfs::sata::proto::{SataCommandInfo, SataPacketHeader},
};
use bytes::Bytes;
#[tokio::test]
pub async fn test_real_removal() {
let (tempdir, fs) = create_temporary_host_filesystem().await;
let base_dir = join_many(tempdir.path(), ["a", "b", "c"]);
tokio::fs::create_dir_all(&base_dir)
.await
.expect("Failed to create temporary directory for test!");
let file_path = join_many(&base_dir, ["file.txt"]);
tokio::fs::write(&file_path, vec![0; 1307])
.await
.expect("Failed to write test file!");
let request = SataRemovePacketBody::new(
base_dir
.to_str()
.expect("Test paths must be UTF-8")
.to_owned(),
)
.expect("Failed to create sata remove packet body!");
let mocked_header = SataPacketHeader::new(0);
let mocked_ci = SataCommandInfo::new((0, 0), (0, 0), 0);
let bytes: Bytes = handle_removal(
State(PcfsServerState::new(false, fs, 0)),
Body(SataRequest::new(mocked_header, mocked_ci, request)),
)
.await
.try_into()
.expect("Failed to serialize real removal response!");
assert!(
!base_dir.exists(),
"Base directory still exists post 'removal', response:\n\n {:02X?}\n",
bytes,
);
}
#[tokio::test]
pub async fn test_fake_removal() {
let (tempdir, fs) = create_temporary_host_filesystem().await;
let base_dir = join_many(tempdir.path(), ["directory-to-test-in"]);
let data_dir = join_many(tempdir.path(), ["data", "slc"]);
let symlink_folder_path = join_many(&base_dir, ["sub-directory-with-symlink"]);
tokio::fs::create_dir_all(&base_dir)
.await
.expect("Failed to create temporary directory for test!");
tokio::fs::create_dir_all(&symlink_folder_path)
.await
.expect("Failed to create temporary directory for test!");
let file_path = join_many(&base_dir, ["file.txt"]);
tokio::fs::write(&file_path, vec![0; 1307])
.await
.expect("Failed to write test file!");
let dir_path_to_symlink = join_many(&symlink_folder_path, ["symlinked-folder"]);
let file_path_to_symlink = join_many(&symlink_folder_path, ["symlinked-file.txt"]);
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
symlink(&data_dir, &dir_path_to_symlink).expect("Failed to symlink directory!");
symlink(&file_path, &file_path_to_symlink).expect("Failed to symlink file!");
}
#[cfg(target_os = "windows")]
{
use std::os::windows::fs::{symlink_dir, symlink_file};
symlink_dir(&data_dir, &dir_path_to_symlink).expect("Failed to symlink directory!");
symlink_file(&file_path, &file_path_to_symlink).expect("Failed to symlink file!");
}
let request = SataRemovePacketBody::new(
base_dir
.to_str()
.expect("Test paths must be UTF-8")
.to_owned(),
)
.expect("Failed to create sata remove packet body!");
let mocked_header = SataPacketHeader::new(0);
let mocked_ci = SataCommandInfo::new((0, 0), (0, 0), 0);
let _bytes: Bytes = handle_removal(
State(PcfsServerState::new(true, fs, 0)),
Body(SataRequest::new(mocked_header, mocked_ci, request)),
)
.await
.try_into()
.expect("Failed to serialize real removal response!");
let renamed_dir = join_many(tempdir.path(), ["directory-to-test-in.rm"]);
assert!(
!base_dir.exists(),
"Base directory still exists post 'removal'",
);
assert!(renamed_dir.exists(), "Renamed directory doesn't exist?");
}
}