]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // daemon.cpp | |
3 | // ~~~~~~~~~~ | |
4 | // | |
b32b8144 | 5 | // Copyright (c) 2003-2017 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> | |
15 | #include <boost/bind.hpp> | |
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_, | |
37 | boost::bind(&udp_daytime_server::handle_receive, this, _1)); | |
38 | } | |
39 | ||
40 | void handle_receive(const boost::system::error_code& ec) | |
41 | { | |
b32b8144 | 42 | if (!ec) |
7c673cae FG |
43 | { |
44 | using namespace std; // For time_t, time and ctime; | |
45 | time_t now = time(0); | |
46 | std::string message = ctime(&now); | |
47 | ||
48 | boost::system::error_code ignored_ec; | |
49 | socket_.send_to(boost::asio::buffer(message), | |
50 | remote_endpoint_, 0, ignored_ec); | |
51 | } | |
52 | ||
53 | start_receive(); | |
54 | } | |
55 | ||
56 | udp::socket socket_; | |
57 | udp::endpoint remote_endpoint_; | |
58 | boost::array<char, 1> recv_buffer_; | |
59 | }; | |
60 | ||
61 | int main() | |
62 | { | |
63 | try | |
64 | { | |
b32b8144 | 65 | boost::asio::io_context io_context; |
7c673cae FG |
66 | |
67 | // Initialise the server before becoming a daemon. If the process is | |
68 | // started from a shell, this means any errors will be reported back to the | |
69 | // user. | |
b32b8144 | 70 | udp_daytime_server server(io_context); |
7c673cae FG |
71 | |
72 | // Register signal handlers so that the daemon may be shut down. You may | |
73 | // also want to register for other signals, such as SIGHUP to trigger a | |
74 | // re-read of a configuration file. | |
b32b8144 | 75 | boost::asio::signal_set signals(io_context, SIGINT, SIGTERM); |
7c673cae | 76 | signals.async_wait( |
b32b8144 | 77 | boost::bind(&boost::asio::io_context::stop, &io_context)); |
7c673cae | 78 | |
b32b8144 FG |
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 | |
7c673cae | 81 | // interfere with forking. |
b32b8144 | 82 | io_context.notify_fork(boost::asio::io_context::fork_prepare); |
7c673cae FG |
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 | |
b32b8144 | 95 | // destroyed. As the io_context object is a local variable, this means |
7c673cae FG |
96 | // we do not have to call: |
97 | // | |
b32b8144 | 98 | // io_context.notify_fork(boost::asio::io_context::fork_parent); |
7c673cae FG |
99 | // |
100 | // However, this line should be added before each call to exit() if | |
b32b8144 | 101 | // using a global io_context object. An additional call: |
7c673cae | 102 | // |
b32b8144 | 103 | // io_context.notify_fork(boost::asio::io_context::fork_prepare); |
7c673cae FG |
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 | ||
b32b8144 FG |
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 | |
7c673cae | 176 | // that need to be private to the new process. |
b32b8144 | 177 | io_context.notify_fork(boost::asio::io_context::fork_child); |
7c673cae | 178 | |
b32b8144 | 179 | // The io_context can now be used normally. |
7c673cae | 180 | syslog(LOG_INFO | LOG_USER, "Daemon started"); |
b32b8144 | 181 | io_context.run(); |
7c673cae FG |
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 | } |