mount_api/
lib.rs

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
18// copied from nix
19macro_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    // https://github.com/rust-lang/libc/pull/1759
47    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    // https://github.com/rust-lang/libc/pull/????
55    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                    // move to p1
610                    mnt.move_mount(AT_FDCWD, p1, MoveMountFlags::empty())
611                        .unwrap();
612                    assert!(p1.join(FILE).exists());
613
614                    // move from p1 to p2
615                    mnt.move_mount(AT_FDCWD, p2, MoveMountFlags::empty())
616                        .unwrap();
617                    assert!(!p1.join(FILE).exists());
618                    assert!(p2.join(FILE).exists());
619
620                    // open tree at p2 and move to p1
621                    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 from p2 to p1
628                    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                    // clone tree at p2 and attach to p1
634                    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            // make read-only
655            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        // open /proc/cpuinfo
673        let _ = close(
674            openat(mnt.as_raw_fd(), "cpuinfo", OFlag::O_RDONLY, Mode::empty()).unwrap(),
675        );
676    }
677}