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