]>
Commit | Line | Data |
---|---|---|
b32b8144 FG |
1 | // |
2 | // daemon.cpp | |
3 | // ~~~~~~~~~~ | |
4 | // | |
92f5a8d4 | 5 | // Copyright (c) 2003-2019 Christopher M. Kohlhoff (chris at kohlhoff dot com) |
b32b8144 FG |
6 | // |
7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
9 | // | |
10 | ||
11 | #include <boost/asio/io_context.hpp> | |
12 | #include <boost/asio/ip/udp.hpp> | |
13 | #include <boost/asio/signal_set.hpp> | |
14 | #include <array> | |
15 | #include <ctime> | |
16 | #include <iostream> | |
17 | #include <syslog.h> | |
18 | #include <unistd.h> | |
19 | ||
20 | using boost::asio::ip::udp; | |
21 | ||
22 | class udp_daytime_server | |
23 | { | |
24 | public: | |
25 | udp_daytime_server(boost::asio::io_context& io_context) | |
26 | : socket_(io_context, {udp::v4(), 13}) | |
27 | { | |
28 | receive(); | |
29 | } | |
30 | ||
31 | private: | |
32 | void receive() | |
33 | { | |
34 | socket_.async_receive_from( | |
35 | boost::asio::buffer(recv_buffer_), remote_endpoint_, | |
36 | [this](boost::system::error_code ec, std::size_t /*n*/) | |
37 | { | |
38 | if (!ec) | |
39 | { | |
40 | using namespace std; // For time_t, time and ctime; | |
41 | time_t now = time(0); | |
42 | std::string message = ctime(&now); | |
43 | ||
44 | boost::system::error_code ignored_ec; | |
45 | socket_.send_to(boost::asio::buffer(message), | |
46 | remote_endpoint_, 0, ignored_ec); | |
47 | } | |
48 | ||
49 | receive(); | |
50 | }); | |
51 | } | |
52 | ||
53 | udp::socket socket_; | |
54 | udp::endpoint remote_endpoint_; | |
55 | std::array<char, 1> recv_buffer_; | |
56 | }; | |
57 | ||
58 | int main() | |
59 | { | |
60 | try | |
61 | { | |
62 | boost::asio::io_context io_context; | |
63 | ||
64 | // Initialise the server before becoming a daemon. If the process is | |
65 | // started from a shell, this means any errors will be reported back to the | |
66 | // user. | |
67 | udp_daytime_server server(io_context); | |
68 | ||
69 | // Register signal handlers so that the daemon may be shut down. You may | |
70 | // also want to register for other signals, such as SIGHUP to trigger a | |
71 | // re-read of a configuration file. | |
72 | boost::asio::signal_set signals(io_context, SIGINT, SIGTERM); | |
73 | signals.async_wait( | |
74 | [&](boost::system::error_code /*ec*/, int /*signo*/) | |
75 | { | |
76 | io_context.stop(); | |
77 | }); | |
78 | ||
79 | // Inform the io_context that we are about to become a daemon. The | |
80 | // io_context cleans up any internal resources, such as threads, that may | |
81 | // interfere with forking. | |
82 | io_context.notify_fork(boost::asio::io_context::fork_prepare); | |
83 | ||
84 | // Fork the process and have the parent exit. If the process was started | |
85 | // from a shell, this returns control to the user. Forking a new process is | |
86 | // also a prerequisite for the subsequent call to setsid(). | |
87 | if (pid_t pid = fork()) | |
88 | { | |
89 | if (pid > 0) | |
90 | { | |
91 | // We're in the parent process and need to exit. | |
92 | // | |
93 | // When the exit() function is used, the program terminates without | |
94 | // invoking local variables' destructors. Only global variables are | |
95 | // destroyed. As the io_context object is a local variable, this means | |
96 | // we do not have to call: | |
97 | // | |
98 | // io_context.notify_fork(boost::asio::io_context::fork_parent); | |
99 | // | |
100 | // However, this line should be added before each call to exit() if | |
101 | // using a global io_context object. An additional call: | |
102 | // | |
103 | // io_context.notify_fork(boost::asio::io_context::fork_prepare); | |
104 | // | |
105 | // should also precede the second fork(). | |
106 | exit(0); | |
107 | } | |
108 | else | |
109 | { | |
110 | syslog(LOG_ERR | LOG_USER, "First fork failed: %m"); | |
111 | return 1; | |
112 | } | |
113 | } | |
114 | ||
115 | // Make the process a new session leader. This detaches it from the | |
116 | // terminal. | |
117 | setsid(); | |
118 | ||
119 | // A process inherits its working directory from its parent. This could be | |
120 | // on a mounted filesystem, which means that the running daemon would | |
121 | // prevent this filesystem from being unmounted. Changing to the root | |
122 | // directory avoids this problem. | |
123 | chdir("/"); | |
124 | ||
125 | // The file mode creation mask is also inherited from the parent process. | |
126 | // We don't want to restrict the permissions on files created by the | |
127 | // daemon, so the mask is cleared. | |
128 | umask(0); | |
129 | ||
130 | // A second fork ensures the process cannot acquire a controlling terminal. | |
131 | if (pid_t pid = fork()) | |
132 | { | |
133 | if (pid > 0) | |
134 | { | |
135 | exit(0); | |
136 | } | |
137 | else | |
138 | { | |
139 | syslog(LOG_ERR | LOG_USER, "Second fork failed: %m"); | |
140 | return 1; | |
141 | } | |
142 | } | |
143 | ||
144 | // Close the standard streams. This decouples the daemon from the terminal | |
145 | // that started it. | |
146 | close(0); | |
147 | close(1); | |
148 | close(2); | |
149 | ||
150 | // We don't want the daemon to have any standard input. | |
151 | if (open("/dev/null", O_RDONLY) < 0) | |
152 | { | |
153 | syslog(LOG_ERR | LOG_USER, "Unable to open /dev/null: %m"); | |
154 | return 1; | |
155 | } | |
156 | ||
157 | // Send standard output to a log file. | |
158 | const char* output = "/tmp/asio.daemon.out"; | |
159 | const int flags = O_WRONLY | O_CREAT | O_APPEND; | |
160 | const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; | |
161 | if (open(output, flags, mode) < 0) | |
162 | { | |
163 | syslog(LOG_ERR | LOG_USER, "Unable to open output file %s: %m", output); | |
164 | return 1; | |
165 | } | |
166 | ||
167 | // Also send standard error to the same log file. | |
168 | if (dup(1) < 0) | |
169 | { | |
170 | syslog(LOG_ERR | LOG_USER, "Unable to dup output descriptor: %m"); | |
171 | return 1; | |
172 | } | |
173 | ||
174 | // Inform the io_context that we have finished becoming a daemon. The | |
175 | // io_context uses this opportunity to create any internal file descriptors | |
176 | // that need to be private to the new process. | |
177 | io_context.notify_fork(boost::asio::io_context::fork_child); | |
178 | ||
179 | // The io_context can now be used normally. | |
180 | syslog(LOG_INFO | LOG_USER, "Daemon started"); | |
181 | io_context.run(); | |
182 | syslog(LOG_INFO | LOG_USER, "Daemon stopped"); | |
183 | } | |
184 | catch (std::exception& e) | |
185 | { | |
186 | syslog(LOG_ERR | LOG_USER, "Exception: %s", e.what()); | |
187 | std::cerr << "Exception: " << e.what() << std::endl; | |
188 | } | |
189 | } |