1extern crate libc as upstream_libc;
2
3use crate::libc::{
4 c_int, c_uint, c_void, FSCONFIG_CMD_CREATE, FSCONFIG_CMD_RECONFIGURE,
5 FSCONFIG_SET_BINARY, FSCONFIG_SET_FD, FSCONFIG_SET_FLAG, FSCONFIG_SET_PATH,
6 FSCONFIG_SET_PATH_EMPTY, FSCONFIG_SET_STRING,
7};
8use bitflags::bitflags;
9use nix::{errno::Errno, unistd::close, Error, NixPath, Result};
10use std::{
11 convert::TryFrom,
12 ffi::CStr,
13 mem,
14 os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
15 ptr,
16};
17
18macro_rules! libc_bitflags {
20 (
21 $(#[$outer:meta])*
22 pub struct $BitFlags:ident: $T:ty {
23 $(
24 $(#[$inner:ident $($args:tt)*])*
25 $Flag:ident $(as $cast:ty)*;
26 )+
27 }
28 ) => {
29 bitflags! {
30 $(#[$outer])*
31 pub struct $BitFlags: $T {
32 $(
33 $(#[$inner $($args)*])*
34 const $Flag = libc::$Flag $(as $cast)*;
35 )+
36 }
37 }
38 };
39}
40
41mod libc {
42 #![allow(non_upper_case_globals)]
43
44 pub use upstream_libc::*;
45
46 pub const SYS_open_tree: c_long = 428;
48 pub const SYS_move_mount: c_long = 429;
49 pub const SYS_fsopen: c_long = 430;
50 pub const SYS_fsconfig: c_long = 431;
51 pub const SYS_fsmount: c_long = 432;
52 pub const SYS_fspick: c_long = 433;
53
54 pub const OPEN_TREE_CLONE: c_uint = 1;
56 pub const OPEN_TREE_CLOEXEC: c_uint = O_CLOEXEC as c_uint;
57
58 pub const MOVE_MOUNT_F_SYMLINKS: c_uint = 0x00000001;
59 pub const MOVE_MOUNT_F_AUTOMOUNTS: c_uint = 0x00000002;
60 pub const MOVE_MOUNT_F_EMPTY_PATH: c_uint = 0x00000004;
61 pub const MOVE_MOUNT_T_SYMLINKS: c_uint = 0x00000010;
62 pub const MOVE_MOUNT_T_AUTOMOUNTS: c_uint = 0x00000020;
63 pub const MOVE_MOUNT_T_EMPTY_PATH: c_uint = 0x00000040;
64 #[allow(dead_code)]
65 pub const MOVE_MOUNT__MASK: c_uint = 0x00000077;
66
67 pub const FSOPEN_CLOEXEC: c_uint = 0x00000001;
68
69 pub const FSPICK_CLOEXEC: c_uint = 0x00000001;
70 pub const FSPICK_SYMLINK_NOFOLLOW: c_uint = 0x00000002;
71 pub const FSPICK_NO_AUTOMOUNT: c_uint = 0x00000004;
72 pub const FSPICK_EMPTY_PATH: c_uint = 0x00000008;
73
74 pub const FSCONFIG_SET_FLAG: c_uint = 0;
75 pub const FSCONFIG_SET_STRING: c_uint = 1;
76 pub const FSCONFIG_SET_BINARY: c_uint = 2;
77 pub const FSCONFIG_SET_PATH: c_uint = 3;
78 pub const FSCONFIG_SET_PATH_EMPTY: c_uint = 4;
79 pub const FSCONFIG_SET_FD: c_uint = 5;
80 pub const FSCONFIG_CMD_CREATE: c_uint = 6;
81 pub const FSCONFIG_CMD_RECONFIGURE: c_uint = 7;
82
83 pub const FSMOUNT_CLOEXEC: c_uint = 0x00000001;
84
85 pub const MOUNT_ATTR_RDONLY: c_uint = 0x00000001;
86 pub const MOUNT_ATTR_NOSUID: c_uint = 0x00000002;
87 pub const MOUNT_ATTR_NODEV: c_uint = 0x00000004;
88 pub const MOUNT_ATTR_NOEXEC: c_uint = 0x00000008;
89 #[allow(dead_code)]
90 pub const MOUNT_ATTR__ATIME: c_uint = 0x00000070;
91 pub const MOUNT_ATTR_RELATIME: c_uint = 0x00000000;
92 pub const MOUNT_ATTR_NOATIME: c_uint = 0x00000010;
93 pub const MOUNT_ATTR_STRICTATIME: c_uint = 0x00000020;
94 pub const MOUNT_ATTR_NODIRATIME: c_uint = 0x00000080;
95
96 pub const AT_RECURSIVE: c_uint = 0x8000;
97}
98
99mod sys {
100 use crate::libc::{
101 c_char, c_int, c_uint, c_void, syscall, SYS_fsconfig, SYS_fsmount, SYS_fsopen,
102 SYS_fspick, SYS_move_mount, SYS_open_tree,
103 };
104
105 pub unsafe fn open_tree(dfd: c_int, filename: *const c_char, flags: c_uint) -> c_int {
106 syscall(
107 SYS_open_tree,
108 dfd as usize,
109 filename as usize,
110 flags as usize,
111 ) as c_int
112 }
113
114 pub unsafe fn move_mount(
115 from_dfd: c_int,
116 from_pathname: *const c_char,
117 to_dfd: c_int,
118 to_pathname: *const c_char,
119 flags: c_uint,
120 ) -> c_int {
121 syscall(
122 SYS_move_mount,
123 from_dfd as usize,
124 from_pathname as usize,
125 to_dfd as usize,
126 to_pathname as usize,
127 flags as usize,
128 ) as c_int
129 }
130
131 pub unsafe fn fsopen(fs_name: *const c_char, flags: c_uint) -> c_int {
132 syscall(SYS_fsopen, fs_name as usize, flags as usize) as c_int
133 }
134
135 pub unsafe fn fsconfig(
136 fd: c_int,
137 cmd: c_uint,
138 key: *const c_char,
139 value: *const c_void,
140 aux: c_int,
141 ) -> c_int {
142 syscall(
143 SYS_fsconfig,
144 fd as usize,
145 cmd as usize,
146 key as usize,
147 value as usize,
148 aux as usize,
149 ) as c_int
150 }
151
152 pub unsafe fn fsmount(fs_fd: c_int, flags: c_uint, attr_flags: c_uint) -> c_int {
153 syscall(
154 SYS_fsmount,
155 fs_fd as usize,
156 flags as usize,
157 attr_flags as usize,
158 ) as c_int
159 }
160
161 pub unsafe fn fspick(dfd: c_int, path: *const c_char, flags: c_uint) -> c_int {
162 syscall(SYS_fspick, dfd as usize, path as usize, flags as usize) as c_int
163 }
164}
165
166libc_bitflags! {
167 pub struct OpenTreeFlags: c_uint {
168 AT_EMPTY_PATH as c_uint;
169 AT_NO_AUTOMOUNT as c_uint;
170 AT_RECURSIVE;
171 AT_SYMLINK_NOFOLLOW as c_uint;
172 OPEN_TREE_CLONE;
173 OPEN_TREE_CLOEXEC;
174 }
175}
176
177libc_bitflags! {
178 pub struct MoveMountFlags: c_uint {
179 MOVE_MOUNT_F_SYMLINKS;
180 MOVE_MOUNT_F_AUTOMOUNTS;
181 MOVE_MOUNT_F_EMPTY_PATH;
182 MOVE_MOUNT_T_SYMLINKS;
183 MOVE_MOUNT_T_AUTOMOUNTS;
184 MOVE_MOUNT_T_EMPTY_PATH;
185 }
186}
187
188libc_bitflags! {
189 pub struct FsopenFlags: c_uint {
190 FSOPEN_CLOEXEC;
191 }
192}
193
194libc_bitflags! {
195 pub struct FsmountFlags: c_uint {
196 FSMOUNT_CLOEXEC;
197 }
198}
199
200libc_bitflags! {
201 pub struct MountAttrFlags: c_uint {
202 MOUNT_ATTR_RDONLY;
203 MOUNT_ATTR_NOSUID;
204 MOUNT_ATTR_NODEV;
205 MOUNT_ATTR_NOEXEC;
206 MOUNT_ATTR_RELATIME;
207 MOUNT_ATTR_NOATIME;
208 MOUNT_ATTR_STRICTATIME;
209 MOUNT_ATTR_NODIRATIME;
210 }
211}
212
213libc_bitflags! {
214 pub struct FspickFlags: c_uint {
215 FSPICK_CLOEXEC;
216 FSPICK_SYMLINK_NOFOLLOW;
217 FSPICK_NO_AUTOMOUNT;
218 FSPICK_EMPTY_PATH;
219 }
220}
221
222pub fn open_tree<P>(dfd: RawFd, filename: &P, flags: OpenTreeFlags) -> Result<RawFd>
223where
224 P: ?Sized + NixPath,
225{
226 let res = filename.with_nix_path(|filename| unsafe {
227 sys::open_tree(dfd, filename.as_ptr(), flags.bits())
228 })?;
229 Errno::result(res)
230}
231
232pub fn move_mount<P, Q>(
233 from_dfd: RawFd,
234 from_pathname: &P,
235 to_dfd: RawFd,
236 to_pathname: &Q,
237 flags: MoveMountFlags,
238) -> Result<()>
239where
240 P: ?Sized + NixPath,
241 Q: ?Sized + NixPath,
242{
243 let res = from_pathname.with_nix_path(|from_pathname| {
244 to_pathname.with_nix_path(|to_pathname| unsafe {
245 sys::move_mount(
246 from_dfd,
247 from_pathname.as_ptr(),
248 to_dfd,
249 to_pathname.as_ptr(),
250 flags.bits(),
251 )
252 })
253 })??;
254 Errno::result(res).map(drop)
255}
256
257pub fn fsopen(fs_name: &CStr, flags: FsopenFlags) -> Result<RawFd> {
258 let res = unsafe { sys::fsopen(fs_name.as_ptr(), flags.bits()) };
259 Errno::result(res)
260}
261
262pub fn fsconfig_set_flag(fs_fd: RawFd, key: &CStr) -> Result<()> {
263 let res =
264 unsafe { sys::fsconfig(fs_fd, FSCONFIG_SET_FLAG, key.as_ptr(), ptr::null(), 0) };
265 Errno::result(res).map(drop)
266}
267
268pub fn fsconfig_set_string(fs_fd: RawFd, key: &CStr, value: &CStr) -> Result<()> {
269 let res = unsafe {
270 sys::fsconfig(
271 fs_fd,
272 FSCONFIG_SET_STRING,
273 key.as_ptr(),
274 value.as_ptr() as *const c_void,
275 0,
276 )
277 };
278 Errno::result(res).map(drop)
279}
280
281pub fn fsconfig_set_binary(fs_fd: RawFd, key: &CStr, value: &[u8]) -> Result<()> {
282 let len = match c_int::try_from(value.len()) {
283 Ok(len) => len,
284 Err(_) => return Err(Error::Sys(Errno::EINVAL)),
285 };
286 let res = unsafe {
287 sys::fsconfig(
288 fs_fd,
289 FSCONFIG_SET_BINARY,
290 key.as_ptr(),
291 value.as_ptr() as *const c_void,
292 len,
293 )
294 };
295 Errno::result(res).map(drop)
296}
297
298fn _fsconfig_set_path<P>(
299 fs_fd: RawFd,
300 cmd: c_uint,
301 key: &CStr,
302 dfd: RawFd,
303 path: &P,
304) -> Result<()>
305where
306 P: ?Sized + NixPath,
307{
308 let res = path.with_nix_path(|path| unsafe {
309 sys::fsconfig(
310 fs_fd,
311 cmd,
312 key.as_ptr(),
313 path.as_ptr() as *const c_void,
314 dfd,
315 )
316 })?;
317 Errno::result(res).map(drop)
318}
319
320pub fn fsconfig_set_path<P>(fs_fd: RawFd, key: &CStr, dfd: RawFd, path: &P) -> Result<()>
321where
322 P: ?Sized + NixPath,
323{
324 _fsconfig_set_path(fs_fd, FSCONFIG_SET_PATH, key, dfd, path)
325}
326
327pub fn fsconfig_set_path_empty<P>(
328 fs_fd: RawFd,
329 key: &CStr,
330 dfd: RawFd,
331 path: &P,
332) -> Result<()>
333where
334 P: ?Sized + NixPath,
335{
336 _fsconfig_set_path(fs_fd, FSCONFIG_SET_PATH_EMPTY, key, dfd, path)
337}
338
339pub fn fsconfig_set_fd(fs_fd: RawFd, key: &CStr, fd: RawFd) -> Result<()> {
340 let res =
341 unsafe { sys::fsconfig(fs_fd, FSCONFIG_SET_FD, key.as_ptr(), ptr::null(), fd) };
342 Errno::result(res).map(drop)
343}
344
345fn _fsconfig_cmd(fs_fd: RawFd, cmd: c_uint) -> Result<()> {
346 let res = unsafe { sys::fsconfig(fs_fd, cmd, ptr::null(), ptr::null(), 0) };
347 Errno::result(res).map(drop)
348}
349
350pub fn fsconfig_cmd_create(fs_fd: RawFd) -> Result<()> {
351 _fsconfig_cmd(fs_fd, FSCONFIG_CMD_CREATE)
352}
353
354pub fn fsconfig_cmd_reconfigure(fs_fd: RawFd) -> Result<()> {
355 _fsconfig_cmd(fs_fd, FSCONFIG_CMD_RECONFIGURE)
356}
357
358pub fn fsmount(
359 fs_fd: RawFd,
360 flags: FsmountFlags,
361 attr_flags: MountAttrFlags,
362) -> Result<RawFd> {
363 let res = unsafe { sys::fsmount(fs_fd, flags.bits(), attr_flags.bits()) };
364 Errno::result(res)
365}
366
367pub fn fspick<P>(dfd: RawFd, path: &P, flags: FspickFlags) -> Result<RawFd>
368where
369 P: ?Sized + NixPath,
370{
371 let res = path
372 .with_nix_path(|path| unsafe { sys::fspick(dfd, path.as_ptr(), flags.bits()) })?;
373 Errno::result(res)
374}
375
376pub struct Mount {
377 mnt_fd: RawFd,
378}
379
380impl Mount {
381 pub fn move_mount<P>(
382 &self,
383 to_dfd: RawFd,
384 to_path: &P,
385 mut flags: MoveMountFlags,
386 ) -> Result<()>
387 where
388 P: ?Sized + NixPath,
389 {
390 flags |= MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH;
391 move_mount(
392 self.mnt_fd,
393 CStr::from_bytes_with_nul(b"\0").unwrap(),
394 to_dfd,
395 to_path,
396 flags,
397 )
398 }
399
400 pub fn open<P>(dfd: RawFd, path: &P, flags: OpenTreeFlags) -> Result<Self>
401 where
402 P: ?Sized + NixPath,
403 {
404 Ok(Mount {
405 mnt_fd: open_tree(dfd, path, flags)?,
406 })
407 }
408}
409
410impl Drop for Mount {
411 fn drop(&mut self) {
412 let _ = close(self.mnt_fd);
413 }
414}
415
416impl AsRawFd for Mount {
417 fn as_raw_fd(&self) -> RawFd {
418 self.mnt_fd
419 }
420}
421
422impl IntoRawFd for Mount {
423 fn into_raw_fd(self) -> RawFd {
424 let mnt_fd = self.mnt_fd;
425 mem::forget(self);
426 mnt_fd
427 }
428}
429
430impl FromRawFd for Mount {
431 unsafe fn from_raw_fd(mnt_fd: RawFd) -> Self {
432 Mount { mnt_fd }
433 }
434}
435
436pub struct Fs {
437 fs_fd: RawFd,
438}
439
440impl Fs {
441 pub fn open(fs_name: &CStr, flags: FsopenFlags) -> Result<Self> {
442 Ok(Fs {
443 fs_fd: fsopen(fs_name, flags)?,
444 })
445 }
446
447 pub fn pick<P>(dfd: RawFd, path: &P, flags: FspickFlags) -> Result<Self>
448 where
449 P: ?Sized + NixPath,
450 {
451 Ok(Fs {
452 fs_fd: fspick(dfd, path, flags)?,
453 })
454 }
455
456 pub fn set_flag(&self, key: &CStr) -> Result<()> {
457 fsconfig_set_flag(self.fs_fd, key)
458 }
459
460 pub fn set_string(&self, key: &CStr, value: &CStr) -> Result<()> {
461 fsconfig_set_string(self.fs_fd, key, value)
462 }
463
464 pub fn set_binary(&self, key: &CStr, value: &[u8]) -> Result<()> {
465 fsconfig_set_binary(self.fs_fd, key, value)
466 }
467
468 pub fn set_path<P>(&self, key: &CStr, dfd: RawFd, path: &P) -> Result<()>
469 where
470 P: ?Sized + NixPath,
471 {
472 fsconfig_set_path(self.fs_fd, key, dfd, path)
473 }
474
475 pub fn set_path_empty<P>(&self, key: &CStr, dfd: RawFd, path: &P) -> Result<()>
476 where
477 P: ?Sized + NixPath,
478 {
479 fsconfig_set_path_empty(self.fs_fd, key, dfd, path)
480 }
481
482 pub fn set_path_fd(&self, key: &CStr, fd: RawFd) -> Result<()> {
483 fsconfig_set_fd(self.fs_fd, key, fd)
484 }
485
486 pub fn create(&self) -> Result<()> {
487 fsconfig_cmd_create(self.fs_fd)
488 }
489
490 pub fn reconfigure(&self) -> Result<()> {
491 fsconfig_cmd_reconfigure(self.fs_fd)
492 }
493
494 pub fn mount(
495 &self,
496 flags: FsmountFlags,
497 attr_flags: MountAttrFlags,
498 ) -> Result<Mount> {
499 Ok(Mount {
500 mnt_fd: fsmount(self.fs_fd, flags, attr_flags)?,
501 })
502 }
503}
504
505impl Drop for Fs {
506 fn drop(&mut self) {
507 let _ = close(self.fs_fd);
508 }
509}
510
511impl AsRawFd for Fs {
512 fn as_raw_fd(&self) -> RawFd {
513 self.fs_fd
514 }
515}
516
517impl IntoRawFd for Fs {
518 fn into_raw_fd(self) -> RawFd {
519 let fs_fd = self.fs_fd;
520 mem::forget(self);
521 fs_fd
522 }
523}
524
525impl FromRawFd for Fs {
526 unsafe fn from_raw_fd(fs_fd: RawFd) -> Self {
527 Fs { fs_fd }
528 }
529}
530
531#[cfg(test)]
532mod test {
533 use crate::*;
534 use nix::{
535 fcntl::{openat, OFlag},
536 libc::AT_FDCWD,
537 mount::{mount, umount2, MntFlags, MsFlags},
538 sys::stat::Mode,
539 unistd::close,
540 NixPath,
541 };
542 use std::{
543 ffi::CStr,
544 fs,
545 os::unix::io::{AsRawFd, RawFd},
546 path::Path,
547 };
548 use tempfile::{tempdir, tempdir_in};
549
550 fn cstr(s: &str) -> &CStr {
551 CStr::from_bytes_with_nul(s.as_bytes()).unwrap()
552 }
553
554 fn tmpfs() -> Mount {
555 let fs = Fs::open(cstr("tmpfs\0"), FsopenFlags::empty()).unwrap();
556 fs.create().unwrap();
557 fs.mount(FsmountFlags::empty(), MountAttrFlags::empty())
558 .unwrap()
559 }
560
561 fn with_mount_point<'a, P: Into<Option<&'a Path>>, T, F: FnOnce(&Path) -> T>(
562 base: P,
563 f: F,
564 ) -> T {
565 struct Umount<'a>(&'a Path);
566
567 impl<'a> Drop for Umount<'a> {
568 fn drop(&mut self) {
569 let _ = umount2(self.0, MntFlags::MNT_DETACH);
570 }
571 }
572
573 let dir = match base.into() {
574 Some(base) => tempdir_in(base),
575 _ => tempdir(),
576 }
577 .unwrap();
578 let _umount = Umount(dir.path());
579 f(dir.path())
580 }
581
582 fn with_private_mount<T, F: FnOnce(&Path) -> T>(f: F) -> T {
583 with_mount_point(None, |path| {
584 let fs = tmpfs();
585 fs.move_mount(AT_FDCWD, path, MoveMountFlags::empty())
586 .unwrap();
587 mount::<Path, _, Path, Path>(None, path, None, MsFlags::MS_PRIVATE, None)
588 .unwrap();
589 f(path)
590 })
591 }
592
593 fn create_file<P: ?Sized + NixPath>(dfd: RawFd, p: &P) {
594 let _ = close(
595 openat(dfd, p, OFlag::O_CREAT | OFlag::O_RDONLY, Mode::empty()).unwrap(),
596 );
597 }
598
599 #[test]
600 fn move_mount1() {
601 with_private_mount(|path| {
602 with_mount_point(path, |p1| {
603 with_mount_point(path, |p2| {
604 const FILE: &str = "test";
605
606 let mnt = tmpfs();
607 create_file(mnt.as_raw_fd(), FILE);
608
609 mnt.move_mount(AT_FDCWD, p1, MoveMountFlags::empty())
611 .unwrap();
612 assert!(p1.join(FILE).exists());
613
614 mnt.move_mount(AT_FDCWD, p2, MoveMountFlags::empty())
616 .unwrap();
617 assert!(!p1.join(FILE).exists());
618 assert!(p2.join(FILE).exists());
619
620 let mnt = Mount::open(AT_FDCWD, p2, OpenTreeFlags::empty()).unwrap();
622 mnt.move_mount(AT_FDCWD, p1, MoveMountFlags::empty())
623 .unwrap();
624 assert!(p1.join(FILE).exists());
625 assert!(!p2.join(FILE).exists());
626
627 move_mount(AT_FDCWD, p1, AT_FDCWD, p2, MoveMountFlags::empty())
629 .unwrap();
630 assert!(!p1.join(FILE).exists());
631 assert!(p2.join(FILE).exists());
632
633 let mnt = Mount::open(AT_FDCWD, p2, OpenTreeFlags::OPEN_TREE_CLONE)
635 .unwrap();
636 mnt.move_mount(AT_FDCWD, p1, MoveMountFlags::empty())
637 .unwrap();
638 assert!(p1.join(FILE).exists());
639 assert!(p2.join(FILE).exists());
640 });
641 });
642 });
643 }
644
645 #[test]
646 fn fspick1() {
647 with_mount_point(None, |p1| {
648 let mnt = tmpfs();
649 mnt.move_mount(AT_FDCWD, p1, MoveMountFlags::empty())
650 .unwrap();
651
652 fs::create_dir(p1.join("a")).unwrap();
653
654 let fs = Fs::pick(AT_FDCWD, p1, FspickFlags::empty()).unwrap();
656 fs.set_flag(CStr::from_bytes_with_nul(b"ro\0").unwrap())
657 .unwrap();
658 fs.reconfigure().unwrap();
659
660 assert!(fs::create_dir(p1.join("b")).is_err());
661 });
662 }
663
664 #[test]
665 fn fsmount1() {
666 let fs = Fs::open(cstr("proc\0"), FsopenFlags::empty()).unwrap();
667 fs.create().unwrap();
668 let mnt = fs
669 .mount(FsmountFlags::empty(), MountAttrFlags::empty())
670 .unwrap();
671
672 let _ = close(
674 openat(mnt.as_raw_fd(), "cpuinfo", OFlag::O_RDONLY, Mode::empty()).unwrap(),
675 );
676 }
677}