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::error;
10 use crate::AsRawDescriptor;
11 use crate::Error;
12 use crate::Result;
13
14 enum LseekOption {
15 Data,
16 Hole,
17 }
18
lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64>19 fn lseek(fd: &dyn AsRawDescriptor, offset: u64, option: LseekOption) -> Result<u64> {
20 let whence = match option {
21 LseekOption::Data => libc::SEEK_DATA,
22 LseekOption::Hole => libc::SEEK_HOLE,
23 };
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 }
73
74 impl<'a> FileDataIterator<'a> {
75 /// Creates the [FileDataIterator]
76 ///
77 /// # Arguments
78 ///
79 /// * `fd` - the [trait@AsRawDescriptor] of the file
80 /// * `offset` - the offset to start traversing from.
81 /// * `len` - the len of the region over which to iterate
new(fd: &'a dyn AsRawDescriptor, offset: u64, len: u64) -> Self82 pub fn new(fd: &'a dyn AsRawDescriptor, offset: u64, len: u64) -> Self {
83 Self {
84 fd,
85 offset,
86 end: offset + len,
87 }
88 }
89 }
90
91 impl<'a> Iterator for FileDataIterator<'a> {
92 type Item = Range<u64>;
93
next(&mut self) -> Option<Self::Item>94 fn next(&mut self) -> Option<Self::Item> {
95 match find_next_data(self.fd, self.offset, self.end - self.offset) {
96 Ok(data_range) => {
97 if let Some(ref data_range) = data_range {
98 self.offset = data_range.end;
99 }
100 data_range
101 }
102 Err(e) => {
103 error!("failed to get data range: {:?}", e);
104 None
105 }
106 }
107 }
108 }
109
110 #[cfg(test)]
111 mod tests {
112 use std::os::unix::fs::FileExt;
113
114 use super::*;
115 use crate::pagesize;
116
117 #[test]
file_data_iterator()118 fn file_data_iterator() {
119 let file = tempfile::tempfile().unwrap();
120
121 file.write_at(&[1_u8], 10).unwrap();
122 file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
123 file.write_at(&[1_u8], (4 * pagesize() - 1) as u64).unwrap();
124
125 let iter = FileDataIterator::new(&file, 0, 4 * pagesize() as u64);
126
127 let result: Vec<Range<u64>> = iter.collect();
128 assert_eq!(result.len(), 2);
129 assert_eq!(result[0], 0..(pagesize() as u64));
130 assert_eq!(result[1], (2 * pagesize() as u64)..(4 * pagesize() as u64));
131 }
132
133 #[test]
file_data_iterator_subrange()134 fn file_data_iterator_subrange() {
135 let file = tempfile::tempfile().unwrap();
136
137 file.write_at(&[1_u8], 0).unwrap();
138 file.write_at(&[1_u8], pagesize() as u64).unwrap();
139 file.write_at(&[1_u8], 2 * pagesize() as u64).unwrap();
140 file.write_at(&[1_u8], 4 * pagesize() as u64).unwrap();
141
142 let iter = FileDataIterator::new(&file, pagesize() as u64, pagesize() as u64);
143
144 let result: Vec<Range<u64>> = iter.collect();
145 assert_eq!(result.len(), 1);
146 assert_eq!(result[0], (pagesize() as u64)..(2 * pagesize() as u64));
147 }
148 }
149