blob: 549df1e4e14d1f7706ae26ad90b7119cf4518f6f [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;
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080024use std::mem::{zeroed, MaybeUninit};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080025use 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};
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080043use crate::fsstat::RemoteFsStatsReader;
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080044use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080045
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080046pub type Inode = u64;
47type Handle = u64;
48
Victor Hsieh26cea2f2021-11-03 10:28:33 -070049const DEFAULT_METADATA_TIMEOUT: Duration = Duration::from_secs(5);
50const ROOT_INODE: Inode = 1;
51
Victor Hsieh766e5332021-11-09 09:41:25 -080052/// Maximum bytes in the write transaction to the FUSE device. This limits the maximum buffer
53/// size in a read request (including FUSE protocol overhead) that the filesystem writes to.
54const MAX_WRITE_BYTES: u32 = 65536;
55
56/// Maximum bytes in a read operation.
57/// TODO(victorhsieh): This option is deprecated by FUSE. Figure out if we can remove this.
58const MAX_READ_BYTES: u32 = 65536;
59
Victor Hsieh26cea2f2021-11-03 10:28:33 -070060/// `AuthFsEntry` defines the filesystem entry type supported by AuthFS.
61pub enum AuthFsEntry {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080062 /// A read-only directory (writable during initialization). Root directory is an example.
63 ReadonlyDirectory { dir: InMemoryDir },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070064 /// A file type that is verified against fs-verity signature (thus read-only). The file is
Victor Hsieh1bcf4112021-03-19 14:26:57 -070065 /// served from a remote server.
Victor Hsieh88e50172021-10-15 13:27:13 -070066 VerifiedReadonly {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070067 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
68 file_size: u64,
69 },
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080070 /// A file type that is a read-only passthrough from a file on a remote server.
Victor Hsieh88e50172021-10-15 13:27:13 -070071 UnverifiedReadonly { reader: RemoteFileReader, file_size: u64 },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070072 /// A file type that is initially empty, and the content is stored on a remote server. File
73 /// integrity is guaranteed with private Merkle tree.
Victor Hsieh88e50172021-10-15 13:27:13 -070074 VerifiedNew { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh45636232021-10-15 17:52:51 -070075 /// A directory type that is initially empty. One can create new file (`VerifiedNew`) and new
76 /// directory (`VerifiedNewDirectory` itself) with integrity guaranteed within the VM.
77 VerifiedNewDirectory { dir: RemoteDirEditor },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080078}
79
Victor Hsieh60c2f412021-11-03 13:02:19 -070080// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080081pub struct AuthFs {
Victor Hsieh60c2f412021-11-03 13:02:19 -070082 /// Table for `Inode` to `AuthFsEntry` lookup. This needs to be `Sync` to be used in
83 /// `fuse::worker::start_message_loop`.
84 inode_table: Mutex<BTreeMap<Inode, AuthFsEntry>>,
85
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080086 /// The next available inode number.
87 next_inode: AtomicU64,
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080088
89 /// A reader to access the remote filesystem stats, which is supposed to be of "the" output
90 /// directory. We assume all output are stored in the same partition.
91 remote_fs_stats_reader: RemoteFsStatsReader,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080092}
93
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080094// Implementation for preparing an `AuthFs` instance, before starting to serve.
95// TODO(victorhsieh): Consider implement a builder to separate the mutable initialization from the
96// immutable / interiorly mutable serving phase.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080097impl AuthFs {
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080098 pub fn new(remote_fs_stats_reader: RemoteFsStatsReader) -> AuthFs {
Victor Hsieh60c2f412021-11-03 13:02:19 -070099 let mut inode_table = BTreeMap::new();
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800100 inode_table.insert(ROOT_INODE, AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() });
Victor Hsieh60c2f412021-11-03 13:02:19 -0700101
Victor Hsiehf7fc3d32021-11-22 10:20:33 -0800102 AuthFs {
103 inode_table: Mutex::new(inode_table),
104 next_inode: AtomicU64::new(ROOT_INODE + 1),
105 remote_fs_stats_reader,
106 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800107 }
108
Victor Hsiehd18b9752021-11-09 16:03:34 -0800109 /// Add an `AuthFsEntry` as `basename` to the filesystem root.
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800110 pub fn add_entry_at_root_dir(
111 &mut self,
112 basename: PathBuf,
113 entry: AuthFsEntry,
114 ) -> Result<Inode> {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800115 validate_basename(&basename)?;
116 self.add_entry_at_ro_dir_by_path(ROOT_INODE, &basename, entry)
117 }
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800118
Victor Hsiehd18b9752021-11-09 16:03:34 -0800119 /// Add an `AuthFsEntry` by path from the `ReadonlyDirectory` represented by `dir_inode`. The
120 /// path must be a related path. If some ancestor directories do not exist, they will be
121 /// created (also as `ReadonlyDirectory`) automatically.
122 pub fn add_entry_at_ro_dir_by_path(
123 &mut self,
124 dir_inode: Inode,
125 path: &Path,
126 entry: AuthFsEntry,
127 ) -> Result<Inode> {
128 // 1. Make sure the parent directories all exist. Derive the entry's parent inode.
129 let parent_path =
130 path.parent().ok_or_else(|| anyhow!("No parent directory: {:?}", path))?;
131 let parent_inode =
132 parent_path.components().try_fold(dir_inode, |current_dir_inode, path_component| {
133 match path_component {
134 Component::RootDir => bail!("Absolute path is not supported"),
135 Component::Normal(name) => {
136 let inode_table = self.inode_table.get_mut().unwrap();
137 // Locate the internal directory structure.
138 let current_dir_entry =
139 inode_table.get_mut(&current_dir_inode).ok_or_else(|| {
140 anyhow!("Unknown directory inode {}", current_dir_inode)
141 })?;
142 let dir = match current_dir_entry {
143 AuthFsEntry::ReadonlyDirectory { dir } => dir,
144 _ => unreachable!("Not a ReadonlyDirectory"),
145 };
146 // Return directory inode. Create first if not exists.
147 if let Some(existing_inode) = dir.lookup_inode(name.as_ref()) {
148 Ok(existing_inode)
149 } else {
150 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
151 let new_dir_entry =
152 AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() };
153
154 // Actually update the tables.
155 dir.add_entry(name.as_ref(), new_inode)?;
156 if inode_table.insert(new_inode, new_dir_entry).is_some() {
157 bail!("Unexpected to find a duplicated inode");
158 }
159 Ok(new_inode)
160 }
161 }
162 _ => Err(anyhow!("Path is not canonical: {:?}", path)),
163 }
164 })?;
165
166 // 2. Insert the entry to the parent directory, as well as the inode table.
167 let inode_table = self.inode_table.get_mut().unwrap();
168 match inode_table.get_mut(&parent_inode).expect("previously returned inode") {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800169 AuthFsEntry::ReadonlyDirectory { dir } => {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800170 let basename =
171 path.file_name().ok_or_else(|| anyhow!("Bad file name: {:?}", path))?;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800172 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
173
Victor Hsiehd18b9752021-11-09 16:03:34 -0800174 // Actually update the tables.
175 dir.add_entry(basename.as_ref(), new_inode)?;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800176 if inode_table.insert(new_inode, entry).is_some() {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800177 bail!("Unexpected to find a duplicated inode");
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800178 }
179 Ok(new_inode)
180 }
Victor Hsiehd18b9752021-11-09 16:03:34 -0800181 _ => unreachable!("Not a ReadonlyDirectory"),
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800182 }
183 }
184}
185
186// Implementation for serving requests.
187impl AuthFs {
Victor Hsieh45636232021-10-15 17:52:51 -0700188 /// Handles the file associated with `inode` if found. This function returns whatever
189 /// `handle_fn` returns.
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700190 fn handle_inode<F, R>(&self, inode: &Inode, handle_fn: F) -> io::Result<R>
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700191 where
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700192 F: FnOnce(&AuthFsEntry) -> io::Result<R>,
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700193 {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700194 let inode_table = self.inode_table.lock().unwrap();
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800195 let entry =
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700196 inode_table.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800197 handle_fn(entry)
Victor Hsieh45636232021-10-15 17:52:51 -0700198 }
199
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800200 /// Adds a new entry `name` created by `create_fn` at `parent_inode`.
201 ///
202 /// The operation involves two updates: adding the name with a new allocated inode to the
203 /// parent directory, and insert the new inode and the actual `AuthFsEntry` to the global inode
204 /// table.
205 ///
206 /// `create_fn` receives the parent directory, through which it can create the new entry at and
207 /// register the new inode to. Its returned entry is then added to the inode table.
208 fn create_new_entry<F>(
209 &self,
210 parent_inode: Inode,
211 name: &CStr,
212 create_fn: F,
213 ) -> io::Result<Inode>
Victor Hsieh45636232021-10-15 17:52:51 -0700214 where
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800215 F: FnOnce(&mut AuthFsEntry, &Path, Inode) -> io::Result<AuthFsEntry>,
Victor Hsieh45636232021-10-15 17:52:51 -0700216 {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700217 let mut inode_table = self.inode_table.lock().unwrap();
Chris Wailes641fc4a2021-12-01 15:03:21 -0800218 let parent_entry = inode_table
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800219 .get_mut(&parent_inode)
220 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
221
222 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
223 let basename: &Path = cstr_to_path(name);
Chris Wailes641fc4a2021-12-01 15:03:21 -0800224 let new_file_entry = create_fn(parent_entry, basename, new_inode)?;
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700225 if let btree_map::Entry::Vacant(entry) = inode_table.entry(new_inode) {
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800226 entry.insert(new_file_entry);
Victor Hsieh45636232021-10-15 17:52:51 -0700227 Ok(new_inode)
228 } else {
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800229 unreachable!("Unexpected duplication of inode {}", new_inode);
Victor Hsieh45636232021-10-15 17:52:51 -0700230 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800231 }
232}
233
234fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
235 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
236 Ok(())
237 } else {
238 Err(io::Error::from_raw_os_error(libc::EACCES))
239 }
240}
241
242cfg_if::cfg_if! {
243 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800244 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800245 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800246 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800247 }
248}
249
Victor Hsieh45636232021-10-15 17:52:51 -0700250#[allow(clippy::enum_variant_names)]
251enum AccessMode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800252 ReadOnly,
253 ReadWrite,
254}
255
Victor Hsieh45636232021-10-15 17:52:51 -0700256fn create_stat(
257 ino: libc::ino_t,
258 file_size: u64,
259 access_mode: AccessMode,
260) -> io::Result<libc::stat64> {
261 // SAFETY: stat64 is a plan C struct without pointer.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800262 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
263
264 st.st_ino = ino;
Victor Hsieh45636232021-10-15 17:52:51 -0700265 st.st_mode = match access_mode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800266 // Until needed, let's just grant the owner access.
Victor Hsieh45636232021-10-15 17:52:51 -0700267 // TODO(205169366): Implement mode properly.
268 AccessMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
269 AccessMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800270 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800271 st.st_nlink = 1;
272 st.st_uid = 0;
273 st.st_gid = 0;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800274 st.st_size = libc::off64_t::try_from(file_size)
275 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
276 st.st_blksize = blk_size();
277 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
278 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
279 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
280 Ok(st)
281}
282
Victor Hsieh45636232021-10-15 17:52:51 -0700283fn create_dir_stat(ino: libc::ino_t, file_number: u16) -> io::Result<libc::stat64> {
284 // SAFETY: stat64 is a plan C struct without pointer.
285 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
286
287 st.st_ino = ino;
288 // TODO(205169366): Implement mode properly.
289 st.st_mode = libc::S_IFDIR
290 | libc::S_IXUSR
291 | libc::S_IWUSR
292 | libc::S_IRUSR
293 | libc::S_IXGRP
294 | libc::S_IXOTH;
295
296 // 2 extra for . and ..
297 st.st_nlink = file_number
298 .checked_add(2)
299 .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?
300 .into();
301
302 st.st_uid = 0;
303 st.st_gid = 0;
304 Ok(st)
305}
306
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800307fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800308 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800309}
310
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700311fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800312 mut w: W,
313 file: &T,
314 file_size: u64,
315 offset: u64,
316 size: u32,
317) -> io::Result<usize> {
318 let remaining = file_size.saturating_sub(offset);
319 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800320 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800321 0,
322 |total, (current_offset, planned_data_size)| {
323 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
324 // instead of accepting a buffer, the writer could expose the final destination buffer
325 // for the reader to write to. It might not be generally applicable though, e.g. with
326 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800327 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800328 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
329 if read_size < planned_data_size {
330 return Err(io::Error::from_raw_os_error(libc::ENODATA));
331 }
332
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800333 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800334 let end = begin + planned_data_size;
335 let s = w.write(&buf[begin..end])?;
336 if s != planned_data_size {
337 return Err(io::Error::from_raw_os_error(libc::EIO));
338 }
339 Ok(total + s)
340 },
341 )?;
342
343 Ok(total)
344}
345
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800346// TODO(205715172): Support enumerating directory entries.
347pub struct EmptyDirectoryIterator {}
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800348
349impl DirectoryIterator for EmptyDirectoryIterator {
350 fn next(&mut self) -> Option<DirEntry> {
351 None
352 }
353}
354
355impl FileSystem for AuthFs {
356 type Inode = Inode;
357 type Handle = Handle;
358 type DirIter = EmptyDirectoryIterator;
359
360 fn max_buffer_size(&self) -> u32 {
Victor Hsieh766e5332021-11-09 09:41:25 -0800361 MAX_WRITE_BYTES
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800362 }
363
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800364 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
365 // Enable writeback cache for better performance especially since our bandwidth to the
366 // backend service is limited.
367 Ok(FsOptions::WRITEBACK_CACHE)
368 }
369
Victor Hsieh45636232021-10-15 17:52:51 -0700370 fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800371 // Look up the entry's inode number in parent directory.
372 let inode = self.handle_inode(&parent, |parent_entry| match parent_entry {
373 AuthFsEntry::ReadonlyDirectory { dir } => {
374 let path = cstr_to_path(name);
375 dir.lookup_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
376 }
377 AuthFsEntry::VerifiedNewDirectory { dir } => {
378 let path = cstr_to_path(name);
379 dir.find_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
380 }
381 _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
382 })?;
383
384 // Normally, `lookup` is required to increase a reference count for the inode (while
385 // `forget` will decrease it). It is not yet necessary until we start to support
386 // deletion (only for `VerifiedNewDirectory`).
387
388 // Create the entry's stat if found.
389 let st = self.handle_inode(&inode, |entry| match entry {
Victor Hsiehd18b9752021-11-09 16:03:34 -0800390 AuthFsEntry::ReadonlyDirectory { dir } => {
391 create_dir_stat(inode, dir.number_of_entries())
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800392 }
393 AuthFsEntry::UnverifiedReadonly { file_size, .. }
394 | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
395 create_stat(inode, *file_size, AccessMode::ReadOnly)
396 }
397 AuthFsEntry::VerifiedNew { editor } => {
398 create_stat(inode, editor.size(), AccessMode::ReadWrite)
399 }
400 AuthFsEntry::VerifiedNewDirectory { dir } => {
401 create_dir_stat(inode, dir.number_of_entries())
402 }
403 })?;
404 Ok(Entry {
405 inode,
406 generation: 0,
407 attr: st,
408 entry_timeout: DEFAULT_METADATA_TIMEOUT,
409 attr_timeout: DEFAULT_METADATA_TIMEOUT,
410 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800411 }
412
413 fn getattr(
414 &self,
415 _ctx: Context,
416 inode: Inode,
417 _handle: Option<Handle>,
418 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700419 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700420 Ok((
421 match config {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800422 AuthFsEntry::ReadonlyDirectory { dir } => {
423 create_dir_stat(inode, dir.number_of_entries())
424 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700425 AuthFsEntry::UnverifiedReadonly { file_size, .. }
426 | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800427 create_stat(inode, *file_size, AccessMode::ReadOnly)
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700428 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700429 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800430 create_stat(inode, editor.size(), AccessMode::ReadWrite)
Victor Hsieh45636232021-10-15 17:52:51 -0700431 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700432 AuthFsEntry::VerifiedNewDirectory { dir } => {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800433 create_dir_stat(inode, dir.number_of_entries())
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700434 }
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800435 }?,
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700436 DEFAULT_METADATA_TIMEOUT,
437 ))
438 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800439 }
440
441 fn open(
442 &self,
443 _ctx: Context,
444 inode: Self::Inode,
445 flags: u32,
446 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
447 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800448 // return None as the handle.
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700449 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700450 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700451 AuthFsEntry::VerifiedReadonly { .. } | AuthFsEntry::UnverifiedReadonly { .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700452 check_access_mode(flags, libc::O_RDONLY)?;
453 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700454 AuthFsEntry::VerifiedNew { .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700455 // No need to check access modes since all the modes are allowed to the
456 // read-writable file.
457 }
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800458 AuthFsEntry::ReadonlyDirectory { .. }
459 | AuthFsEntry::VerifiedNewDirectory { .. } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700460 // TODO(victorhsieh): implement when needed.
461 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
462 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800463 }
Victor Hsieh45636232021-10-15 17:52:51 -0700464 // Always cache the file content. There is currently no need to support direct I/O or
465 // avoid the cache buffer. Memory mapping is only possible with cache enabled.
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700466 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
467 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800468 }
469
Victor Hsieh45636232021-10-15 17:52:51 -0700470 fn create(
471 &self,
472 _ctx: Context,
473 parent: Self::Inode,
474 name: &CStr,
475 _mode: u32,
476 _flags: u32,
477 _umask: u32,
478 ) -> io::Result<(Entry, Option<Self::Handle>, fuse::sys::OpenOptions)> {
479 // TODO(205169366): Implement mode properly.
480 // TODO(205172873): handle O_TRUNC and O_EXCL properly.
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800481 let new_inode =
482 self.create_new_entry(parent, name, |parent_entry, basename, new_inode| {
483 match parent_entry {
484 AuthFsEntry::VerifiedNewDirectory { dir } => {
485 if dir.find_inode(basename).is_some() {
486 return Err(io::Error::from_raw_os_error(libc::EEXIST));
487 }
488 let new_file = dir.create_file(basename, new_inode)?;
489 Ok(AuthFsEntry::VerifiedNew { editor: new_file })
490 }
491 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh45636232021-10-15 17:52:51 -0700492 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800493 })?;
Victor Hsieh45636232021-10-15 17:52:51 -0700494
495 Ok((
496 Entry {
497 inode: new_inode,
498 generation: 0,
499 attr: create_stat(new_inode, /* file_size */ 0, AccessMode::ReadWrite)?,
500 entry_timeout: DEFAULT_METADATA_TIMEOUT,
501 attr_timeout: DEFAULT_METADATA_TIMEOUT,
502 },
503 // See also `open`.
504 /* handle */ None,
505 fuse::sys::OpenOptions::KEEP_CACHE,
506 ))
507 }
508
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800509 fn read<W: io::Write + ZeroCopyWriter>(
510 &self,
511 _ctx: Context,
512 inode: Inode,
513 _handle: Handle,
514 w: W,
515 size: u32,
516 offset: u64,
517 _lock_owner: Option<u64>,
518 _flags: u32,
519 ) -> io::Result<usize> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700520 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700521 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700522 AuthFsEntry::VerifiedReadonly { reader, file_size } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700523 read_chunks(w, reader, *file_size, offset, size)
524 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700525 AuthFsEntry::UnverifiedReadonly { reader, file_size } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700526 read_chunks(w, reader, *file_size, offset, size)
527 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700528 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700529 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
530 // request a read even if the file is open with O_WRONLY.
531 read_chunks(w, editor, editor.size(), offset, size)
532 }
Victor Hsieh45636232021-10-15 17:52:51 -0700533 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800534 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700535 })
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800536 }
537
538 fn write<R: io::Read + ZeroCopyReader>(
539 &self,
540 _ctx: Context,
541 inode: Self::Inode,
542 _handle: Self::Handle,
543 mut r: R,
544 size: u32,
545 offset: u64,
546 _lock_owner: Option<u64>,
547 _delayed_write: bool,
548 _flags: u32,
549 ) -> io::Result<usize> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700550 self.handle_inode(&inode, |config| match config {
551 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800552 let mut buf = vec![0; size as usize];
553 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700554 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800555 }
556 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700557 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800558 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700559
560 fn setattr(
561 &self,
562 _ctx: Context,
563 inode: Inode,
564 attr: libc::stat64,
565 _handle: Option<Handle>,
566 valid: SetattrValid,
567 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700568 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700569 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700570 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700571 // Initialize the default stat.
Victor Hsieh45636232021-10-15 17:52:51 -0700572 let mut new_attr = create_stat(inode, editor.size(), AccessMode::ReadWrite)?;
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700573 // `valid` indicates what fields in `attr` are valid. Update to return correctly.
574 if valid.contains(SetattrValid::SIZE) {
575 // st_size is i64, but the cast should be safe since kernel should not give a
576 // negative size.
577 debug_assert!(attr.st_size >= 0);
578 new_attr.st_size = attr.st_size;
579 editor.resize(attr.st_size as u64)?;
580 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700581
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700582 if valid.contains(SetattrValid::MODE) {
583 warn!("Changing st_mode is not currently supported");
584 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
585 }
586 if valid.contains(SetattrValid::UID) {
587 warn!("Changing st_uid is not currently supported");
588 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
589 }
590 if valid.contains(SetattrValid::GID) {
591 warn!("Changing st_gid is not currently supported");
592 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
593 }
594 if valid.contains(SetattrValid::CTIME) {
595 debug!(
596 "Ignoring ctime change as authfs does not maintain timestamp currently"
597 );
598 }
599 if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
600 debug!(
601 "Ignoring atime change as authfs does not maintain timestamp currently"
602 );
603 }
604 if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
605 debug!(
606 "Ignoring mtime change as authfs does not maintain timestamp currently"
607 );
608 }
609 Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700610 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700611 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700612 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700613 })
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700614 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700615
616 fn getxattr(
617 &self,
618 _ctx: Context,
619 inode: Self::Inode,
620 name: &CStr,
621 size: u32,
622 ) -> io::Result<GetxattrReply> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700623 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700624 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700625 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700626 // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
627 // change (see b/196635431). Until it's possible, use xattr to expose what we need
628 // as an authfs specific API.
629 if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
630 return Err(io::Error::from_raw_os_error(libc::ENODATA));
631 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700632
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700633 if size == 0 {
634 // Per protocol, when size is 0, return the value size.
635 Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
Victor Hsieh71f10032021-08-13 11:24:02 -0700636 } else {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700637 let digest = editor.calculate_fsverity_digest()?;
638 if digest.len() > size as usize {
639 Err(io::Error::from_raw_os_error(libc::ERANGE))
640 } else {
641 Ok(GetxattrReply::Value(digest.to_vec()))
642 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700643 }
644 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700645 _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
Victor Hsieh71f10032021-08-13 11:24:02 -0700646 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700647 })
Victor Hsieh71f10032021-08-13 11:24:02 -0700648 }
Victor Hsieh45636232021-10-15 17:52:51 -0700649
650 fn mkdir(
651 &self,
652 _ctx: Context,
653 parent: Self::Inode,
654 name: &CStr,
655 _mode: u32,
656 _umask: u32,
657 ) -> io::Result<Entry> {
658 // TODO(205169366): Implement mode properly.
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800659 let new_inode =
660 self.create_new_entry(parent, name, |parent_entry, basename, new_inode| {
661 match parent_entry {
662 AuthFsEntry::VerifiedNewDirectory { dir } => {
663 if dir.find_inode(basename).is_some() {
664 return Err(io::Error::from_raw_os_error(libc::EEXIST));
665 }
666 let new_dir = dir.mkdir(basename, new_inode)?;
667 Ok(AuthFsEntry::VerifiedNewDirectory { dir: new_dir })
668 }
Victor Hsiehd18b9752021-11-09 16:03:34 -0800669 AuthFsEntry::ReadonlyDirectory { .. } => {
670 Err(io::Error::from_raw_os_error(libc::EACCES))
671 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800672 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh45636232021-10-15 17:52:51 -0700673 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800674 })?;
Victor Hsieh45636232021-10-15 17:52:51 -0700675
676 Ok(Entry {
677 inode: new_inode,
678 generation: 0,
679 attr: create_dir_stat(new_inode, /* file_number */ 0)?,
680 entry_timeout: DEFAULT_METADATA_TIMEOUT,
681 attr_timeout: DEFAULT_METADATA_TIMEOUT,
682 })
683 }
Victor Hsiehf7fc3d32021-11-22 10:20:33 -0800684
685 fn statfs(&self, _ctx: Context, _inode: Self::Inode) -> io::Result<libc::statvfs64> {
686 let remote_stat = self.remote_fs_stats_reader.statfs()?;
687
688 // Safe because we are zero-initializing a struct with only POD fields. Not all fields
689 // matter to FUSE. See also:
690 // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/fuse/inode.c?h=v5.15#n460
691 let mut st: libc::statvfs64 = unsafe { zeroed() };
692
693 // Use the remote stat as a template, since it'd matter the most to consider the writable
694 // files/directories that are written to the remote.
695 st.f_bsize = remote_stat.block_size;
696 st.f_frsize = remote_stat.fragment_size;
697 st.f_blocks = remote_stat.block_numbers;
698 st.f_bavail = remote_stat.block_available;
699 st.f_favail = remote_stat.inodes_available;
700 st.f_namemax = remote_stat.max_filename;
701 // Assuming we are not privileged to use all free spaces on the remote server, set the free
702 // blocks/fragment to the same available amount.
703 st.f_bfree = st.f_bavail;
704 st.f_ffree = st.f_favail;
705 // Number of inodes on the filesystem
706 st.f_files = self.inode_table.lock().unwrap().len() as u64;
707
708 Ok(st)
709 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800710}
711
712/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
713pub fn loop_forever(
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800714 authfs: AuthFs,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800715 mountpoint: &Path,
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700716 extra_options: &Option<String>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800717) -> Result<(), fuse::Error> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800718 let dev_fuse = OpenOptions::new()
719 .read(true)
720 .write(true)
721 .open("/dev/fuse")
722 .expect("Failed to open /dev/fuse");
723
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700724 let mut mount_options = vec![
725 MountOption::FD(dev_fuse.as_raw_fd()),
726 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
727 MountOption::AllowOther,
728 MountOption::UserId(0),
729 MountOption::GroupId(0),
Victor Hsieh766e5332021-11-09 09:41:25 -0800730 MountOption::MaxRead(MAX_READ_BYTES),
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700731 ];
732 if let Some(value) = extra_options {
733 mount_options.push(MountOption::Extra(value));
734 }
735
736 fuse::mount(mountpoint, "authfs", libc::MS_NOSUID | libc::MS_NODEV, &mount_options)
737 .expect("Failed to mount fuse");
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800738
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800739 fuse::worker::start_message_loop(dev_fuse, MAX_WRITE_BYTES, MAX_READ_BYTES, authfs)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800740}
Victor Hsieh45636232021-10-15 17:52:51 -0700741
742fn cstr_to_path(cstr: &CStr) -> &Path {
743 OsStr::from_bytes(cstr.to_bytes()).as_ref()
744}