2

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

[Unit]
Description=SBFspot Upload Daemon

[Service]
User=pi
Type=forking
TimeoutStopSec=60
ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon -p /run/user/1000/sbfspotupload.pid 2>&1> /dev/null
PIDFile=/run/user/1000/sbfspotupload.pid
Restart=no
RestartSec=15
SuccessExitStatus=SIGKILL

[Install]
WantedBy=default.target

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?

  • User=pi makes it run as user pi, no? $UID of pi is 1000 – SBF Jun 26 '18 at 10:25
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;

    CommonServiceCode();

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

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

    /* parse commandline */
    while(1)
    {
        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;
                break;
            default:
                return EXIT_FAILURE;
                break;
        }
    }

    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);
    db.close();

    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
    pvo_upload();

    return EXIT_SUCCESS;
}

And the service unit is shorter, too:

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

[Service]
Type=simple
TimeoutStopSec=10
ExecStart=/usr/local/bin/sbfspot.3/SBFspotUploadDaemon
Restart=on-success

[Install]
WantedBy=multi-user.target

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

Further reading

  • Well, I'm impressed by your answer and will take your guidelines into account. The use of systemd is added very recently. Before V3.4.1 the daemon was started with /etc/rc.local About the logging: I suppose all logging is lost after reboot? In that case it's difficult troubleshooting, no? I don't get the use of Restart=on-success instead of on-failure - Can you explain this? Thanks. – SBF Jun 29 '18 at 21:16
  • I have to add User=pi or daemon will run as root – SBF Jun 29 '18 at 22:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.