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}