xref: /aosp_15_r20/external/crosvm/base/src/sys/linux/file.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #![deny(missing_docs)]
6 
7 use std::ops::Range;
8 
9 use crate::AsRawDescriptor;
10 use crate::Error;
11 use crate::Result;
12 
13 enum LseekOption {
14     Data,
15     Hole,
16 }
17 
lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64>18 fn lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64> {
19     let whence = match option {
20         LseekOption::Data => libc::SEEK_DATA,
21         LseekOption::Hole => libc::SEEK_HOLE,
22     };
23     // SAFETY:
24     // safe because this doesn't modify any memory.
25     let ret = unsafe { libc::lseek64(fd.as_raw_descriptor(), offset as i64, whence) };
26     if ret < 0 {
27         return Err(Error::last());
28     }
29     Ok(ret as u64)
30 }
31 
32 /// Find the offset range of the next data in the file.
33 ///
34 /// # Arguments
35 ///
36 /// * `fd` - the [trait@AsRawDescriptor] of the file
37 /// * `offset` - the offset to start traversing from
38 /// * `len` - the len of the region over which to traverse
find_next_data( fd: &dyn AsRawDescriptor, offset: u64, len: u64, ) -> Result<Option<Range<u64>>>39 pub fn find_next_data(
40     fd: &dyn AsRawDescriptor,
41     offset: u64,
42     len: u64,
43 ) -> Result<Option<Range<u64>>> {
44     let end = offset + len;
45     let offset_data = match lseek(fd, offset, LseekOption::Data) {
46         Ok(offset) => {
47             if offset >= end {
48                 return Ok(None);
49             } else {
50                 offset
51             }
52         }
53         Err(e) => {
54             return match e.errno() {
55                 libc::ENXIO => Ok(None),
56                 _ => Err(e),
57             }
58         }
59     };
60     let offset_hole = lseek(fd, offset_data, LseekOption::Hole)?;
61 
62     Ok(Some(offset_data..offset_hole.min(end)))
63 }
64 
65 /// Iterator returning the offset range of data in the file.
66 ///
67 /// This uses `lseek(2)` internally, and thus it changes the file offset.
68 pub struct FileDataIterator<'a> {
69     fd: &'a dyn AsRawDescriptor,
70     offset: u64,
71     end: u64,
72     failed: bool,
73 }
74 
75 impl<'a> FileDataIterator<'a> {
76     /// Creates the [FileDataIterator]
77     ///
78     /// # Arguments
79     ///
80     /// * `fd` - the [trait@AsRawDescriptor] of the file
81     /// * `offset` - the offset to start traversing from.
82     /// * `len` - the len of the region over which to iterate
new(fd: &'a dyn AsRawDescriptor, offset: u64, len: u64) -> Self83     pub fn new(fd: &'a dyn AsRawDescriptor, offset: u64, len: u64) -> Self {
84         Self {
85             fd,
86             offset,
87             end: offset + len,
88             failed: false,
89         }
90     }
91 }
92 
93 impl<'a> Iterator for FileDataIterator<'a> {
94     type Item = Result<Range<u64>>;
95 
next(&mut self) -> Option<Self::Item>96     fn next(&mut self) -> Option<Self::Item> {
97         if self.failed {
98             return None;
99         }
100         match find_next_data(self.fd, self.offset, self.end - self.offset) {
101             Ok(data_range) => {
102                 if let Some(ref data_range) = data_range {
103                     self.offset = data_range.end;
104                 }
105                 data_range.map(Ok)
106             }
107             Err(e) => {
108                 self.failed = true;
109                 Some(Err(e))
110             }
111         }
112     }
113 }
114 
115 #[cfg(test)]
116 mod tests {
117     use std::os::unix::fs::FileExt;
118 
119     use super::*;
120     use crate::pagesize;
121 
122     #[test]
file_data_iterator()123     fn file_data_iterator() {
124         let file = tempfile::tempfile().unwrap();
125 
126         file.write_at(&[1_u8], 10).unwrap();
127         file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
128         file.write_at(&[1_u8], (4 * pagesize() - 1) as u64).unwrap();
129 
130         let iter = FileDataIterator::new(&file, 0, 4 * pagesize() as u64);
131 
132         let result = iter.collect::<Result<Vec<Range<u64>>>>().unwrap();
133         assert_eq!(result.len(), 2);
134         assert_eq!(result[0], 0..(pagesize() as u64));
135         assert_eq!(result[1], (2 * pagesize() as u64)..(4 * pagesize() as u64));
136     }
137 
138     #[test]
file_data_iterator_subrange()139     fn file_data_iterator_subrange() {
140         let file = tempfile::tempfile().unwrap();
141 
142         file.write_at(&[1_u8], 0).unwrap();
143         file.write_at(&[1_u8], pagesize() as u64).unwrap();
144         file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
145         file.write_at(&[1_u8], 4 * pagesize() as u64).unwrap();
146 
147         let iter = FileDataIterator::new(&file, pagesize() as u64, pagesize() as u64);
148 
149         let result = iter.collect::<Result<Vec<Range<u64>>>>().unwrap();
150         assert_eq!(result.len(), 1);
151         assert_eq!(result[0], (pagesize() as u64)..(2 * pagesize() as u64));
152     }
153 }
154