cat_dev/fsemul/pcfs/sata_proto/
change_mode.rs1use crate::{
8 errors::{CatBridgeError, NetworkParseError},
9 fsemul::{
10 host_filesystem::ResolvedLocation,
11 pcfs::sata_proto::{construct_sata_response, SataPacketHeader},
12 HostFilesystem,
13 },
14};
15use bytes::Bytes;
16use std::{ffi::CStr, fs::set_permissions};
17use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
18
19const FS_ERROR: u32 = 0xFFF0_FFE0;
21const PATH_NOT_EXIST_ERROR: u32 = 0xFFF0_FFE9;
27
28#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct SataChangeModePacketBody {
31 path: String,
42 set_write_mode: bool,
44}
45
46impl SataChangeModePacketBody {
47 #[must_use]
48 pub fn path(&self) -> &str {
49 self.path.as_str()
50 }
51 #[must_use]
52 pub const fn set_write_mode(&self) -> bool {
53 self.set_write_mode
54 }
55
56 pub fn handle(
65 &self,
66 request_header: &SataPacketHeader,
67 host_filesystem: &HostFilesystem,
68 ) -> Result<Bytes, CatBridgeError> {
69 let Ok(final_location) = host_filesystem.resolve_path(&self.path) else {
70 return Ok(construct_sata_response(
71 request_header,
72 0,
73 Bytes::from(Vec::from(PATH_NOT_EXIST_ERROR.to_be_bytes())),
74 )?);
75 };
76 let ResolvedLocation::Filesystem(fs_location) = final_location else {
77 todo!("network shares not yet implemented!")
78 };
79 if !fs_location.canonicalized_is_exact() {
81 return Ok(construct_sata_response(
82 request_header,
83 0,
84 Bytes::from(Vec::from(PATH_NOT_EXIST_ERROR.to_be_bytes())),
85 )?);
86 }
87
88 let Ok(metadata) = fs_location.closest_resolved_path().metadata() else {
89 return Ok(construct_sata_response(
90 request_header,
91 0,
92 Bytes::from(Vec::from(FS_ERROR.to_be_bytes())),
93 )?);
94 };
95
96 let mut perms = metadata.permissions();
98 if self.set_write_mode
99 && !host_filesystem.path_allows_writes(fs_location.closest_resolved_path())
100 {
101 return Ok(construct_sata_response(
102 request_header,
103 0,
104 Bytes::from(Vec::from(FS_ERROR.to_be_bytes())),
105 )?);
106 }
107 perms.set_readonly(!self.set_write_mode);
108 if set_permissions(fs_location.closest_resolved_path(), perms).is_err() {
109 return Ok(construct_sata_response(
110 request_header,
111 0,
112 Bytes::from(Vec::from(FS_ERROR.to_be_bytes())),
113 )?);
114 }
115
116 Ok(construct_sata_response(
117 request_header,
118 0,
119 Bytes::from(vec![0; 4]),
120 )?)
121 }
122}
123
124impl TryFrom<Bytes> for SataChangeModePacketBody {
125 type Error = NetworkParseError;
126
127 fn try_from(value: Bytes) -> Result<Self, Self::Error> {
128 if value.len() < 0x204 {
129 return Err(NetworkParseError::FieldNotLongEnough(
130 "SataChangeMode",
131 "Body",
132 0x204,
133 value.len(),
134 value,
135 ));
136 }
137 if value.len() > 0x204 {
138 return Err(NetworkParseError::UnexpectedTrailer(
139 "SataChangeMode",
140 value.slice(0x204..),
141 ));
142 }
143
144 let (path_bytes, num) = value.split_at(0x200);
145 let path_c_str =
146 CStr::from_bytes_until_nul(path_bytes).map_err(NetworkParseError::BadCString)?;
147 let write_mode_flags = u32::from_be_bytes([num[0], num[1], num[2], num[3]]);
148 let final_path = path_c_str.to_str()?.to_owned();
149
150 Ok(Self {
151 path: final_path,
152 set_write_mode: write_mode_flags & 0x222 != 0,
153 })
154 }
155}
156
157const SATA_CHANGE_MODE_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
158
159impl Structable for SataChangeModePacketBody {
160 fn definition(&self) -> StructDef<'_> {
161 StructDef::new_static(
162 "SataChangeModePacketBody",
163 Fields::Named(SATA_CHANGE_MODE_PACKET_BODY_FIELDS),
164 )
165 }
166}
167
168impl Valuable for SataChangeModePacketBody {
169 fn as_value(&self) -> Value<'_> {
170 Value::Structable(self)
171 }
172
173 fn visit(&self, visitor: &mut dyn Visit) {
174 visitor.visit_named_fields(&NamedValues::new(
175 SATA_CHANGE_MODE_PACKET_BODY_FIELDS,
176 &[Valuable::as_value(&self.path)],
177 ));
178 }
179}
180
181#[cfg(test)]
182mod unit_tests {
183 use super::*;
184 use crate::fsemul::host_filesystem::test_helpers::{
185 create_temporary_host_filesystem, join_many,
186 };
187
188 #[tokio::test]
189 pub async fn change_mode_request() {
190 let (tempdir, fs) = create_temporary_host_filesystem().await;
191 let request = SataChangeModePacketBody {
192 path: "/%SLC_EMU_DIR/to-query/file.txt".to_owned(),
193 set_write_mode: false,
194 };
195 let mocked_header = SataPacketHeader {
196 packet_data_len: 0,
197 packet_id: 0,
198 flags: 0,
199 version: 0,
200 timestamp_on_host: 0,
201 pid_on_host: 0,
202 };
203
204 let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
206 tokio::fs::create_dir(&base_dir)
207 .await
208 .expect("Failed to create temporary directory for test!");
209 tokio::fs::write(join_many(&base_dir, ["file.txt"]), vec![0; 1307])
210 .await
211 .expect("Failed to write test file!");
212
213 let mut response = request
214 .handle(&mocked_header, &fs)
215 .expect("Failed to handle change mode!");
216 assert_eq!(response.len(), 4 + 0x20, "Packet is not correct size!");
217 _ = response.split_to(0x20);
219 assert_eq!(
220 response,
221 Bytes::from(vec![
222 0x00, 0x00, 0x00, 0x00, ]),
224 );
225 }
226}