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