Skip to content

Commit

Permalink
tests: add integration tests for capi from Rust
Browse files Browse the repository at this point in the history
This lets us finally get test coverage of our C API directly, as well as
coverage information.The only thing to note is that we can't test
resolve_partial because that is an internal resolver thing that isn't
exposed to the C API.

One thing to note is that the pathrs_reopen() tests are testing the
~O_CLOEXEC path right now even though that doesn't match the Rust API.
We probably want to add fd flag tests to pathrs_reopen(), which will
require correcting this issue.

Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
  • Loading branch information
cyphar committed Sep 10, 2024
1 parent 4213ce3 commit 4ad9067
Show file tree
Hide file tree
Showing 11 changed files with 894 additions and 15 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ lint-rust:

.PHONY: test-rust-doctest
test-rust-doctest:
$(CARGO_NIGHTLY) llvm-cov --no-report --branch --doc
$(CARGO_NIGHTLY) llvm-cov --no-report --branch --all-features --doc

.PHONY: test-rust-unpriv
test-rust-unpriv:
$(CARGO_NIGHTLY) llvm-cov --no-report --branch nextest --no-fail-fast
$(CARGO_NIGHTLY) llvm-cov --no-report --branch --features capi nextest --no-fail-fast

.PHONY: test-rust-root
test-rust-root:
Expand All @@ -73,7 +73,7 @@ test-rust-root:
# support cfg(feature=...) for target runner configs.
# See <https://github.com/rust-lang/cargo/issues/14306>.
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' \
$(CARGO_NIGHTLY) llvm-cov --no-report --branch --features _test_as_root nextest --no-fail-fast
$(CARGO_NIGHTLY) llvm-cov --no-report --branch --features capi,_test_as_root nextest --no-fail-fast

.PHONY: test-rust
test-rust:
Expand Down
9 changes: 9 additions & 0 deletions src/capi/procfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ impl From<CProcfsBase> for ProcfsBase {
}
}

impl From<ProcfsBase> for CProcfsBase {
fn from(base: ProcfsBase) -> Self {
match base {
ProcfsBase::ProcSelf => CProcfsBase::PATHRS_PROC_SELF,
ProcfsBase::ProcThreadSelf => CProcfsBase::PATHRS_PROC_THREAD_SELF,
}
}
}

/// Safely open a path inside a `/proc` handle.
///
/// Any bind-mounts or other over-mounts will (depending on what kernel features
Expand Down
101 changes: 101 additions & 0 deletions src/tests/capi/handle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2024 Aleksa Sarai <cyphar@cyphar.com>
* 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 crate::{
capi,
flags::OpenFlags,
tests::{
capi::utils::{self as capi_utils, CapiError},
traits::HandleImpl,
},
};

use std::{
fs::File,
os::unix::io::{AsFd, BorrowedFd, OwnedFd},
};

#[derive(Debug)]
pub struct CapiHandle {
inner: OwnedFd,
}

impl CapiHandle {
fn from_fd_unchecked<Fd: Into<OwnedFd>>(fd: Fd) -> Self {
Self { inner: fd.into() }
}

fn try_clone(&self) -> Result<Self, anyhow::Error> {
Ok(Self::from_fd_unchecked(self.inner.try_clone()?))
}

fn reopen<F: Into<OpenFlags>>(&self, flags: F) -> Result<File, CapiError> {
let fd = self.inner.as_fd();
let flags = flags.into();

capi_utils::call_capi_fd(|| capi::core::pathrs_reopen(fd.into(), flags.bits()))
.map(File::from)
}
}

impl AsFd for CapiHandle {
fn as_fd(&self) -> BorrowedFd<'_> {
self.inner.as_fd()
}
}

impl From<CapiHandle> for OwnedFd {
fn from(handle: CapiHandle) -> Self {
handle.inner
}
}

impl HandleImpl for CapiHandle {
type Cloned = CapiHandle;
type Error = CapiError;

fn from_fd_unchecked<Fd: Into<OwnedFd>>(fd: Fd) -> Self::Cloned {
Self::Cloned::from_fd_unchecked(fd)
}

fn try_clone(&self) -> Result<Self::Cloned, anyhow::Error> {
self.try_clone().map_err(From::from)
}

fn reopen<F: Into<OpenFlags>>(&self, flags: F) -> Result<File, Self::Error> {
self.reopen(flags)
}
}

impl HandleImpl for &CapiHandle {
type Cloned = CapiHandle;
type Error = CapiError;

fn from_fd_unchecked<Fd: Into<OwnedFd>>(fd: Fd) -> Self::Cloned {
Self::Cloned::from_fd_unchecked(fd)
}

fn try_clone(&self) -> Result<Self::Cloned, anyhow::Error> {
CapiHandle::try_clone(self).map_err(From::from)
}

fn reopen<F: Into<OpenFlags>>(&self, flags: F) -> Result<File, Self::Error> {
CapiHandle::reopen(self, flags)
}
}
31 changes: 31 additions & 0 deletions src/tests/capi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2024 Aleksa Sarai <cyphar@cyphar.com>
* 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/>.
*/

#![allow(unsafe_code)]

mod utils;

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

mod handle;
pub(in crate::tests) use handle::*;

mod procfs;
pub(in crate::tests) use procfs::*;
104 changes: 104 additions & 0 deletions src/tests/capi/procfs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2024 Aleksa Sarai <cyphar@cyphar.com>
* 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 crate::{
capi::{self, procfs::CProcfsBase},
flags::OpenFlags,
procfs::ProcfsBase,
tests::{
capi::utils::{self as capi_utils, CapiError},
traits::ProcfsHandleImpl,
},
};

use std::{
fs::File,
path::{Path, PathBuf},
};

// NOTE: The C API only lets us access the global PROCFS_HANDLE reference.
#[derive(Debug)]
pub struct CapiProcfsHandle;

impl CapiProcfsHandle {
fn open_follow<P: AsRef<Path>, F: Into<OpenFlags>>(
&self,
base: ProcfsBase,
subpath: P,
oflags: F,
) -> Result<File, CapiError> {
let base: CProcfsBase = base.into();
let subpath = capi_utils::path_to_cstring(subpath);
let oflags = oflags.into();

capi_utils::call_capi_fd(|| unsafe {
capi::procfs::pathrs_proc_open(base, subpath.as_ptr(), oflags.bits())
})
.map(File::from)
}

fn open<P: AsRef<Path>, F: Into<OpenFlags>>(
&self,
base: ProcfsBase,
subpath: P,
oflags: F,
) -> Result<File, CapiError> {
// The C API exposes ProcfsHandle::open using O_NOFOLLOW.
self.open_follow(base, subpath, oflags.into() | OpenFlags::O_NOFOLLOW)
}

fn readlink<P: AsRef<Path>>(&self, base: ProcfsBase, subpath: P) -> Result<PathBuf, CapiError> {
let base: CProcfsBase = base.into();
let subpath = capi_utils::path_to_cstring(subpath);

capi_utils::call_capi_readlink(|linkbuf, linkbuf_size| unsafe {
capi::procfs::pathrs_proc_readlink(base, subpath.as_ptr(), linkbuf, linkbuf_size)
})
}
}

impl ProcfsHandleImpl for CapiProcfsHandle {
type Error = CapiError;

fn open_follow<P: AsRef<Path>, F: Into<OpenFlags>>(
&self,
base: ProcfsBase,
subpath: P,
flags: F,
) -> Result<File, Self::Error> {
self.open_follow(base, subpath, flags)
}

fn open<P: AsRef<Path>, F: Into<OpenFlags>>(
&self,
base: ProcfsBase,
subpath: P,
flags: F,
) -> Result<File, Self::Error> {
self.open(base, subpath, flags)
}

fn readlink<P: AsRef<Path>>(
&self,
base: ProcfsBase,
subpath: P,
) -> Result<PathBuf, Self::Error> {
self.readlink(base, subpath)
}
}
Loading

0 comments on commit 4ad9067

Please sign in to comment.