component_debug/storage/
delete.rs

1// Copyright 2022 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, RemoteDirectory};
6use crate::path::RemoteComponentStoragePath;
7use anyhow::{anyhow, Result};
8
9use flex_client::ProxyHasDomain;
10use flex_fuchsia_io as fio;
11use flex_fuchsia_sys2::StorageAdminProxy;
12
13/// Delete a file component's storage.
14///
15/// # Arguments
16/// * `storage_admin`: The StorageAdminProxy
17/// * `path`: The name of a file on the target component
18pub async fn delete(storage_admin: StorageAdminProxy, path: String) -> Result<()> {
19    let remote_path = RemoteComponentStoragePath::parse(&path)?;
20
21    let (dir_proxy, server) = storage_admin.domain().create_proxy::<fio::DirectoryMarker>();
22    let server = server.into_channel();
23    let storage_dir = RemoteDirectory::from_proxy(dir_proxy);
24
25    storage_admin
26        .open_component_storage_by_id(&remote_path.instance_id, server.into())
27        .await?
28        .map_err(|e| anyhow!("Could not open component storage: {:?}", e))?;
29
30    if remote_path.relative_path.as_os_str().is_empty() {
31        return Err(anyhow!("can't delete empty path"));
32    };
33
34    let path_str = match remote_path.relative_path.to_str() {
35        Some(p) => p,
36        None => return Err(anyhow!("error parsing `{}`", &remote_path.relative_path.display())),
37    };
38
39    if !storage_dir.exists(&path_str).await? {
40        return Err(anyhow!("file does not exist: {}", &path_str));
41    }
42
43    storage_dir.remove(&path_str).await?;
44
45    println!("Deleted {}", &path_str);
46    Ok(())
47}
48
49////////////////////////////////////////////////////////////////////////////////
50// tests
51
52#[cfg(test)]
53mod test {
54    use super::*;
55    use crate::storage::test::setup_fake_storage_admin;
56    use flex_fuchsia_io as fio;
57    use futures::TryStreamExt;
58
59    pub fn dirents(names: Vec<&'static str>) -> Vec<u8> {
60        let mut bytes = vec![];
61        for name in names {
62            // inode: u64
63            for _ in 0..8 {
64                bytes.push(0);
65            }
66            // size: u8
67            bytes.push(name.len() as u8);
68            // type: u8
69            bytes.push(fio::DirentType::File.into_primitive());
70            // name: [u8]
71            for byte in name.bytes() {
72                bytes.push(byte);
73            }
74        }
75        bytes
76    }
77
78    fn setup_fake_directory(mut root_dir: fio::DirectoryRequestStream) {
79        fuchsia_async::Task::local(async move {
80            let dirents = dirents(vec!["foo", "bar"]);
81
82            // Rewind on root directory should succeed.
83            let request = root_dir.try_next().await;
84            if let Ok(Some(fio::DirectoryRequest::Rewind { responder, .. })) = request {
85                responder.send(0).unwrap();
86            } else {
87                panic!("did not get rewind request: {:?}", request)
88            }
89
90            // ReadDirents should report two files in the root directory.
91            let request = root_dir.try_next().await;
92            if let Ok(Some(fio::DirectoryRequest::ReadDirents { max_bytes, responder })) = request {
93                assert!(dirents.len() as u64 <= max_bytes);
94                responder.send(0, dirents.as_slice()).unwrap();
95            } else {
96                panic!("did not get readdirents request: {:?}", request)
97            }
98
99            // ReadDirents should not report any more contents
100            let request = root_dir.try_next().await;
101            if let Ok(Some(fio::DirectoryRequest::ReadDirents { responder, .. })) = request {
102                responder.send(0, &[]).unwrap();
103            } else {
104                panic!("did not get 2nd readdirents request: {:?}", request)
105            }
106
107            match root_dir.try_next().await {
108                Ok(Some(fio::DirectoryRequest::Unlink { name: a, options: o, responder })) => {
109                    assert_eq!(a, "foo");
110                    assert_eq!(o, fio::UnlinkOptions::default());
111                    responder.send(Ok(())).unwrap();
112                }
113                request => {
114                    panic!("did not get delete request; received: {:?}", request)
115                }
116            }
117        })
118        .detach();
119    }
120
121    #[fuchsia_async::run_singlethreaded(test)]
122    async fn test_delete_file() -> Result<()> {
123        let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
124        delete(storage_admin.clone(), "123456::foo".to_string()).await
125    }
126
127    #[fuchsia_async::run_singlethreaded(test)]
128    async fn test_delete_file_no_file() -> Result<()> {
129        let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
130        match delete(storage_admin.clone(), "123456::nope".to_string()).await {
131            Err(e) => {
132                assert_eq!(e.to_string(), "file does not exist: nope");
133                Ok(())
134            }
135            Ok(()) => panic!("did not receive expected no-file error"),
136        }
137    }
138
139    #[fuchsia_async::run_singlethreaded(test)]
140    async fn test_delete_file_empty_path() -> Result<()> {
141        let storage_admin = setup_fake_storage_admin("123456", setup_fake_directory);
142        match delete(storage_admin.clone(), "123456::".to_string()).await {
143            Err(e) => {
144                assert_eq!(e.to_string(), "can't delete empty path");
145                Ok(())
146            }
147            Ok(()) => panic!("did not receive expected empty-path error"),
148        }
149    }
150}