cat_dev/fsemul/filesystem/host/
open_file.rs1use crate::{
5 errors::{CatBridgeError, FSError},
6 fsemul::filesystem::host::utilities::get_new_unique_file_fd,
7};
8use std::path::{Path, PathBuf};
9use tokio::fs::{File, OpenOptions};
10use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
11
12#[cfg(feature = "nus")]
13use crate::fsemul::filesystem::nus_fuse::NUSFuse;
14#[cfg(feature = "nus")]
15use sachet::{common::CafeContentFileInformation, title::TitleID};
16
17#[cfg(any(feature = "clients", feature = "servers"))]
18use crate::fsemul::{filesystem::host::HostFilesystem, pcfs::sata::proto::SataFDInfo};
19
20#[derive(Debug)]
21pub struct OpenFileHandle {
24 disk_path: PathBuf,
28 file_size: u64,
30 local_file_handle: Option<(File, i32)>,
32 #[cfg(feature = "nus")]
33 nus_file_info: Option<(TitleID, CafeContentFileInformation, i32)>,
35 open_options: OpenOptions,
37 owner: Option<u64>,
40}
41
42impl OpenFileHandle {
43 pub(crate) async fn open_file(
49 force_unique_fds: bool,
50 open_options: OpenOptions,
51 full_disk_path: &Path,
52 stream_owner: Option<u64>,
53 #[cfg(feature = "nus")] mlc_usr_title_path: &Path,
54 #[cfg(feature = "nus")] nus: Option<&NUSFuse>,
55 ) -> Result<Self, FSError> {
56 #[cfg(feature = "nus")]
57 if let Some(result) = Self::check_and_potentially_open_from_nus(
58 nus,
59 mlc_usr_title_path,
60 full_disk_path,
61 open_options.clone(),
62 stream_owner,
63 )
64 .await
65 {
66 return Ok(result);
67 }
68
69 let local_file = open_options.clone().open(full_disk_path).await?;
70 let raw_fd;
71 #[cfg(unix)]
72 {
73 use std::os::fd::AsRawFd;
74 raw_fd = local_file.as_raw_fd();
75 }
76 #[cfg(target_os = "windows")]
77 {
78 use std::os::windows::io::AsRawHandle;
79 raw_fd = local_file.as_raw_handle() as i32;
80 }
81 #[cfg(all(not(unix), not(target_os = "windows")))]
82 {
83 raw_fd = get_new_unique_file_fd();
84 }
85 let md = local_file.metadata().await?;
86 let final_fd = if force_unique_fds {
87 get_new_unique_file_fd()
88 } else {
89 raw_fd
90 };
91
92 Ok(Self {
93 disk_path: full_disk_path.to_path_buf(),
94 file_size: md.len(),
95 local_file_handle: Some((local_file, final_fd)),
96 #[cfg(feature = "nus")]
97 nus_file_info: None,
98 open_options,
99 owner: stream_owner,
100 })
101 }
102
103 #[must_use]
104 pub fn disk_path(&self) -> &Path {
105 &self.disk_path
106 }
107
108 #[must_use]
110 pub const fn fd(&self) -> i32 {
111 if let Some((_, fd)) = self.local_file_handle.as_ref() {
112 return *fd;
113 }
114 #[cfg(feature = "nus")]
115 {
116 if let Some((_, _, fd)) = self.nus_file_info.as_ref() {
117 return *fd;
118 }
119 }
120
121 unreachable!();
122 }
123
124 #[must_use]
126 pub const fn file_size(&self) -> u64 {
127 self.file_size
128 }
129
130 pub async fn force_file_handle(
141 &mut self,
142 #[cfg(feature = "nus")] nus: Option<&NUSFuse>,
143 ) -> Result<&mut File, CatBridgeError> {
144 #[cfg(feature = "nus")]
145 if let Some((title_id, file_info, fd)) = self.nus_file_info {
146 let nc = nus.ok_or_else(|| {
147 FSError::IO(std::io::Error::other(
148 "NUS file pointer without NUS client?",
149 ))
150 })?;
151 nc.download_to(&self.disk_path, title_id, file_info).await?;
152 self.nus_file_info = None;
153 _ = self.local_file_handle.insert((
154 self.open_options
155 .open(&self.disk_path)
156 .await
157 .map_err(FSError::IO)?,
158 fd,
159 ));
160 }
161
162 if let Some((handle, _)) = self.local_file_handle.as_mut() {
163 return Ok(handle);
164 }
165
166 unreachable!();
167 }
168
169 #[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "servers"))))]
170 #[cfg(any(feature = "clients", feature = "servers"))]
171 pub async fn sata_fd_info(
177 &self,
178 host_filesystem: &HostFilesystem,
179 ) -> Result<SataFDInfo, FSError> {
180 #[cfg(feature = "nus")]
181 {
182 if let Some((_title_id, file_info, _fd)) = self.nus_file_info.as_ref() {
183 return Ok(SataFDInfo::new_from_nus(
184 host_filesystem,
185 &self.disk_path,
186 Some(*file_info),
187 )
188 .await);
189 }
190 }
191
192 if let Some((handle, _)) = self.local_file_handle.as_ref() {
193 return Ok(SataFDInfo::get_info(
194 host_filesystem,
195 &handle.metadata().await?,
196 &self.disk_path,
197 )
198 .await);
199 }
200
201 unreachable!()
202 }
203
204 #[must_use]
206 pub const fn stream_owner(&self) -> Option<u64> {
207 self.owner
208 }
209
210 #[cfg(feature = "nus")]
211 #[must_use]
213 async fn check_and_potentially_open_from_nus(
214 opt_nus: Option<&NUSFuse>,
215 mlc_user_title_path: &Path,
216 full_disk_path: &Path,
217 open_options: OpenOptions,
218 stream_owner: Option<u64>,
219 ) -> Option<Self> {
220 let nus = opt_nus?;
221 if full_disk_path.exists() {
222 return None;
223 }
224 let relative_path = full_disk_path.strip_prefix(mlc_user_title_path).ok()?;
225 if relative_path.components().count() < 3 {
226 return None;
227 }
228 let mut comp = relative_path.components();
229 let group =
230 u32::from_str_radix(comp.next()?.as_os_str().to_string_lossy().as_ref(), 16).ok()?;
231 let title =
232 u32::from_str_radix(comp.next()?.as_os_str().to_string_lossy().as_ref(), 16).ok()?;
233 let tid = TitleID::new_with_ids(group, title);
234 let relative_path = comp.as_path();
235 let nus_info = nus.exists(tid, relative_path).await??;
236 let unique_fd = get_new_unique_file_fd();
237
238 Some(Self {
239 disk_path: full_disk_path.to_path_buf(),
240 file_size: u64::from(nus_info.file_size()),
241 local_file_handle: None,
242 nus_file_info: Some((tid, nus_info, unique_fd)),
243 open_options,
244 owner: stream_owner,
245 })
246 }
247}
248
249const OPEN_FILE_HANDLE_FIELDS: &[NamedField<'static>] = &[
250 NamedField::new("disk_path"),
251 NamedField::new("file_size"),
252 NamedField::new("local_file_handle"),
253 #[cfg(feature = "nus")]
254 NamedField::new("nus_file_info"),
255 NamedField::new("open_options"),
256];
257
258impl Structable for OpenFileHandle {
259 fn definition(&self) -> StructDef<'_> {
260 StructDef::new_static("OpenFileHandle", Fields::Named(OPEN_FILE_HANDLE_FIELDS))
261 }
262}
263
264impl Valuable for OpenFileHandle {
265 fn as_value(&self) -> Value<'_> {
266 Value::Structable(self)
267 }
268
269 fn visit(&self, visitor: &mut dyn Visit) {
270 visitor.visit_named_fields(&NamedValues::new(
271 OPEN_FILE_HANDLE_FIELDS,
272 &[
273 Valuable::as_value(&self.disk_path),
274 Valuable::as_value(&self.file_size),
275 Valuable::as_value(&format!("{:?}", self.local_file_handle)),
276 #[cfg(feature = "nus")]
277 Valuable::as_value(&self.nus_file_info),
278 Valuable::as_value(&format!("{:?}", self.open_options)),
279 ],
280 ));
281 }
282}