:: [devuan-dev] [PATCH] runit: shutdow…
Top Page
Delete this message
Reply to this message
Author: enricopitea
Date:  
To: devuan-dev
Subject: [devuan-dev] [PATCH] runit: shutdown -h / poweroff does not cut the power — execv bypasses syscall_shutdown fallback
Hello Devuan developers,

I found a bug in runit's shutdown binary (debian/contrib/shutdown.c) that
affects Devuan systems using runit as PID 1.

== Symptom ==

On Devuan Ceres with runit, `shutdown -h now` and `poweroff` leave the system
in a state where services are stopped (stage 3 completes) but the hardware
never powers off. The kernel calls RB_HALT instead of RB_POWER_OFF. The user
must hold the power button to turn off the machine. The filesystem may show a
dirty bit on the next boot.

This was initially worked around on my machine by using `loginctl poweroff`,
but the root cause is in shutdown.distrib.

== Root Cause ==

In debian/contrib/shutdown.c (package runit 2.3.1-4), the function
runit_shutdown() at line 144 does:

    char* args[] = {"/usr/sbin/init", "0", NULL};
    execv("/usr/sbin/init", args);

execv() REPLACES the current process with init. The code that follows in
main():

    sleep(15);
    syscall_shutdown(cfg.action);   // calls reboot(RB_POWER_OFF)

is NEVER reached — the process no longer exists. PID 1 runs stage 3 and then
halts via RB_HALT, which stops the CPU but does not cut the power on modern
hardware.

Additionally, the flags -h, -P, and -H are all treated identically (the
source comment literally says "Halt actions not distinguished"), all mapping
to ACTION_HALT.

== Fix ==

Replace execv() with sysv_shutdown() — a function already present in the same
file (line 92) that writes to /run/initctl and RETURNS. The process survives,
sleep(15) runs, and syscall_shutdown(RB_POWER_OFF) is reached, correctly
powering off the hardware.

One function changed: -4 lines, +8 lines including the comment.

== Test Results ==

6 consecutive shutdowns on Devuan Ceres (kernel 7.1.5, runit 2.3.1-4):
  - Realistic scenario: Firefox + Nemo + active NFS mount + 1.7 GB disk cache
  - 6/6 clean: zero dirty bits, zero segfaults, BTRFS 0 corruption_errs
  - Boot time: 93 seconds

Verification:
  $ sudo dmesg | grep -i dirty        # empty
  $ sudo dmesg | grep -i segfault     # empty
  $ strings /usr/lib/runit/shutdown.distrib | grep /usr/sbin/init  # 0 matches
  $ sudo btrfs device stats /          # all zeros

== Patch ==

The patch (shutdown.patch) is attached. It applies against runit 2.3.1-4,
file debian/contrib/shutdown.c.

== Notes ==

- The binary shutdown.distrib is shipped as a pre-compiled blob in
  debian/contrib/ (not compiled during dpkg-buildpackage). The fix is in
  the C source debian/contrib/shutdown.c.

- Rollback: cp /usr/lib/runit/shutdown.distrib.orig /usr/lib/runit/shutdown.distrib

- I am not submitting this to the Debian BTS directly because runit is a
  first-class init system on Devuan and I believe the Devuan community
  would review this more attentively. Feel free to forward it upstream if
  appropriate.

Tested on: Devuan Ceres, i5-9600KF, 32 GB RAM, runit PID 1, Cinnamon desktop.
Date: 30 July 2026.

Disclosure: I used an AI assistant (DeepSeek V4 Pro) during the analysis. I
have personally reviewed the code and the patch to the best of my knowledge.

Regards,
Enrico Pitea