When creating init scripts for persistent daemons, proper PID file handling is crucial for process management. The standard skeleton init script assumes the daemon will create its own PID file, but this isn't always the case - especially with custom applications like IRC loggers.
The key issue lies in the --background
flag combined with missing PID file handling:
start-stop-daemon --start --background --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_ARGS
This command tells start-stop-daemon to fork the process to background but doesn't guarantee PID file creation if the daemon itself doesn't implement it.
Option 1: Let start-stop-daemon handle forking
Modify your daemon to avoid self-forking and let start-stop-daemon manage it:
do_start() {
start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE \
--background --exec $DAEMON -- $DAEMON_ARGS || return 2
}
The --make-pidfile
flag forces PID file creation regardless of daemon behavior.
Option 2: Manual PID file creation
For daemons that must self-fork, implement PID handling in your init script:
do_start() {
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test \
|| return 1
# Start the process and capture PID
start-stop-daemon --start --background --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_ARGS || return 2
# Verify and write PID
sleep 1 # Allow process to start
if ! pgrep -f "$DAEMON $DAEMON_ARGS" >/dev/null; then
return 2
fi
pgrep -f "$DAEMON $DAEMON_ARGS" > $PIDFILE
}
For production systems, consider these enhancements:
# In configuration section
PIDDIR=$(dirname $PIDFILE)
[ -d "$PIDDIR" ] || install -d -m 755 -o root -g root "$PIDDIR"
This ensures the PID directory exists with proper permissions. Also add cleanup to your stop function:
do_stop() {
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
# Additional cleanup
[ -f "$PIDFILE" ] && rm -f "$PIDFILE"
return "$RETVAL"
}
When working with init scripts derived from /etc/init.d/skeleton, a common pitfall occurs when adapting them for background processes. The standard template assumes the daemon will fork and create its own PID file, but this breaks when:
start-stop-daemon --start --background --pidfile $PIDFILE --exec $DAEMON
is required because the process doesn't daemonize itself. Let's examine why this happens and how to properly handle PID file management.
The traditional init script workflow expects:
- Daemon forks and runs in background
- Child process writes its PID to the specified file
- Parent process exits
When using --background
with start-stop-daemon, this flow changes fundamentally. The process remains attached to the init system but doesn't handle PID file creation.
Here are two reliable approaches to fix PID file handling:
Method 1: Manual PID Capture
Modify your do_start function to explicitly capture and write the PID:
do_start() {
start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test \
|| return 1
# Start process and capture PID
start-stop-daemon --start --background --make-pidfile --pidfile $PIDFILE \
--exec $DAEMON -- $DAEMON_ARGS \
|| return 2
# Verify PID file was created
[ -f "$PIDFILE" ] || return 2
}
Method 2: Daemon Modification
If you control the daemon source code, implement proper PID file handling:
// C example for daemon initialization
void create_pidfile(const char *pidfile) {
FILE *f = fopen(pidfile, "w");
if (f) {
fprintf(f, "%d\n", getpid());
fclose(f);
}
}
If issues persist, consider these diagnostic steps:
# Check for permission issues
sudo -u daemonuser touch $PIDFILE
# Verify filesystem space
df -h /var/run
# Test manual process management
/usr/sbin/irclogger irc.freenode.net channel &
echo $! > /var/run/irclogger.pid
For modern systems using systemd, create a service unit file instead:
[Unit]
Description=IRC Logger Service
After=network.target
[Service]
Type=forking
User=irclogger
ExecStart=/usr/sbin/irclogger irc.freenode.net channel
PIDFile=/run/irclogger.pid
[Install]
WantedBy=multi-user.target