blob: d365a419024007c6ffe16e8b2cbe2a34d659784c [file] [log] [blame]
Victor Hsieh1fe51c42020-11-05 11:08:10 -08001/*
2 * Copyright (C) 2020 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
17//! A module for reading data by chunks.
18
19use std::fs::File;
20use std::io::Result;
21use std::os::unix::fs::FileExt;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080022
23use crate::common::COMMON_PAGE_SIZE;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080024
25/// A trait for reading data by chunks. The data is assumed readonly and has fixed length. Chunks
26/// can be read by specifying the chunk index. Only the last chunk may have incomplete chunk size.
27pub trait ReadOnlyDataByChunk {
28 /// Default chunk size.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080029 const CHUNK_SIZE: u64 = COMMON_PAGE_SIZE;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080030
31 /// Read the `chunk_index`-th chunk to `buf`. Each slice/chunk has size `CHUNK_SIZE` except for
32 /// the last one, which can be an incomplete chunk. `buf` is currently required to be large
33 /// enough to hold a full chunk of data. Reading beyond the file size (including empty file)
34 /// will crash.
35 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize>;
36}
37
38fn chunk_index_to_range(size: u64, chunk_size: u64, chunk_index: u64) -> Result<(u64, u64)> {
39 let start = chunk_index * chunk_size;
40 assert!(start < size);
41 let end = std::cmp::min(size, start + chunk_size);
42 Ok((start, end))
43}
44
45/// A read-only file that can be read by chunks.
46pub struct ChunkedFileReader {
47 file: File,
48 size: u64,
49}
50
51impl ChunkedFileReader {
52 /// Creates a `ChunkedFileReader` to read from for the specified `path`.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080053 pub fn new(file: File) -> Result<ChunkedFileReader> {
Victor Hsieh1fe51c42020-11-05 11:08:10 -080054 let size = file.metadata()?.len();
55 Ok(ChunkedFileReader { file, size })
56 }
Victor Hsiehfa4477a2021-02-08 10:51:50 -080057
58 pub fn len(&self) -> u64 {
59 self.size
60 }
Victor Hsieh1fe51c42020-11-05 11:08:10 -080061}
62
63impl ReadOnlyDataByChunk for ChunkedFileReader {
64 fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> Result<usize> {
65 debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
66 let (start, end) = chunk_index_to_range(self.size, Self::CHUNK_SIZE, chunk_index)?;
67 let size = (end - start) as usize;
68 self.file.read_at(&mut buf[..size], start)
69 }
70}
71
Victor Hsieh1fe51c42020-11-05 11:08:10 -080072#[cfg(test)]
73mod tests {
74 use super::*;
Victor Hsiehfa4477a2021-02-08 10:51:50 -080075 use std::env::temp_dir;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080076
Victor Hsiehfa4477a2021-02-08 10:51:50 -080077 #[test]
78 fn test_read_4k_file() -> Result<()> {
79 let file_reader = ChunkedFileReader::new(File::open("testdata/input.4k")?)?;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080080 let mut buf = [0u8; 4096];
Victor Hsiehfa4477a2021-02-08 10:51:50 -080081 let size = file_reader.read_chunk(0, &mut buf)?;
82 assert_eq!(size, buf.len());
Victor Hsieh1fe51c42020-11-05 11:08:10 -080083 Ok(())
84 }
85
Victor Hsieh1fe51c42020-11-05 11:08:10 -080086 #[test]
Victor Hsiehfa4477a2021-02-08 10:51:50 -080087 fn test_read_4k1_file() -> Result<()> {
88 let file_reader = ChunkedFileReader::new(File::open("testdata/input.4k1")?)?;
Victor Hsieh1fe51c42020-11-05 11:08:10 -080089 let mut buf = [0u8; 4096];
Victor Hsiehfa4477a2021-02-08 10:51:50 -080090 let size = file_reader.read_chunk(0, &mut buf)?;
91 assert_eq!(size, buf.len());
92 let size = file_reader.read_chunk(1, &mut buf)?;
93 assert_eq!(size, 1);
94 Ok(())
95 }
96
97 #[test]
98 fn test_read_4m_file() -> Result<()> {
99 let file_reader = ChunkedFileReader::new(File::open("testdata/input.4m")?)?;
100 for index in 0..file_reader.len() / 4096 {
101 let mut buf = [0u8; 4096];
102 let size = file_reader.read_chunk(index, &mut buf)?;
103 assert_eq!(size, buf.len());
104 }
105 Ok(())
Victor Hsieh1fe51c42020-11-05 11:08:10 -0800106 }
107
108 #[test]
109 #[should_panic]
Victor Hsieh1fe51c42020-11-05 11:08:10 -0800110 fn test_read_beyond_file_size() {
Victor Hsiehfa4477a2021-02-08 10:51:50 -0800111 let file_reader = ChunkedFileReader::new(File::open("testdata/input.4k").unwrap()).unwrap();
Victor Hsieh1fe51c42020-11-05 11:08:10 -0800112 let mut buf = [0u8; 4096];
Victor Hsiehfa4477a2021-02-08 10:51:50 -0800113 let _ = file_reader.read_chunk(1u64, &mut buf); // should panic
114 }
115
116 #[test]
117 #[should_panic]
118 fn test_read_empty_file() {
119 let mut temp_file = temp_dir();
120 temp_file.push("authfs_test_empty_file");
121 let file_reader = ChunkedFileReader::new(File::create(temp_file).unwrap()).unwrap();
122 let mut buf = [0u8; 4096];
123 let _ = file_reader.read_chunk(0, &mut buf); // should panic
Victor Hsieh1fe51c42020-11-05 11:08:10 -0800124 }
125}