├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
└── daemon
├── CMakeLists.txt
├── include
├── command-line-parser.hpp
├── daemon.hpp
└── log.hpp
├── resource
├── config
│ └── daemon-template.conf
└── systemd
│ └── daemon-template.service
└── source
├── daemon.cpp
└── main.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | build/
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10)
2 |
3 | # Define project name
4 | project(mydaemon VERSION 1.0.0)
5 |
6 | # Set up required subdirectories
7 | add_subdirectory(daemon)
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Fabiano Traple
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Table of contents
3 |
4 | - [Linux Daemon Template ](#linux-daemon-template)
5 | - [New-Style Daemons ](#new-style-daemons)
6 | - [How To Install ](#how-to-install)
7 | - [How To Start ](#how-to-start)
8 |
9 | # Linux Daemon Template
10 |
11 | A daemon is a computer program that runs as a background process, rather than being under the direct control of an interactive user. Traditionally, the process names of a daemon end with the letter d, for clarification that the process is in fact a daemon, and for differentiation between a daemon and a normal computer program.
12 |
13 | ## New-Style Daemons
14 |
15 | Modern services for Linux should be implemented as new-style daemons. This makes it easier to supervise and control them at runtime and simplifies their implementation.
16 |
17 | For developing a new-style daemon, none of the initialization steps recommended for SysV daemons need to be implemented. New-style init systems such as systemd make all of them redundant. Moreover, since some of these steps interfere with process monitoring, file descriptor passing and other functionality of the init system, it is recommended not to execute them when run as new-style service.
18 |
19 | Note that new-style init systems guarantee execution of daemon processes in a clean process context: it is guaranteed that the environment block is sanitized, that the signal handlers and mask is reset and that no left-over file descriptors are passed. Daemons will be executed in their own session, with standard input connected to /dev/null and standard output/error connected to the systemd-journald.service logging service, unless otherwise configured. The umask is reset.
20 |
21 | It is recommended for new-style daemons to implement the following:
22 |
23 | 1. - [x] If SIGTERM is received, shut down the daemon and exit cleanly.
24 |
25 | 2. - [x] If SIGHUP is received, reload the configuration files, if this applies.
26 |
27 | 3. - [x] Provide a correct exit code from the main daemon process, as this is used by the init system to detect service errors and problems. It is recommended to follow the exit code scheme as defined in the LSB recommendations for SysV init scripts.
28 |
29 | 4. - [ ] If possible and applicable, expose the daemon's control interface via the D-Bus IPC system and grab a bus name as last step of initialization.
30 |
31 | 5. - [x] For integration in systemd, provide a .service unit file that carries information about starting, stopping and otherwise maintaining the daemon.
32 |
33 | 6. - [x] As much as possible, rely on the init system's functionality to limit the access of the daemon to files, services and other resources, i.e. in the case of systemd, rely on systemd's resource limit control instead of implementing your own, rely on systemd's privilege dropping code instead of implementing it in the daemon, and similar.
34 |
35 | 7. - [ ] If D-Bus is used, make your daemon bus-activatable by supplying a D-Bus service activation configuration file. This has multiple advantages: your daemon may be started lazily on-demand; it may be started in parallel to other daemons requiring it — which maximizes parallelization and boot-up speed; your daemon can be restarted on failure without losing any bus requests, as the bus queues requests for activatable services.
36 |
37 | 8. - [ ] If your daemon provides services to other local processes or remote clients via a socket, it should be made socket-activatable following the scheme pointed out below. Like D-Bus activation, this enables on-demand starting of services as well as it allows improved parallelization of service start-up. Also, for state-less protocols (such as syslog, DNS), a daemon implementing socket-based activation can be restarted without losing a single request.
38 |
39 | 9. - [ ] If applicable, a daemon should notify the init system about startup completion or status updates via the sd_notify interface.
40 |
41 | 10. - [x] Instead of using the syslog() call to log directly to the system syslog service, a new-style daemon may choose to simply log to standard error via fprintf(), which is then forwarded to syslog by the init system. If log levels are necessary, these can be encoded by prefixing individual log lines with strings like "<4>" (for log level 4 "WARNING" in the syslog priority scheme), following a similar style as the Linux kernel's printk() level system.
42 |
43 | 11. - [x] As new-style daemons are invoked without a controlling TTY (but as their own session leaders) care should be taken to always specify `O_NOCTTY` on `open()` calls that possibly reference a TTY device node, so that no controlling TTY is accidentally acquired.
44 |
45 | To know more about access [this Link](https://www.freedesktop.org/software/systemd/man/daemon.html#New-Style%20Daemons).
46 |
47 | To avoid mistakes when designing Unix daemon programs read [this link](https://jdebp.eu/FGA/unix-daemon-design-mistakes-to-avoid.html).
48 |
49 |
50 | ## How To Install
51 |
52 | **Tested on Ubuntu 18.04 LTS**
53 |
54 | Firt you need to check if all dependencies are available.
55 |
56 | The library [libconfig++](https://hyperrealm.github.io/libconfig/) is used to read the config file.
57 | ```bash
58 | sudo apt install libconfig++-dev
59 | ```
60 |
61 | Clone the git repository, compile and install.
62 |
63 | ```bash
64 | git clone git@github.com:ftraple/cpp-daemon-template.git
65 | cd cpp-daemon-template
66 | mkdir build
67 | cd build
68 | cmake ..
69 | make
70 | sudo make install
71 | ```
72 |
73 | ## How To Start
74 |
75 | To start, stop and check the state of the daemon use the follow commands.
76 |
77 | ```bash
78 | sudo systemctl start daemon-template
79 |
80 | sudo systemctl stop daemon-template
81 |
82 | sudo systemctl status daemon-template
83 | ```
84 | To see the all syslog:
85 |
86 | ```bash
87 | tail -f /var/log/syslog
88 | ```
89 | To see the all logs generated by the daemon:
90 |
91 | ```bash
92 | tail -f /var/log/syslog | grep daemon-template
93 | ```
--------------------------------------------------------------------------------
/daemon/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | set(THIS daemon-template)
3 |
4 | include_directories(include)
5 |
6 | set(SOURCE_FILES
7 | source/daemon.cpp
8 | source/main.cpp)
9 |
10 | set (CMAKE_CXX_STANDARD 17)
11 |
12 | # Make build flags compiler specific
13 | if (CMAKE_COMPILER_IS_GNUCC)
14 | if (CMAKE_BUILD_TYPE STREQUAL "Debug")
15 | set (CMAKE_C_FLAGS "-D_REETRANT -ggdb -Wall -Wextra -pedantic -O0")
16 | elseif( CMAKE_BUILD_TYPE STREQUAL "Release" )
17 | set (CMAKE_C_FLAGS "-D_REETRANT -DNDEBUG -Wall -Wextra -pedantic -O3")
18 | endif ()
19 | endif (CMAKE_COMPILER_IS_GNUCC)
20 |
21 |
22 | add_executable(${THIS} ${SOURCE_FILES})
23 |
24 | target_link_libraries(${THIS} libconfig++.a)
25 |
26 | # Install the config file
27 | install(FILES resource/config/daemon-template.conf DESTINATION /etc/daemon-template)
28 |
29 | # Intall the systemd file
30 | install(FILES resource/systemd/daemon-template.service DESTINATION /etc/systemd/system)
31 |
32 | # Install the binary program
33 | install(TARGETS ${THIS} DESTINATION /usr/bin)
--------------------------------------------------------------------------------
/daemon/include/command-line-parser.hpp:
--------------------------------------------------------------------------------
1 | #ifndef COMMAND_LINE_PARSER_HPP_
2 | #define COMMAND_LINE_PARSER_HPP_
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | class CommandLineParser {
11 | public:
12 | CommandLineParser(int argc, char** argv) {
13 | for (int i = 1; i < argc; i++) {
14 | this->m_tokens.push_back(std::string(argv[i]));
15 | }
16 | }
17 |
18 | const std::string& getCmdOptionValue(const std::string& option) const {
19 | auto it = std::find(this->m_tokens.begin(), this->m_tokens.end(), option);
20 | if (it != this->m_tokens.end() && ++it != this->m_tokens.end()) {
21 | return *it;
22 | }
23 | static const std::string emptyString("");
24 | return emptyString;
25 | }
26 |
27 | bool cmdOptionExist(const std::string& option) const {
28 | auto it = std::find(this->m_tokens.begin(), this->m_tokens.end(), option);
29 | return (it != this->m_tokens.end());
30 | }
31 |
32 | private:
33 | std::vector m_tokens;
34 | };
35 |
36 | #endif // COMMAND_LINE_PARSER_HPP_
--------------------------------------------------------------------------------
/daemon/include/daemon.hpp:
--------------------------------------------------------------------------------
1 | #ifndef DAEMON_HPP_
2 | #define DAEMON_HPP_
3 |
4 | #include
5 | #include
6 |
7 | class Daemon {
8 | public:
9 | static Daemon& instance() {
10 | static Daemon instance;
11 | return instance;
12 | }
13 |
14 | void setReloadFunction(std::function func);
15 |
16 | bool IsRunning();
17 |
18 | private:
19 | std::function m_reloadFunc;
20 | bool m_isRunning;
21 | bool m_reload;
22 |
23 | Daemon();
24 | Daemon(Daemon const&) = delete;
25 | void operator=(Daemon const&) = delete;
26 |
27 | void Reload();
28 |
29 | static void signalHandler(int signal);
30 | };
31 |
32 | #endif // DAEMON_HPP_
33 |
--------------------------------------------------------------------------------
/daemon/include/log.hpp:
--------------------------------------------------------------------------------
1 | #ifndef LOG_HPP_
2 | #define LOG_HPP_
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #define LOG_INFO(...) Log(std::clog, "INFO").write(__VA_ARGS__)
11 | #define LOG_WARNING(...) Log(std::clog, "WARNING", __FUNCTION__, __LINE__).write(__VA_ARGS__)
12 | #define LOG_ERROR(...) Log(std::clog, "ERROR", __FUNCTION__, __LINE__).write(__VA_ARGS__)
13 | #define LOG_DEBUG(...) Log(std::clog, "DEBUG", __FUNCTION__, __LINE__).write(__VA_ARGS__)
14 |
15 | inline std::string getLogTime(std::chrono::time_point time) {
16 | auto epoch_seconds = std::chrono::system_clock::to_time_t(time);
17 | std::stringstream stream;
18 | stream << std::put_time(gmtime(&epoch_seconds), "[%F_%T");
19 | auto truncated = std::chrono::system_clock::from_time_t(epoch_seconds);
20 | auto delta_us = std::chrono::duration_cast(time - truncated).count();
21 | stream << "." << std::fixed << std::setw(3) << std::setfill('0') << delta_us << "]";
22 | return stream.str();
23 | }
24 |
25 | template
26 | void writeLog(std::ostream &out, TF const &f) {
27 | out << f;
28 | }
29 |
30 | struct Log {
31 | std::ostream &out;
32 |
33 | Log(std::ostream &out,
34 | const char *type,
35 | const char *functionName,
36 | int line) : out(out) {
37 | out << getLogTime(std::chrono::system_clock::now()) << " " << type << " " << line << ":" << functionName << "()-> ";
38 | }
39 |
40 | Log(std::ostream &out,
41 | const char *type) : out(out) {
42 | out << getLogTime(std::chrono::system_clock::now()) << " " << type << "-> ";
43 | }
44 |
45 | ~Log() {
46 | out << std::endl;
47 | }
48 |
49 | template
50 | void write(TF const &f, TR const &... rest) {
51 | writeLog(out, f);
52 | write(rest...);
53 | }
54 |
55 | template
56 | void write(TF const &f) {
57 | writeLog(out, f);
58 | }
59 |
60 | void write() {} // Handle the empty params case
61 | };
62 |
63 | #endif // LOG_HPP_
--------------------------------------------------------------------------------
/daemon/resource/config/daemon-template.conf:
--------------------------------------------------------------------------------
1 |
2 | test = "daemon-template";
3 |
--------------------------------------------------------------------------------
/daemon/resource/systemd/daemon-template.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Simple daemon template
3 | After=network.target
4 |
5 | [Service]
6 | Type=simple
7 | ExecStart=/usr/bin/daemon-template --config /etc/daemon-template/daemon-template.conf
8 | ExecReload=/bin/kill -HUP $MAINPID
9 | User=root
10 | StandardOutput=syslog
11 | StandardError=syslog
12 | SyslogIdentifier=daemon-template
13 |
14 | [Install]
15 | WantedBy=multi-user.target
16 |
--------------------------------------------------------------------------------
/daemon/source/daemon.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include "daemon.hpp"
3 | #include "log.hpp"
4 |
5 | Daemon::Daemon() {
6 | m_isRunning = true;
7 | m_reload = false;
8 | signal(SIGINT, Daemon::signalHandler);
9 | signal(SIGTERM, Daemon::signalHandler);
10 | signal(SIGHUP, Daemon::signalHandler);
11 | }
12 |
13 | void Daemon::setReloadFunction(std::function func) {
14 | m_reloadFunc = func;
15 | }
16 |
17 | bool Daemon::IsRunning() {
18 | if (m_reload) {
19 | m_reload = false;
20 | m_reloadFunc();
21 | }
22 | return m_isRunning;
23 | }
24 |
25 | void Daemon::signalHandler(int signal) {
26 | LOG_INFO("Interrup signal number [", signal, "] recived.");
27 | switch (signal) {
28 | case SIGINT:
29 | case SIGTERM: {
30 | Daemon::instance().m_isRunning = false;
31 | break;
32 | }
33 | case SIGHUP: {
34 | Daemon::instance().m_reload = true;
35 | break;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/daemon/source/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | #include "command-line-parser.hpp"
7 | #include "daemon.hpp"
8 | #include "log.hpp"
9 |
10 | // This function will be called when the daemon receive a SIGHUP signal.
11 | void reload() {
12 | LOG_INFO("Reload function called.");
13 | }
14 |
15 | int main(int argc, char** argv) {
16 | // Command line and config file example
17 | CommandLineParser commandLine(argc, argv);
18 | if (commandLine.cmdOptionExist("--config")) {
19 | const std::string configFileName = commandLine.getCmdOptionValue("--config");
20 | LOG_INFO("Config file name = ", configFileName);
21 | libconfig::Config config;
22 | config.readFile(configFileName.c_str());
23 | std::string test{};
24 | config.lookupValue("test", test);
25 | LOG_INFO("Config option test = ", test);
26 | }
27 |
28 | // The Daemon class is a singleton to avoid be instantiate more than once
29 | Daemon& daemon = Daemon::instance();
30 |
31 | // Set the reload function to be called in case of receiving a SIGHUP signal
32 | daemon.setReloadFunction(reload);
33 |
34 | // Daemon main loop
35 | int count = 0;
36 | while (daemon.IsRunning()) {
37 | LOG_DEBUG("Count: ", count++);
38 | std::this_thread::sleep_for(std::chrono::seconds(1));
39 | }
40 |
41 | LOG_INFO("The daemon process ended gracefully.");
42 | }
43 |
--------------------------------------------------------------------------------