1use crate::io::{Directory, LocalDirectory, RemoteDirectory};
6use crate::path::{
7 add_source_filename_to_path_if_absent, LocalOrRemoteComponentStoragePath,
8 REMOTE_COMPONENT_STORAGE_PATH_HELP,
9};
10use anyhow::{anyhow, bail, Result};
11use fidl::endpoints::create_proxy;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_sys2::StorageAdminProxy;
14use std::path::PathBuf;
15
16pub async fn copy(
24 storage_admin: StorageAdminProxy,
25 source_path: String,
26 destination_path: String,
27) -> Result<()> {
28 let (dir_proxy, server) = create_proxy::<fio::DirectoryMarker>();
29 let server = server.into_channel();
30 let storage_dir = RemoteDirectory::from_proxy(dir_proxy);
31
32 match (
33 LocalOrRemoteComponentStoragePath::parse(&source_path),
34 LocalOrRemoteComponentStoragePath::parse(&destination_path),
35 ) {
36 (
37 LocalOrRemoteComponentStoragePath::Remote(source),
38 LocalOrRemoteComponentStoragePath::Local(destination_path),
39 ) => {
40 storage_admin
42 .open_component_storage_by_id(&source.instance_id, server.into())
43 .await?
44 .map_err(|e| anyhow!("Could not open component storage: {:?}", e))?;
45
46 let destination_dir = LocalDirectory::new();
47 do_copy(&storage_dir, &source.relative_path, &destination_dir, &destination_path).await
48 }
49 (
50 LocalOrRemoteComponentStoragePath::Local(source_path),
51 LocalOrRemoteComponentStoragePath::Remote(destination),
52 ) => {
53 storage_admin
55 .open_component_storage_by_id(&destination.instance_id, server.into())
56 .await?
57 .map_err(|e| anyhow!("Could not open component storage: {:?}", e))?;
58
59 let source_dir = LocalDirectory::new();
60 do_copy(&source_dir, &source_path, &storage_dir, &destination.relative_path).await
61 }
62 _ => {
63 bail!(
64 "One path must be remote and the other must be host. {}",
65 REMOTE_COMPONENT_STORAGE_PATH_HELP
66 )
67 }
68 }
69}
70
71async fn do_copy<S: Directory, D: Directory>(
72 source_dir: &S,
73 source_path: &PathBuf,
74 destination_dir: &D,
75 destination_path: &PathBuf,
76) -> Result<()> {
77 let destination_path_path =
78 add_source_filename_to_path_if_absent(destination_dir, source_path, destination_path)
79 .await?;
80
81 let data = source_dir.read_file_bytes(source_path).await?;
82 destination_dir.write_file(destination_path_path, &data).await
83}
84
85#[cfg(test)]
89mod test {
90 use super::*;
91 use crate::storage::test::{
92 node_to_file, setup_fake_storage_admin, setup_fake_storage_admin_with_tmp,
93 };
94 use fidl_fuchsia_io as fio;
95 use futures::TryStreamExt;
96 use std::collections::HashMap;
97 use std::fs::{read, write};
98 use tempfile::tempdir;
99
100 const EXPECTED_DATA: [u8; 4] = [0x0, 0x1, 0x2, 0x3];
101
102 fn setup_fake_directory(mut root_dir: fio::DirectoryRequestStream) {
105 fuchsia_async::Task::local(async move {
106 let request = root_dir.try_next().await;
109 if let Ok(Some(fio::DirectoryRequest::Open { path, flags, object, .. })) = request {
110 if path == "from_local" {
111 assert!(flags.intersects(fio::Flags::FLAG_MAYBE_CREATE));
112 setup_fake_file_from_local(node_to_file(object.into()));
113 } else if path == "from_device" {
114 setup_fake_file_from_device(node_to_file(object.into()));
115 } else {
116 panic!("incorrect path: {}", path);
117 }
118 } else {
119 panic!("did not get open request: {:?}", request)
120 }
121 })
122 .detach();
123 }
124
125 fn setup_fake_file_from_local(mut file: fio::FileRequestStream) {
126 fuchsia_async::Task::local(async move {
127 let request = file.try_next().await;
130 if let Ok(Some(fio::FileRequest::Resize { length, responder })) = request {
131 assert_eq!(length, 0);
132 responder.send(Ok(())).unwrap();
133 } else {
134 panic!("did not get resize request: {:?}", request)
135 }
136
137 let request = file.try_next().await;
139 if let Ok(Some(fio::FileRequest::Write { data, responder })) = request {
140 assert_eq!(data, EXPECTED_DATA);
141 responder.send(Ok(data.len() as u64)).unwrap();
142 } else {
143 panic!("did not get write request: {:?}", request)
144 }
145
146 let request = file.try_next().await;
148 if let Ok(Some(fio::FileRequest::Close { responder })) = request {
149 responder.send(Ok(())).unwrap();
150 } else {
151 panic!("did not get close request: {:?}", request)
152 }
153 })
154 .detach();
155 }
156
157 fn setup_fake_file_from_device(mut file: fio::FileRequestStream) {
158 fuchsia_async::Task::local(async move {
159 let request = file.try_next().await;
162 if let Ok(Some(fio::FileRequest::Read { responder, .. })) = request {
163 responder.send(Ok(&EXPECTED_DATA)).unwrap();
164 } else {
165 panic!("did not get read request: {:?}", request)
166 }
167
168 let request = file.try_next().await;
170 if let Ok(Some(fio::FileRequest::Read { responder, .. })) = request {
171 responder.send(Ok(&[])).unwrap();
172 } else {
173 panic!("did not get read request: {:?}", request)
174 }
175
176 let request = file.try_next().await;
178 if let Ok(Some(fio::FileRequest::Close { responder })) = request {
179 responder.send(Ok(())).unwrap();
180 } else {
181 panic!("did not get close request: {:?}", request)
182 }
183 })
184 .detach();
185 }
186
187 #[fuchsia::test]
188 async fn test_copy_local_to_device() -> Result<()> {
189 let dir = tempdir().unwrap();
190 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
191 let from_local_filepath = dir.path().join("from_local");
192 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
193 copy(
194 storage_admin,
195 from_local_filepath.display().to_string(),
196 "123456::from_local".to_string(),
197 )
198 .await
199 }
200
201 #[fuchsia::test]
202 async fn test_copy_local_to_device_different_file_names() -> Result<()> {
203 let dir = tempdir().unwrap();
204 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
205 let from_local_filepath = dir.path().join("from_local");
206 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
207 copy(
208 storage_admin,
209 from_local_filepath.display().to_string(),
210 "123456::from_local_test".to_string(),
211 )
212 .await
213 }
214
215 #[fuchsia::test]
216 async fn test_copy_local_to_device_infer_path() -> Result<()> {
217 let dir = tempdir().unwrap();
218 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
219 let from_local_filepath = dir.path().join("from_local");
220 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
221 copy(storage_admin, from_local_filepath.display().to_string(), "123456::".to_string()).await
222 }
223
224 #[fuchsia::test]
225 async fn test_copy_local_to_device_infer_slash_path() -> Result<()> {
226 let dir = tempdir().unwrap();
227 let storage_admin = setup_fake_storage_admin_with_tmp("123456", HashMap::new());
228 let from_local_filepath = dir.path().join("from_local");
229 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
230 copy(storage_admin, from_local_filepath.display().to_string(), "123456::/".to_string())
231 .await
232 }
233
234 #[fuchsia::test]
235 async fn test_copy_local_to_device_overwrite_file() -> Result<()> {
236 let dir = tempdir().unwrap();
237 let mut seed_files = HashMap::new();
238 seed_files.insert("from_local", "Lorem Ipsum");
239 let storage_admin = setup_fake_storage_admin_with_tmp("123456", seed_files);
240 let from_local_filepath = dir.path().join("from_local");
241 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
242 copy(
243 storage_admin,
244 from_local_filepath.display().to_string(),
245 "123456::from_local".to_string(),
246 )
247 .await
248 }
249
250 #[fuchsia::test]
251 async fn test_copy_local_to_device_populated_directory() -> Result<()> {
252 let dir = tempdir().unwrap();
253 let mut seed_files = HashMap::new();
254
255 seed_files.insert("foo.txt", "Lorem Ipsum");
256
257 let storage_admin = setup_fake_storage_admin_with_tmp("123456", seed_files);
258 let from_local_filepath = dir.path().join("from_local");
259 write(&from_local_filepath, &EXPECTED_DATA).unwrap();
260 copy(
261 storage_admin,
262 from_local_filepath.display().to_string(),
263 "123456::from_local".to_string(),
264 )
265 .await
266 }
267
268 #[fuchsia::test]
269 async fn test_copy_device_to_local_infer_path() -> Result<()> {
270 let dir = tempdir().unwrap();
271 let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
272 let dest_filepath = dir.path();
273
274 copy(storage_admin, "123456::from_device".to_string(), dest_filepath.display().to_string())
275 .await?;
276
277 let final_path = dest_filepath.join("from_device");
278 let actual_data = read(final_path).unwrap();
279 assert_eq!(actual_data, EXPECTED_DATA);
280 Ok(())
281 }
282
283 #[fuchsia::test]
284 async fn test_copy_device_to_local_infer_slash_path() -> Result<()> {
285 let dir = tempdir().unwrap();
286 let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
287 let dest_filepath = dir.path();
288
289 copy(
290 storage_admin,
291 "123456::from_device".to_string(),
292 dest_filepath.display().to_string() + "/",
293 )
294 .await?;
295
296 let final_path = dest_filepath.join("from_device");
297 let actual_data = read(final_path).unwrap();
298 assert_eq!(actual_data, EXPECTED_DATA);
299 Ok(())
300 }
301
302 #[fuchsia::test]
303 async fn test_copy_device_to_local() -> Result<()> {
304 let dir = tempdir().unwrap();
305 let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
306 let dest_filepath = dir.path().join("from_device");
307 copy(storage_admin, "123456::from_device".to_string(), dest_filepath.display().to_string())
308 .await?;
309 let actual_data = read(dest_filepath).unwrap();
310 assert_eq!(actual_data, EXPECTED_DATA);
311 Ok(())
312 }
313}