authfs: add readers for chunked data am: 1fe51c473a
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Virtualization/+/1491137
Change-Id: Ie38ccd64d48c8745d7ee194367a39cc36f39bd0f
diff --git a/authfs/Android.bp b/authfs/Android.bp
new file mode 100644
index 0000000..3edd800
--- /dev/null
+++ b/authfs/Android.bp
@@ -0,0 +1,31 @@
+rust_defaults {
+ name: "authfs_defaults",
+ crate_name: "authfs",
+ srcs: [
+ "src/lib.rs",
+ ],
+ edition: "2018",
+ rustlibs: [
+ "liblibc",
+ ],
+ host_supported: true,
+ clippy_lints: "android",
+}
+
+rust_library {
+ name: "libauthfs",
+ defaults: ["authfs_defaults"],
+}
+
+rust_test_host {
+ name: "authfs_host_test_src_lib",
+ test_suites: ["general-tests"],
+ defaults: ["authfs_defaults"],
+}
+
+// TODO(victorhsieh): Enable the test once "undefined symbol: _Unwind_Resume" is fixed, then add to
+// TEST_MAPPING.
+//rust_test {
+// name: "authfs_device_test_src_lib",
+// defaults: ["authfs_defaults"],
+//}
diff --git a/authfs/TEST_MAPPING b/authfs/TEST_MAPPING
new file mode 100644
index 0000000..9e1505d
--- /dev/null
+++ b/authfs/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+ "presubmit": [
+ {
+ "name": "authfs_host_test_src_lib",
+ "host": true
+ }
+ ]
+}
diff --git a/authfs/src/lib.rs b/authfs/src/lib.rs
new file mode 100644
index 0000000..6b136a4
--- /dev/null
+++ b/authfs/src/lib.rs
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! This crate provides a FUSE-based, non-generic filesystem that I/O is authenticated. This
+//! filesystem assumes the storage layer is not trusted, e.g. file is provided by an untrusted VM,
+//! and the content can't be simply trusted. The filesystem can use its public key to verify a
+//! (read-only) file against its associated fs-verity signature by a trusted party. With the Merkle
+//! tree, each read of file block can be verified individually.
+//!
+//! The implementation is not finished.
+
+mod reader;
diff --git a/authfs/src/reader.rs b/authfs/src/reader.rs
new file mode 100644
index 0000000..135a793
--- /dev/null
+++ b/authfs/src/reader.rs
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A module for reading data by chunks.
+
+use std::fs::File;
+use std::io::Result;
+use std::os::unix::fs::FileExt;
+use std::path::Path;
+
+/// A trait for reading data by chunks. The data is assumed readonly and has fixed length. Chunks
+/// can be read by specifying the chunk index. Only the last chunk may have incomplete chunk size.
+pub trait ReadOnlyDataByChunk {
+ /// Default chunk size.
+ const CHUNK_SIZE: u64 = 4096;
+
+ /// Read the `chunk_index`-th chunk to `buf`. Each slice/chunk has size `CHUNK_SIZE` except for
+ /// the last one, which can be an incomplete chunk. `buf` is currently required to be large
+ /// enough to hold a full chunk of data. Reading beyond the file size (including empty file)
+ /// will crash.
+ fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize>;
+}
+
+fn chunk_index_to_range(size: u64, chunk_size: u64, chunk_index: u64) -> Result<(u64, u64)> {
+ let start = chunk_index * chunk_size;
+ assert!(start < size);
+ let end = std::cmp::min(size, start + chunk_size);
+ Ok((start, end))
+}
+
+/// A read-only file that can be read by chunks.
+pub struct ChunkedFileReader {
+ file: File,
+ size: u64,
+}
+
+impl ChunkedFileReader {
+ /// Creates a `ChunkedFileReader` to read from for the specified `path`.
+ #[allow(dead_code)]
+ pub fn new<P: AsRef<Path>>(path: P) -> Result<ChunkedFileReader> {
+ let file = File::open(path)?;
+ let size = file.metadata()?.len();
+ Ok(ChunkedFileReader { file, size })
+ }
+}
+
+impl ReadOnlyDataByChunk for ChunkedFileReader {
+ fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
+ debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
+ let (start, end) = chunk_index_to_range(self.size, Self::CHUNK_SIZE, chunk_index)?;
+ let size = (end - start) as usize;
+ self.file.read_at(&mut buf[..size], start)
+ }
+}
+
+impl ReadOnlyDataByChunk for &[u8] {
+ fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
+ debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
+ let chunk = &self.chunks(Self::CHUNK_SIZE as usize).nth(chunk_index as usize).unwrap();
+ buf[..chunk.len()].copy_from_slice(&chunk);
+ Ok(chunk.len())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn test_reading_more_than_4kb_data<T: ReadOnlyDataByChunk>(
+ reader: T,
+ data_size: u64,
+ ) -> Result<()> {
+ let mut buf = [0u8; 4096];
+ assert_eq!(reader.read_chunk(0, &mut buf)?, 4096);
+ let last_index = (data_size + 4095) / 4096 - 1;
+ assert_eq!(reader.read_chunk(last_index, &mut buf)?, (data_size % 4096) as usize);
+ Ok(())
+ }
+
+ // TODO(victorhsieh): test ChunkedFileReader once there is a way to access testdata in the test
+ // environement.
+
+ #[test]
+ fn test_read_in_memory_data() -> Result<()> {
+ let data = &[1u8; 5000][..];
+ test_reading_more_than_4kb_data(data, data.len() as u64)
+ }
+
+ #[test]
+ #[should_panic]
+ #[allow(unused_must_use)]
+ fn test_read_in_memory_empty_data() {
+ let data = &[][..]; // zero length slice
+ let mut buf = [0u8; 4096];
+ data.read_chunk(0, &mut buf); // should panic
+ }
+
+ #[test]
+ #[should_panic]
+ #[allow(unused_must_use)]
+ fn test_read_beyond_file_size() {
+ let data = &[1u8; 5000][..];
+ let mut buf = [0u8; 4096];
+ let last_index_plus_1 = (data.len() + 4095) / 4096;
+ data.read_chunk(last_index_plus_1 as u64, &mut buf); // should panic
+ }
+}