drill_press/
unix.rs

1//! lseek based implementation that uses `SEEK_DATA` and `SEEK_HOLE` to
2//! reconstruct which segments of the file are data or holes
3use super::*;
4
5use std::fs::File;
6use std::io::Error;
7use std::os::unix::io::AsRawFd;
8
9use errno::errno;
10use libc::{c_int, lseek, off_t, EINVAL, ENXIO, SEEK_END};
11
12cfg_if::cfg_if! {
13    // libc module for macos is missing these, values stolen from _seek_set.h
14    if #[cfg(target_os = "macos")]{
15        const SEEK_HOLE: c_int  = 3;
16        const SEEK_DATA: c_int  = 4;
17    } else {
18        use libc::{SEEK_DATA, SEEK_HOLE};
19    }
20}
21
22impl SparseFile for File {
23    fn scan_chunks(&mut self) -> Result<Vec<Segment>, ScanError> {
24        // Create our output vec
25        let mut tags: Vec<Segment> = Vec::new();
26        // Extract the raw fd from the file
27        let fd = self.as_raw_fd();
28        // Find the end
29        let end = safe_lseek(fd, 0, SEEK_END)?.unwrap_or(0);
30
31        if end == 0 {
32            return Ok(vec![]);
33        }
34
35        // Our seeking loop assumes that we know what type the previous segment
36        // is, so grab the first hole and if it does not exist or is not at the
37        // start add then the file starts with a data block.
38        let mut last_seek = safe_lseek(fd, 0, SEEK_HOLE)?.unwrap_or(end);
39        let mut last_type = SegmentType::Hole;
40        if last_seek > 0 {
41            tags.push(Segment {
42                segment_type: SegmentType::Data,
43                range: 0..last_seek,
44            })
45        }
46
47        while last_seek < end {
48            let seek_type = match last_type {
49                SegmentType::Hole => SEEK_DATA,
50                SegmentType::Data => SEEK_HOLE,
51            };
52
53            let next_seek = safe_lseek(fd, last_seek, seek_type)?.unwrap_or(end);
54            tags.push(Segment {
55                segment_type: last_type,
56                range: last_seek..next_seek,
57            });
58            last_seek = next_seek;
59            last_type = last_type.opposite();
60        }
61        Ok(tags)
62    }
63
64    #[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd",))]
65    fn drill_hole(&self, start: u64, end: u64) -> Result<(), ScanError> {
66        unsafe {
67            use libc::{fallocate, FALLOC_FL_KEEP_SIZE, FALLOC_FL_PUNCH_HOLE};
68            use std::io::Error;
69            use std::os::unix::io::AsRawFd;
70
71            if fallocate(
72                self.as_raw_fd(),
73                FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
74                start as libc::off_t,
75                (end - start) as libc::off_t,
76            ) < 0
77            {
78                return Err(Error::last_os_error().into());
79            }
80        }
81        Ok(())
82    }
83
84    #[cfg(target_os = "macos")]
85    fn drill_hole(&self, start: u64, end: u64) -> Result<(), ScanError> {
86        use libc::fcntl;
87        use std::os::unix::io::AsRawFd;
88
89        #[repr(C)]
90        struct fpunchhole_t {
91            fp_flags: c_int, /* unused */
92            reserved: c_int, /* (to maintain 8-byte alignment) */
93            fp_offset: u64,  /* IN: start of the region */
94            fp_length: u64,  /* IN: size of the region */
95        }
96
97        // from fcntl.h
98        const F_PUNCHHOLE: c_int = 99;
99
100        let hole = fpunchhole_t {
101            fp_flags: 0,
102            reserved: 0,
103            fp_offset: start,
104            fp_length: (end - start),
105        };
106
107        // Try to punch the hole
108        unsafe {
109            let ret = fcntl(self.as_raw_fd(), F_PUNCHHOLE, &hole);
110            if ret < 0 {
111                return Err(Error::last_os_error().into());
112            }
113        }
114        Ok(())
115    }
116}
117
118fn safe_lseek(fd: c_int, offset: u64, seek_type: c_int) -> Result<Option<u64>, ScanError> {
119    unsafe {
120        let new_offset = lseek(fd, offset as off_t, seek_type);
121        // if the return value of lseek is less than 0, an error has occurred
122        if new_offset < 0 {
123            // find and deref errno, honestly the scariest thing we do here
124            let errno = errno().into();
125            match errno {
126                // EINVAL indicates that the file system does not support
127                // SEEK_HOLE or SEEK_DATA, so we indicate as such
128                EINVAL => Err(ScanError::UnsupportedFileSystem),
129                // ENXIO indicates that the the file offset we are looking for
130                // either doesn't exist, or would be beyond the end of the file.
131                // In our case, this just means there is no next segment, so we
132                // return Ok(none) to indicate as such.
133                ENXIO => Ok(None),
134                // None of the other error codes require special handling, so we
135                // just turn them into an std::io::Error for user friendliness
136                _ => Err(Error::last_os_error().into()),
137            }
138        } else {
139            Ok(Some(new_offset as u64))
140        }
141    }
142}