Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/cli/install/phase.zig
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,19 @@ pub fn run(allocator: std.mem.Allocator, opts: InstallOptions) !void {
udev.removeServiceSentinel(allocator, plan.opts.destdir);
}

if (plan.do_enable_systemctl) {
services.runSystemctlPhase(&plan);
} else if (!plan.staging_mode) {
// Order is load-bearing: reload the udev ruleset, THEN mutate live driver
// state, THEN start the service. applyDriverState re-probes driverless
// interfaces, which generates bind uevents udevd evaluates against its
// loaded ruleset — so a stale "block usbhid" rule must be unloaded first,
// or it re-unbinds usbhid and the device loses its hidraw node (#355).
if (plan.do_enable_systemctl or !plan.staging_mode) {
_ = std.posix.write(std.posix.STDOUT_FILENO, "\nReloading system daemons...\n") catch {};
runCmd(&.{ "udevadm", "control", "--reload-rules" });
runCmd(&.{ "udevadm", "trigger" });
udev.applyDriverState(allocator, &plan, device_entries.items);
}
if (plan.do_enable_systemctl) {
services.runSystemctlUnits(&plan);
}

if (mapping_failed) {
Expand Down
9 changes: 4 additions & 5 deletions src/cli/install/services.zig
Original file line number Diff line number Diff line change
Expand Up @@ -480,11 +480,10 @@ pub fn installReconnectScript(allocator: std.mem.Allocator, plan: *const Install
_ = std.posix.write(std.posix.STDOUT_FILENO, "\n") catch {};
}

pub fn runSystemctlPhase(plan: *const InstallPlan) void {
_ = std.posix.write(std.posix.STDOUT_FILENO, "\nReloading system daemons...\n") catch {};
runCmd(&.{ "udevadm", "control", "--reload-rules" });
runCmd(&.{ "udevadm", "trigger" });

// Manage the systemd units. The caller reloads the udev ruleset and applies
// driver state first, so the service starts against a device already in its
// final driver state.
pub fn runSystemctlUnits(plan: *const InstallPlan) void {
if (plan.systemctl_plan.mode == .skip) {
var groups: [3][]const []const u8 = undefined;
var n: usize = 0;
Expand Down
29 changes: 18 additions & 11 deletions src/cli/install/udev.zig
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,16 @@ pub fn installUdevRules(allocator: std.mem.Allocator, plan: *const InstallPlan,
const msg = std.fmt.bufPrint(&errbuf, "warning: driver block rules not generated: {}\n", .{err}) catch "warning: driver block rules error\n";
_ = std.posix.write(std.posix.STDERR_FILENO, msg) catch {};
};
}

/// Mutate live kernel driver state to match the freshly written udev rules.
/// MUST be called AFTER `udevadm control --reload-rules`: re-probe generates
/// bind uevents that udevd evaluates against its currently loaded ruleset, so a
/// stale "block usbhid" rule still loaded from a previous install would re-unbind
/// usbhid and strip the device's hidraw node ("no device"). Self-gates to a
/// no-op outside root or in staging mode.
pub fn applyDriverState(allocator: std.mem.Allocator, plan: *const InstallPlan, entries: []const UdevEntry) void {
if (plan.staging_mode or !plan.is_root) return;

// Re-probe interfaces left driverless by an earlier install whose block
// list this install no longer covers, BEFORE evicting currently-blocked
Expand All @@ -300,17 +310,14 @@ pub fn installUdevRules(allocator: std.mem.Allocator, plan: *const InstallPlan,
// skips bound ones, so it never re-binds a driver the unbind step below is
// about to evict. Not gated by shouldProactiveUnbind so it still recovers
// when the block list is empty/reduced.
if (!plan.staging_mode and plan.is_root) {
probeAndReprobeDrivers(allocator, entries, "");
}

// Evict already-attached devices without waiting for reboot. udevadm
// trigger only sends add events; bind rules fire on the next plug cycle.
// Proactively writing to sysfs unbind covers devices already claimed by a
// blocking driver at install time — but only when this install actually
// starts a runnable service, otherwise an unbound device is left ownerless.
// Runs last so the eviction is authoritative over the reprobe above.
if (!plan.staging_mode and plan.is_root and shouldProactiveUnbind(plan)) {
probeAndReprobeDrivers(allocator, entries, "");

// Evict already-attached devices without waiting for reboot. Proactively
// writing to sysfs unbind covers devices already claimed by a blocking
// driver at install time — but only when this install actually starts a
// runnable service, otherwise an unbound device is left ownerless. Runs
// last so the eviction is authoritative over the reprobe above.
if (shouldProactiveUnbind(plan)) {
probeAndUnbindDrivers(allocator, entries, "");
}
}
Expand Down
Loading