linapi/
extensions.rs

1//! Linux-specific extensions to std types
2use nix::{
3    errno::Errno,
4    fcntl::{fallocate, flock, FallocateFlags, FlockArg},
5    sys::memfd::{memfd_create, MemFdCreateFlag},
6};
7use std::{
8    ffi::CString,
9    fs::File,
10    io,
11    os::unix::{
12        ffi::OsStringExt,
13        fs::FileTypeExt,
14        io::{AsRawFd, FromRawFd, RawFd},
15    },
16    path::Path,
17};
18
19/// Internal ioctl stuff
20mod _impl {
21    use nix::{
22        ioctl_none,
23        ioctl_write_ptr_bad,
24        libc::{c_char, c_int, c_longlong, c_void},
25    };
26    use std::{convert::TryInto, marker::PhantomData, mem};
27
28    pub const BLOCK_ADD_PART: i32 = 1;
29    pub const BLOCK_DEL_PART: i32 = 2;
30    pub const _BLOCK_RESIZE_PART: i32 = 3;
31
32    #[repr(C)]
33    pub struct BlockPageIoctlArgs<'a> {
34        /// Requested operation
35        op: c_int,
36
37        /// Always zero, kernel doesn't use?
38        flags: c_int,
39
40        /// size_of::<BlockPagePartArgs>().
41        /// Also unused by the kernel, size is hard-coded?
42        data_len: c_int,
43
44        /// [`BlockPagePartArgs`]
45        data: *mut c_void,
46
47        _phantom: PhantomData<&'a mut BlockPagePartArgs>,
48    }
49
50    impl<'a> BlockPageIoctlArgs<'a> {
51        pub fn new(op: i32, data: &'a mut BlockPagePartArgs) -> Self {
52            BlockPageIoctlArgs {
53                op,
54                flags: 0,
55                data_len: mem::size_of::<BlockPagePartArgs>().try_into().unwrap(),
56                data: data as *mut _ as *mut _,
57                _phantom: PhantomData,
58            }
59        }
60    }
61
62    #[repr(C)]
63    pub struct BlockPagePartArgs {
64        /// Starting offset, in bytes
65        start: c_longlong,
66
67        /// Length, in bytes.
68        length: c_longlong,
69
70        /// Partition number
71        part_num: c_int,
72
73        /// Unused by the kernel?
74        dev_name: [c_char; 64],
75
76        /// Unused by the kernel?
77        vol_name: [c_char; 64],
78    }
79
80    impl BlockPagePartArgs {
81        pub fn new(part_num: i32, start: i64, end: i64) -> Self {
82            let length = end - start;
83            BlockPagePartArgs {
84                start,
85                length,
86                part_num,
87                dev_name: [0; 64],
88                vol_name: [0; 64],
89            }
90        }
91    }
92
93    ioctl_none! {
94        /// The `BLKRRPART` ioctl, defined in
95        /// <linux/fs.h>
96        block_reread_part,
97        0x12,
98        95
99    }
100
101    ioctl_write_ptr_bad!(
102        /// The `BLKPG` ioctl, defined in
103        /// <linux/blkpg.h>
104        ///
105        /// Incorrectly defined as `_IO`, actually takes one argument
106        block_page,
107        0x1269,
108        BlockPageIoctlArgs
109    );
110}
111
112/// Impl for [`FileExt::lock`] and co.
113fn lock_impl(fd: RawFd, lock: LockType, non_block: bool) -> nix::Result<()> {
114    flock(
115        fd,
116        match lock {
117            LockType::Shared => {
118                if non_block {
119                    FlockArg::LockSharedNonblock
120                } else {
121                    FlockArg::LockShared
122                }
123            }
124            LockType::Exclusive => {
125                if non_block {
126                    FlockArg::LockExclusiveNonblock
127                } else {
128                    FlockArg::LockExclusive
129                }
130            }
131        },
132    )
133}
134
135/// Type of lock to use for [`FileExt::lock`]
136#[derive(Debug, Copy, Clone)]
137pub enum LockType {
138    /// Multiple processes may acquire this lock
139    Shared,
140
141    /// Only one process may acquire this lock
142    Exclusive,
143}
144
145/// Extends [`File`]
146pub trait FileExt: AsRawFd {
147    /// Like [`File::create`] except the file exists only in memory.
148    ///
149    /// As the file exists only in memory, `path` doesn't matter
150    /// and is only used as a debugging marker in `/proc/self/fd/`
151    ///
152    /// # Implementation
153    ///
154    /// This uses `memfd_create(2)`
155    ///
156    /// # Panics
157    ///
158    /// - If `path` is more than 249 bytes. This is a Linux Kernel limit.
159    /// - If `path` has any internal null bytes.
160    /// - The per process/system file limit is reached.
161    /// - Insufficient memory.
162    fn create_memory<P: AsRef<Path>>(path: P) -> File {
163        let path = path.as_ref();
164        let fd = memfd_create(
165            &CString::new(path.as_os_str().to_os_string().into_vec()).unwrap(),
166            MemFdCreateFlag::MFD_CLOEXEC,
167        )
168        .unwrap();
169        // Safe because this is a newly created file descriptor.
170        unsafe { File::from_raw_fd(fd) }
171    }
172
173    /// Apply an advisory lock
174    ///
175    /// A single file can only have one [`LockType`] at a time.
176    /// It doesn't matter whether the file was opened for reading or writing.
177    ///
178    /// Calling this on an already locked file will change the [`LockType`].
179    ///
180    /// This may block until the lock can be acquired.
181    /// See [`FileExt::lock_nonblock`] if thats unacceptable.
182    ///
183    /// # Implementation
184    ///
185    /// This uses `flock(2)`.
186    ///
187    /// This will retry as necessary on `EINTR`
188    ///
189    /// # Panics
190    ///
191    /// - Kernel runs out of memory for lock records
192    fn lock(&self, lock: LockType) {
193        let fd = self.as_raw_fd();
194        loop {
195            let e = lock_impl(fd, lock, false);
196            match e {
197                Ok(_) => break,
198                Err(nix::Error::Sys(Errno::EINTR)) => continue,
199                Err(e @ nix::Error::Sys(Errno::ENOLCK)) => panic!("{}", e),
200                Err(_) => unreachable!("Lock had nix errors it shouldn't have"),
201            }
202        }
203    }
204
205    /// Apply an advisory lock, without blocking
206    ///
207    /// See [`FileExt::lock`] for more details
208    ///
209    /// # Errors
210    ///
211    /// - [`io::ErrorKind::WouldBlock`] if the operation would block
212    fn lock_nonblock(&self, lock: LockType) -> io::Result<()> {
213        let fd = self.as_raw_fd();
214        // FIXME: Can the non-blocking variants get interrupted?
215        loop {
216            let e = lock_impl(fd, lock, true);
217            match e {
218                Ok(_) => break,
219                Err(nix::Error::Sys(Errno::EINTR)) => continue,
220                Err(nix::Error::Sys(e @ nix::errno::EWOULDBLOCK)) => return Err(e.into()),
221                Err(e @ nix::Error::Sys(Errno::ENOLCK)) => panic!("{}", e),
222                Err(_) => unreachable!("Lock_nonblock had nix errors it shouldn't have"),
223            }
224        }
225        Ok(())
226    }
227
228    /// Remove an advisory lock
229    ///
230    /// This may block until the lock can be acquired.
231    /// See [`FileExt::unlock_nonblock`] if thats unacceptable.
232    ///
233    /// # Implementation
234    ///
235    /// This uses `flock(2)`.
236    ///
237    /// This will retry as necessary on `EINTR`
238    fn unlock(&self) {
239        let fd = self.as_raw_fd();
240        loop {
241            let e = flock(fd, FlockArg::Unlock);
242            match e {
243                Ok(_) => break,
244                Err(nix::Error::Sys(Errno::EINTR)) => continue,
245                Err(_) => unreachable!("Unlock had nix errors it shouldn't have"),
246            }
247        }
248    }
249
250    /// Remove an advisory lock, without blocking
251    ///
252    /// See [`FileExt::unlock`] for more details
253    ///
254    /// # Errors
255    ///
256    /// - [`io::ErrorKind::WouldBlock`] if the operation would block
257    fn unlock_nonblock(&self) -> io::Result<()> {
258        let fd = self.as_raw_fd();
259        // FIXME: Can the non-blocking variants get interrupted?
260        loop {
261            let e = flock(fd, FlockArg::UnlockNonblock);
262            match e {
263                Ok(_) => break,
264                Err(nix::Error::Sys(Errno::EINTR)) => continue,
265                Err(nix::Error::Sys(e @ nix::errno::EWOULDBLOCK)) => return Err(e.into()),
266                Err(_) => unreachable!("Unlock_nonblock had nix errors it shouldn't have"),
267            }
268        }
269        Ok(())
270    }
271
272    /// Allocate space on disk for at least `size` bytes
273    ///
274    /// Unlike [`File::set_len`], which on Linux creates a sparse file
275    /// without reserving disk space,
276    /// this will actually reserve `size` bytes of zeros, without having to
277    /// write them.
278    ///
279    /// Subsequent writes up to `size` bytes are guaranteed not to fail
280    /// because of lack of disk space.
281    ///
282    /// # Implementation
283    ///
284    /// This uses `fallocate(2)`
285    ///
286    /// This will retry as necessary on `EINTR`
287    ///
288    /// # Errors
289    ///
290    /// - If `self` is not opened for writing.
291    /// - If `self` is not a regular file.
292    /// - If I/O does.
293    ///
294    /// # Panics
295    ///
296    /// - If `size` is zero
297    fn allocate(&self, size: i64) -> io::Result<()> {
298        assert_ne!(size, 0, "Size cannot be zero");
299        let fd = self.as_raw_fd();
300        loop {
301            let e = fallocate(fd, FallocateFlags::empty(), 0, size).map(|_| ());
302            match e {
303                Ok(_) => break,
304                Err(nix::Error::Sys(Errno::EINTR)) => continue,
305                // Not opened for writing
306                Err(nix::Error::Sys(e @ Errno::EBADF)) => return Err(e.into()),
307                // I/O
308                Err(nix::Error::Sys(e @ Errno::EFBIG)) => return Err(e.into()),
309                Err(nix::Error::Sys(e @ Errno::EIO)) => return Err(e.into()),
310                Err(nix::Error::Sys(e @ Errno::EPERM)) => return Err(e.into()),
311                Err(nix::Error::Sys(e @ Errno::ENOSPC)) => return Err(e.into()),
312                // Not regular file
313                Err(nix::Error::Sys(e @ Errno::ENODEV)) => return Err(e.into()),
314                Err(nix::Error::Sys(e @ Errno::ESPIPE)) => return Err(e.into()),
315                Err(_) => unreachable!("Allocate had nix errors it shouldn't have"),
316            }
317        }
318        Ok(())
319    }
320
321    // TODO: Dig holes, see `fallocate(1)`.
322
323    /// Tell the kernel to re-read the partition table.
324    /// This call may be unreliable and require reboots.
325    ///
326    /// You may instead want the newer [`FileExt::add_partition`], or
327    /// [`FileExt::remove_partition`]
328    ///
329    /// # Implementation
330    ///
331    /// This uses the `BLKRRPART` ioctl.
332    ///
333    /// # Errors
334    ///
335    /// - If `self` is not a block device
336    /// - If the underlying ioctl does.
337    fn reread_partitions(&self) -> io::Result<()>;
338
339    /// Inform the kernel of a partition, number `part`.
340    ///
341    /// The partition starts at `start` bytes and ends at `end` bytes,
342    /// relative to the start of `self`.
343    ///
344    /// # Implementation
345    ///
346    /// This uses the `BLKPG` ioctl.
347    ///
348    /// # Errors
349    ///
350    /// - If `self` is not a block device.
351    /// - If the underlying ioctl does.
352    fn add_partition(&self, part: i32, start: i64, end: i64) -> io::Result<()>;
353
354    /// Remove partition number `part`.
355    ///
356    /// # Implementation
357    ///
358    /// This uses the `BLKPG` ioctl.
359    ///
360    /// # Errors
361    ///
362    /// - If `self` is not a block device.
363    /// - If the underlying ioctl does.
364    fn remove_partition(&self, part: i32) -> io::Result<()>;
365}
366
367impl FileExt for File {
368    fn reread_partitions(&self) -> io::Result<()> {
369        if !self.metadata()?.file_type().is_block_device() {
370            return Err(io::Error::new(
371                io::ErrorKind::InvalidInput,
372                "File was not a block device",
373            ));
374        }
375        match unsafe { _impl::block_reread_part(self.as_raw_fd()) } {
376            Ok(_) => Ok(()),
377            Err(nix::Error::Sys(e)) => Err(e.into()),
378            Err(e) => Err(io::Error::new(io::ErrorKind::InvalidInput, e)),
379        }
380    }
381
382    fn add_partition(&self, part: i32, start: i64, end: i64) -> io::Result<()> {
383        if !self.metadata()?.file_type().is_block_device() {
384            return Err(io::Error::new(
385                io::ErrorKind::InvalidInput,
386                "File was not a block device",
387            ));
388        }
389        let mut part = _impl::BlockPagePartArgs::new(part, start, end);
390        let args = _impl::BlockPageIoctlArgs::new(_impl::BLOCK_ADD_PART, &mut part);
391        match unsafe { _impl::block_page(self.as_raw_fd(), &args) } {
392            Ok(_) => Ok(()),
393            Err(nix::Error::Sys(e)) => Err(e.into()),
394            Err(e) => Err(io::Error::new(io::ErrorKind::InvalidInput, e)),
395        }
396    }
397
398    fn remove_partition(&self, part: i32) -> io::Result<()> {
399        if !self.metadata()?.file_type().is_block_device() {
400            return Err(io::Error::new(
401                io::ErrorKind::InvalidInput,
402                "File was not a block device",
403            ));
404        }
405        let mut part = _impl::BlockPagePartArgs::new(part, 0, 0);
406        let args = _impl::BlockPageIoctlArgs::new(_impl::BLOCK_DEL_PART, &mut part);
407        match unsafe { _impl::block_page(self.as_raw_fd(), &args) } {
408            Ok(_) => Ok(()),
409            Err(nix::Error::Sys(e)) => Err(e.into()),
410            Err(e) => Err(io::Error::new(io::ErrorKind::InvalidInput, e)),
411        }
412    }
413}