blob: 3d1e2c75c929e4b485768539ddff6ee00cbe0b47 [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
17use anyhow::Result;
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080018use log::{debug, warn};
Victor Hsieh60c2f412021-11-03 13:02:19 -070019use std::collections::{btree_map, BTreeMap, HashMap};
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 Hsieh60c2f412021-11-03 13:02:19 -070027use std::path::{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 Hsieh45636232021-10-15 17:52:51 -070040 RandomWrite, ReadByChunk, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
41 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 Hsieh1bcf4112021-03-19 14:26:57 -070061 /// A file type that is verified against fs-verity signature (thus read-only). The file is
Victor Hsieh1bcf4112021-03-19 14:26:57 -070062 /// served from a remote server.
Victor Hsieh88e50172021-10-15 13:27:13 -070063 VerifiedReadonly {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070064 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
65 file_size: u64,
66 },
67 /// A file type that is a read-only passthrough from a file on a remote serrver.
Victor Hsieh88e50172021-10-15 13:27:13 -070068 UnverifiedReadonly { reader: RemoteFileReader, file_size: u64 },
Victor Hsieh1bcf4112021-03-19 14:26:57 -070069 /// A file type that is initially empty, and the content is stored on a remote server. File
70 /// integrity is guaranteed with private Merkle tree.
Victor Hsieh88e50172021-10-15 13:27:13 -070071 VerifiedNew { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh45636232021-10-15 17:52:51 -070072 /// A directory type that is initially empty. One can create new file (`VerifiedNew`) and new
73 /// directory (`VerifiedNewDirectory` itself) with integrity guaranteed within the VM.
74 VerifiedNewDirectory { dir: RemoteDirEditor },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080075}
76
Victor Hsieh60c2f412021-11-03 13:02:19 -070077// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080078struct AuthFs {
Victor Hsieh60c2f412021-11-03 13:02:19 -070079 /// Table for `Inode` to `AuthFsEntry` lookup. This needs to be `Sync` to be used in
80 /// `fuse::worker::start_message_loop`.
81 inode_table: Mutex<BTreeMap<Inode, AuthFsEntry>>,
82
83 /// Root directory entry table for path to `Inode` lookup. The root directory content should
84 /// remain constant throughout the filesystem's lifetime.
85 root_entries: HashMap<PathBuf, Inode>,
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -080086
87 /// The next available inode number.
88 next_inode: AtomicU64,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080089}
90
91impl AuthFs {
Victor Hsieh766e5332021-11-09 09:41:25 -080092 pub fn new(root_entries_by_path: HashMap<PathBuf, AuthFsEntry>) -> AuthFs {
Victor Hsieh60c2f412021-11-03 13:02:19 -070093 let mut next_inode = ROOT_INODE + 1;
94 let mut inode_table = BTreeMap::new();
95 let mut root_entries = HashMap::new();
96
97 root_entries_by_path.into_iter().for_each(|(path_buf, entry)| {
Victor Hsieh60c2f412021-11-03 13:02:19 -070098 root_entries.insert(path_buf, next_inode);
99 inode_table.insert(next_inode, entry);
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800100 next_inode += 1;
Victor Hsieh60c2f412021-11-03 13:02:19 -0700101 });
102
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800103 AuthFs {
104 inode_table: Mutex::new(inode_table),
105 root_entries,
106 next_inode: AtomicU64::new(next_inode),
107 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800108 }
109
Victor Hsieh45636232021-10-15 17:52:51 -0700110 /// Handles the file associated with `inode` if found. This function returns whatever
111 /// `handle_fn` returns.
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700112 fn handle_inode<F, R>(&self, inode: &Inode, handle_fn: F) -> io::Result<R>
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700113 where
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700114 F: FnOnce(&AuthFsEntry) -> io::Result<R>,
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700115 {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700116 let inode_table = self.inode_table.lock().unwrap();
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800117 let entry =
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700118 inode_table.get(inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800119 handle_fn(entry)
Victor Hsieh45636232021-10-15 17:52:51 -0700120 }
121
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800122 /// Adds a new entry `name` created by `create_fn` at `parent_inode`.
123 ///
124 /// The operation involves two updates: adding the name with a new allocated inode to the
125 /// parent directory, and insert the new inode and the actual `AuthFsEntry` to the global inode
126 /// table.
127 ///
128 /// `create_fn` receives the parent directory, through which it can create the new entry at and
129 /// register the new inode to. Its returned entry is then added to the inode table.
130 fn create_new_entry<F>(
131 &self,
132 parent_inode: Inode,
133 name: &CStr,
134 create_fn: F,
135 ) -> io::Result<Inode>
Victor Hsieh45636232021-10-15 17:52:51 -0700136 where
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800137 F: FnOnce(&mut AuthFsEntry, &Path, Inode) -> io::Result<AuthFsEntry>,
Victor Hsieh45636232021-10-15 17:52:51 -0700138 {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700139 let mut inode_table = self.inode_table.lock().unwrap();
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800140 let mut parent_entry = inode_table
141 .get_mut(&parent_inode)
142 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
143
144 let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
145 let basename: &Path = cstr_to_path(name);
146 let new_file_entry = create_fn(&mut parent_entry, basename, new_inode)?;
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700147 if let btree_map::Entry::Vacant(entry) = inode_table.entry(new_inode) {
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800148 entry.insert(new_file_entry);
Victor Hsieh45636232021-10-15 17:52:51 -0700149 Ok(new_inode)
150 } else {
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800151 unreachable!("Unexpected duplication of inode {}", new_inode);
Victor Hsieh45636232021-10-15 17:52:51 -0700152 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800153 }
154}
155
156fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
157 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
158 Ok(())
159 } else {
160 Err(io::Error::from_raw_os_error(libc::EACCES))
161 }
162}
163
164cfg_if::cfg_if! {
165 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800166 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800167 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800168 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800169 }
170}
171
Victor Hsieh45636232021-10-15 17:52:51 -0700172#[allow(clippy::enum_variant_names)]
173enum AccessMode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800174 ReadOnly,
175 ReadWrite,
176}
177
Victor Hsieh45636232021-10-15 17:52:51 -0700178fn create_stat(
179 ino: libc::ino_t,
180 file_size: u64,
181 access_mode: AccessMode,
182) -> io::Result<libc::stat64> {
183 // SAFETY: stat64 is a plan C struct without pointer.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800184 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
185
186 st.st_ino = ino;
Victor Hsieh45636232021-10-15 17:52:51 -0700187 st.st_mode = match access_mode {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800188 // Until needed, let's just grant the owner access.
Victor Hsieh45636232021-10-15 17:52:51 -0700189 // TODO(205169366): Implement mode properly.
190 AccessMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
191 AccessMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800192 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800193 st.st_nlink = 1;
194 st.st_uid = 0;
195 st.st_gid = 0;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800196 st.st_size = libc::off64_t::try_from(file_size)
197 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
198 st.st_blksize = blk_size();
199 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
200 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
201 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
202 Ok(st)
203}
204
Victor Hsieh45636232021-10-15 17:52:51 -0700205fn create_dir_stat(ino: libc::ino_t, file_number: u16) -> io::Result<libc::stat64> {
206 // SAFETY: stat64 is a plan C struct without pointer.
207 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
208
209 st.st_ino = ino;
210 // TODO(205169366): Implement mode properly.
211 st.st_mode = libc::S_IFDIR
212 | libc::S_IXUSR
213 | libc::S_IWUSR
214 | libc::S_IRUSR
215 | libc::S_IXGRP
216 | libc::S_IXOTH;
217
218 // 2 extra for . and ..
219 st.st_nlink = file_number
220 .checked_add(2)
221 .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?
222 .into();
223
224 st.st_uid = 0;
225 st.st_gid = 0;
226 Ok(st)
227}
228
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800229fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800230 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800231}
232
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700233fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800234 mut w: W,
235 file: &T,
236 file_size: u64,
237 offset: u64,
238 size: u32,
239) -> io::Result<usize> {
240 let remaining = file_size.saturating_sub(offset);
241 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800242 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800243 0,
244 |total, (current_offset, planned_data_size)| {
245 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
246 // instead of accepting a buffer, the writer could expose the final destination buffer
247 // for the reader to write to. It might not be generally applicable though, e.g. with
248 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800249 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800250 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
251 if read_size < planned_data_size {
252 return Err(io::Error::from_raw_os_error(libc::ENODATA));
253 }
254
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800255 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800256 let end = begin + planned_data_size;
257 let s = w.write(&buf[begin..end])?;
258 if s != planned_data_size {
259 return Err(io::Error::from_raw_os_error(libc::EIO));
260 }
261 Ok(total + s)
262 },
263 )?;
264
265 Ok(total)
266}
267
268// No need to support enumerating directory entries.
269struct EmptyDirectoryIterator {}
270
271impl DirectoryIterator for EmptyDirectoryIterator {
272 fn next(&mut self) -> Option<DirEntry> {
273 None
274 }
275}
276
277impl FileSystem for AuthFs {
278 type Inode = Inode;
279 type Handle = Handle;
280 type DirIter = EmptyDirectoryIterator;
281
282 fn max_buffer_size(&self) -> u32 {
Victor Hsieh766e5332021-11-09 09:41:25 -0800283 MAX_WRITE_BYTES
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800284 }
285
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800286 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
287 // Enable writeback cache for better performance especially since our bandwidth to the
288 // backend service is limited.
289 Ok(FsOptions::WRITEBACK_CACHE)
290 }
291
Victor Hsieh45636232021-10-15 17:52:51 -0700292 fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700293 if parent == ROOT_INODE {
Victor Hsieh60c2f412021-11-03 13:02:19 -0700294 let inode = *self
295 .root_entries
296 .get(cstr_to_path(name))
297 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?;
Victor Hsieh45636232021-10-15 17:52:51 -0700298 // Normally, `lookup` is required to increase a reference count for the inode (while
Victor Hsieh60c2f412021-11-03 13:02:19 -0700299 // `forget` will decrease it). It is not yet necessary until we start to support
300 // deletion (only for `VerifiedNewDirectory`).
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700301 let st = self.handle_inode(&inode, |config| match config {
302 AuthFsEntry::UnverifiedReadonly { file_size, .. }
303 | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700304 create_stat(inode, *file_size, AccessMode::ReadOnly)
305 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700306 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700307 create_stat(inode, editor.size(), AccessMode::ReadWrite)
308 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700309 AuthFsEntry::VerifiedNewDirectory { dir } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700310 create_dir_stat(inode, dir.number_of_entries())
311 }
312 })?;
313 Ok(Entry {
314 inode,
315 generation: 0,
316 attr: st,
317 entry_timeout: DEFAULT_METADATA_TIMEOUT,
318 attr_timeout: DEFAULT_METADATA_TIMEOUT,
319 })
320 } else {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700321 let inode = self.handle_inode(&parent, |config| match config {
322 AuthFsEntry::VerifiedNewDirectory { dir } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700323 let path: &Path = cstr_to_path(name);
324 dir.find_inode(path).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
325 }
326 _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
327 })?;
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700328 let st = self.handle_inode(&inode, |config| match config {
329 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700330 create_stat(inode, editor.size(), AccessMode::ReadWrite)
331 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700332 AuthFsEntry::VerifiedNewDirectory { dir } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700333 create_dir_stat(inode, dir.number_of_entries())
334 }
335 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
336 })?;
337 Ok(Entry {
338 inode,
339 generation: 0,
340 attr: st,
341 entry_timeout: DEFAULT_METADATA_TIMEOUT,
342 attr_timeout: DEFAULT_METADATA_TIMEOUT,
343 })
344 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800345 }
346
347 fn getattr(
348 &self,
349 _ctx: Context,
350 inode: Inode,
351 _handle: Option<Handle>,
352 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700353 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700354 Ok((
355 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700356 AuthFsEntry::UnverifiedReadonly { file_size, .. }
357 | AuthFsEntry::VerifiedReadonly { file_size, .. } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700358 create_stat(inode, *file_size, AccessMode::ReadOnly)?
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700359 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700360 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700361 create_stat(inode, editor.size(), AccessMode::ReadWrite)?
362 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700363 AuthFsEntry::VerifiedNewDirectory { dir } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700364 create_dir_stat(inode, dir.number_of_entries())?
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700365 }
366 },
367 DEFAULT_METADATA_TIMEOUT,
368 ))
369 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800370 }
371
372 fn open(
373 &self,
374 _ctx: Context,
375 inode: Self::Inode,
376 flags: u32,
377 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
378 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800379 // return None as the handle.
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700380 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700381 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700382 AuthFsEntry::VerifiedReadonly { .. } | AuthFsEntry::UnverifiedReadonly { .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700383 check_access_mode(flags, libc::O_RDONLY)?;
384 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700385 AuthFsEntry::VerifiedNew { .. } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700386 // No need to check access modes since all the modes are allowed to the
387 // read-writable file.
388 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700389 AuthFsEntry::VerifiedNewDirectory { .. } => {
Victor Hsieh45636232021-10-15 17:52:51 -0700390 // TODO(victorhsieh): implement when needed.
391 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
392 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800393 }
Victor Hsieh45636232021-10-15 17:52:51 -0700394 // Always cache the file content. There is currently no need to support direct I/O or
395 // avoid the cache buffer. Memory mapping is only possible with cache enabled.
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700396 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
397 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800398 }
399
Victor Hsieh45636232021-10-15 17:52:51 -0700400 fn create(
401 &self,
402 _ctx: Context,
403 parent: Self::Inode,
404 name: &CStr,
405 _mode: u32,
406 _flags: u32,
407 _umask: u32,
408 ) -> io::Result<(Entry, Option<Self::Handle>, fuse::sys::OpenOptions)> {
409 // TODO(205169366): Implement mode properly.
410 // TODO(205172873): handle O_TRUNC and O_EXCL properly.
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800411 let new_inode =
412 self.create_new_entry(parent, name, |parent_entry, basename, new_inode| {
413 match parent_entry {
414 AuthFsEntry::VerifiedNewDirectory { dir } => {
415 if dir.find_inode(basename).is_some() {
416 return Err(io::Error::from_raw_os_error(libc::EEXIST));
417 }
418 let new_file = dir.create_file(basename, new_inode)?;
419 Ok(AuthFsEntry::VerifiedNew { editor: new_file })
420 }
421 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh45636232021-10-15 17:52:51 -0700422 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800423 })?;
Victor Hsieh45636232021-10-15 17:52:51 -0700424
425 Ok((
426 Entry {
427 inode: new_inode,
428 generation: 0,
429 attr: create_stat(new_inode, /* file_size */ 0, AccessMode::ReadWrite)?,
430 entry_timeout: DEFAULT_METADATA_TIMEOUT,
431 attr_timeout: DEFAULT_METADATA_TIMEOUT,
432 },
433 // See also `open`.
434 /* handle */ None,
435 fuse::sys::OpenOptions::KEEP_CACHE,
436 ))
437 }
438
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800439 fn read<W: io::Write + ZeroCopyWriter>(
440 &self,
441 _ctx: Context,
442 inode: Inode,
443 _handle: Handle,
444 w: W,
445 size: u32,
446 offset: u64,
447 _lock_owner: Option<u64>,
448 _flags: u32,
449 ) -> io::Result<usize> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700450 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700451 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700452 AuthFsEntry::VerifiedReadonly { reader, file_size } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700453 read_chunks(w, reader, *file_size, offset, size)
454 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700455 AuthFsEntry::UnverifiedReadonly { reader, file_size } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700456 read_chunks(w, reader, *file_size, offset, size)
457 }
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700458 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700459 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
460 // request a read even if the file is open with O_WRONLY.
461 read_chunks(w, editor, editor.size(), offset, size)
462 }
Victor Hsieh45636232021-10-15 17:52:51 -0700463 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800464 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700465 })
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800466 }
467
468 fn write<R: io::Read + ZeroCopyReader>(
469 &self,
470 _ctx: Context,
471 inode: Self::Inode,
472 _handle: Self::Handle,
473 mut r: R,
474 size: u32,
475 offset: u64,
476 _lock_owner: Option<u64>,
477 _delayed_write: bool,
478 _flags: u32,
479 ) -> io::Result<usize> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700480 self.handle_inode(&inode, |config| match config {
481 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800482 let mut buf = vec![0; size as usize];
483 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700484 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800485 }
486 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700487 })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800488 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700489
490 fn setattr(
491 &self,
492 _ctx: Context,
493 inode: Inode,
494 attr: libc::stat64,
495 _handle: Option<Handle>,
496 valid: SetattrValid,
497 ) -> io::Result<(libc::stat64, Duration)> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700498 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700499 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700500 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700501 // Initialize the default stat.
Victor Hsieh45636232021-10-15 17:52:51 -0700502 let mut new_attr = create_stat(inode, editor.size(), AccessMode::ReadWrite)?;
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700503 // `valid` indicates what fields in `attr` are valid. Update to return correctly.
504 if valid.contains(SetattrValid::SIZE) {
505 // st_size is i64, but the cast should be safe since kernel should not give a
506 // negative size.
507 debug_assert!(attr.st_size >= 0);
508 new_attr.st_size = attr.st_size;
509 editor.resize(attr.st_size as u64)?;
510 }
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700511
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700512 if valid.contains(SetattrValid::MODE) {
513 warn!("Changing st_mode is not currently supported");
514 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
515 }
516 if valid.contains(SetattrValid::UID) {
517 warn!("Changing st_uid is not currently supported");
518 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
519 }
520 if valid.contains(SetattrValid::GID) {
521 warn!("Changing st_gid is not currently supported");
522 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
523 }
524 if valid.contains(SetattrValid::CTIME) {
525 debug!(
526 "Ignoring ctime change as authfs does not maintain timestamp currently"
527 );
528 }
529 if valid.intersects(SetattrValid::ATIME | SetattrValid::ATIME_NOW) {
530 debug!(
531 "Ignoring atime change as authfs does not maintain timestamp currently"
532 );
533 }
534 if valid.intersects(SetattrValid::MTIME | SetattrValid::MTIME_NOW) {
535 debug!(
536 "Ignoring mtime change as authfs does not maintain timestamp currently"
537 );
538 }
539 Ok((new_attr, DEFAULT_METADATA_TIMEOUT))
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700540 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700541 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700542 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700543 })
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700544 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700545
546 fn getxattr(
547 &self,
548 _ctx: Context,
549 inode: Self::Inode,
550 name: &CStr,
551 size: u32,
552 ) -> io::Result<GetxattrReply> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700553 self.handle_inode(&inode, |config| {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700554 match config {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700555 AuthFsEntry::VerifiedNew { editor } => {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700556 // FUSE ioctl is limited, thus we can't implement fs-verity ioctls without a kernel
557 // change (see b/196635431). Until it's possible, use xattr to expose what we need
558 // as an authfs specific API.
559 if name != CStr::from_bytes_with_nul(b"authfs.fsverity.digest\0").unwrap() {
560 return Err(io::Error::from_raw_os_error(libc::ENODATA));
561 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700562
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700563 if size == 0 {
564 // Per protocol, when size is 0, return the value size.
565 Ok(GetxattrReply::Count(editor.get_fsverity_digest_size() as u32))
Victor Hsieh71f10032021-08-13 11:24:02 -0700566 } else {
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700567 let digest = editor.calculate_fsverity_digest()?;
568 if digest.len() > size as usize {
569 Err(io::Error::from_raw_os_error(libc::ERANGE))
570 } else {
571 Ok(GetxattrReply::Value(digest.to_vec()))
572 }
Victor Hsieh71f10032021-08-13 11:24:02 -0700573 }
574 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700575 _ => Err(io::Error::from_raw_os_error(libc::ENODATA)),
Victor Hsieh71f10032021-08-13 11:24:02 -0700576 }
Victor Hsiehc85e4ef2021-10-18 15:28:53 -0700577 })
Victor Hsieh71f10032021-08-13 11:24:02 -0700578 }
Victor Hsieh45636232021-10-15 17:52:51 -0700579
580 fn mkdir(
581 &self,
582 _ctx: Context,
583 parent: Self::Inode,
584 name: &CStr,
585 _mode: u32,
586 _umask: u32,
587 ) -> io::Result<Entry> {
588 // TODO(205169366): Implement mode properly.
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800589 let new_inode =
590 self.create_new_entry(parent, name, |parent_entry, basename, new_inode| {
591 match parent_entry {
592 AuthFsEntry::VerifiedNewDirectory { dir } => {
593 if dir.find_inode(basename).is_some() {
594 return Err(io::Error::from_raw_os_error(libc::EEXIST));
595 }
596 let new_dir = dir.mkdir(basename, new_inode)?;
597 Ok(AuthFsEntry::VerifiedNewDirectory { dir: new_dir })
598 }
599 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh45636232021-10-15 17:52:51 -0700600 }
Victor Hsiehd5a5b1e2021-11-09 11:42:34 -0800601 })?;
Victor Hsieh45636232021-10-15 17:52:51 -0700602
603 Ok(Entry {
604 inode: new_inode,
605 generation: 0,
606 attr: create_dir_stat(new_inode, /* file_number */ 0)?,
607 entry_timeout: DEFAULT_METADATA_TIMEOUT,
608 attr_timeout: DEFAULT_METADATA_TIMEOUT,
609 })
610 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800611}
612
613/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
614pub fn loop_forever(
Victor Hsieh60c2f412021-11-03 13:02:19 -0700615 root_entries: HashMap<PathBuf, AuthFsEntry>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800616 mountpoint: &Path,
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700617 extra_options: &Option<String>,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800618) -> Result<(), fuse::Error> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800619 let dev_fuse = OpenOptions::new()
620 .read(true)
621 .write(true)
622 .open("/dev/fuse")
623 .expect("Failed to open /dev/fuse");
624
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700625 let mut mount_options = vec![
626 MountOption::FD(dev_fuse.as_raw_fd()),
627 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
628 MountOption::AllowOther,
629 MountOption::UserId(0),
630 MountOption::GroupId(0),
Victor Hsieh766e5332021-11-09 09:41:25 -0800631 MountOption::MaxRead(MAX_READ_BYTES),
Victor Hsieh4cc3b792021-08-04 12:00:04 -0700632 ];
633 if let Some(value) = extra_options {
634 mount_options.push(MountOption::Extra(value));
635 }
636
637 fuse::mount(mountpoint, "authfs", libc::MS_NOSUID | libc::MS_NODEV, &mount_options)
638 .expect("Failed to mount fuse");
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800639
640 fuse::worker::start_message_loop(
641 dev_fuse,
Victor Hsieh766e5332021-11-09 09:41:25 -0800642 MAX_WRITE_BYTES,
643 MAX_READ_BYTES,
644 AuthFs::new(root_entries),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800645 )
646}
Victor Hsieh45636232021-10-15 17:52:51 -0700647
648fn cstr_to_path(cstr: &CStr) -> &Path {
649 OsStr::from_bytes(cstr.to_bytes()).as_ref()
650}