vmm_sys_util/linux/
seek_hole.rs

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
9use std::fs::File;
10use std::io::{Error, Result};
11use std::os::unix::io::AsRawFd;
12
13#[cfg(target_env = "musl")]
14use libc::{c_int, lseek64, ENXIO};
15
16#[cfg(target_env = "gnu")]
17use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
18
19#[cfg(all(not(target_env = "musl"), target_os = "android"))]
20use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
21
22/// A trait for seeking to the next hole or non-hole position in a file.
23pub 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.
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.
41    fn seek_data(&mut self, offset: u64) -> Result<Option<u64>>;
42}
43
44#[cfg(target_env = "musl")]
45const SEEK_DATA: c_int = 3;
46#[cfg(target_env = "musl")]
47const SEEK_HOLE: c_int = 4;
48
49// Safe wrapper for `libc::lseek64()`
50fn 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
68impl SeekHole for File {
69    fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>> {
70        lseek(self, offset as i64, SEEK_HOLE)
71    }
72
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)]
79mod 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
86    fn seek_cur(file: &mut File) -> u64 {
87        file.stream_position().unwrap()
88    }
89
90    #[test]
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)]
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}