cat_dev/fsemul/pcfs/sata_proto/
create_directory.rs1use crate::{
6 errors::{CatBridgeError, NetworkParseError},
7 fsemul::{
8 host_filesystem::ResolvedLocation,
9 pcfs::sata_proto::{construct_sata_response, SataPacketHeader},
10 HostFilesystem,
11 },
12};
13use bytes::{BufMut, Bytes, BytesMut};
14use std::ffi::CStr;
15use tokio::fs::{create_dir_all, set_permissions};
16use tracing::error;
17use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
18
19const FS_ERROR: u32 = 0xFFF0_FFE0;
21
22#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct SataCreateDirectoryPacketBody {
27 path: String,
38 set_write_mode: bool,
40}
41
42impl SataCreateDirectoryPacketBody {
43 #[must_use]
44 pub fn path(&self) -> &str {
45 self.path.as_str()
46 }
47 #[must_use]
48 pub const fn set_write_mode(&self) -> bool {
49 self.set_write_mode
50 }
51
52 pub async fn handle(
59 &self,
60 request_header: &SataPacketHeader,
61 host_filesystem: &HostFilesystem,
62 ) -> Result<Bytes, CatBridgeError> {
63 let Ok(final_location) = host_filesystem.resolve_path(&self.path) else {
64 return Self::construct_error(request_header, FS_ERROR);
65 };
66 let ResolvedLocation::Filesystem(fs_location) = final_location else {
67 todo!("network shares not yet implemented!")
68 };
69
70 if self.set_write_mode
72 && !host_filesystem.path_allows_writes(fs_location.closest_resolved_path())
73 {
74 return Self::construct_error(request_header, FS_ERROR);
75 }
76
77 if !fs_location.resolved_path().exists() {
78 if let Err(cause) = create_dir_all(fs_location.resolved_path()).await {
79 error!(
80 ?cause,
81 path = %fs_location.resolved_path().display(),
82 "Failed to create directory for PCFS.",
83 );
84 return Self::construct_error(request_header, FS_ERROR);
85 }
86 }
87 if !self.set_write_mode {
89 let Ok(metadata) = fs_location.resolved_path().metadata() else {
90 return Self::construct_error(request_header, FS_ERROR);
91 };
92 let mut perms = metadata.permissions();
93 perms.set_readonly(true);
94 if set_permissions(fs_location.resolved_path(), perms)
95 .await
96 .is_err()
97 {
98 return Self::construct_error(request_header, FS_ERROR);
99 }
100 }
101
102 Ok(construct_sata_response(
103 request_header,
104 0,
105 BytesMut::zeroed(4).freeze(),
106 )?)
107 }
108
109 fn construct_error(
110 packet_header: &SataPacketHeader,
111 error_code: u32,
112 ) -> Result<Bytes, CatBridgeError> {
113 let mut buff = BytesMut::with_capacity(8);
114 buff.put_u32(error_code);
115 buff.put_u32(0);
116
117 Ok(construct_sata_response(packet_header, 0, buff.freeze())?)
118 }
119}
120
121impl TryFrom<Bytes> for SataCreateDirectoryPacketBody {
122 type Error = NetworkParseError;
123
124 fn try_from(value: Bytes) -> Result<Self, Self::Error> {
125 if value.len() < 0x204 {
126 return Err(NetworkParseError::FieldNotLongEnough(
127 "SataCreateDirectory",
128 "Body",
129 0x204,
130 value.len(),
131 value,
132 ));
133 }
134 if value.len() > 0x204 {
135 return Err(NetworkParseError::UnexpectedTrailer(
136 "SataCreateDirectory",
137 value.slice(0x204..),
138 ));
139 }
140
141 let path_c_str =
142 CStr::from_bytes_until_nul(&value[..0x200]).map_err(NetworkParseError::BadCString)?;
143 let mode = u32::from_be_bytes([value[0x200], value[0x201], value[0x202], value[0x203]]);
144
145 Ok(Self {
146 path: path_c_str.to_str()?.to_owned(),
147 set_write_mode: mode & 0x222 != 0,
148 })
149 }
150}
151
152const SATA_CREATE_DIRECTORY_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
153
154impl Structable for SataCreateDirectoryPacketBody {
155 fn definition(&self) -> StructDef<'_> {
156 StructDef::new_static(
157 "SataCreateDirectoryPacketBody",
158 Fields::Named(SATA_CREATE_DIRECTORY_PACKET_BODY_FIELDS),
159 )
160 }
161}
162
163impl Valuable for SataCreateDirectoryPacketBody {
164 fn as_value(&self) -> Value<'_> {
165 Value::Structable(self)
166 }
167
168 fn visit(&self, visitor: &mut dyn Visit) {
169 visitor.visit_named_fields(&NamedValues::new(
170 SATA_CREATE_DIRECTORY_PACKET_BODY_FIELDS,
171 &[Valuable::as_value(&self.path)],
172 ));
173 }
174}
175
176#[cfg(test)]
177mod unit_tests {
178 use super::*;
179 use crate::fsemul::host_filesystem::test_helpers::{
180 create_temporary_host_filesystem, join_many,
181 };
182
183 #[tokio::test]
184 pub async fn test_simple_create_directory() {
185 let (tempdir, fs) = create_temporary_host_filesystem().await;
186
187 let base_dir = join_many(tempdir.path(), ["a", "b", "c"]);
188
189 let request = SataCreateDirectoryPacketBody {
190 path: base_dir
191 .to_str()
192 .expect("Test paths must be UTF-8")
193 .to_owned(),
194 set_write_mode: true,
195 };
196 let mocked_header = SataPacketHeader {
197 packet_data_len: 0,
198 packet_id: 0,
199 flags: 0,
200 version: 0,
201 timestamp_on_host: 0,
202 pid_on_host: 0,
203 };
204
205 assert!(!base_dir.exists());
206 let _ = request
207 .handle(&mocked_header, &fs)
208 .await
209 .expect("Failed to handle create directory!");
210 assert!(base_dir.exists());
211 }
212}