Troubleshooting Init Script PID File Creation Issues for IRC Logger Daemons


2 views

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:

  1. Daemon forks and runs in background
  2. Child process writes its PID to the specified file
  3. 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