1use crate::io::{Directory, DirentKind};
6use anyhow::{anyhow, bail, Result};
7use fidl_fuchsia_sys2 as fsys;
8use std::path::{Component, PathBuf};
9use std::str::FromStr;
10use thiserror::Error;
11
12const REMOTE_PATH_SEPARATOR: &'static str = "::";
15
16pub const REMOTE_COMPONENT_STORAGE_PATH_HELP: &'static str = r#"Remote storage paths allow the following formats:
171) [instance ID]::[path relative to storage]
18 Example: "c1a6d0aebbf7c092c53e8e696636af8ec0629ff39b7f2e548430b0034d809da4::/path/to/file"
19
20 `..` is not valid anywhere in the remote path.
21
22 To learn about component instance IDs, see https://fuchsia.dev/go/components/instance-id"#;
23
24pub const REMOTE_DIRECTORY_PATH_HELP: &'static str = r#"Remote directory paths must be:
251) [moniker]::[path in namespace]
26 Example: /foo/bar::/config/data/sample.json
27
28 To learn more about monikers, see https://fuchsia.dev/go/components/moniker#absolute
29
302) [moniker]::[dir type]::[path] where [dir type] is one of "in", "out", or "pkg", specifying
31 the component's namespace directory, outgoing directory, or package directory (if packaged).
32"#;
33
34#[derive(Clone, Debug, PartialEq)]
35pub struct RemoteDirectoryPath {
36 pub moniker: String,
37 pub dir_type: fsys::OpenDirType,
38 pub relative_path: PathBuf,
39}
40
41#[derive(Clone, Debug, PartialEq)]
42pub struct RemoteComponentStoragePath {
43 pub instance_id: String,
44 pub relative_path: PathBuf,
45}
46
47#[derive(Error, Debug, PartialEq)]
48pub enum ParsePathError {
49 #[error("Unsupported directory type: {dir_type}. {}", REMOTE_DIRECTORY_PATH_HELP)]
50 UnsupportedDirectory { dir_type: String },
51
52 #[error("Disallowed path component: {component}. {}", REMOTE_DIRECTORY_PATH_HELP)]
53 DisallowedPathComponent { component: String },
54
55 #[error("Malformatted remote directory path. {}", REMOTE_DIRECTORY_PATH_HELP)]
56 InvalidFormat,
57}
58
59impl FromStr for RemoteDirectoryPath {
60 type Err = ParsePathError;
61
62 fn from_str(input: &str) -> Result<Self, Self::Err> {
63 let parts: Vec<&str> = input.split(REMOTE_PATH_SEPARATOR).collect();
64 if parts.len() < 2 || parts.len() > 3 {
65 return Err(ParsePathError::InvalidFormat);
66 }
67
68 let moniker = parts.first().unwrap().to_string();
70 let (dir_type, path_str) = if parts.len() == 3 {
71 let parsed = parse_dir_type_from_str(parts[1])?;
72 (parsed, parts[2])
73 } else {
74 (fsys::OpenDirType::NamespaceDir, parts[1])
75 };
76 let path = PathBuf::from(path_str);
77
78 let mut normalized_path = PathBuf::new();
80 for component in path.components() {
81 match component {
82 Component::Normal(c) => normalized_path.push(c),
83 Component::RootDir => continue,
84 Component::CurDir => continue,
85 c => {
86 return Err(ParsePathError::DisallowedPathComponent {
87 component: format!("{:?}", c),
88 })
89 }
90 }
91 }
92
93 Ok(Self { moniker, dir_type, relative_path: normalized_path })
94 }
95}
96
97fn parse_dir_type_from_str(s: &str) -> Result<fsys::OpenDirType, ParsePathError> {
98 match s {
101 "in" | "namespace" => Ok(fsys::OpenDirType::NamespaceDir),
102 "out" => Ok(fsys::OpenDirType::OutgoingDir),
103 "pkg" => Ok(fsys::OpenDirType::PackageDir),
104 _ => Err(ParsePathError::UnsupportedDirectory { dir_type: s.into() }),
105 }
106}
107
108fn dir_type_to_str(dir_type: &fsys::OpenDirType) -> Result<&str> {
109 match dir_type {
112 fsys::OpenDirType::NamespaceDir => Ok("in"),
113 fsys::OpenDirType::OutgoingDir => Ok("out"),
114 fsys::OpenDirType::PackageDir => Ok("pkg"),
115 _ => Err(anyhow!("Unsupported OpenDirType: {:?}", dir_type)),
116 }
117}
118
119impl RemoteComponentStoragePath {
121 pub fn parse(input: &str) -> Result<Self> {
122 match input.split_once(REMOTE_PATH_SEPARATOR) {
123 Some((first, second)) => {
124 if second.contains(REMOTE_PATH_SEPARATOR) {
125 bail!(
126 "Remote storage path must contain exactly one `{}` separator. {}",
127 REMOTE_PATH_SEPARATOR,
128 REMOTE_COMPONENT_STORAGE_PATH_HELP
129 )
130 }
131
132 let instance_id = first.to_string();
133 let relative_path = PathBuf::from(second);
134
135 let mut normalized_relative_path = PathBuf::new();
137 for component in relative_path.components() {
138 match component {
139 Component::Normal(c) => normalized_relative_path.push(c),
140 Component::RootDir => continue,
141 Component::CurDir => continue,
142 c => bail!(
143 "Unsupported path component: {:?}. {}",
144 c,
145 REMOTE_COMPONENT_STORAGE_PATH_HELP
146 ),
147 }
148 }
149
150 Ok(Self { instance_id, relative_path: normalized_relative_path })
151 }
152 None => {
153 bail!(
154 "Remote storage path must contain exactly one `{}` separator. {}",
155 REMOTE_PATH_SEPARATOR,
156 REMOTE_COMPONENT_STORAGE_PATH_HELP
157 )
158 }
159 }
160 }
161
162 pub fn contains_wildcard(&self) -> bool {
163 return self.to_string().contains("*");
164 }
165
166 pub fn relative_path_string(&self) -> String {
167 return self.relative_path.to_string_lossy().to_string();
168 }
169}
170
171impl ToString for RemoteComponentStoragePath {
172 fn to_string(&self) -> String {
173 format!(
174 "{}{sep}/{}",
175 self.instance_id,
176 self.relative_path.to_string_lossy(),
177 sep = REMOTE_PATH_SEPARATOR
178 )
179 }
180}
181
182impl ToString for RemoteDirectoryPath {
183 fn to_string(&self) -> String {
184 format!(
185 "{}{sep}{}{sep}/{}",
186 self.moniker,
187 dir_type_to_str(&self.dir_type).unwrap(),
188 self.relative_path.to_string_lossy(),
189 sep = REMOTE_PATH_SEPARATOR
190 )
191 }
192}
193
194#[derive(Clone)]
195pub enum LocalOrRemoteDirectoryPath {
198 Local(PathBuf),
199 Remote(RemoteDirectoryPath),
200}
201
202impl LocalOrRemoteDirectoryPath {
203 pub fn parse(path: &str) -> LocalOrRemoteDirectoryPath {
204 match RemoteDirectoryPath::from_str(path) {
205 Ok(path) => LocalOrRemoteDirectoryPath::Remote(path),
206 Err(_) => LocalOrRemoteDirectoryPath::Local(PathBuf::from(path)),
208 }
209 }
210}
211
212#[derive(Clone)]
213pub enum LocalOrRemoteComponentStoragePath {
216 Local(PathBuf),
217 Remote(RemoteComponentStoragePath),
218}
219
220impl LocalOrRemoteComponentStoragePath {
221 pub fn parse(path: &str) -> LocalOrRemoteComponentStoragePath {
222 match RemoteComponentStoragePath::parse(path) {
223 Ok(path) => LocalOrRemoteComponentStoragePath::Remote(path),
224 Err(_) => LocalOrRemoteComponentStoragePath::Local(PathBuf::from(path)),
226 }
227 }
228}
229
230pub fn open_parent_subdir_readable<D: Directory>(path: &PathBuf, dir: &D) -> Result<D> {
235 if path.components().count() < 2 {
236 return dir.clone();
239 }
240
241 dir.open_dir_readonly(path.parent().unwrap())
242}
243
244pub async fn add_source_filename_to_path_if_absent<D: Directory>(
263 destination_dir: &D,
264 source_path: &PathBuf,
265 destination_path: &PathBuf,
266) -> Result<PathBuf> {
267 let source_file = source_path
268 .file_name()
269 .map_or_else(|| Err(anyhow!("Source path is empty")), |file| Ok(PathBuf::from(file)))?;
270 let source_file_str = source_file.display().to_string();
271
272 if let Some(destination_file) = destination_path.file_name() {
274 let parent_dir = open_parent_subdir_readable(destination_path, destination_dir)?;
275 match parent_dir.entry_type(destination_file.to_string_lossy().as_ref()).await? {
276 Some(DirentKind::File) | None => Ok(destination_path.clone()),
277 Some(DirentKind::Directory) => Ok(destination_path.join(source_file_str)),
278 }
279 } else {
280 Ok(destination_path.join(source_file_str))
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use crate::path::{dir_type_to_str, parse_dir_type_from_str, RemoteDirectoryPath};
287 use fidl_fuchsia_sys2 as fsys;
288 use std::str::FromStr;
289
290 #[test]
291 fn test_parse_dir_type_from_str() {
292 assert_eq!(parse_dir_type_from_str("in"), Ok(fsys::OpenDirType::NamespaceDir));
293 assert_eq!(parse_dir_type_from_str("namespace"), Ok(fsys::OpenDirType::NamespaceDir));
294 assert_eq!(parse_dir_type_from_str("out"), Ok(fsys::OpenDirType::OutgoingDir));
295 assert_eq!(parse_dir_type_from_str("pkg"), Ok(fsys::OpenDirType::PackageDir));
296 assert!(parse_dir_type_from_str("nonexistent").is_err());
297 }
298
299 #[test]
300 fn test_dir_type_to_str() {
301 assert_eq!(dir_type_to_str(&fsys::OpenDirType::NamespaceDir).unwrap(), "in");
302 assert_eq!(dir_type_to_str(&fsys::OpenDirType::OutgoingDir).unwrap(), "out");
303 assert_eq!(dir_type_to_str(&fsys::OpenDirType::PackageDir).unwrap(), "pkg");
304 assert!(dir_type_to_str(&fsys::OpenDirType::RuntimeDir).is_err());
305 assert!(dir_type_to_str(&fsys::OpenDirType::ExposedDir).is_err());
306 }
307
308 #[test]
309 fn test_remote_directory_path_from_str() {
310 assert_eq!(
311 RemoteDirectoryPath::from_str("/foo/bar::/path"),
312 Ok(RemoteDirectoryPath {
313 moniker: "/foo/bar".into(),
314 dir_type: fsys::OpenDirType::NamespaceDir,
315 relative_path: "path".into(),
316 })
317 );
318
319 assert_eq!(
320 RemoteDirectoryPath::from_str("/foo/bar::out::/path"),
321 Ok(RemoteDirectoryPath {
322 moniker: "/foo/bar".into(),
323 dir_type: fsys::OpenDirType::OutgoingDir,
324 relative_path: "path".into(),
325 })
326 );
327
328 assert_eq!(
329 RemoteDirectoryPath::from_str("/foo/bar::pkg::/path"),
330 Ok(RemoteDirectoryPath {
331 moniker: "/foo/bar".into(),
332 dir_type: fsys::OpenDirType::PackageDir,
333 relative_path: "path".into(),
334 })
335 );
336
337 assert!(RemoteDirectoryPath::from_str("/foo/bar").is_err());
338 assert!(RemoteDirectoryPath::from_str("/foo/bar::one::two::three").is_err());
339 assert!(RemoteDirectoryPath::from_str("/foo/bar::not_a_dir::three").is_err());
340 }
341}