Skip to content

Support dm without udev#1036

Open
Apokleos wants to merge 1 commit into
stratis-storage:masterfrom
Apokleos:support-dm-without-udev
Open

Support dm without udev#1036
Apokleos wants to merge 1 commit into
stratis-storage:masterfrom
Apokleos:support-dm-without-udev

Conversation

@Apokleos
Copy link
Copy Markdown

@Apokleos Apokleos commented May 12, 2026

It aims to address issue #1035

Summary by CodeRabbit

  • Bug Fixes

    • Improved Device Mapper device lifecycle operations when udev synchronization is disabled. Device operations (create, load, suspend, resume, remove) now proceed gracefully instead of failing when udev integration is unavailable or explicitly disabled.
  • Tests

    • Added test coverage for device lifecycle validation with udev synchronization fully disabled.

Review Change Stack

@packit-as-a-service
Copy link
Copy Markdown

Congratulations! One of the builds has completed. 🍾

You can install the built RPMs by following these steps:

  • sudo dnf install -y 'dnf*-command(copr)'
  • dnf copr enable packit/stratis-storage-devicemapper-rs-1036
  • And now you can install the packages.

Please note that the RPMs should be used only in a testing environment.

@Apokleos
Copy link
Copy Markdown
Author

Hey @mulkieran I need your help to review this PR. Really appreciate it.

@mulkieran
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Walkthrough

The PR modifies device-mapper's udev synchronization handling to gracefully degrade when udev is disabled or unavailable. DM::do_ioctl now clears event_nr for ioctls that do not support udev event numbering, while UdevSync::begin returns an inactive synchronization object instead of failing when udev is unavailable or disabled. Comprehensive tests validate the full device lifecycle with all udev flags disabled.

Changes

Udev disable and graceful fallback handling

Layer / File(s) Summary
DM ioctl event_nr gating
src/core/dm.rs
DM::do_ioctl conditionally clears hdr.event_nr based on ioctl command, preserving it only for device remove, rename, and suspend commands that support udev event numbering.
UdevSync fallback when udev is disabled or missing
src/core/dm_udev_sync.rs
UdevSync::begin refactors ioctl gating logic and changes fallback behavior: when udev is disabled via DM_UDEV_DISABLE_LIBRARY_FALLBACK or the udev daemon is not running, it clears hdr.event_nr and returns an inactive UdevSync. For ioctls that do not require synchronization, it returns immediately without allocating a semaphore.
Test coverage for udev disable and lifecycle
src/core/dm.rs, src/core/dm_udev_sync.rs
New no_udev_dm_options() helper and sudo_test_no_udev_device_lifecycle test validate a complete device lifecycle (create, load table, resume, query info, remove) with all udev flags disabled. End-to-end validation confirms that udev-sync commands return inactive UdevSync with event_nr cleared for each relevant ioctl when disable flags are set.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #1035: Directly addresses the UdevSync::begin failure when udev is unavailable by implementing graceful fallback instead of hard-failing, while respecting DM_UDEV_DISABLE_* flags and allocating semaphores only for ioctls that require synchronization.

Suggested reviewers

  • jbaublitz
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main change: enabling devicemapper to function without udev dependency, which is the core objective addressed across both modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mulkieran
Copy link
Copy Markdown
Member

@Apokleos Thanks for the PR! Please fix the clippy error and force push to resume testing.

@mulkieran mulkieran moved this to In Review in 2026May May 12, 2026
@Apokleos Apokleos force-pushed the support-dm-without-udev branch from 2052ecc to cd043a1 Compare May 13, 2026 01:52
@Apokleos
Copy link
Copy Markdown
Author

Apokleos commented May 13, 2026

@Apokleos Thanks for the PR! Please fix the clippy error and force push to resume testing.

Hey @mulkieran Thanks very much. I have updated the code, but I cannot run the reset CI, which I just can see

2 workflows awaiting approval
This workflow requires approval from a maintainer. 

Could you please help approve to run the CI ? appreciate it.

@mulkieran
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
src/core/dm.rs (1)

1181-1217: 💤 Low value

Test reads as a good end-to-end sanity check, one small consistency nit.

The lifecycle test correctly exercises create → table_load → resume (suspend without DM_SUSPEND) → device_info → remove with the no-udev option set, and the post-resume assertion !info.flags().contains(DmFlags::DM_SUSPEND) is a solid invariant.

One small inconsistency worth fixing while you're here: the final device_remove call rebuilds the options via no_udev_dm_options() instead of reusing the local opts you already created on Line 1188. Using opts keeps the test uniform with steps 3 and would also catch any accidental divergence if no_udev_dm_options() is later changed.

♻️ Reuse the existing options binding
-        // Step 5: Remove device with no-udev options
-        dm.device_remove(&id, no_udev_dm_options()).unwrap();
+        // Step 5: Remove device with no-udev options
+        dm.device_remove(&id, opts).unwrap();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/dm.rs` around lines 1181 - 1217, The test
sudo_test_no_udev_device_lifecycle creates a local binding opts =
no_udev_dm_options() but then calls dm.device_remove(&id, no_udev_dm_options())
instead of reusing opts; change the final dm.device_remove call to use the
existing opts variable (dm.device_remove(&id, opts).unwrap()) so the test
consistently reuses the same options and will detect any future divergence in
no_udev_dm_options().
src/core/dm_udev_sync.rs (2)

298-298: ⚡ Quick win

Dense one-liner — break out the predicate for readability.

matches!(ioctl as u32, A | B | C if *SYSV_SEM_SUPPORTED && (hdr.flags & DmFlags::DM_SUSPEND.bits()) == 0) is syntactically fine but it mixes "which ioctl?" with "is the environment usable?" and "is this actually a resume, not a suspend?" on one ~150-char line. Splitting it makes the intent (and the SUSPEND-vs-resume distinction) obvious and easier to test/maintain.

♻️ Suggested split
-            // First check if this ioctl command requires udev synchronization.
-            // Only REMOVE, RENAME, and SUSPEND (non-suspended) operations need it.
-            let requires_sync = matches!(ioctl as u32, dmi::DM_DEV_REMOVE_CMD | dmi::DM_DEV_RENAME_CMD | dmi::DM_DEV_SUSPEND_CMD if *SYSV_SEM_SUPPORTED && (hdr.flags & DmFlags::DM_SUSPEND.bits()) == 0);
+            // Only REMOVE, RENAME, and SUSPEND-as-resume operations generate
+            // udev events that we need to synchronize on, and we can only do
+            // that synchronization when the kernel supports SysV semaphores.
+            let is_udev_cookie_cmd = matches!(
+                ioctl as u32,
+                dmi::DM_DEV_REMOVE_CMD | dmi::DM_DEV_RENAME_CMD | dmi::DM_DEV_SUSPEND_CMD
+            );
+            let is_actual_suspend = (hdr.flags & DmFlags::DM_SUSPEND.bits()) != 0;
+            let requires_sync = is_udev_cookie_cmd && *SYSV_SEM_SUPPORTED && !is_actual_suspend;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/dm_udev_sync.rs` at line 298, The one-liner computing requires_sync
is dense; split the predicate into clearly named boolean temporaries to improve
readability and testability: extract checks for the ioctl type (e.g.,
is_remove_or_rename_or_suspend using ioctl and dmi::DM_DEV_REMOVE_CMD |
dmi::DM_DEV_RENAME_CMD | dmi::DM_DEV_SUSPEND_CMD), the environment support
(is_sysv_supported using *SYSV_SEM_SUPPORTED) and the suspend-vs-resume check
(is_not_resume using (hdr.flags & DmFlags::DM_SUSPEND.bits()) == 0), then set
requires_sync = is_ioctl_of_type && is_sysv_supported && is_not_resume so intent
and each condition are obvious and easy to unit test.

505-540: ⚡ Quick win

Good end-to-end coverage; consider also asserting the early-return contract.

The test validates the udev_disabled branch for the three udev-cookie ioctls and asserts the right post-conditions (cookie == 0, semid == None, event_nr == 0). Nice.

If you act on the early-return-clears-event_nr fix in the comment above, it would be worth adding a complementary case that exercises the !requires_sync path — e.g., DM_DEV_SUSPEND_CMD with hdr.flags |= DmFlags::DM_SUSPEND.bits() and udev flags set — to lock in that event_nr is also cleared there. That guards against future regressions in either path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/dm_udev_sync.rs` around lines 505 - 540, Extend the
test_udevsync_no_udev_options_all_sync_cmds test to also exercise the
early-return (non-requires_sync) path: for the DM_DEV_SUSPEND_CMD case set
hdr.flags |= DmFlags::DM_SUSPEND.bits() before calling UdevSync::begin so the
code follows the !requires_sync branch, then assert the same post-conditions
(sync.cookie == 0, sync.semid == None, and hdr.event_nr == 0) and that
sync.end(...) returns Ok; this ensures both the requires_sync and early-return
branches clear event_nr when udev is disabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/dm_udev_sync.rs`:
- Around line 295-329: The early-return in UdevSync::begin when !requires_sync
leaves hdr.event_nr non-zero in some cases (e.g. SYSV_SEM_SUPPORTED == false or
suspended DM_DEV_SUSPEND_CMD), which can send a udev cookie to the kernel
without a semaphore; modify begin (the block guarded by requires_sync / the
!requires_sync early return) to clear hdr.event_nr = 0 before returning the
inactive UdevSync (cookie 0, semid None), or refactor to always zero
hdr.event_nr before any inactive return; update the branch that currently
returns Ok(UdevSync { cookie: 0, semid: None }) when !requires_sync to match the
udev_disabled || !udev_running() path by zeroing hdr.event_nr.

In `@src/core/dm.rs`:
- Around line 131-140: Extract the repeated ioctl predicate into a single
function (e.g., is_udev_cookie_cmd(ioctl: u32) -> bool) that checks the
DM_DEV_REMOVE_CMD | DM_DEV_RENAME_CMD | DM_DEV_SUSPEND_CMD set and return true
for those that support udev cookies; replace the inline match in do_ioctl (where
hdr.event_nr is currently cleared based on ioctl) with a call to
is_udev_cookie_cmd, and also call the same is_udev_cookie_cmd from
UdevSync::begin to decide semaphore/allocation behavior so both sites use the
single source of truth.

---

Nitpick comments:
In `@src/core/dm_udev_sync.rs`:
- Line 298: The one-liner computing requires_sync is dense; split the predicate
into clearly named boolean temporaries to improve readability and testability:
extract checks for the ioctl type (e.g., is_remove_or_rename_or_suspend using
ioctl and dmi::DM_DEV_REMOVE_CMD | dmi::DM_DEV_RENAME_CMD |
dmi::DM_DEV_SUSPEND_CMD), the environment support (is_sysv_supported using
*SYSV_SEM_SUPPORTED) and the suspend-vs-resume check (is_not_resume using
(hdr.flags & DmFlags::DM_SUSPEND.bits()) == 0), then set requires_sync =
is_ioctl_of_type && is_sysv_supported && is_not_resume so intent and each
condition are obvious and easy to unit test.
- Around line 505-540: Extend the test_udevsync_no_udev_options_all_sync_cmds
test to also exercise the early-return (non-requires_sync) path: for the
DM_DEV_SUSPEND_CMD case set hdr.flags |= DmFlags::DM_SUSPEND.bits() before
calling UdevSync::begin so the code follows the !requires_sync branch, then
assert the same post-conditions (sync.cookie == 0, sync.semid == None, and
hdr.event_nr == 0) and that sync.end(...) returns Ok; this ensures both the
requires_sync and early-return branches clear event_nr when udev is disabled.

In `@src/core/dm.rs`:
- Around line 1181-1217: The test sudo_test_no_udev_device_lifecycle creates a
local binding opts = no_udev_dm_options() but then calls dm.device_remove(&id,
no_udev_dm_options()) instead of reusing opts; change the final dm.device_remove
call to use the existing opts variable (dm.device_remove(&id, opts).unwrap()) so
the test consistently reuses the same options and will detect any future
divergence in no_udev_dm_options().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6153e97b-0b6c-465e-b973-01587aa33be3

📥 Commits

Reviewing files that changed from the base of the PR and between 0dcbf18 and cd043a1.

📒 Files selected for processing (2)
  • src/core/dm.rs
  • src/core/dm_udev_sync.rs

Comment thread src/core/dm_udev_sync.rs
Comment thread src/core/dm.rs
@mulkieran
Copy link
Copy Markdown
Member

@Apokleos Thanks for the PR! Please fix the clippy error and force push to resume testing.

Hey @mulkieran Thanks very much. I have updated the code, but I cannot run the reset CI, which I just can see

2 workflows awaiting approval
This workflow requires approval from a maintainer. 

Could you please help approve to run the CI ? appreciate it.

@Apokleos Thanks for the repush. Try running "make fmt" to fix the formatting issue and push again. I'll review the CodeRabbit remarks and let you know what I think about them.

@Apokleos Apokleos force-pushed the support-dm-without-udev branch from cd043a1 to ba8b1e5 Compare May 14, 2026 02:55
@Apokleos
Copy link
Copy Markdown
Author

@Apokleos Thanks for the PR! Please fix the clippy error and force push to resume testing.

Hey @mulkieran Thanks very much. I have updated the code, but I cannot run the reset CI, which I just can see

2 workflows awaiting approval
This workflow requires approval from a maintainer. 

Could you please help approve to run the CI ? appreciate it.

@Apokleos Thanks for the repush. Try running "make fmt" to fix the formatting issue and push again. I'll review the CodeRabbit remarks and let you know what I think about them.

@mulkieran Thanks very much for the patiance. code changes are updated now based on the CodeRabbit's advice. PTAL !

@Apokleos
Copy link
Copy Markdown
Author

Hey @mulkieran Looking forward to your input and advice about this PR. If there's some places should be fixed or corrected ? I'd like to devote myself totally to move it forward. Really appreciate it.

@Apokleos Apokleos force-pushed the support-dm-without-udev branch from ba8b1e5 to 464aa70 Compare May 19, 2026 08:17
@Apokleos
Copy link
Copy Markdown
Author

Hey @mulkieran FYI

Previously, UdevSync::begin() returned a hard error when udev was not
running, blocking ALL DM ioctl operations — even those that don't
require udev sync (CREATE, TABLE_LOAD, STATUS, etc.). Setting
DM_UDEV_DISABLE_LIBRARY_FALLBACK could not bypass this because the
flag was checked after the udev_running() gate.

This commit aims to address the issue with the following key changes:
(1) Clear event_nr in do_ioctl() for commands that don't carry a udev
cookie (only REMOVE, RENAME, and SUSPEND do). A non-zero event_nr on
other commands could cause the kernel to return EINVAL or wait
indefinitely for a udev completion that never arrives.

(2) Reorder UdevSync::begin() to check whether the ioctl actually
requires sync before checking udev_running(). Non-sync ioctls now
return an inactive UdevSync immediately. When sync is required but
udev is disabled or not running, clear event_nr and return inactive
instead of erroring.

(3) Extract ioctl_uses_udev_cookie(ioctl: u8) -> bool into dm_ioctl.rs as
the single source of truth, replacing duplicated match arms in do_ioctl
and UdevSync::begin.

(4) Add unit test (test_udevsync_no_udev_options_all_sync_cmds) and
integration test (sudo_test_no_udev_device_lifecycle) validating the full
dm-verity device lifecycle with all DM_UDEV_DISABLE_* flags set.

Signed-off-by: Alex Lyn <alex.lyn@antgroup.com>
@Apokleos Apokleos force-pushed the support-dm-without-udev branch from 464aa70 to f4f4760 Compare May 25, 2026 11:03
@Apokleos
Copy link
Copy Markdown
Author

Hey @mulkieran Looking forward to your feedback.

Apokleos added a commit to Apokleos/kata-containers that referenced this pull request May 26, 2026
The kata guest VM runs without udev, so device-mapper nodes under
/dev/mapper are never created automatically. Add the foundational
helpers that subsequent dm-verity integration will rely on:

It focus on the following key points:
(1) DmOptions builders that disable all udev synchronization flags,
  with read-only and deferred-remove variants.
(2) mknod-based device node creation/removal under /dev/mapper, since
  devtmpfs nodes are not auto-created without udev.

Also add the devicemapper crate dependency (default-features = false).

But note that the commit depends on device mapper with no-udev support
with the PR:stratis-storage/devicemapper-rs#1036

Signed-off-by: Alex Lyn <alex.lyn@antgroup.com>
@mulkieran
Copy link
Copy Markdown
Member

@Apokleos Thank you for keeping PR up-to-date. I hope to be able to give you a aresponse next week.

Apokleos added a commit to Apokleos/kata-containers that referenced this pull request May 27, 2026
The kata guest VM runs without udev, so device-mapper nodes under
/dev/mapper are never created automatically. Add the foundational
helpers that subsequent dm-verity integration will rely on:

It focus on the following key points:
(1) DmOptions builders that disable all udev synchronization flags,
  with read-only and deferred-remove variants.
(2) mknod-based device node creation/removal under /dev/mapper, since
  devtmpfs nodes are not auto-created without udev.

Also add the devicemapper crate dependency (default-features = false).

But note that the commit depends on device mapper with no-udev support
with the PR:stratis-storage/devicemapper-rs#1036

Signed-off-by: Alex Lyn <alex.lyn@antgroup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants