Skip to main content

trussed_staging/
manage.rs

1// Copyright (C) Nitrokey GmbH
2// SPDX-License-Identifier: Apache-2.0 or MIT
3
4use littlefs2_core::{path, DirEntry, Path};
5use trussed::{serde_extensions::ExtensionImpl, store::Store};
6use trussed_core::{serde_extensions::Extension, types::Location, Error};
7use trussed_manage::{
8    FactoryResetClientReply, FactoryResetClientRequest, FactoryResetDeviceReply,
9    FactoryResetDeviceRequest, ManageExtension, ManageReply, ManageRequest,
10    FACTORY_RESET_MARKER_FILE,
11};
12
13use crate::StagingBackend;
14
15#[derive(Debug, Clone)]
16pub struct State {
17    /// Function called during a factory reset (of a client or the whole device)
18    ///
19    /// The path start all  start with the root. Here is an example such function:
20    /// ```rust
21    ///# use trussed_core::types::Location;
22    ///# use littlefs2_core::{path, Path};
23    /// fn should_preserve(path: &Path, location: Location) -> bool {
24    ///     (location == Location::Internal && path == path!("/client1/dat/to_save_internal"))
25    ///         || (location == Location::External && path == path!("/client1/dat/to_save_external"))
26    ///         || (location == Location::Volatile && path == path!("/client1/dat/to_save_volatile"))
27    /// }
28    /// ```
29    pub should_preserve_file: fn(&Path, location: Location) -> bool,
30}
31
32impl Default for State {
33    fn default() -> State {
34        State {
35            should_preserve_file: |_, _| false,
36        }
37    }
38}
39
40fn callback(
41    should_preserve_file: fn(&Path, location: Location) -> bool,
42    location: Location,
43) -> impl Fn(&DirEntry) -> bool {
44    move |f| !should_preserve_file(f.path(), location)
45}
46
47impl ExtensionImpl<ManageExtension> for StagingBackend {
48    fn extension_request<P: trussed::Platform>(
49        &mut self,
50        _core_ctx: &mut trussed::types::CoreContext,
51        _backend_ctx: &mut Self::Context,
52        request: &<ManageExtension as Extension>::Request,
53        resources: &mut trussed::service::ServiceResources<P>,
54    ) -> Result<<ManageExtension as Extension>::Reply, Error> {
55        match request {
56            ManageRequest::FactoryResetDevice(FactoryResetDeviceRequest) => {
57                let platform = resources.platform();
58                let store = platform.store();
59
60                for location in [Location::Internal, Location::External, Location::Volatile] {
61                    let fs = store.fs(location);
62                    fs.remove_dir_all_where(
63                        path!("/"),
64                        &callback(self.manage.should_preserve_file, location),
65                    )
66                    .map_err(|_err| {
67                        debug!("Failed to delete {location:?} fs: {_err:?}");
68                        Error::FunctionFailed
69                    })?;
70                    if location == Location::External {
71                        let is_empty = fs
72                            .read_dir_and_then(path!("/"), &mut |dir| match dir.next() {
73                                Some(Ok(_)) => Ok(false),
74                                Some(Err(err)) => Err(err),
75                                None => Ok(true),
76                            })
77                            .map_err(|_err| {
78                                debug!("Failed to check emptyness {location:?} fs: {_err:?}");
79                                Error::FunctionFailed
80                            })?;
81                        if is_empty {
82                            fs.write(FACTORY_RESET_MARKER_FILE, &[])
83                            .map_err(|_err| {
84                                debug!("Failed to write reformat instruction for {location:?} fs: {_err:?}");
85                                Error::FunctionFailed
86                            })?;
87                        }
88                    }
89                }
90                Ok(ManageReply::FactoryResetDevice(FactoryResetDeviceReply))
91            }
92            ManageRequest::FactoryResetClient(FactoryResetClientRequest { client }) => {
93                let platform = resources.platform();
94                let store = platform.store();
95
96                if client.parent().is_some() {
97                    return Err(Error::InvalidPath);
98                }
99
100                let path = path!("/").join(client);
101
102                for location in [Location::Internal, Location::External, Location::Volatile] {
103                    store
104                        .fs(location)
105                        .remove_dir_all_where(
106                            &path,
107                            &callback(self.manage.should_preserve_file, location),
108                        )
109                        .map_err(|_err| {
110                            debug!("Failed to delete {location:?} fs: {_err:?}");
111                            Error::FunctionFailed
112                        })?;
113                }
114                Ok(ManageReply::FactoryResetClient(FactoryResetClientReply))
115            }
116        }
117    }
118}