When I reboot my Raspberry Pi (Stretch) a daemon fails to start because /run/user/1000 does not exist. This is my unit file:

Description=SBFspot Upload Daemon

ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon -p /run/user/1000/sbfspotupload.pid 2>&1> /dev/null


When I configure to Restart=on-failure all goes well after a few retries, but that's not really what I want. I want the daemon to wait for /run/user/1000 gets mounted. I tried with After=run-user-1000.mount but it still fails.

Is this possible or do I have to stick with the Restart=on-failure?


1 Answer 1


/run/user/1000, which of course does not exist until user #1000 logs in or explicitly starts up xyr per-user service management, is a red herring. The entire mechanism that uses it should not be there.

Bug #215 for this program runs a lot deeper than you think. This service unit file is very wrong, as is the operation of the program itself. There is a lot of cargo cult programming, based upon not actually understanding the fundamentals of systemd service units.

  • Service units are not shell script. The systemd manual does explain this. The ExecStart setting here causes the service program to be run with some extra arguments, 2>&1> and /dev/null.
  • The service manager already ensures that only one service runs. All of this code added here is unnecessary junk.
  • The rickety and dangerous PID file mechanism should not be used. It has no place in proper service management.
  • The service manager also handles invoking the service in a dæmon context. A lot of the other code in main() is also unnecessary junk, based upon the dæmonization fallacy.
    • The program should not be fork()ing at all, and the service readiness mechanism should not be specified as Type=forking. Like so many programs in the real world, this program is not speaking the forking readiness protocol in the first place.
    • The program is already running as the superuser. User=root is unnecessary, and indeed the service should be redesigned to not need running with superuser privileges but rather run under the aegis of a dedicated unprivileged service account.
    • The service manager is already logging the standard output and error, and doing a better job of it than this program is. This home-grown logging system just grows a log file until it fills up an entire filesystem, consuming all of the emergency space reserved for the superuser.
    • Your log is simply the standard error, handily accessible from C++ as std::clog.
    • In fact, all of the code from the fork() to the redirection of standard error should not be used. Service management handles all of this, from session leadership through working directory and umask to standard I/O, and does it properly. This program does not, and it should not be attempting to do any of this for itself when used under a service manager.

      Everything that you took from Boost was wrong.

  • Three service units is unnecessary maintenance overhead. They only differ in their After settings, and those can just be merged into one.
  • Graceless termination is not success. Given that there was already one problem with cleaning up files upon termination, SuccessExitStatus=SIGKILL is wrongheaded. Normal termination should be graceful, via SIGTERM, and SIGKILL should be considered abnormal. (Of course, the whole output file mechanism is a badly implemented home-grown logging mechanism that should not be used under service management, as already explained.) This is the systemd default.
  • Destructors of the database objects and other stuff should run. Do not leave main() with exit().

A dæmon program implemented properly for running under a service manager, be it from daemontools, runit, s6, nosh, systemd, or something else, is a lot shorter:

// the same until this point
void pvo_upload(void)
    std::clog << "Starting Daemon..." << std::endl;


    std::clog << "Stopping Daemon..." << std::endl;

int main(int argc, char *argv[])
    int c;
    const char *config_file = "";

    /* parse commandline */
        static struct option long_options[] =
            { "config-file", required_argument, 0, 'c' },
            { 0, 0, 0, 0 }

        int option_index = 0;
        c = getopt_long (argc, argv, "c:", long_options, &option_index);

        if (c == -1) break;

        switch (c)
            case 'c':
                config_file = optarg;
                return EXIT_FAILURE;

    if (cfg.readSettings(argv[0], config_file) != Configuration::CFG_OK)
        return EXIT_FAILURE;

    std::clog << "Starting SBFspotUploadDaemon Version " << VERSION << std::endl;

    // Check if DB is accessible
    db_SQL_Base db = db_SQL_Base();
    db.open(cfg.getSqlHostname(), cfg.getSqlUsername(), cfg.getSqlPassword(), cfg.getSqlDatabase());
    if (!db.isopen())
        std::clog << "Unable to open database. Check configuration." << std::endl;
        return EXIT_FAILURE;

    // Check DB Version
    int schema_version = 0;
    db.get_config(SQL_SCHEMAVERSION, schema_version);

    if (schema_version < SQL_MINIMUM_SCHEMA_VERSION)
        std::clog << "Upgrade your database to version " << SQL_MINIMUM_SCHEMA_VERSION << std::endl;
        return EXIT_FAILURE;

    // Install our signal handler.
    // This responds to the service manager signalling the service to stop.
    signal(SIGTERM, handler);

    // Start daemon loop

    return EXIT_SUCCESS;

And the service unit is shorter, too:

Description=SBFspot upload daemon
After=mysql.service mariadb.service network.target



The log output is viewable with systemctl status and journalctl (with the -u option and the service name, if desired).

Further reading

  • I have to add User=pi or daemon will run as root
    – SBF
    Jun 29, 2018 at 22:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .