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 fidl::endpoints::create_proxy;
12use fidl_fuchsia_io as fio;
13use fidl_fuchsia_sys2::StorageAdminProxy;
14use std::path::PathBuf;
15
16/// Transfer a file between the host machine and the Fuchsia device.
17/// Can be used to upload a file to or from the Fuchsia device.
18///
19/// # Arguments
20/// * `storage_admin`: The StorageAdminProxy.
21/// * `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
22/// * `destination_path`: The path and filename on the target component or the host machine where to save the file
23pub 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            // Copying from remote to host
41            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            // Copying from host to remote
54            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////////////////////////////////////////////////////////////////////////////////
86// tests
87
88#[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    // TODO(xbhatnag): Replace this mock with something more robust like VFS.
103    // Currently VFS is not cross-platform.
104    fn setup_fake_directory(mut root_dir: fio::DirectoryRequestStream) {
105        fuchsia_async::Task::local(async move {
106            // Serve the root directory
107            // Rewind on root directory should succeed
108            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            // Serve the root directory
128            // Truncating the file should succeed
129            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            // Writing the file should succeed
138            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            // Closing file should succeed
147            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            // Serve the root directory
160            // Reading the file should succeed
161            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            // Reading the file should not return any more data
169            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            // Closing file should succeed
177            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}