base/sys/linux/
file.rs

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
7use std::ops::Range;
8
9use crate::AsRawDescriptor;
10use crate::Error;
11use crate::Result;
12
13enum LseekOption {
14    Data,
15    Hole,
16}
17
18fn 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
39pub 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.
68pub struct FileDataIterator<'a> {
69    fd: &'a dyn AsRawDescriptor,
70    offset: u64,
71    end: u64,
72    failed: bool,
73}
74
75impl<'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
83    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
93impl Iterator for FileDataIterator<'_> {
94    type Item = Result<Range<u64>>;
95
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)]
116mod tests {
117    use std::os::unix::fs::FileExt;
118
119    use super::*;
120    use crate::pagesize;
121
122    #[test]
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]
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}