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