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