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