blob: b456f33d4306e7a1e8fa8ec7daf3403c96d834aa [file] [log] [blame]
Victor Hsieh88ac6ca2020-11-13 15:20:24 -08001/*
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 Hsiehd18b9752021-11-09 16:03:34 -080017use anyhow::{anyhow, bail, Result};
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080018use log::{debug, warn};
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080019use std::collections::{btree_map, BTreeMap};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080020use std::convert::TryFrom;
Victor Hsieh45636232021-10-15 17:52:51 -070021use std::ffi::{CStr, OsStr};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080022use std::fs::OpenOptions;
23use std::io;
24use std::mem::MaybeUninit;
25use std::option::Option;
Victor Hsieh45636232021-10-15 17:52:51 -070026use std::os::unix::{ffi::OsStrExt, io::AsRawFd};
Victor Hsiehd18b9752021-11-09 16:03:34 -080027use std::path::{Component, Path, PathBuf};
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080028use std::sync::atomic::{AtomicU64, Ordering};
Victor Hsieh60c2f412021-11-03 13:02:19 -070029use std::sync::Mutex;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080030use std::time::Duration;
31
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080032use fuse::filesystem::{
Victor Hsieh71f10032021-08-13 11:24:02 -070033 Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, GetxattrReply,
34 SetattrValid, ZeroCopyReader, ZeroCopyWriter,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080035};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080036use fuse::mount::MountOption;
37
Victor Hsiehac4f3f42021-02-26 12:35:58 -080038use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080039use crate::file::{
Victor Hsiehd18b9752021-11-09 16:03:34 -080040 validate_basename, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor,
41 RemoteFileReader, RemoteMerkleTreeReader,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080042};
43use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080044
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080045pub type Inode = u64;
46type Handle = u64;
47
Victor Hsieh26cea2f2021-11-03 10:28:33 -070048const DEFAULT_METADATA_TIMEOUT: Duration = Duration::from_secs(5);
49const ROOT_INODE: Inode = 1;
50
Victor Hsieh766e5332021-11-09 09:41:25 -080051/// Maximum bytes in the write transaction to the FUSE device. This limits the maximum buffer
52/// size in a read request (including FUSE protocol overhead) that the filesystem writes to.
53const MAX_WRITE_BYTES: u32 = 65536;
54
55/// Maximum bytes in a read operation.
56/// TODO(victorhsieh): This option is deprecated by FUSE. Figure out if we can remove this.
57const MAX_READ_BYTES: u32 = 65536;
58
Victor Hsieh26cea2f2021-11-03 10:28:33 -070059/// `AuthFsEntry` defines the filesystem entry type supported by AuthFS.
60pub enum AuthFsEntry {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080061 /// A read-only directory (writable during initialization). Root directory is an example.
62 ReadonlyDirectory { dir: InMemoryDir },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070063 /// A file type that is verified against fs-verity signature (thus read-only). The file is
Victor Hsieh1bcf4112021-03-19 14:26:57 -070064 /// served from a remote server.
Victor Hsieh88e50172021-10-15 13:27:13 -070065 VerifiedReadonly {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070066 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
67 file_size: u64,
68 },
69 /// A file type that is a read-only passthrough from a file on a remote serrver.
Victor Hsieh88e50172021-10-15 13:27:13 -070070 UnverifiedReadonly { reader: RemoteFileReader, file_size: u64 },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070071 /// A file type that is initially empty, and the content is stored on a remote server. File
72 /// integrity is guaranteed with private Merkle tree.
Victor Hsieh88e50172021-10-15 13:27:13 -070073 VerifiedNew { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh45636232021-10-15 17:52:51 -070074 /// A directory type that is initially empty. One can create new file (`VerifiedNew`) and new
75 /// directory (`VerifiedNewDirectory` itself) with integrity guaranteed within the VM.
76 VerifiedNewDirectory { dir: RemoteDirEditor },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080077}
78
Victor Hsieh60c2f412021-11-03 13:02:19 -070079// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080080pub struct AuthFs {
Victor Hsieh60c2f412021-11-03 13:02:19 -070081 /// Table for `Inode` to `AuthFsEntry` lookup. This needs to be `Sync` to be used in
82 /// `fuse::worker::start_message_loop`.
83 inode_table: Mutex<BTreeMap<Inode, AuthFsEntry>>,
84
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080085 /// The next available inode number.
86 next_inode: AtomicU64,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080087}
88
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080089// Implementation for preparing an `AuthFs` instance, before starting to serve.
90// TODO(victorhsieh): Consider implement a builder to separate the mutable initialization from the
91// immutable / interiorly mutable serving phase.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080092impl AuthFs {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080093 pub fn new() -> AuthFs {
Victor Hsieh60c2f412021-11-03 13:02:19 -070094 let mut inode_table = BTreeMap::new();
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080095 inode_table.insert(ROOT_INODE, AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() });
Victor Hsieh60c2f412021-11-03 13:02:19 -070096
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080097 AuthFs { inode_table: Mutex::new(inode_table), next_inode: AtomicU64::new(ROOT_INODE + 1) }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080098 }
99
Victor Hsiehd18b9752021-11-09 16:03:34 -0800100 /// Add an `AuthFsEntry` as `basename` to the filesystem root.
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800101 pub fn add_entry_at_root_dir(
102 &mut self,
103 basename: PathBuf,
104 entry: AuthFsEntry,
105 ) -> Result<Inode> {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800106 validate_basename(&basename)?;
107 self.add_entry_at_ro_dir_by_path(ROOT_INODE, &basename, entry)
108 }
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800109
Victor Hsiehd18b9752021-11-09 16:03:34 -0800110 /// Add an `AuthFsEntry` by path from the `ReadonlyDirectory` represented by `dir_inode`. The
111 /// path must be a related path. If some ancestor directories do not exist, they will be
112 /// created (also as `ReadonlyDirectory`) automatically.
113 pub fn add_entry_at_ro_dir_by_path(
114 &mut self,
115 dir_inode: Inode,
116 path: &Path,
117 entry: AuthFsEntry,
118 ) -> Result<Inode> {
119 // 1. Make sure the parent directories all exist. Derive the entry's parent inode.
120 let parent_path =
121 path.parent().ok_or_else(|| anyhow!("No parent directory: {:?}", path))?;
122 let parent_inode =
123 parent_path.components().try_fold(dir_inode, |current_dir_inode, path_component| {
124 match path_component {
125 Component::RootDir => bail!("Absolute path is not supported"),
126 Component::Normal(name) => {
127 let inode_table = self.inode_table.get_mut().unwrap();
128 // Locate the internal directory structure.
129 let current_dir_entry =
130 inode_table.get_mut(&current_dir_inode).ok_or_else(|| {
131 anyhow!("Unknown directory inode {}", current_dir_inode)
132 })?;
133 let dir = match current_dir_entry {
134 AuthFsEntry::ReadonlyDirectory { dir } => dir,
135 _ => unreachable!("Not a ReadonlyDirectory"),
136 };
137 // Return directory inode. Create first if not exists.
138 if let Some(existing_inode) = dir.lookup_inode(name.as_ref()) {
139 Ok(existing_inode)
140 } else {
141 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
142 let new_dir_entry =
143 AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() };
144
145 // Actually update the tables.
146 dir.add_entry(name.as_ref(), new_inode)?;
147 if inode_table.insert(new_inode, new_dir_entry).is_some() {
148 bail!("Unexpected to find a duplicated inode");
149 }
150 Ok(new_inode)
151 }
152 }
153 _ => Err(anyhow!("Path is not canonical: {:?}", path)),
154 }
155 })?;
156
157 // 2. Insert the entry to the parent directory, as well as the inode table.
158 let inode_table = self.inode_table.get_mut().unwrap();
159 match inode_table.get_mut(&parent_inode).expect("previously returned inode") {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800160 AuthFsEntry::ReadonlyDirectory { dir } => {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800161 let basename =
162 path.file_name().ok_or_else(|| anyhow!("Bad file name: {:?}", path))?;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800163 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
164
Victor Hsiehd18b9752021-11-09 16:03:34 -0800165 // Actually update the tables.
166 dir.add_entry(basename.as_ref(), new_inode)?;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800167 if inode_table.insert(new_inode, entry).is_some() {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800168 bail!("Unexpected to find a duplicated inode");
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800169 }
170 Ok(new_inode)
171 }
Victor Hsiehd18b9752021-11-09 16:03:34 -0800172 _ => unreachable!("Not a ReadonlyDirectory"),
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800173 }
174 }
175}
176
177// Implementation for serving requests.
178impl AuthFs {
Victor Hsieh45636232021-10-15 17:52:51 -0700179 /// Handles the file associated with `inode` if found. This function returns whatever
180 /// `handle_fn` returns.
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700181 fn handle_inode<F, R>(&self, inode: &Inode, handle_fn: F) -> io::Result<R>
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700182 where
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700183 F: FnOnce(&AuthFsEntry) -> io::Result<R>,
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700184 {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700185 let inode_table = self.inode_table.lock().unwrap();
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800186 let entry =
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700187 inode_table.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800188 handle_fn(entry)
Victor Hsieh45636232021-10-15 17:52:51 -0700189 }
190
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800191 /// Adds a new entry `name` created by `create_fn` at `parent_inode`.
192 ///
193 /// The operation involves two updates: adding the name with a new allocated inode to the
194 /// parent directory, and insert the new inode and the actual `AuthFsEntry` to the global inode
195 /// table.
196 ///
197 /// `create_fn` receives the parent directory, through which it can create the new entry at and
198 /// register the new inode to. Its returned entry is then added to the inode table.
199 fn create_new_entry<F>(
200 &self,
201 parent_inode: Inode,
202 name: &CStr,
203 create_fn: F,
204 ) -> io::Result<Inode>
Victor Hsieh45636232021-10-15 17:52:51 -0700205 where
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800206 F: FnOnce(&mut AuthFsEntry, &Path, Inode) -> io::Result<AuthFsEntry>,
Victor Hsieh45636232021-10-15 17:52:51 -0700207 {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700208 let mut inode_table = self.inode_table.lock().unwrap();
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800209 let mut parent_entry = inode_table
210 .get_mut(&parent_inode)
211 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
212
213 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
214 let basename: &Path = cstr_to_path(name);
215 let new_file_entry = create_fn(&mut parent_entry, basename, new_inode)?;
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700216 if let btree_map::Entry::Vacant(entry) = inode_table.entry(new_inode) {
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800217 entry.insert(new_file_entry);
Victor Hsieh45636232021-10-15 17:52:51 -0700218 Ok(new_inode)
219 } else {
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800220 unreachable!("Unexpected duplication of inode {}", new_inode);
Victor Hsieh45636232021-10-15 17:52:51 -0700221 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800222 }
223}
224
225fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
226 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
227 Ok(())
228 } else {
229 Err(io::Error::from_raw_os_error(libc::EACCES))
230 }
231}
232
233cfg_if::cfg_if! {
234 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800235 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800236 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800237 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800238 }
239}
240
Victor Hsieh45636232021-10-15 17:52:51 -0700241#[allow(clippy::enum_variant_names)]
242enum AccessMode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800243 ReadOnly,
244 ReadWrite,
245}
246
Victor Hsieh45636232021-10-15 17:52:51 -0700247fn create_stat(
248 ino: libc::ino_t,
249 file_size: u64,
250 access_mode: AccessMode,
251) -> io::Result<libc::stat64> {
252 // SAFETY: stat64 is a plan C struct without pointer.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800253 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
254
255 st.st_ino = ino;
Victor Hsieh45636232021-10-15 17:52:51 -0700256 st.st_mode = match access_mode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800257 // Until needed, let's just grant the owner access.
Victor Hsieh45636232021-10-15 17:52:51 -0700258 // TODO(205169366): Implement mode properly.
259 AccessMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
260 AccessMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800261 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800262 st.st_nlink = 1;
263 st.st_uid = 0;
264 st.st_gid = 0;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800265 st.st_size = libc::off64_t::try_from(file_size)
266 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
267 st.st_blksize = blk_size();
268 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
269 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
270 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
271 Ok(st)
272}
273
Victor Hsieh45636232021-10-15 17:52:51 -0700274fn create_dir_stat(ino: libc::ino_t, file_number: u16) -> io::Result<libc::stat64> {
275 // SAFETY: stat64 is a plan C struct without pointer.
276 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
277
278 st.st_ino = ino;
279 // TODO(205169366): Implement mode properly.
280 st.st_mode = libc::S_IFDIR
281 | libc::S_IXUSR
282 | libc::S_IWUSR
283 | libc::S_IRUSR
284 | libc::S_IXGRP
285 | libc::S_IXOTH;
286
287 // 2 extra for . and ..
288 st.st_nlink = file_number
289 .checked_add(2)
290 .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?
291 .into();
292
293 st.st_uid = 0;
294 st.st_gid = 0;
295 Ok(st)
296}
297
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800298fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800299 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800300}
301
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700302fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800303 mut w: W,
304 file: &T,
305 file_size: u64,
306 offset: u64,
307 size: u32,
308) -> io::Result<usize> {
309 let remaining = file_size.saturating_sub(offset);
310 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800311 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800312 0,
313 |total, (current_offset, planned_data_size)| {
314 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
315 // instead of accepting a buffer, the writer could expose the final destination buffer
316 // for the reader to write to. It might not be generally applicable though, e.g. with
317 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800318 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800319 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
320 if read_size < planned_data_size {
321 return Err(io::Error::from_raw_os_error(libc::ENODATA));
322 }
323
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800324 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800325 let end = begin + planned_data_size;
326 let s = w.write(&buf[begin..end])?;
327 if s != planned_data_size {
328 return Err(io::Error::from_raw_os_error(libc::EIO));
329 }
330 Ok(total + s)
331 },
332 )?;
333
334 Ok(total)
335}
336
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800337// TODO(205715172): Support enumerating directory entries.
338pub struct EmptyDirectoryIterator {}
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800339
340impl DirectoryIterator for EmptyDirectoryIterator {
341 fn next(&mut self) -> Option<DirEntry> {
342 None
343 }
344}
345
346impl FileSystem for AuthFs {
347 type Inode = Inode;
348 type Handle = Handle;
349 type DirIter = EmptyDirectoryIterator;
350
351 fn max_buffer_size(&self) -> u32 {
Victor Hsieh766e5332021-11-09 09:41:25 -0800352 MAX_WRITE_BYTES
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800353 }
354
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800355 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
356 // Enable writeback cache for better performance especially since our bandwidth to the
357 // backend service is limited.
358 Ok(FsOptions::WRITEBACK_CACHE)
359 }
360
Victor Hsieh45636232021-10-15 17:52:51 -0700361 fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800362 // Look up the entry's inode number in parent directory.
363 let inode = self.handle_inode(&parent, |parent_entry| match parent_entry {
364 AuthFsEntry::ReadonlyDirectory { dir } => {
365 let path = cstr_to_path(name);
366 dir.lookup_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
367 }
368 AuthFsEntry::VerifiedNewDirectory { dir } => {
369 let path = cstr_to_path(name);
370 dir.find_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
371 }
372 _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
373 })?;
374
375 // Normally, `lookup` is required to increase a reference count for the inode (while
376 // `forget` will decrease it). It is not yet necessary until we start to support
377 // deletion (only for `VerifiedNewDirectory`).
378
379 // Create the entry's stat if found.
380 let st = self.handle_inode(&inode, |entry| match entry {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800381 AuthFsEntry::ReadonlyDirectory { dir } => {
382 create_dir_stat(inode, dir.number_of_entries())
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800383 }
384 AuthFsEntry::UnverifiedReadonly { file_size, .. }
385 | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
386 create_stat(inode, *file_size, AccessMode::ReadOnly)
387 }
388 AuthFsEntry::VerifiedNew { editor } => {
389 create_stat(inode, editor.size(), AccessMode::ReadWrite)
390 }
391 AuthFsEntry::VerifiedNewDirectory { dir } => {
392 create_dir_stat(inode, dir.number_of_entries())
393 }
394 })?;
395 Ok(Entry {
396 inode,
397 generation: 0,
398 attr: st,
399 entry_timeout: DEFAULT_METADATA_TIMEOUT,
400 attr_timeout: DEFAULT_METADATA_TIMEOUT,
401 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800402 }
403
404 fn getattr(
405 &self,
406 _ctx: Context,
407 inode: Inode,
408 _handle: Option<Handle>,
409 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700410 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700411 Ok((
412 match config {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800413 AuthFsEntry::ReadonlyDirectory { dir } => {
414 create_dir_stat(inode, dir.number_of_entries())
415 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700416 AuthFsEntry::UnverifiedReadonly { file_size, .. }
417 | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800418 create_stat(inode, *file_size, AccessMode::ReadOnly)
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700419 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700420 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800421 create_stat(inode, editor.size(), AccessMode::ReadWrite)
Victor Hsieh45636232021-10-15 17:52:51 -0700422 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700423 AuthFsEntry::VerifiedNewDirectory { dir } => {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800424 create_dir_stat(inode, dir.number_of_entries())
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700425 }
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800426 }?,
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700427 DEFAULT_METADATA_TIMEOUT,
428 ))
429 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800430 }
431
432 fn open(
433 &self,
434 _ctx: Context,
435 inode: Self::Inode,
436 flags: u32,
437 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
438 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800439 // return None as the handle.
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700440 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700441 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700442 AuthFsEntry::VerifiedReadonly { .. } | AuthFsEntry::UnverifiedReadonly { .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700443 check_access_mode(flags, libc::O_RDONLY)?;
444 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700445 AuthFsEntry::VerifiedNew { .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700446 // No need to check access modes since all the modes are allowed to the
447 // read-writable file.
448 }
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800449 AuthFsEntry::ReadonlyDirectory { .. }
450 | AuthFsEntry::VerifiedNewDirectory { .. } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700451 // TODO(victorhsieh): implement when needed.
452 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
453 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800454 }
Victor Hsieh45636232021-10-15 17:52:51 -0700455 // Always cache the file content. There is currently no need to support direct I/O or
456 // avoid the cache buffer. Memory mapping is only possible with cache enabled.
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700457 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
458 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800459 }
460
Victor Hsieh45636232021-10-15 17:52:51 -0700461 fn create(
462 &self,
463 _ctx: Context,
464 parent: Self::Inode,
465 name: &CStr,
466 _mode: u32,
467 _flags: u32,
468 _umask: u32,
469 ) -> io::Result<(Entry, Option<Self::Handle>, fuse::sys::OpenOptions)> {
470 // TODO(205169366): Implement mode properly.
471 // TODO(205172873): handle O_TRUNC and O_EXCL properly.
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800472 let new_inode =
473 self.create_new_entry(parent, name, |parent_entry, basename, new_inode| {
474 match parent_entry {
475 AuthFsEntry::VerifiedNewDirectory { dir } => {
476 if dir.find_inode(basename).is_some() {
477 return Err(io::Error::from_raw_os_error(libc::EEXIST));
478 }
479 let new_file = dir.create_file(basename, new_inode)?;
480 Ok(AuthFsEntry::VerifiedNew { editor: new_file })
481 }
482 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh45636232021-10-15 17:52:51 -0700483 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800484 })?;
Victor Hsieh45636232021-10-15 17:52:51 -0700485
486 Ok((
487 Entry {
488 inode: new_inode,
489 generation: 0,
490 attr: create_stat(new_inode, /* file_size */ 0, AccessMode::ReadWrite)?,
491 entry_timeout: DEFAULT_METADATA_TIMEOUT,
492 attr_timeout: DEFAULT_METADATA_TIMEOUT,
493 },
494 // See also `open`.
495 /* handle */ None,
496 fuse::sys::OpenOptions::KEEP_CACHE,
497 ))
498 }
499
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800500 fn read<W: io::Write + ZeroCopyWriter>(
501 &self,
502 _ctx: Context,
503 inode: Inode,
504 _handle: Handle,
505 w: W,
506 size: u32,
507 offset: u64,
508 _lock_owner: Option<u64>,
509 _flags: u32,
510 ) -> io::Result<usize> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700511 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700512 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700513 AuthFsEntry::VerifiedReadonly { reader, file_size } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700514 read_chunks(w, reader, *file_size, offset, size)
515 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700516 AuthFsEntry::UnverifiedReadonly { reader, file_size } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700517 read_chunks(w, reader, *file_size, offset, size)
518 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700519 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700520 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
521 // request a read even if the file is open with O_WRONLY.
522 read_chunks(w, editor, editor.size(), offset, size)
523 }
Victor Hsieh45636232021-10-15 17:52:51 -0700524 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800525 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700526 })
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800527 }
528
529 fn write<R: io::Read + ZeroCopyReader>(
530 &self,
531 _ctx: Context,
532 inode: Self::Inode,
533 _handle: Self::Handle,
534 mut r: R,
535 size: u32,
536 offset: u64,
537 _lock_owner: Option<u64>,
538 _delayed_write: bool,
539 _flags: u32,
540 ) -> io::Result<usize> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700541 self.handle_inode(&inode, |config| match config {
542 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800543 let mut buf = vec![0; size as usize];
544 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700545 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800546 }
547 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700548 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800549 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700550
551 fn setattr(
552 &self,
553 _ctx: Context,
554 inode: Inode,
555 attr: libc::stat64,
556 _handle: Option<Handle>,
557 valid: SetattrValid,
558 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700559 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700560 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700561 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700562 // Initialize the default stat.
Victor Hsieh45636232021-10-15 17:52:51 -0700563 let mut new_attr = create_stat(inode, editor.size(), AccessMode::ReadWrite)?;
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700564 // `valid` indicates what fields in `attr` are valid. Update to return correctly.
565 if valid.contains(SetattrValid::SIZE) {
566 // st_size is i64, but the cast should be safe since kernel should not give a
567 // negative size.
568 debug_assert!(attr.st_size >= 0);
569 new_attr.st_size = attr.st_size;
570 editor.resize(attr.st_size as u64)?;
571 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700572
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700573 if valid.contains(SetattrValid::MODE) {
574 warn!("Changing st_mode is not currently supported");
575 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
576 }
577 if valid.contains(SetattrValid::UID) {
578 warn!("Changing st_uid is not currently supported");
579 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
580 }
581 if valid.contains(SetattrValid::GID) {
582 warn!("Changing st_gid is not currently supported");
583 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
584 }
585 if valid.contains(SetattrValid::CTIME) {
586 debug!(
587 "Ignoring ctime change as authfs does not maintain timestamp currently"
588 );
589 }
590 if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
591 debug!(
592 "Ignoring atime change as authfs does not maintain timestamp currently"
593 );
594 }
595 if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
596 debug!(
597 "Ignoring mtime change as authfs does not maintain timestamp currently"
598 );
599 }
600 Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700601 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700602 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700603 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700604 })
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700605 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700606
607 fn getxattr(
608 &self,
609 _ctx: Context,
610 inode: Self::Inode,
611 name: &CStr,
612 size: u32,
613 ) -> io::Result<GetxattrReply> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700614 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700615 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700616 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700617 // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
618 // change (see b/196635431). Until it's possible, use xattr to expose what we need
619 // as an authfs specific API.
620 if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
621 return Err(io::Error::from_raw_os_error(libc::ENODATA));
622 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700623
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700624 if size == 0 {
625 // Per protocol, when size is 0, return the value size.
626 Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
Victor Hsieh71f10032021-08-13 11:24:02 -0700627 } else {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700628 let digest = editor.calculate_fsverity_digest()?;
629 if digest.len() > size as usize {
630 Err(io::Error::from_raw_os_error(libc::ERANGE))
631 } else {
632 Ok(GetxattrReply::Value(digest.to_vec()))
633 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700634 }
635 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700636 _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
Victor Hsieh71f10032021-08-13 11:24:02 -0700637 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700638 })
Victor Hsieh71f10032021-08-13 11:24:02 -0700639 }
Victor Hsieh45636232021-10-15 17:52:51 -0700640
641 fn mkdir(
642 &self,
643 _ctx: Context,
644 parent: Self::Inode,
645 name: &CStr,
646 _mode: u32,
647 _umask: u32,
648 ) -> io::Result<Entry> {
649 // TODO(205169366): Implement mode properly.
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800650 let new_inode =
651 self.create_new_entry(parent, name, |parent_entry, basename, new_inode| {
652 match parent_entry {
653 AuthFsEntry::VerifiedNewDirectory { dir } => {
654 if dir.find_inode(basename).is_some() {
655 return Err(io::Error::from_raw_os_error(libc::EEXIST));
656 }
657 let new_dir = dir.mkdir(basename, new_inode)?;
658 Ok(AuthFsEntry::VerifiedNewDirectory { dir: new_dir })
659 }
Victor Hsiehd18b9752021-11-09 16:03:34 -0800660 AuthFsEntry::ReadonlyDirectory { .. } => {
661 Err(io::Error::from_raw_os_error(libc::EACCES))
662 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800663 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh45636232021-10-15 17:52:51 -0700664 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800665 })?;
Victor Hsieh45636232021-10-15 17:52:51 -0700666
667 Ok(Entry {
668 inode: new_inode,
669 generation: 0,
670 attr: create_dir_stat(new_inode, /* file_number */ 0)?,
671 entry_timeout: DEFAULT_METADATA_TIMEOUT,
672 attr_timeout: DEFAULT_METADATA_TIMEOUT,
673 })
674 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800675}
676
677/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
678pub fn loop_forever(
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800679 authfs: AuthFs,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800680 mountpoint: &Path,
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700681 extra_options: &Option<String>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800682) -> Result<(), fuse::Error> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800683 let dev_fuse = OpenOptions::new()
684 .read(true)
685 .write(true)
686 .open("/dev/fuse")
687 .expect("Failed to open /dev/fuse");
688
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700689 let mut mount_options = vec![
690 MountOption::FD(dev_fuse.as_raw_fd()),
691 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
692 MountOption::AllowOther,
693 MountOption::UserId(0),
694 MountOption::GroupId(0),
Victor Hsieh766e5332021-11-09 09:41:25 -0800695 MountOption::MaxRead(MAX_READ_BYTES),
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700696 ];
697 if let Some(value) = extra_options {
698 mount_options.push(MountOption::Extra(value));
699 }
700
701 fuse::mount(mountpoint, "authfs", libc::MS_NOSUID | libc::MS_NODEV, &mount_options)
702 .expect("Failed to mount fuse");
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800703
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800704 fuse::worker::start_message_loop(dev_fuse, MAX_WRITE_BYTES, MAX_READ_BYTES, authfs)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800705}
Victor Hsieh45636232021-10-15 17:52:51 -0700706
707fn cstr_to_path(cstr: &CStr) -> &Path {
708 OsStr::from_bytes(cstr.to_bytes()).as_ref()
709}