blob: bbcb84f359e488782eaa5814a6f9e2eb57cddf35 [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
29use fuse::filesystem::{Context, DirEntry, DirectoryIterator, Entry, FileSystem, ZeroCopyWriter};
30use fuse::mount::MountOption;
31
Victor Hsiehac4f3f42021-02-26 12:35:58 -080032use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080033use crate::fsverity::FsverityChunkedFileReader;
34use crate::reader::{ChunkedFileReader, ReadOnlyDataByChunk};
Victor Hsiehf01f3232020-12-11 13:31:31 -080035use crate::remote_file::{RemoteChunkedFileReader, RemoteFsverityMerkleTreeReader};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080036
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080037const DEFAULT_METADATA_TIMEOUT: std::time::Duration = Duration::from_secs(5);
38
39pub type Inode = u64;
40type Handle = u64;
41
Victor Hsiehf01f3232020-12-11 13:31:31 -080042type RemoteFsverityChunkedFileReader =
43 FsverityChunkedFileReader<RemoteChunkedFileReader, RemoteFsverityMerkleTreeReader>;
44
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080045// A debug only type where everything are stored as local files.
46type FileBackedFsverityChunkedFileReader =
47 FsverityChunkedFileReader<ChunkedFileReader, ChunkedFileReader>;
48
49pub enum FileConfig {
50 LocalVerifiedFile(FileBackedFsverityChunkedFileReader, u64),
51 LocalUnverifiedFile(ChunkedFileReader, u64),
Victor Hsiehf01f3232020-12-11 13:31:31 -080052 RemoteVerifiedFile(RemoteFsverityChunkedFileReader, u64),
53 RemoteUnverifiedFile(RemoteChunkedFileReader, u64),
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080054}
55
56struct AuthFs {
57 /// Store `FileConfig`s using the `Inode` number as the search index.
58 ///
59 /// For further optimization to minimize the search cost, since Inode is integer, we may
60 /// consider storing them in a Vec if we can guarantee that the numbers are small and
61 /// consecutive.
62 file_pool: BTreeMap<Inode, FileConfig>,
63
64 /// Maximum bytes in the write transaction to the FUSE device. This limits the maximum size to
65 /// a read request (including FUSE protocol overhead).
66 max_write: u32,
67}
68
69impl AuthFs {
70 pub fn new(file_pool: BTreeMap<Inode, FileConfig>, max_write: u32) -> AuthFs {
71 AuthFs { file_pool, max_write }
72 }
73
74 fn get_file_config(&self, inode: &Inode) -> io::Result<&FileConfig> {
75 self.file_pool.get(&inode).ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))
76 }
77}
78
79fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
80 if (flags & libc::O_ACCMODE as u32) == mode as u32 {
81 Ok(())
82 } else {
83 Err(io::Error::from_raw_os_error(libc::EACCES))
84 }
85}
86
87cfg_if::cfg_if! {
88 if #[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] {
Victor Hsiehda3fbc42021-02-23 16:12:49 -080089 fn blk_size() -> libc::c_int { CHUNK_SIZE as libc::c_int }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080090 } else {
Victor Hsiehda3fbc42021-02-23 16:12:49 -080091 fn blk_size() -> libc::c_long { CHUNK_SIZE as libc::c_long }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080092 }
93}
94
95fn create_stat(ino: libc::ino_t, file_size: u64) -> io::Result<libc::stat64> {
96 let mut st = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
97
98 st.st_ino = ino;
99 st.st_mode = libc::S_IFREG | libc::S_IRUSR | libc::S_IRGRP | libc::S_IROTH;
100 st.st_dev = 0;
101 st.st_nlink = 1;
102 st.st_uid = 0;
103 st.st_gid = 0;
104 st.st_rdev = 0;
105 st.st_size = libc::off64_t::try_from(file_size)
106 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
107 st.st_blksize = blk_size();
108 // Per man stat(2), st_blocks is "Number of 512B blocks allocated".
109 st.st_blocks = libc::c_longlong::try_from(divide_roundup(file_size, 512))
110 .map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))?;
111 Ok(st)
112}
113
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800114fn offset_to_chunk_index(offset: u64) -> u64 {
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800115 offset / CHUNK_SIZE
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800116}
117
118fn read_chunks<W: io::Write, T: ReadOnlyDataByChunk>(
119 mut w: W,
120 file: &T,
121 file_size: u64,
122 offset: u64,
123 size: u32,
124) -> io::Result<usize> {
125 let remaining = file_size.saturating_sub(offset);
126 let size_to_read = std::cmp::min(size as usize, remaining as usize);
Victor Hsiehac4f3f42021-02-26 12:35:58 -0800127 let total = ChunkedSizeIter::new(size_to_read, offset, CHUNK_SIZE as usize).try_fold(
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800128 0,
129 |total, (current_offset, planned_data_size)| {
130 // TODO(victorhsieh): There might be a non-trivial way to avoid this copy. For example,
131 // instead of accepting a buffer, the writer could expose the final destination buffer
132 // for the reader to write to. It might not be generally applicable though, e.g. with
133 // virtio transport, the buffer may not be continuous.
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800134 let mut buf = [0u8; CHUNK_SIZE as usize];
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800135 let read_size = file.read_chunk(offset_to_chunk_index(current_offset), &mut buf)?;
136 if read_size < planned_data_size {
137 return Err(io::Error::from_raw_os_error(libc::ENODATA));
138 }
139
Victor Hsiehda3fbc42021-02-23 16:12:49 -0800140 let begin = (current_offset % CHUNK_SIZE) as usize;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800141 let end = begin + planned_data_size;
142 let s = w.write(&buf[begin..end])?;
143 if s != planned_data_size {
144 return Err(io::Error::from_raw_os_error(libc::EIO));
145 }
146 Ok(total + s)
147 },
148 )?;
149
150 Ok(total)
151}
152
153// No need to support enumerating directory entries.
154struct EmptyDirectoryIterator {}
155
156impl DirectoryIterator for EmptyDirectoryIterator {
157 fn next(&mut self) -> Option<DirEntry> {
158 None
159 }
160}
161
162impl FileSystem for AuthFs {
163 type Inode = Inode;
164 type Handle = Handle;
165 type DirIter = EmptyDirectoryIterator;
166
167 fn max_buffer_size(&self) -> u32 {
168 self.max_write
169 }
170
171 fn lookup(&self, _ctx: Context, _parent: Inode, name: &CStr) -> io::Result<Entry> {
172 // Only accept file name that looks like an integrer. Files in the pool are simply exposed
173 // by their inode number. Also, there is currently no directory structure.
174 let num = name.to_str().map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
175 // Normally, `lookup` is required to increase a reference count for the inode (while
176 // `forget` will decrease it). It is not necessary here since the files are configured to
177 // be static.
178 let inode = num.parse::<Inode>().map_err(|_| io::Error::from_raw_os_error(libc::ENOENT))?;
179 let st = match self.get_file_config(&inode)? {
180 FileConfig::LocalVerifiedFile(_, file_size)
Victor Hsiehf01f3232020-12-11 13:31:31 -0800181 | FileConfig::LocalUnverifiedFile(_, file_size)
182 | FileConfig::RemoteUnverifiedFile(_, file_size)
183 | FileConfig::RemoteVerifiedFile(_, file_size) => create_stat(inode, *file_size)?,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800184 };
185 Ok(Entry {
186 inode,
187 generation: 0,
188 attr: st,
189 entry_timeout: DEFAULT_METADATA_TIMEOUT,
190 attr_timeout: DEFAULT_METADATA_TIMEOUT,
191 })
192 }
193
194 fn getattr(
195 &self,
196 _ctx: Context,
197 inode: Inode,
198 _handle: Option<Handle>,
199 ) -> io::Result<(libc::stat64, Duration)> {
200 Ok((
201 match self.get_file_config(&inode)? {
202 FileConfig::LocalVerifiedFile(_, file_size)
Victor Hsiehf01f3232020-12-11 13:31:31 -0800203 | FileConfig::LocalUnverifiedFile(_, file_size)
204 | FileConfig::RemoteUnverifiedFile(_, file_size)
205 | FileConfig::RemoteVerifiedFile(_, file_size) => create_stat(inode, *file_size)?,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800206 },
207 DEFAULT_METADATA_TIMEOUT,
208 ))
209 }
210
211 fn open(
212 &self,
213 _ctx: Context,
214 inode: Self::Inode,
215 flags: u32,
216 ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
217 // Since file handle is not really used in later operations (which use Inode directly),
218 // return None as the handle..
219 match self.get_file_config(&inode)? {
Victor Hsiehf01f3232020-12-11 13:31:31 -0800220 FileConfig::LocalVerifiedFile(_, _) | FileConfig::RemoteVerifiedFile(_, _) => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800221 check_access_mode(flags, libc::O_RDONLY)?;
222 // Once verified, and only if verified, the file content can be cached. This is not
223 // really needed for a local file, but is the behavior of RemoteVerifiedFile later.
224 Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
225 }
Victor Hsiehf01f3232020-12-11 13:31:31 -0800226 FileConfig::LocalUnverifiedFile(_, _) | FileConfig::RemoteUnverifiedFile(_, _) => {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800227 check_access_mode(flags, libc::O_RDONLY)?;
228 // Do not cache the content. This type of file is supposed to be verified using
229 // dm-verity. The filesystem mount over dm-verity already is already cached, so use
230 // direct I/O here to avoid double cache.
231 Ok((None, fuse::sys::OpenOptions::DIRECT_IO))
232 }
233 }
234 }
235
236 fn read<W: io::Write + ZeroCopyWriter>(
237 &self,
238 _ctx: Context,
239 inode: Inode,
240 _handle: Handle,
241 w: W,
242 size: u32,
243 offset: u64,
244 _lock_owner: Option<u64>,
245 _flags: u32,
246 ) -> io::Result<usize> {
247 match self.get_file_config(&inode)? {
248 FileConfig::LocalVerifiedFile(file, file_size) => {
249 read_chunks(w, file, *file_size, offset, size)
250 }
251 FileConfig::LocalUnverifiedFile(file, file_size) => {
252 read_chunks(w, file, *file_size, offset, size)
253 }
Victor Hsiehf01f3232020-12-11 13:31:31 -0800254 FileConfig::RemoteVerifiedFile(file, file_size) => {
255 read_chunks(w, file, *file_size, offset, size)
256 }
257 FileConfig::RemoteUnverifiedFile(file, file_size) => {
258 read_chunks(w, file, *file_size, offset, size)
259 }
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800260 }
261 }
262}
263
264/// Mount and start the FUSE instance. This requires CAP_SYS_ADMIN.
265pub fn loop_forever(
266 file_pool: BTreeMap<Inode, FileConfig>,
267 mountpoint: &Path,
268) -> Result<(), fuse::Error> {
269 let max_read: u32 = 65536;
270 let max_write: u32 = 65536;
271 let dev_fuse = OpenOptions::new()
272 .read(true)
273 .write(true)
274 .open("/dev/fuse")
275 .expect("Failed to open /dev/fuse");
276
277 fuse::mount(
278 mountpoint,
279 "authfs",
280 libc::MS_NOSUID | libc::MS_NODEV,
281 &[
282 MountOption::FD(dev_fuse.as_raw_fd()),
283 MountOption::RootMode(libc::S_IFDIR | libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH),
284 MountOption::AllowOther,
285 MountOption::UserId(0),
286 MountOption::GroupId(0),
287 MountOption::MaxRead(max_read),
288 ],
289 )
290 .expect("Failed to mount fuse");
291
292 fuse::worker::start_message_loop(
293 dev_fuse,
294 max_write,
295 max_read,
296 AuthFs::new(file_pool, max_write),
297 )
298}