From d9301b855d981ebb14e8f0f919eb2699ec5a67b8 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 13:02:29 +0200 Subject: [PATCH 1/7] fix(clippy): implement Default for UffdBuilder Fix a new without default warning in `UffdBuilder` new() implementation. Implement `Default` for `UffdBuilder` by hand and make `UffdBuilder::new()` return `UffdBuilder::default()`. `#[derive(Default)]` can't be used here: it would set `user_mode_only` to `false`, while `new()` has always defaulted it to `true`. Add a unit test that checks the defaults of `UffdBuilder::new()`. Also derive `Default` for `FeatureFlags` and `IoctlFlags`; the default value is the empty set of flags. Signed-off-by: Babis Chalios --- src/builder.rs | 33 +++++++++++++++++++++++++++------ src/lib.rs | 2 +- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 7b2a599..a65af79 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -13,7 +13,7 @@ cfg_if::cfg_if! { if #[cfg(any(feature = "linux5_7", feature = "linux4_14"))] { bitflags! { /// Used with `UffdBuilder` to determine which features are available in the current kernel. - #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct FeatureFlags: u64 { const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; @@ -29,7 +29,7 @@ cfg_if::cfg_if! { } else { bitflags! { /// Used with `UffdBuilder` to determine which features are available in the current kernel. - #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct FeatureFlags: u64 { const PAGEFAULT_FLAG_WP = raw::UFFD_FEATURE_PAGEFAULT_FLAG_WP; const EVENT_FORK = raw::UFFD_FEATURE_EVENT_FORK; @@ -62,10 +62,8 @@ pub struct UffdBuilder { req_ioctls: IoctlFlags, } -impl UffdBuilder { - /// Create a new builder with no required features or ioctls, `close_on_exec` and - /// `non_blocking` both set to `false`, and `user_mode_only` set to `true`. - pub fn new() -> UffdBuilder { +impl Default for UffdBuilder { + fn default() -> Self { UffdBuilder { close_on_exec: false, non_blocking: false, @@ -74,6 +72,14 @@ impl UffdBuilder { req_ioctls: IoctlFlags::empty(), } } +} + +impl UffdBuilder { + /// Create a new builder with no required features or ioctls, `close_on_exec` and + /// `non_blocking` both set to `false`, and `user_mode_only` set to `true`. + pub fn new() -> UffdBuilder { + UffdBuilder::default() + } /// Enable the close-on-exec flag for the new userfaultfd object (see the description of /// `O_CLOEXEC` in [`open(2)`](http://man7.org/linux/man-pages/man2/open.2.html)). @@ -195,3 +201,18 @@ impl UffdBuilder { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_new_defaults() { + let builder = UffdBuilder::new(); + assert!(!builder.close_on_exec); + assert!(!builder.non_blocking); + assert!(builder.user_mode_only); + assert!(builder.req_features.is_empty()); + assert!(builder.req_ioctls.is_empty()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 59c2ce8..53ddc9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -402,7 +402,7 @@ impl Uffd { bitflags! { /// Used with `UffdBuilder` and `Uffd::register()` to determine which operations are available. - #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct IoctlFlags: u64 { const REGISTER = 1 << raw::_UFFDIO_REGISTER; const UNREGISTER = 1 << raw::_UFFDIO_UNREGISTER; From 55ca1f2fa2a26034f800734f3ac126551572741b Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 13:10:40 +0200 Subject: [PATCH 2/7] fix(clippy): add Safety sections in public methods docstring `Uffd::copy` and `Uffd::zeropage` write to a memory range of the calling process behind Rust's back. Document that the caller must ensure it is sound for the contents of that range to change, e.g. that no Rust references to it are live. The other requirements on the arguments (page alignment, the range being registered with the Uffd object, `src` being readable) are checked by the kernel, which returns an error if they don't hold, so they are not safety requirements. Signed-off-by: Babis Chalios --- src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 53ddc9d..7d7f140 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -141,6 +141,12 @@ impl Uffd { /// /// If `wake` is `true`, wake up the thread waiting for page fault resolution on the memory /// range. + /// + /// # Safety + /// + /// This writes to the memory range `[dst, dst + len)` of the calling process. The caller must + /// ensure that it is sound for the contents of that range to change, e.g. that no Rust + /// references to it are live. pub unsafe fn copy( &self, src: *const c_void, @@ -180,6 +186,12 @@ impl Uffd { /// /// If `wake` is `true`, wake up the thread waiting for page fault resolution on the memory /// address range. + /// + /// # Safety + /// + /// This writes to the memory range `[start, start + len)` of the calling process. The caller + /// must ensure that it is sound for the contents of that range to change, e.g. that no Rust + /// references to it are live. pub unsafe fn zeropage(&self, start: *mut c_void, len: usize, wake: bool) -> Result { let mut zeropage = raw::uffdio_zeropage { range: raw::uffdio_range { From a3de996057ae32e7fba1d6ddfbe7a1ef56629538 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 13:13:07 +0200 Subject: [PATCH 3/7] fix(clippy): simplify pattern match style Apply pattern match directly on the Errno variant (Errno::EAGAIN) instead of using a guard. Signed-off-by: Babis Chalios --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7d7f140..f0e3a0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -392,7 +392,7 @@ impl Uffd { }; let count = match read(self, buf) { - Err(e) if e == Errno::EAGAIN => 0, + Err(Errno::EAGAIN) => 0, Err(e) => return Err(Error::SystemError(e)), Ok(0) => return Err(Error::ReadEof), Ok(bytes_read) => { From 6378837c113c337d3df021a1f8c394481dee760f Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 13:14:37 +0200 Subject: [PATCH 4/7] fix(clippy): remove redundant closure from .map() Drop the closure and pass the associated function itself, directly. Signed-off-by: Babis Chalios --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f0e3a0b..3ce4a4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -408,7 +408,7 @@ impl Uffd { } }; - Ok(msgs.iter().take(count).map(|msg| Event::from_uffd_msg(msg))) + Ok(msgs.iter().take(count).map(Event::from_uffd_msg)) } } From 7c9c707bea2ed413cf5667701753059ca2f839c0 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 13:16:53 +0200 Subject: [PATCH 5/7] fix(clippy): don't use deprecated INT_MAX Use c_int::MAX instead. Signed-off-by: Babis Chalios --- src/raw.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/raw.rs b/src/raw.rs index 385b1f2..8b21464 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -1,9 +1,9 @@ -use libc::{c_int, c_long, syscall, SYS_userfaultfd, INT_MAX}; +use libc::{c_int, c_long, syscall, SYS_userfaultfd}; pub use userfaultfd_sys::*; pub unsafe fn userfaultfd(flags: c_int) -> c_int { let fd = syscall(SYS_userfaultfd, flags as c_long); - if fd > INT_MAX as c_long { + if fd > c_int::MAX as c_long { panic!("fd doesn't fit in a c_int"); } else { fd as c_int From 4e6162629559341cb19942685f0de00ee7ec18f0 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 13:31:00 +0200 Subject: [PATCH 6/7] fix(clippy): fix the remaining warnings in tests Use `assert!` instead of `assert_eq!` with a literal bool, and drop a needless borrow of the arguments passed to `Command::args()`. Signed-off-by: Babis Chalios --- src/lib.rs | 2 +- tests/manpage_example.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ce4a4c..48ac4be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -611,7 +611,7 @@ mod test { .. } => { let index = (addr as usize - mapping as usize) / PAGE_SIZE; - assert_eq!(seen[index], false); + assert!(!seen[index]); seen[index] = true; uffd.zeropage(addr, PAGE_SIZE, true)?; } diff --git a/tests/manpage_example.rs b/tests/manpage_example.rs index 49a061f..355bfe7 100644 --- a/tests/manpage_example.rs +++ b/tests/manpage_example.rs @@ -1,7 +1,7 @@ #[test] fn run_manpage_example() { let output = std::process::Command::new("cargo") - .args(&["run", "--example", "manpage", "--", "3"]) + .args(["run", "--example", "manpage", "--", "3"]) .output() .expect("manpage example failed to start"); assert!(output.status.success(), "manpage example succeeded"); From c4a19cddb28beab070d0158cdb597003e3c5ac97 Mon Sep 17 00:00:00 2001 From: Babis Chalios Date: Sun, 18 Oct 2026 17:06:09 +0200 Subject: [PATCH 7/7] ci: add lint job running rustfmt and clippy Check formatting with `cargo fmt --check` and run clippy on all targets for every kernel feature level, failing on any warning. This keeps the codebase warning-free now that the existing clippy warnings are fixed. Signed-off-by: Babis Chalios --- .github/workflows/rust.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ed3c9d2..f9088ce 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,6 +37,20 @@ jobs: - name: Run tests (Linux 5.7 support) run: cargo test --verbose --features linux5_7 + lint: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Check formatting + run: cargo fmt --all --check + - name: Clippy + run: | + for f in "" linux4_14 linux5_7 linux5_13; do + cargo clippy --all-targets ${f:+--features $f} -- -D warnings + done + audit: runs-on: ubuntu-latest