use crate::{
errors::FSError,
fsemul::{
host_filesystem::ResolvedLocation,
pcfs::sata::{
proto::{SataChangeModePacketBody, SataRequest, SataResponse, SataResultCode},
server::PcfsServerState,
},
},
net::server::requestable::{Body, State},
};
use std::fs::set_permissions;
use tracing::debug;
const FS_ERROR: u32 = 0xFFF0_FFE0;
const PATH_NOT_EXIST_ERROR: u32 = 0xFFF0_FFE9;
pub async fn handle_change_mode(
State(state): State<PcfsServerState>,
Body(request): Body<SataRequest<SataChangeModePacketBody>>,
) -> 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 = "PcfsSrvChangeMode",
"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.canonicalized_is_exact() {
debug!(
packet.path = packet.path(),
packet.typ = "PcfsSrvChangeMode",
"Cannot change mode of path that does not exist!",
);
return SataResponse::new(
state.pid(),
request_header,
SataResultCode::error(PATH_NOT_EXIST_ERROR),
);
}
let Ok(metadata) = fs_location.closest_resolved_path().metadata() else {
debug!(
packet.path = packet.path(),
packet.typ = "PcfsSrvChangeMode",
"Failed to get path metadata!",
);
return SataResponse::new(state.pid(), request_header, SataResultCode::error(FS_ERROR));
};
let mut perms = metadata.permissions();
if packet.will_set_write_mode()
&& !state
.host_filesystem()
.path_allows_writes(fs_location.closest_resolved_path())
{
debug!(
packet.path = packet.path(),
packet.typ = "PcfsSrvChangeMode",
"Path cannot become writable!",
);
return SataResponse::new(state.pid(), request_header, SataResultCode::error(FS_ERROR));
}
perms.set_readonly(!packet.will_set_write_mode());
let result = if fs_location.closest_resolved_path().is_dir() {
if packet.will_set_write_mode() {
state
.host_filesystem()
.ensure_folder_not_read_only(fs_location.closest_resolved_path())
.await;
} else {
state
.host_filesystem()
.mark_folder_read_only(fs_location.closest_resolved_path().clone())
.await;
}
Ok(())
} else {
set_permissions(fs_location.closest_resolved_path(), perms).map_err(FSError::IO)
};
if result.is_err() {
debug!(
cause = ?result,
packet.path = packet.path(),
packet.typ = "PcfsSrvChangeMode",
"Failed to change read-only attribute!",
);
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 change_mode_request() {
let (tempdir, fs) = create_temporary_host_filesystem().await;
let request =
SataChangeModePacketBody::new("/%SLC_EMU_DIR/to-query/file.txt".to_owned(), false)
.expect("Failed to create change mode packet!");
let mocked_header = SataPacketHeader::new(0);
let mocked_ci = SataCommandInfo::new((0, 0), (0, 0), 0);
let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
tokio::fs::create_dir(&base_dir)
.await
.expect("Failed to create temporary directory for test!");
tokio::fs::write(join_many(&base_dir, ["file.txt"]), vec![0; 1307])
.await
.expect("Failed to write test file!");
let mut mode_resp: Bytes = handle_change_mode(
State(PcfsServerState::new(true, fs, 0)),
Body(SataRequest::new(mocked_header, mocked_ci, request)),
)
.await
.try_into()
.expect("Failed to serialize change mode response!");
assert_eq!(mode_resp.len(), 4 + 0x20, "Packet is not correct size!");
_ = mode_resp.split_to(0x20);
assert_eq!(
mode_resp,
Bytes::from(vec![
0x00, 0x00, 0x00, 0x00, ]),
);
}
}