Skip to content

Ash Shell Background Process PID Tracking Issue

Michael Reid edited this page Feb 2, 2026 · 1 revision

Overview

This document explains a known limitation when using SVC_BACKGROUND=y with SVC_WRITE_PID=y on DSM 5 and SRM systems where /bin/sh is ash (not bash).

The Problem

When the spksrc framework starts a service on DSM < 6 with a SERVICE_USER set, it uses:

su ${EFF_USER} -s /bin/sh -c "${service}" >> ${LOG} 2>&1 &

Then captures $! to write to the PID file.

Why This Fails with Ash

In ash (used in DSM 5 and SRM), when you run:

/bin/sh -c "command arg1 arg2" &

The $! returns the PID of the /bin/sh process, not the actual command being executed. The subshell may exit quickly while the command continues running (or vice versa), leaving an incorrect PID in the file.

In bash, this typically works because bash handles this differently, but ash has this limitation.

Affected Configurations

This issue affects packages that have ALL of:

  • SERVICE_USER set in Makefile (triggers su wrapper)
  • SERVICE_COMMAND with parameters (triggers /bin/sh -c)
  • SVC_BACKGROUND=y (runs command in background)
  • SVC_WRITE_PID=y (attempts to capture PID with $!)
  • Target firmware is DSM < 6 or SRM (uses ash shell)

Workaround Options

Option 1: Wrapper Script (Recommended for DSM 5/SRM)

Create a wrapper script that:

  1. Accepts no parameters (SERVICE_COMMAND runs it directly without /bin/sh -c)
  2. Runs the actual binary in background
  3. Captures and writes the PID

Example start.sh:

#!/bin/sh

if [ -z "${SYNOPKG_PKGDEST}" ] || [ -z "${PID_FILE}" ]; then
    echo "ERROR: Required variables not set"
    exit 1
fi

${SYNOPKG_PKGDEST}/bin/myapp --some-arg &
echo "$!" > "${PID_FILE}"

Important Notes:

  • The wrapper itself runs the binary with & and captures the PID
  • Pass required variables via environment exports in service-setup.sh
  • The wrapper must NOT be set to run in background by the framework (i.e., do NOT set SVC_BACKGROUND=y when using a wrapper that handles backgrounding itself)

Option 2: Use Application's Native Daemon Mode

If the application supports a daemon/background mode with PID file writing:

SERVICE_COMMAND="${SYNOPKG_PKGDEST}/bin/myapp --daemon --pid-file ${PID_FILE}"
# Don't set SVC_BACKGROUND or SVC_WRITE_PID - let the app handle it

Option 3: Limit Target Firmware

If DSM 5/SRM support is not required, the issue is moot:

  • DSM 6+ uses conf/privilege for user context (no su wrapper)
  • The framework runs ${service} >> ${OUT} 2>&1 & directly
  • $! correctly captures the PID

Framework Code Reference

See mk/spksrc.service.start-stop-status, relevant sections:

# Line 52-67: DSM < 6 code path (problematic with ash)
if [ -n "${USER}" -a "$SYNOPKG_DSM_VERSION_MAJOR" -lt 6 ]; then
    ...
    $SU /bin/sh -c "${service}" >> ${OUT} 2>&1 &
fi

# Line 69-81: DSM 6+ code path (works correctly)
else
    ${service} >> ${OUT} 2>&1 &
fi

# Line 82-85: PID capture (uses $!)
if [ -n "${SVC_WRITE_PID}" -a -n "${SVC_BACKGROUND}" ]; then
    echo -ne "$!" > ${PID_FILE}
fi

Historical Context

This issue was documented in PR #5652 for the ympd package. A wrapper script was introduced to work around the ash shell limitation, but the implementation had a bug:

Original buggy start.sh:

${SYNOPKG_PKGDEST}/bin/ympd -w ${SERVICE_PORT}   # No & here - blocks!
echo "$!" > ${PID_FILE}                          # Never reached, or captures wrong PID

The command ran in foreground (blocking), so echo "$!" either:

  1. Never executed (blocked forever), or
  2. If ympd exited, captured the PID of some previous background process

The wrapper was later reverted in favor of direct SERVICE_COMMAND usage, which works on DSM 6/7 but may have issues on DSM 5/SRM if all the affected conditions apply.

Current Status

  • DSM 5 reached end-of-life in 2019
  • Default toolchains are DSM 6.2.4 and 7.1
  • SRM (Synology Router Manager) still uses ash
  • Most packages no longer explicitly support DSM 5 or SRM

This issue is primarily a concern for SRM deployments or legacy DSM 5 systems.

Related PRs

  • PR #5652: Original ympd start.sh wrapper implementation
  • PR #6932: Reverted wrapper, discussion on SRM compatibility

Clone this wiki locally