Skip to content

Commit

Permalink
tests: add ProcfsHandle tests
Browse files Browse the repository at this point in the history
Signed-off-by: Aleksa Sarai <[email protected]>
  • Loading branch information
cyphar committed Jul 28, 2024
1 parent 845e212 commit c3b8c39
Show file tree
Hide file tree
Showing 7 changed files with 419 additions and 5 deletions.
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,19 @@ jobs:
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@nextest

- name: cargo nextest
- name: unit tests
run: cargo llvm-cov --no-report nextest
- name: cargo test --doc
- name: doctests
run: cargo llvm-cov --no-report --doc

# Run the unit tests as root.
# NOTE: Ideally this would be configured in .cargo/config.toml so it
# would also work locally, but unfortunately it seems cargo doesn't
# support cfg(feature=...) for target runner configs.
# See <https://github.com/rust-lang/cargo/issues/14306>.
- name: unit tests (root)
run: CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' cargo llvm-cov --no-report --features _test_as_root nextest

- name: calculate coverage
run: cargo llvm-cov report
- name: generate coverage html
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ travis-ci = { repository = "openSUSE/libpathrs" }
[lib]
crate-type = ["rlib", "cdylib", "staticlib"]

[features]
# Only used for tests.
_test_as_root = []

[profile.release]
# Enable link-time optimisations.
lto = true
Expand Down
6 changes: 3 additions & 3 deletions src/procfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl ProcfsHandle {
// This is part of Linux's ABI.
const PROC_ROOT_INO: u64 = 1;

fn new_fsopen() -> Result<Self, Error> {
pub(crate) fn new_fsopen() -> Result<Self, Error> {
let sfd =
syscalls::fsopen("proc", FsopenFlags::FSOPEN_CLOEXEC).context(error::RawOsSnafu {
operation: "create procfs suberblock",
Expand All @@ -164,7 +164,7 @@ impl ProcfsHandle {
.and_then(Self::try_from)
}

fn new_open_tree(flags: OpenTreeFlags) -> Result<Self, Error> {
pub(crate) fn new_open_tree(flags: OpenTreeFlags) -> Result<Self, Error> {
syscalls::open_tree(
-libc::EBADF,
"/proc",
Expand All @@ -177,7 +177,7 @@ impl ProcfsHandle {
.and_then(Self::try_from)
}

fn new_unsafe_open() -> Result<Self, Error> {
pub(crate) fn new_unsafe_open() -> Result<Self, Error> {
syscalls::openat(libc::AT_FDCWD, "/proc", libc::O_PATH | libc::O_DIRECTORY, 0)
.context(error::RawOsSnafu {
operation: "open /proc handle",
Expand Down
117 changes: 117 additions & 0 deletions src/tests/common/mntns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2024 Aleksa Sarai <[email protected]>
* Copyright (C) 2019-2024 SUSE LLC
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/

use std::{
ffi::CString,
fs::File,
io::Error as IOError,
os::fd::{AsRawFd, RawFd},
path::{Path, PathBuf},
ptr,
};

use crate::syscalls;

use anyhow::Error;
use libc::c_int;

unsafe fn unshare(flags: c_int) -> Result<(), IOError> {
// SAFETY: Caller guarantees that this unshare operation is safe.
let ret = unsafe { libc::unshare(flags) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err)
}
}

unsafe fn setns(fd: RawFd, flags: c_int) -> Result<(), IOError> {
// SAFETY: Caller guarantees that this setns operation is safe.
let ret = unsafe { libc::setns(fd, flags) };
let err = IOError::last_os_error();
if ret >= 0 {
Ok(())
} else {
Err(err)
}
}

#[derive(Debug, Clone)]
pub(crate) enum MountType {
Tmpfs,
Bind { src: PathBuf },
}

pub(crate) fn mount<P: AsRef<Path>>(dst: P, ty: MountType) -> Result<(), Error> {
let dst = dst.as_ref();
let dst_file = syscalls::openat(libc::AT_FDCWD, dst, libc::O_NOFOLLOW | libc::O_PATH, 0)?;
let dst_path = CString::new(format!("/proc/self/fd/{}", dst_file.as_raw_fd()))?;

let ret = match ty {
MountType::Tmpfs => unsafe {
libc::mount(
CString::new("")?.as_ptr(),
dst_path.as_ptr(),
CString::new("tmpfs")?.as_ptr(),
0,
ptr::null(),
)
},
MountType::Bind { src } => {
let src_file =
syscalls::openat(libc::AT_FDCWD, src, libc::O_NOFOLLOW | libc::O_PATH, 0)?;
let src_path = CString::new(format!("/proc/self/fd/{}", src_file.as_raw_fd()))?;
unsafe {
libc::mount(
src_path.as_ptr(),
dst_path.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
}
}
};
let err = IOError::last_os_error();

if ret >= 0 {
Ok(())
} else {
Err(err.into())
}
}

pub(crate) fn in_mnt_ns<F, T>(func: F) -> Result<T, Error>
where
F: FnOnce() -> Result<T, Error>,
{
let old_ns = File::open("/proc/self/ns/mnt")?;

// TODO: Run this in a subprocess.

unsafe { unshare(libc::CLONE_FS | libc::CLONE_NEWNS) }
.expect("unable to create a mount namespace");

let ret = func();

unsafe { setns(old_ns.as_raw_fd(), libc::CLONE_NEWNS) }
.expect("unable to rejoin old namespace");

ret
}
3 changes: 3 additions & 0 deletions src/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@

mod root;
pub(crate) use root::*;

mod mntns;
pub(in crate::tests) use mntns::*;
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@

pub(crate) mod common;

mod test_procfs;
mod test_resolve;
Loading

0 comments on commit c3b8c39

Please sign in to comment.