blob: 0cd8b68177daac47c52ccc4e3ab4efdd759e87c8 [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;
18use std::collections::BTreeMap;
19use std::convert::TryFrom;
20use std::ffi::CStr;
21use std::fs::OpenOptions;
22use std::io;
23use std::mem::MaybeUninit;
24use std::option::Option;
25use std::os::unix::io::AsRawFd;
26use std::path::Path;
27use std::time::Duration;
28
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080029use fuse::filesystem::{
30 Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, ZeroCopyReader,
31 ZeroCopyWriter,
32};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080033use fuse::mount::MountOption;
34
Victor Hsiehac4f3f42021-02-26 12:35:58 -080035use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080036use crate::file::{
Victor Hsiehd0bb5d32021-03-19 12:48:03 -070037 LocalFileReader, RandomWrite, ReadByChunk, RemoteFileEditor, RemoteFileReader,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080038 RemoteMerkleTreeReader,
39};
40use crate::fsverity::{VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080041
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080042const DEFAULT_METADATA_TIMEOUT: std::time::Duration = Duration::from_secs(5);
43
44pub type Inode = u64;
45type Handle = u64;
46
Victor Hsieh1bcf4112021-03-19 14:26:57 -070047/// `FileConfig` defines the file type supported by AuthFS.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080048pub enum FileConfig {
Victor Hsieh1bcf4112021-03-19 14:26:57 -070049 /// A file type that is verified against fs-verity signature (thus read-only). The file is
50 /// backed by a local file. Debug only.
51 LocalVerifiedReadonlyFile {
52 reader: VerifiedFileReader<LocalFileReader, LocalFileReader>,
53 file_size: u64,
54 },
55 /// A file type that is a read-only passthrough from a local file. Debug only.
56 LocalUnverifiedReadonlyFile { reader: LocalFileReader, file_size: u64 },
57 /// A file type that is verified against fs-verity signature (thus read-only). The file is
58 /// served from a remote server.
59 RemoteVerifiedReadonlyFile {
60 reader: VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>,
61 file_size: u64,
62 },
63 /// A file type that is a read-only passthrough from a file on a remote serrver.
64 RemoteUnverifiedReadonlyFile { reader: RemoteFileReader, file_size: u64 },
65 /// A file type that is initially empty, and the content is stored on a remote server. File
66 /// integrity is guaranteed with private Merkle tree.
67 RemoteVerifiedNewFile { editor: VerifiedFileEditor<RemoteFileEditor> },
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080068}
69
70struct AuthFs {
71 /// Store `FileConfig`s using the `Inode` number as the search index.
72 ///
73 /// For further optimization to minimize the search cost, since Inode is integer, we may
74 /// consider storing them in a Vec if we can guarantee that the numbers are small and
75 /// consecutive.
76 file_pool: BTreeMap<Inode, FileConfig>,
77
78 /// Maximum bytes in the write transaction to the FUSE device. This limits the maximum size to
79 /// a read request (including FUSE protocol overhead).
80 max_write: u32,
81}
82
83impl AuthFs {
84 pub fn new(file_pool: BTreeMap<Inode, FileConfig>, max_write: u32) -> AuthFs {
85 AuthFs { file_pool, max_write }
86 }
87
88 fn get_file_config(&self, inode: &Inode) -> io::Result<&FileConfig> {
89 self.file_pool.get(&inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
90 }
91}
92
93fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
94 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
95 Ok(())
96 } else {
97 Err(io::Error::from_raw_os_error(libc::EACCES))
98 }
99}
100
101cfg_if::cfg_if! {
102 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800103 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800104 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800105 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800106 }
107}
108
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800109enum FileMode {
110 ReadOnly,
111 ReadWrite,
112}
113
114fn create_stat(ino: libc::ino_t, file_size: u64, file_mode: FileMode) -> io::Result<libc::stat64> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800115 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
116
117 st.st_ino = ino;
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800118 st.st_mode = match file_mode {
119 // Until needed, let's just grant the owner access.
120 FileMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
121 FileMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
122 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800123 st.st_dev = 0;
124 st.st_nlink = 1;
125 st.st_uid = 0;
126 st.st_gid = 0;
127 st.st_rdev = 0;
128 st.st_size = libc::off64_t::try_from(file_size)
129 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
130 st.st_blksize = blk_size();
131 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
132 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
133 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
134 Ok(st)
135}
136
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800137fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800138 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800139}
140
Victor Hsiehd0bb5d32021-03-19 12:48:03 -0700141fn read_chunks<W: io::Write, T: ReadByChunk>(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800142 mut w: W,
143 file: &T,
144 file_size: u64,
145 offset: u64,
146 size: u32,
147) -> io::Result<usize> {
148 let remaining = file_size.saturating_sub(offset);
149 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800150 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800151 0,
152 |total, (current_offset, planned_data_size)| {
153 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
154 // instead of accepting a buffer, the writer could expose the final destination buffer
155 // for the reader to write to. It might not be generally applicable though, e.g. with
156 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800157 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800158 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
159 if read_size < planned_data_size {
160 return Err(io::Error::from_raw_os_error(libc::ENODATA));
161 }
162
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800163 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800164 let end = begin + planned_data_size;
165 let s = w.write(&buf[begin..end])?;
166 if s != planned_data_size {
167 return Err(io::Error::from_raw_os_error(libc::EIO));
168 }
169 Ok(total + s)
170 },
171 )?;
172
173 Ok(total)
174}
175
176// No need to support enumerating directory entries.
177struct EmptyDirectoryIterator {}
178
179impl DirectoryIterator for EmptyDirectoryIterator {
180 fn next(&mut self) -> Option<DirEntry> {
181 None
182 }
183}
184
185impl FileSystem for AuthFs {
186 type Inode = Inode;
187 type Handle = Handle;
188 type DirIter = EmptyDirectoryIterator;
189
190 fn max_buffer_size(&self) -> u32 {
191 self.max_write
192 }
193
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800194 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
195 // Enable writeback cache for better performance especially since our bandwidth to the
196 // backend service is limited.
197 Ok(FsOptions::WRITEBACK_CACHE)
198 }
199
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800200 fn lookup(&self, _ctx: Context, _parent: Inode, name: &CStr) -> io::Result<Entry> {
201 // Only accept file name that looks like an integrer. Files in the pool are simply exposed
202 // by their inode number. Also, there is currently no directory structure.
203 let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
204 // Normally, `lookup` is required to increase a reference count for the inode (while
205 // `forget` will decrease it). It is not necessary here since the files are configured to
206 // be static.
207 let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
208 let st = match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700209 FileConfig::LocalVerifiedReadonlyFile { file_size, .. }
210 | FileConfig::LocalUnverifiedReadonlyFile { file_size, .. }
211 | FileConfig::RemoteUnverifiedReadonlyFile { file_size, .. }
212 | FileConfig::RemoteVerifiedReadonlyFile { file_size, .. } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800213 create_stat(inode, *file_size, FileMode::ReadOnly)?
214 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700215 FileConfig::RemoteVerifiedNewFile { editor } => {
216 create_stat(inode, editor.size(), FileMode::ReadWrite)?
Victor Hsieh09e26262021-03-03 16:00:55 -0800217 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800218 };
219 Ok(Entry {
220 inode,
221 generation: 0,
222 attr: st,
223 entry_timeout: DEFAULT_METADATA_TIMEOUT,
224 attr_timeout: DEFAULT_METADATA_TIMEOUT,
225 })
226 }
227
228 fn getattr(
229 &self,
230 _ctx: Context,
231 inode: Inode,
232 _handle: Option<Handle>,
233 ) -> io::Result<(libc::stat64, Duration)> {
234 Ok((
235 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700236 FileConfig::LocalVerifiedReadonlyFile { file_size, .. }
237 | FileConfig::LocalUnverifiedReadonlyFile { file_size, .. }
238 | FileConfig::RemoteUnverifiedReadonlyFile { file_size, .. }
239 | FileConfig::RemoteVerifiedReadonlyFile { file_size, .. } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800240 create_stat(inode, *file_size, FileMode::ReadOnly)?
241 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700242 FileConfig::RemoteVerifiedNewFile { editor } => {
243 create_stat(inode, editor.size(), FileMode::ReadWrite)?
Victor Hsieh09e26262021-03-03 16:00:55 -0800244 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800245 },
246 DEFAULT_METADATA_TIMEOUT,
247 ))
248 }
249
250 fn open(
251 &self,
252 _ctx: Context,
253 inode: Self::Inode,
254 flags: u32,
255 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
256 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800257 // return None as the handle.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800258 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700259 FileConfig::LocalVerifiedReadonlyFile { .. }
Victor Hsieh8e161b52021-04-26 17:47:19 -0700260 | FileConfig::LocalUnverifiedReadonlyFile { .. }
261 | FileConfig::RemoteVerifiedReadonlyFile { .. }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700262 | FileConfig::RemoteUnverifiedReadonlyFile { .. } => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800263 check_access_mode(flags, libc::O_RDONLY)?;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800264 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700265 FileConfig::RemoteVerifiedNewFile { .. } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800266 // No need to check access modes since all the modes are allowed to the
267 // read-writable file.
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800268 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800269 }
Victor Hsieh8e161b52021-04-26 17:47:19 -0700270 // Always cache the file content. There is currently no need to support direct I/O or avoid
271 // the cache buffer. Memory mapping is only possible with cache enabled.
272 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800273 }
274
275 fn read<W: io::Write + ZeroCopyWriter>(
276 &self,
277 _ctx: Context,
278 inode: Inode,
279 _handle: Handle,
280 w: W,
281 size: u32,
282 offset: u64,
283 _lock_owner: Option<u64>,
284 _flags: u32,
285 ) -> io::Result<usize> {
286 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700287 FileConfig::LocalVerifiedReadonlyFile { reader, file_size } => {
288 read_chunks(w, reader, *file_size, offset, size)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800289 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700290 FileConfig::LocalUnverifiedReadonlyFile { reader, file_size } => {
291 read_chunks(w, reader, *file_size, offset, size)
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800292 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700293 FileConfig::RemoteVerifiedReadonlyFile { reader, file_size } => {
294 read_chunks(w, reader, *file_size, offset, size)
Victor Hsiehf01f3232020-12-11 13:31:31 -0800295 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700296 FileConfig::RemoteUnverifiedReadonlyFile { reader, file_size } => {
297 read_chunks(w, reader, *file_size, offset, size)
Victor Hsiehf01f3232020-12-11 13:31:31 -0800298 }
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700299 FileConfig::RemoteVerifiedNewFile { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800300 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
301 // request a read even if the file is open with O_WRONLY.
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700302 read_chunks(w, editor, editor.size(), offset, size)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800303 }
304 }
305 }
306
307 fn write<R: io::Read + ZeroCopyReader>(
308 &self,
309 _ctx: Context,
310 inode: Self::Inode,
311 _handle: Self::Handle,
312 mut r: R,
313 size: u32,
314 offset: u64,
315 _lock_owner: Option<u64>,
316 _delayed_write: bool,
317 _flags: u32,
318 ) -> io::Result<usize> {
319 match self.get_file_config(&inode)? {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700320 FileConfig::RemoteVerifiedNewFile { editor } => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800321 let mut buf = vec![0; size as usize];
322 r.read_exact(&mut buf)?;
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700323 editor.write_at(&buf, offset)
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800324 }
325 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800326 }
327 }
328}
329
330/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
331pub fn loop_forever(
332 file_pool: BTreeMap<Inode, FileConfig>,
333 mountpoint: &Path,
334) -> Result<(), fuse::Error> {
335 let max_read: u32 = 65536;
336 let max_write: u32 = 65536;
337 let dev_fuse = OpenOptions::new()
338 .read(true)
339 .write(true)
340 .open("/dev/fuse")
341 .expect("Failed to open /dev/fuse");
342
343 fuse::mount(
344 mountpoint,
345 "authfs",
346 libc::MS_NOSUID | libc::MS_NODEV,
347 &[
348 MountOption::FD(dev_fuse.as_raw_fd()),
349 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
350 MountOption::AllowOther,
351 MountOption::UserId(0),
352 MountOption::GroupId(0),
353 MountOption::MaxRead(max_read),
354 ],
355 )
356 .expect("Failed to mount fuse");
357
358 fuse::worker::start_message_loop(
359 dev_fuse,
360 max_write,
361 max_read,
362 AuthFs::new(file_pool, max_write),
363 )
364}