component_debug/storage/
copy.rs

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