blob: 13ec87d9b0f21b2f3ac078e0b0d1b0f31ed38f2d [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::{
37 LocalFileReader, RandomWrite, ReadOnlyDataByChunk, RemoteFileEditor, RemoteFileReader,
38 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 Hsieh88ac6ca2020-11-13 15:20:24 -080047pub enum FileConfig {
Victor Hsieh09e26262021-03-03 16:00:55 -080048 LocalVerifiedReadonlyFile(VerifiedFileReader<LocalFileReader, LocalFileReader>, u64),
49 LocalUnverifiedReadonlyFile(LocalFileReader, u64),
50 RemoteVerifiedReadonlyFile(VerifiedFileReader<RemoteFileReader, RemoteMerkleTreeReader>, u64),
51 RemoteUnverifiedReadonlyFile(RemoteFileReader, u64),
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080052 RemoteVerifiedNewFile(VerifiedFileEditor<RemoteFileEditor>),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080053}
54
55struct AuthFs {
56 /// Store `FileConfig`s using the `Inode` number as the search index.
57 ///
58 /// For further optimization to minimize the search cost, since Inode is integer, we may
59 /// consider storing them in a Vec if we can guarantee that the numbers are small and
60 /// consecutive.
61 file_pool: BTreeMap<Inode, FileConfig>,
62
63 /// Maximum bytes in the write transaction to the FUSE device. This limits the maximum size to
64 /// a read request (including FUSE protocol overhead).
65 max_write: u32,
66}
67
68impl AuthFs {
69 pub fn new(file_pool: BTreeMap<Inode, FileConfig>, max_write: u32) -> AuthFs {
70 AuthFs { file_pool, max_write }
71 }
72
73 fn get_file_config(&self, inode: &Inode) -> io::Result<&FileConfig> {
74 self.file_pool.get(&inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
75 }
76}
77
78fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
79 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
80 Ok(())
81 } else {
82 Err(io::Error::from_raw_os_error(libc::EACCES))
83 }
84}
85
86cfg_if::cfg_if! {
87 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -080088 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080089 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -080090 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080091 }
92}
93
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080094enum FileMode {
95 ReadOnly,
96 ReadWrite,
97}
98
99fn create_stat(ino: libc::ino_t, file_size: u64, file_mode: FileMode) -> io::Result<libc::stat64> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800100 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
101
102 st.st_ino = ino;
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800103 st.st_mode = match file_mode {
104 // Until needed, let's just grant the owner access.
105 FileMode::ReadOnly => libc::S_IFREG | libc::S_IRUSR,
106 FileMode::ReadWrite => libc::S_IFREG | libc::S_IRUSR | libc::S_IWUSR,
107 };
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800108 st.st_dev = 0;
109 st.st_nlink = 1;
110 st.st_uid = 0;
111 st.st_gid = 0;
112 st.st_rdev = 0;
113 st.st_size = libc::off64_t::try_from(file_size)
114 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
115 st.st_blksize = blk_size();
116 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
117 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
118 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
119 Ok(st)
120}
121
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800122fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800123 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800124}
125
126fn read_chunks<W: io::Write, T: ReadOnlyDataByChunk>(
127 mut w: W,
128 file: &T,
129 file_size: u64,
130 offset: u64,
131 size: u32,
132) -> io::Result<usize> {
133 let remaining = file_size.saturating_sub(offset);
134 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800135 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800136 0,
137 |total, (current_offset, planned_data_size)| {
138 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
139 // instead of accepting a buffer, the writer could expose the final destination buffer
140 // for the reader to write to. It might not be generally applicable though, e.g. with
141 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800142 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800143 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
144 if read_size < planned_data_size {
145 return Err(io::Error::from_raw_os_error(libc::ENODATA));
146 }
147
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800148 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800149 let end = begin + planned_data_size;
150 let s = w.write(&buf[begin..end])?;
151 if s != planned_data_size {
152 return Err(io::Error::from_raw_os_error(libc::EIO));
153 }
154 Ok(total + s)
155 },
156 )?;
157
158 Ok(total)
159}
160
161// No need to support enumerating directory entries.
162struct EmptyDirectoryIterator {}
163
164impl DirectoryIterator for EmptyDirectoryIterator {
165 fn next(&mut self) -> Option<DirEntry> {
166 None
167 }
168}
169
170impl FileSystem for AuthFs {
171 type Inode = Inode;
172 type Handle = Handle;
173 type DirIter = EmptyDirectoryIterator;
174
175 fn max_buffer_size(&self) -> u32 {
176 self.max_write
177 }
178
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800179 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
180 // Enable writeback cache for better performance especially since our bandwidth to the
181 // backend service is limited.
182 Ok(FsOptions::WRITEBACK_CACHE)
183 }
184
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800185 fn lookup(&self, _ctx: Context, _parent: Inode, name: &CStr) -> io::Result<Entry> {
186 // Only accept file name that looks like an integrer. Files in the pool are simply exposed
187 // by their inode number. Also, there is currently no directory structure.
188 let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
189 // Normally, `lookup` is required to increase a reference count for the inode (while
190 // `forget` will decrease it). It is not necessary here since the files are configured to
191 // be static.
192 let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
193 let st = match self.get_file_config(&inode)? {
Victor Hsieh09e26262021-03-03 16:00:55 -0800194 FileConfig::LocalVerifiedReadonlyFile(_, file_size)
195 | FileConfig::LocalUnverifiedReadonlyFile(_, file_size)
196 | FileConfig::RemoteUnverifiedReadonlyFile(_, file_size)
197 | FileConfig::RemoteVerifiedReadonlyFile(_, file_size) => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800198 create_stat(inode, *file_size, FileMode::ReadOnly)?
199 }
200 FileConfig::RemoteVerifiedNewFile(file) => {
201 create_stat(inode, file.size(), FileMode::ReadWrite)?
Victor Hsieh09e26262021-03-03 16:00:55 -0800202 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800203 };
204 Ok(Entry {
205 inode,
206 generation: 0,
207 attr: st,
208 entry_timeout: DEFAULT_METADATA_TIMEOUT,
209 attr_timeout: DEFAULT_METADATA_TIMEOUT,
210 })
211 }
212
213 fn getattr(
214 &self,
215 _ctx: Context,
216 inode: Inode,
217 _handle: Option<Handle>,
218 ) -> io::Result<(libc::stat64, Duration)> {
219 Ok((
220 match self.get_file_config(&inode)? {
Victor Hsieh09e26262021-03-03 16:00:55 -0800221 FileConfig::LocalVerifiedReadonlyFile(_, file_size)
222 | FileConfig::LocalUnverifiedReadonlyFile(_, file_size)
223 | FileConfig::RemoteUnverifiedReadonlyFile(_, file_size)
224 | FileConfig::RemoteVerifiedReadonlyFile(_, file_size) => {
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800225 create_stat(inode, *file_size, FileMode::ReadOnly)?
226 }
227 FileConfig::RemoteVerifiedNewFile(file) => {
228 create_stat(inode, file.size(), FileMode::ReadWrite)?
Victor Hsieh09e26262021-03-03 16:00:55 -0800229 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800230 },
231 DEFAULT_METADATA_TIMEOUT,
232 ))
233 }
234
235 fn open(
236 &self,
237 _ctx: Context,
238 inode: Self::Inode,
239 flags: u32,
240 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
241 // Since file handle is not really used in later operations (which use Inode directly),
Victor Hsieh09e26262021-03-03 16:00:55 -0800242 // return None as the handle.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800243 match self.get_file_config(&inode)? {
Victor Hsieh09e26262021-03-03 16:00:55 -0800244 FileConfig::LocalVerifiedReadonlyFile(_, _)
245 | FileConfig::RemoteVerifiedReadonlyFile(_, _) => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800246 check_access_mode(flags, libc::O_RDONLY)?;
247 // Once verified, and only if verified, the file content can be cached. This is not
Victor Hsieh09e26262021-03-03 16:00:55 -0800248 // really needed for a local file, but is the behavior of RemoteVerifiedReadonlyFile
249 // later.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800250 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
251 }
Victor Hsieh09e26262021-03-03 16:00:55 -0800252 FileConfig::LocalUnverifiedReadonlyFile(_, _)
253 | FileConfig::RemoteUnverifiedReadonlyFile(_, _) => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800254 check_access_mode(flags, libc::O_RDONLY)?;
255 // Do not cache the content. This type of file is supposed to be verified using
256 // dm-verity. The filesystem mount over dm-verity already is already cached, so use
257 // direct I/O here to avoid double cache.
258 Ok((None, fuse::sys::OpenOptions::DIRECT_IO))
259 }
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800260 FileConfig::RemoteVerifiedNewFile(_) => {
261 // No need to check access modes since all the modes are allowed to the
262 // read-writable file.
263 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
264 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800265 }
266 }
267
268 fn read<W: io::Write + ZeroCopyWriter>(
269 &self,
270 _ctx: Context,
271 inode: Inode,
272 _handle: Handle,
273 w: W,
274 size: u32,
275 offset: u64,
276 _lock_owner: Option<u64>,
277 _flags: u32,
278 ) -> io::Result<usize> {
279 match self.get_file_config(&inode)? {
Victor Hsieh09e26262021-03-03 16:00:55 -0800280 FileConfig::LocalVerifiedReadonlyFile(file, file_size) => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800281 read_chunks(w, file, *file_size, offset, size)
282 }
Victor Hsieh09e26262021-03-03 16:00:55 -0800283 FileConfig::LocalUnverifiedReadonlyFile(file, file_size) => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800284 read_chunks(w, file, *file_size, offset, size)
285 }
Victor Hsieh09e26262021-03-03 16:00:55 -0800286 FileConfig::RemoteVerifiedReadonlyFile(file, file_size) => {
Victor Hsiehf01f3232020-12-11 13:31:31 -0800287 read_chunks(w, file, *file_size, offset, size)
288 }
Victor Hsieh09e26262021-03-03 16:00:55 -0800289 FileConfig::RemoteUnverifiedReadonlyFile(file, file_size) => {
Victor Hsiehf01f3232020-12-11 13:31:31 -0800290 read_chunks(w, file, *file_size, offset, size)
291 }
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800292 FileConfig::RemoteVerifiedNewFile(file) => {
293 // Note that with FsOptions::WRITEBACK_CACHE, it's possible for the kernel to
294 // request a read even if the file is open with O_WRONLY.
295 read_chunks(w, file, file.size(), offset, size)
296 }
297 }
298 }
299
300 fn write<R: io::Read + ZeroCopyReader>(
301 &self,
302 _ctx: Context,
303 inode: Self::Inode,
304 _handle: Self::Handle,
305 mut r: R,
306 size: u32,
307 offset: u64,
308 _lock_owner: Option<u64>,
309 _delayed_write: bool,
310 _flags: u32,
311 ) -> io::Result<usize> {
312 match self.get_file_config(&inode)? {
313 FileConfig::RemoteVerifiedNewFile(file) => {
314 let mut buf = vec![0; size as usize];
315 r.read_exact(&mut buf)?;
316 file.write_at(&buf, offset)
317 }
318 _ => Err(io::Error::from_raw_os_error(libc::EBADF)),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800319 }
320 }
321}
322
323/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
324pub fn loop_forever(
325 file_pool: BTreeMap<Inode, FileConfig>,
326 mountpoint: &Path,
327) -> Result<(), fuse::Error> {
328 let max_read: u32 = 65536;
329 let max_write: u32 = 65536;
330 let dev_fuse = OpenOptions::new()
331 .read(true)
332 .write(true)
333 .open("/dev/fuse")
334 .expect("Failed to open /dev/fuse");
335
336 fuse::mount(
337 mountpoint,
338 "authfs",
339 libc::MS_NOSUID | libc::MS_NODEV,
340 &[
341 MountOption::FD(dev_fuse.as_raw_fd()),
342 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
343 MountOption::AllowOther,
344 MountOption::UserId(0),
345 MountOption::GroupId(0),
346 MountOption::MaxRead(max_read),
347 ],
348 )
349 .expect("Failed to mount fuse");
350
351 fuse::worker::start_message_loop(
352 dev_fuse,
353 max_write,
354 max_read,
355 AuthFs::new(file_pool, max_write),
356 )
357}