Skip to content

Converting a daemon by deleting machinery

 2 min The systemd conversion checklist from a mailing-list reply: forking, pidfiles, private logs, and stop scripts all come out.
Published

Jul 27, 2012

I wrote a reply on arch-general about converting a traditional daemon to systemd. The whole conversion is subtraction. A classic daemon carries a stack of self-management machinery—forking, pidfiles, log redirection, a stop script—and the service manager can own those jobs. The unit file gets shorter as the daemon gets simpler.

The deletions, one piece at a time:

The fork. Run the daemon in the foreground—daemon --foreground or whatever flag it offers—and let the manager own the lifecycle. The double-fork dance detached a process from its controlling terminal: the first child could call setsid(), and the second fork left the final daemon unable to reacquire a controlling terminal because it was no longer a session leader. Under a supervisor, detaching is worse than useless, because it hides the main process from the thing whose job is to track it.

The pidfile. Once the manager launches the process directly, it already knows the main PID. A private pidfile is a second, worse copy of that fact—one that goes stale on crashes and lies after PID reuse. Delete the writing, delete the reading.

The private log file. Write to stdout and stderr and let the manager’s configured capture handle storage. The process exposes events; the supervisor supplies retention, rotation, and policy. A daemon redirecting itself into /var/log/foo.log keeps that policy inside the application.

The stop wrapper. A stop script exists to rediscover the daemon—usually by reading the pidfile just deleted—and signal it. In that reply I collapsed that wrapper to one directive:

ExecStop=-/bin/kill $MAINPID

That was an intermediate deletion, not the current endpoint. For an ordinary foreground service, modern systemd needs no ExecStop= at all: when stop is requested it signals the process according to KillMode= and KillSignal=. The current service documentation also warns that an ExecStop= which merely sends a signal and exits is not a clean shutdown protocol. Keep ExecStop= for a synchronous service-specific stop operation; delete this one-line kill.

One 2012 contract change came with the territory: Exec* directives took absolute paths—/usr/bin/program, not program. A unit file is not an interactive shell script and does not inherit a shell’s PATH habits. (The same boundary produces a subtler tokenization trap around variables holding multiple arguments.)

The verification step matters as much as the deletions: after each removal, confirm the manager sees the right main process and that stop terminates it. Each piece came out one at a time precisely so a regression names its own cause.