blob: 2a8f359645b52a0a3eb70d2fe5180768f8e5e88e [file] [log] [blame]
Victor Hsieh45636232021-10-15 17:52:51 -07001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Victor Hsiehdd99b462021-12-02 17:36:15 -080017use log::warn;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080018use std::collections::{hash_map, HashMap};
Victor Hsieh45636232021-10-15 17:52:51 -070019use std::io;
20use std::path::{Path, PathBuf};
21
22use super::remote_file::RemoteFileEditor;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080023use super::{validate_basename, VirtFdService, VirtFdServiceStatus};
Victor Hsieh45636232021-10-15 17:52:51 -070024use crate::fsverity::VerifiedFileEditor;
25use crate::fusefs::Inode;
26
27const MAX_ENTRIES: u16 = 100; // Arbitrary limit
28
Victor Hsiehdd99b462021-12-02 17:36:15 -080029struct DirEntry {
30 inode: Inode,
31
32 // This information is duplicated since it is also available in `AuthFs::inode_table` via the
33 // type system. But it makes it simple to deal with deletion, where otherwise we need to get a
34 // mutable parent directory in the table, and query the table for directory/file type checking
35 // at the same time.
36 is_dir: bool,
37}
38
Victor Hsieh45636232021-10-15 17:52:51 -070039/// A remote directory backed by a remote directory FD, where the provider/fd_server is not
40/// trusted.
41///
42/// The directory is assumed empty initially without the trust to the storage. Functionally, when
43/// the backing storage is not clean, the fd_server can fail to create a file or directory when
44/// there is name collision. From RemoteDirEditor's perspective of security, the creation failure
45/// is just one of possible errors that can happen, and what matters is RemoteDirEditor maintains
46/// the integrity itself.
47///
48/// When new files are created through RemoteDirEditor, the file integrity are maintained within the
49/// VM. Similarly, integrity (namely the list of entries) of the directory, or new directories
50/// created within such a directory, are also maintained within the VM. A compromised fd_server or
51/// malicious client can't affect the view to the files and directories within such a directory in
52/// the VM.
53pub struct RemoteDirEditor {
54 service: VirtFdService,
55 remote_dir_fd: i32,
56
Victor Hsiehdd99b462021-12-02 17:36:15 -080057 /// Mapping of entry names to the corresponding inode. The actual file/directory is stored in
58 /// the global pool in fusefs.
59 entries: HashMap<PathBuf, DirEntry>,
Victor Hsieh45636232021-10-15 17:52:51 -070060}
61
62impl RemoteDirEditor {
63 pub fn new(service: VirtFdService, remote_dir_fd: i32) -> Self {
64 RemoteDirEditor { service, remote_dir_fd, entries: HashMap::new() }
65 }
66
67 /// Returns the number of entries created.
68 pub fn number_of_entries(&self) -> u16 {
69 self.entries.len() as u16 // limited to MAX_ENTRIES
70 }
71
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080072 /// Creates a remote file named `basename` with corresponding `inode` at the current directory.
Victor Hsieh45636232021-10-15 17:52:51 -070073 pub fn create_file(
74 &mut self,
75 basename: &Path,
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080076 inode: Inode,
77 ) -> io::Result<VerifiedFileEditor<RemoteFileEditor>> {
Victor Hsieh45636232021-10-15 17:52:51 -070078 self.validate_argument(basename)?;
79
80 let basename_str =
81 basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
82 let new_fd = self
83 .service
84 .createFileInDirectory(self.remote_dir_fd, basename_str)
85 .map_err(into_io_error)?;
Victor Hsieh45636232021-10-15 17:52:51 -070086
87 let new_remote_file =
88 VerifiedFileEditor::new(RemoteFileEditor::new(self.service.clone(), new_fd));
Victor Hsiehdd99b462021-12-02 17:36:15 -080089 self.entries.insert(basename.to_path_buf(), DirEntry { inode, is_dir: false });
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080090 Ok(new_remote_file)
Victor Hsieh45636232021-10-15 17:52:51 -070091 }
92
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080093 /// Creates a remote directory named `basename` with corresponding `inode` at the current
94 /// directory.
95 pub fn mkdir(&mut self, basename: &Path, inode: Inode) -> io::Result<RemoteDirEditor> {
Victor Hsieh45636232021-10-15 17:52:51 -070096 self.validate_argument(basename)?;
97
98 let basename_str =
99 basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
100 let new_fd = self
101 .service
102 .createDirectoryInDirectory(self.remote_dir_fd, basename_str)
103 .map_err(into_io_error)?;
Victor Hsieh45636232021-10-15 17:52:51 -0700104
105 let new_remote_dir = RemoteDirEditor::new(self.service.clone(), new_fd);
Victor Hsiehdd99b462021-12-02 17:36:15 -0800106 self.entries.insert(basename.to_path_buf(), DirEntry { inode, is_dir: true });
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800107 Ok(new_remote_dir)
Victor Hsieh45636232021-10-15 17:52:51 -0700108 }
109
Victor Hsiehdd99b462021-12-02 17:36:15 -0800110 /// Deletes a file
111 pub fn delete_file(&mut self, basename: &Path) -> io::Result<Inode> {
112 let inode = self.force_delete_entry(basename, /* expect_dir */ false)?;
113
114 let basename_str =
115 basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
116 if let Err(e) = self.service.deleteFile(self.remote_dir_fd, basename_str) {
117 // Ignore the error to honor the local state.
118 warn!("Deletion on the host is reportedly failed: {:?}", e);
119 }
120 Ok(inode)
121 }
122
123 /// Forces to delete a directory. The caller must only call if `basename` is a directory and
124 /// empty.
125 pub fn force_delete_directory(&mut self, basename: &Path) -> io::Result<Inode> {
126 let inode = self.force_delete_entry(basename, /* expect_dir */ true)?;
127
128 let basename_str =
129 basename.to_str().ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
130 if let Err(e) = self.service.deleteDirectory(self.remote_dir_fd, basename_str) {
131 // Ignore the error to honor the local state.
132 warn!("Deletion on the host is reportedly failed: {:?}", e);
133 }
134 Ok(inode)
135 }
136
Victor Hsieh45636232021-10-15 17:52:51 -0700137 /// Returns the inode number of a file or directory named `name` previously created through
138 /// `RemoteDirEditor`.
Victor Hsiehdd99b462021-12-02 17:36:15 -0800139 pub fn find_inode(&self, name: &Path) -> io::Result<Inode> {
140 self.entries
141 .get(name)
142 .map(|entry| entry.inode)
143 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
144 }
145
146 /// Returns whether the directory has an entry of the given name.
147 pub fn has_entry(&self, name: &Path) -> bool {
148 self.entries.contains_key(name)
149 }
150
151 fn force_delete_entry(&mut self, basename: &Path, expect_dir: bool) -> io::Result<Inode> {
152 // Kernel should only give us a basename.
153 debug_assert!(validate_basename(basename).is_ok());
154
155 if let Some(entry) = self.entries.get(basename) {
156 match (expect_dir, entry.is_dir) {
157 (true, false) => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
158 (false, true) => Err(io::Error::from_raw_os_error(libc::EISDIR)),
159 _ => {
160 let inode = entry.inode;
161 let _ = self.entries.remove(basename);
162 Ok(inode)
163 }
164 }
165 } else {
166 Err(io::Error::from_raw_os_error(libc::ENOENT))
167 }
Victor Hsieh45636232021-10-15 17:52:51 -0700168 }
169
170 fn validate_argument(&self, basename: &Path) -> io::Result<()> {
171 // Kernel should only give us a basename.
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800172 debug_assert!(validate_basename(basename).is_ok());
173
Victor Hsieh45636232021-10-15 17:52:51 -0700174 if self.entries.contains_key(basename) {
175 Err(io::Error::from_raw_os_error(libc::EEXIST))
176 } else if self.entries.len() >= MAX_ENTRIES.into() {
177 Err(io::Error::from_raw_os_error(libc::EMLINK))
178 } else {
179 Ok(())
180 }
181 }
182}
183
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800184/// An in-memory directory representation of a directory structure.
185pub struct InMemoryDir(HashMap<PathBuf, Inode>);
186
187impl InMemoryDir {
188 /// Creates an empty instance of `InMemoryDir`.
189 pub fn new() -> Self {
190 // Hash map is empty since "." and ".." are excluded in entries.
191 InMemoryDir(HashMap::new())
192 }
193
194 /// Returns the number of entries in the directory (not including "." and "..").
195 pub fn number_of_entries(&self) -> u16 {
196 self.0.len() as u16 // limited to MAX_ENTRIES
197 }
198
199 /// Adds an entry (name and the inode number) to the directory. Fails if already exists. The
200 /// caller is responsible for ensure the inode uniqueness.
201 pub fn add_entry(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
202 validate_basename(basename)?;
203 if self.0.len() >= MAX_ENTRIES.into() {
204 return Err(io::Error::from_raw_os_error(libc::EMLINK));
205 }
206
207 if let hash_map::Entry::Vacant(entry) = self.0.entry(basename.to_path_buf()) {
208 entry.insert(inode);
209 Ok(())
210 } else {
211 Err(io::Error::from_raw_os_error(libc::EEXIST))
212 }
213 }
214
215 /// Looks up an entry inode by name. `None` if not found.
216 pub fn lookup_inode(&self, basename: &Path) -> Option<Inode> {
217 self.0.get(basename).copied()
218 }
219}
220
Victor Hsieh45636232021-10-15 17:52:51 -0700221fn into_io_error(e: VirtFdServiceStatus) -> io::Error {
222 let maybe_errno = e.service_specific_error();
223 if maybe_errno > 0 {
224 io::Error::from_raw_os_error(maybe_errno)
225 } else {
226 io::Error::new(io::ErrorKind::Other, e.get_description())
227 }
228}