2019-08-25

11: How to Create a Minimal Systemd Daemon in C++

<The previous article in this series | The table of contents of this series | The next article in this series>

Most of the most-promoted documents begin to "fork", "setsid", etc., which are about SysV daemons, while a systemd daemon can be created more easily.

Topics


About: C++

The table of contents of this article


Starting Context


  • The reader has a basic knowledge on C++ programming.

Target Context


  • The reader will understand how to create a minimal systemd daemon in C++.

Orientation


Hypothesizer 7
I want to create a daemon.

When I search the internet, most of the most-highly-ranked answers begin to "fork", "setsid", . . .

In fact, they are explaining how to create SysV daemons without qualifying so, as though any daemon that is not any SysV daemon is not any daemon. . . . As there seem to be a controversy around systemd, those authors may be adamantly denying the existence of any systemd daemon.

However, my Linux computer's 'man' command page describes 'systemd daemon' as "new-style daemon" and says "Modern services for Linux should be implemented as new-style daemons.".

Well, according to the 'man' page, 'SysV daemon' sounds like an ancient relic.

I do not delve into the controversy because I am not qualified to do so, and simply have accepted the recommendation by the 'man' page.

It has turned out that creating a systemd daemon is quite easy, at least, creating a minimal systemd daemon is.


Main Body


1: How to Create a Minimal Systemd Daemon


Hypothesizer 7
The 'man' page says "For developing a new-style daemon, none of the initialization steps recommended for SysV daemons need to be implemented.".

Well, that is good. In fact, as I read along the 'man' page, "new-style daemon" seems quite easy to create.

So, what need to be implemented by my minimal daemon?

First, my daemon seems to have to catch any 'SIGTERM' signal and shut itself down cleanly. . . . Fair enough.

Second, my daemon seems to have to provide a correct exit code. . . . Understood.

Third, my daemon seems should notify systemd about the startup completion and any status update via the 'sd_notify' function. . . . No problem.

Forth, my daemon seems to be able to log to the system syslog service by just outputting to the standard error output. . . . Cool.

That seems to be all for the C++ program, although I have to prepare a '.service' file later.

There can be some other things to be implemented if the daemon goes beyond being minimal, but they do not concern me right now (for example, any 'SIGHUP' signal does not matter because the minimal daemon does not have any configuration file, and 'D-Bus' does not matter at all).

This is a minimal systemd daemon.

theBiasPlanet/samples/minimalSystemdDaemon/programs/MinimalSystemdDaemon.hpp

@C++ Source Code
#ifndef __theBiasPlanet_samples_minimalSystemdDaemon_programs_MinimalSystemdDaemon_hpp__
	#define __theBiasPlanet_samples_minimalSystemdDaemon_programs_MinimalSystemdDaemon_hpp__
	
	namespace theBiasPlanet {
		namespace samples {
			namespace minimalSystemdDaemon {
				namespace programs {
					class MinimalSystemdDaemon {
						public:
							static int main (int const & a_argumentsNumber, char const * const a_argumentsArray []);
					};
				}
			}
		}
	}
#endif

theBiasPlanet/samples/minimalSystemdDaemon/programs/MinimalSystemdDaemon.cpp

@C++ Source Code
#include "theBiasPlanet/samples/minimalSystemdDaemon/programs/MinimalSystemdDaemon.hpp"
#include <iostream>
#include <exception>
#include <signal.h>
#include <systemd/sd-daemon.h>

using namespace ::std;

namespace theBiasPlanet {
	namespace samples {
		namespace minimalSystemdDaemon {
			namespace programs {
				int MinimalSystemdDaemon::main (int const & a_argumentsNumber, char const * const a_argumentsArray []) {
					int l_resultStatus = 1;
					try {
						if (a_argumentsNumber != 3) {
							l_resultStatus = 2;
							throw runtime_error ("The arguments have to be these.\nThe argument 1: ~ 2: ~");
						}
						sigset_t l_waitedSignals;
						sigemptyset (&l_waitedSignals);
						sigaddset (&l_waitedSignals, SIGTERM);
						sigprocmask (SIG_BLOCK, &l_waitedSignals, nullptr);
						sd_notify (0, "READY=1");
						cerr << string ("The office daemon has successfully started up.") << endl << flush;
						int l_signal;
						sigwait (&l_waitedSignals, &l_signal);
						sd_notify (0, "STOPPING=1");
						cerr << string ("The office daemon has been successfully shut down.") << endl << flush;
					}
					catch (exception & l_exception) {
						sd_notifyf (0, "STATUS=Failed to start up: %s\n ERRNO=%i", l_exception.what (), l_resultStatus);
						return (l_resultStatus);
					}
					l_resultStatus = 0;
					return (l_resultStatus);
				}
			}
		}
	}
}

Note that in order to use 'sd_notify' and 'sd_notifyf', a package ('libsystemd-dev' for Ubuntu 18.04) has to be installed into the operating system and a library ('systemd' for Ubuntu 18.04) has to be linked into the daemon.

Is that it? Yes, except that, of course, 'MinimalSystemdDaemon::main (int const & a_argumentsNumber, char const * const a_argumentsArray [])' has to be called from a real 'main'.


2: Registering the Daemon


Hypothesizer 7
In order to register the daemon, I create a '.service' file in the '/etc/systemd/system' directory.

This is my '.service' file.

'minimalSystemdDaemon.service'

@systemd .service Source Code
[Unit]
Description=The minimal systemd daemon service
After=network.target
StartLimitBurst=5
StartLimitIntervalSec=10

[Service]
Type=simple
Restart=always
RestartSec=1
User=%the operating system user name%
Environment="%the variable 1 name%=%the variable 1 value%"
Environment="%the variable 2 name%=%the variable 2 value%"
ExecStart=%the execution file path% "%the argument 1%" "%the argument 2%"
KillMode=process

[Install]
WantedBy=multi-user.target

As can be seen above, the operating system user by whom the daemon program is executed can be set; the environment variables can be set; the arguments to the daemon can be set.

In order to enable the daemon (make the daemon start automatically), I execute this command.

@bash Source Code
sudo systemctl enable minimalSystemdDaemon.service

In order to disable the daemon, I execute this command.

@bash Source Code
sudo systemctl disable minimalSystemdDaemon.service


3: Administering the Daemon


Hypothesizer 7
In order to start the daemon, I execute this command.

@bash Source Code
sudo systemctl start minimalSystemdDaemon.service

In order to stop the daemon, I execute this command.

@bash Source Code
sudo systemctl stop minimalSystemdDaemon.service

In order to see the status of the daemon, I execute this command.

@bash Source Code
sudo systemctl status minimalSystemdDaemon.service


4: The Conclusion and Beyond


Hypothesizer 7
Now, I seem to understand how to create a minimal systemd daemon.

I do not need to write those tedious things like calling 'fork', 'setsid', etc., which look like required if one believes in some documents a typical internet search engine promotes the most.

In fact, this article is a preparation for creating an office (LibreOffice or Apache OpenOffice) daemon.


References


<The previous article in this series | The table of contents of this series | The next article in this series>