1 // Copyright 2019 Intel Corporation. All Rights Reserved.
2 //
3 // Copyright 2018 The Chromium OS Authors. All rights reserved.
4 //
5 // SPDX-License-Identifier: BSD-3-Clause
6 
7 //! Traits and implementations over [lseek64](https://linux.die.net/man/3/lseek64).
8 
9 use std::fs::File;
10 use std::io::{Error, Result};
11 use std::os::unix::io::AsRawFd;
12 
13 #[cfg(target_env = "musl")]
14 use libc::{c_int, lseek64, ENXIO};
15 
16 #[cfg(target_env = "gnu")]
17 use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
18 
19 #[cfg(all(not(target_env = "musl"), target_os = "android"))]
20 use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
21 
22 /// A trait for seeking to the next hole or non-hole position in a file.
23 pub trait SeekHole {
24     /// Seek to the first hole in a file.
25     ///
26     /// Seek at a position greater than or equal to `offset`. If no holes exist
27     /// after `offset`, the seek position will be set to the end of the file.
28     /// If `offset` is at or after the end of the file, the seek position is
29     /// unchanged, and None is returned.
30     ///
31     /// Returns the current seek position after the seek or an error.
seek_hole(&mut self, offset: u64) -> Result<Option<u64>>32     fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>>;
33 
34     /// Seek to the first data in a file.
35     ///
36     /// Seek at a position greater than or equal to `offset`.
37     /// If no data exists after `offset`, the seek position is unchanged,
38     /// and None is returned.
39     ///
40     /// Returns the current offset after the seek or an error.
seek_data(&mut self, offset: u64) -> Result<Option<u64>>41     fn seek_data(&mut self, offset: u64) -> Result<Option<u64>>;
42 }
43 
44 #[cfg(target_env = "musl")]
45 const SEEK_DATA: c_int = 3;
46 #[cfg(target_env = "musl")]
47 const SEEK_HOLE: c_int = 4;
48 
49 // Safe wrapper for `libc::lseek64()`
lseek(file: &mut File, offset: i64, whence: i32) -> Result<Option<u64>>50 fn lseek(file: &mut File, offset: i64, whence: i32) -> Result<Option<u64>> {
51     // SAFETY: This is safe because we pass a known-good file descriptor.
52     let res = unsafe { lseek64(file.as_raw_fd(), offset, whence) };
53 
54     if res < 0 {
55         // Convert ENXIO into None; pass any other error as-is.
56         let err = Error::last_os_error();
57         if let Some(errno) = Error::raw_os_error(&err) {
58             if errno == ENXIO {
59                 return Ok(None);
60             }
61         }
62         Err(err)
63     } else {
64         Ok(Some(res as u64))
65     }
66 }
67 
68 impl SeekHole for File {
seek_hole(&mut self, offset: u64) -> Result<Option<u64>>69     fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>> {
70         lseek(self, offset as i64, SEEK_HOLE)
71     }
72 
seek_data(&mut self, offset: u64) -> Result<Option<u64>>73     fn seek_data(&mut self, offset: u64) -> Result<Option<u64>> {
74         lseek(self, offset as i64, SEEK_DATA)
75     }
76 }
77 
78 #[cfg(test)]
79 mod tests {
80     use super::*;
81     use crate::tempdir::TempDir;
82     use std::fs::File;
83     use std::io::{Seek, SeekFrom, Write};
84     use std::path::PathBuf;
85 
seek_cur(file: &mut File) -> u6486     fn seek_cur(file: &mut File) -> u64 {
87         file.stream_position().unwrap()
88     }
89 
90     #[test]
seek_data()91     fn seek_data() {
92         let tempdir = TempDir::new_with_prefix("/tmp/seek_data_test").unwrap();
93         let mut path = PathBuf::from(tempdir.as_path());
94         path.push("test_file");
95         let mut file = File::create(&path).unwrap();
96 
97         // Empty file
98         assert_eq!(file.seek_data(0).unwrap(), None);
99         assert_eq!(seek_cur(&mut file), 0);
100 
101         // File with non-zero length consisting entirely of a hole
102         file.set_len(0x10000).unwrap();
103         assert_eq!(file.seek_data(0).unwrap(), None);
104         assert_eq!(seek_cur(&mut file), 0);
105 
106         // seek_data at or after the end of the file should return None
107         assert_eq!(file.seek_data(0x10000).unwrap(), None);
108         assert_eq!(seek_cur(&mut file), 0);
109         assert_eq!(file.seek_data(0x10001).unwrap(), None);
110         assert_eq!(seek_cur(&mut file), 0);
111 
112         // Write some data to [0x10000, 0x20000)
113         let b = [0x55u8; 0x10000];
114         file.seek(SeekFrom::Start(0x10000)).unwrap();
115         file.write_all(&b).unwrap();
116         assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
117         assert_eq!(seek_cur(&mut file), 0x10000);
118 
119         // seek_data within data should return the same offset
120         assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
121         assert_eq!(seek_cur(&mut file), 0x10000);
122         assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
123         assert_eq!(seek_cur(&mut file), 0x10001);
124         assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
125         assert_eq!(seek_cur(&mut file), 0x1FFFF);
126 
127         // Extend the file to add another hole after the data
128         file.set_len(0x30000).unwrap();
129         assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
130         assert_eq!(seek_cur(&mut file), 0x10000);
131         assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
132         assert_eq!(seek_cur(&mut file), 0x1FFFF);
133         assert_eq!(file.seek_data(0x20000).unwrap(), None);
134         assert_eq!(seek_cur(&mut file), 0x1FFFF);
135     }
136 
137     #[test]
138     #[allow(clippy::cognitive_complexity)]
seek_hole()139     fn seek_hole() {
140         let tempdir = TempDir::new_with_prefix("/tmp/seek_hole_test").unwrap();
141         let mut path = PathBuf::from(tempdir.as_path());
142         path.push("test_file");
143         let mut file = File::create(&path).unwrap();
144 
145         // Empty file
146         assert_eq!(file.seek_hole(0).unwrap(), None);
147         assert_eq!(seek_cur(&mut file), 0);
148 
149         // File with non-zero length consisting entirely of a hole
150         file.set_len(0x10000).unwrap();
151         assert_eq!(file.seek_hole(0).unwrap(), Some(0));
152         assert_eq!(seek_cur(&mut file), 0);
153         assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
154         assert_eq!(seek_cur(&mut file), 0xFFFF);
155 
156         // seek_hole at or after the end of the file should return None
157         file.rewind().unwrap();
158         assert_eq!(file.seek_hole(0x10000).unwrap(), None);
159         assert_eq!(seek_cur(&mut file), 0);
160         assert_eq!(file.seek_hole(0x10001).unwrap(), None);
161         assert_eq!(seek_cur(&mut file), 0);
162 
163         // Write some data to [0x10000, 0x20000)
164         let b = [0x55u8; 0x10000];
165         file.seek(SeekFrom::Start(0x10000)).unwrap();
166         file.write_all(&b).unwrap();
167 
168         // seek_hole within a hole should return the same offset
169         assert_eq!(file.seek_hole(0).unwrap(), Some(0));
170         assert_eq!(seek_cur(&mut file), 0);
171         assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
172         assert_eq!(seek_cur(&mut file), 0xFFFF);
173 
174         // seek_hole within data should return the next hole (EOF)
175         file.rewind().unwrap();
176         assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
177         assert_eq!(seek_cur(&mut file), 0x20000);
178         file.rewind().unwrap();
179         assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
180         assert_eq!(seek_cur(&mut file), 0x20000);
181         file.rewind().unwrap();
182         assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
183         assert_eq!(seek_cur(&mut file), 0x20000);
184 
185         // seek_hole at EOF after data should return None
186         file.rewind().unwrap();
187         assert_eq!(file.seek_hole(0x20000).unwrap(), None);
188         assert_eq!(seek_cur(&mut file), 0);
189 
190         // Extend the file to add another hole after the data
191         file.set_len(0x30000).unwrap();
192         assert_eq!(file.seek_hole(0).unwrap(), Some(0));
193         assert_eq!(seek_cur(&mut file), 0);
194         assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
195         assert_eq!(seek_cur(&mut file), 0xFFFF);
196         file.rewind().unwrap();
197         assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
198         assert_eq!(seek_cur(&mut file), 0x20000);
199         file.rewind().unwrap();
200         assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
201         assert_eq!(seek_cur(&mut file), 0x20000);
202         file.rewind().unwrap();
203         assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
204         assert_eq!(seek_cur(&mut file), 0x20000);
205         file.rewind().unwrap();
206         assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
207         assert_eq!(seek_cur(&mut file), 0x20001);
208 
209         // seek_hole at EOF after a hole should return None
210         file.rewind().unwrap();
211         assert_eq!(file.seek_hole(0x30000).unwrap(), None);
212         assert_eq!(seek_cur(&mut file), 0);
213 
214         // Write some data to [0x20000, 0x30000)
215         file.seek(SeekFrom::Start(0x20000)).unwrap();
216         file.write_all(&b).unwrap();
217 
218         // seek_hole within [0x20000, 0x30000) should now find the hole at EOF
219         assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
220         assert_eq!(seek_cur(&mut file), 0x30000);
221         file.rewind().unwrap();
222         assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
223         assert_eq!(seek_cur(&mut file), 0x30000);
224         file.rewind().unwrap();
225         assert_eq!(file.seek_hole(0x30000).unwrap(), None);
226         assert_eq!(seek_cur(&mut file), 0);
227     }
228 }
229