1 /*
2 * Copyright (c) 2021, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "posix/platform/daemon.hpp"
30
31 #include <fcntl.h>
32 #include <signal.h>
33 #include <stdarg.h>
34 #include <string.h>
35 #include <sys/file.h>
36 #include <sys/socket.h>
37 #include <sys/un.h>
38 #include <unistd.h>
39
40 #include <openthread/cli.h>
41
42 #include "cli/cli_config.h"
43 #include "common/code_utils.hpp"
44 #include "posix/platform/platform-posix.h"
45
46 #if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
47
48 #define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock"
49 static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockaddr_un::sun_path),
50 "OpenThread daemon socket name too long!");
51
52 namespace ot {
53 namespace Posix {
54
55 namespace {
56
57 typedef char(Filename)[sizeof(sockaddr_un::sun_path)];
58
GetFilename(Filename & aFilename,const char * aPattern)59 void GetFilename(Filename &aFilename, const char *aPattern)
60 {
61 int rval;
62
63 rval = snprintf(aFilename, sizeof(aFilename), aPattern, gNetifName);
64 if (rval < 0 && static_cast<size_t>(rval) >= sizeof(aFilename))
65 {
66 DieNow(OT_EXIT_INVALID_ARGUMENTS);
67 }
68 }
69
70 } // namespace
71
OutputFormatV(const char * aFormat,va_list aArguments)72 int Daemon::OutputFormatV(const char *aFormat, va_list aArguments)
73 {
74 char buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH + 1];
75 int rval;
76
77 buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH] = '\0';
78
79 rval = vsnprintf(buf, sizeof(buf) - 1, aFormat, aArguments);
80
81 VerifyOrExit(rval >= 0, otLogWarnPlat("Failed to format CLI output: %s", strerror(errno)));
82
83 VerifyOrExit(mSessionSocket != -1);
84
85 #if defined(__linux__)
86 // Don't die on SIGPIPE
87 rval = send(mSessionSocket, buf, static_cast<size_t>(rval), MSG_NOSIGNAL);
88 #else
89 rval = static_cast<int>(write(mSessionSocket, buf, static_cast<size_t>(rval)));
90 #endif
91
92 if (rval < 0)
93 {
94 otLogWarnPlat("Failed to write CLI output: %s", strerror(errno));
95 close(mSessionSocket);
96 mSessionSocket = -1;
97 }
98
99 exit:
100 return rval;
101 }
102
InitializeSessionSocket(void)103 void Daemon::InitializeSessionSocket(void)
104 {
105 int newSessionSocket;
106 int rval;
107
108 VerifyOrExit((newSessionSocket = accept(mListenSocket, nullptr, nullptr)) != -1, rval = -1);
109
110 VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1);
111
112 rval |= FD_CLOEXEC;
113
114 VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1);
115
116 #ifndef __linux__
117 // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL
118 // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE
119 // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going
120 // to simply ignore it.
121 #if defined(SO_NOSIGPIPE)
122 rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval));
123 VerifyOrExit(rval != -1);
124 #else
125 #warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE"
126 #endif
127 #endif // __linux__
128
129 if (mSessionSocket != -1)
130 {
131 close(mSessionSocket);
132 }
133 mSessionSocket = newSessionSocket;
134
135 exit:
136 if (rval == -1)
137 {
138 otLogWarnPlat("Failed to initialize session socket: %s", strerror(errno));
139 if (newSessionSocket != -1)
140 {
141 close(newSessionSocket);
142 }
143 }
144 else
145 {
146 otLogInfoPlat("Session socket is ready", strerror(errno));
147 }
148 }
149
SetUp(void)150 void Daemon::SetUp(void)
151 {
152 struct sockaddr_un sockname;
153 int ret;
154
155 // This allows implementing pseudo reset.
156 VerifyOrExit(mListenSocket == -1);
157
158 mListenSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock);
159
160 if (mListenSocket == -1)
161 {
162 DieNow(OT_EXIT_FAILURE);
163 }
164
165 {
166 static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK) == sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME),
167 "sock and lock file name pattern should have the same length!");
168 Filename lockfile;
169
170 GetFilename(lockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK);
171
172 mDaemonLock = open(lockfile, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);
173 }
174
175 if (mDaemonLock == -1)
176 {
177 DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO);
178 }
179
180 if (flock(mDaemonLock, LOCK_EX | LOCK_NB) == -1)
181 {
182 DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO);
183 }
184
185 memset(&sockname, 0, sizeof(struct sockaddr_un));
186
187 sockname.sun_family = AF_UNIX;
188 GetFilename(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
189 (void)unlink(sockname.sun_path);
190
191 ret = bind(mListenSocket, (const struct sockaddr *)&sockname, sizeof(struct sockaddr_un));
192
193 if (ret == -1)
194 {
195 DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO);
196 }
197
198 //
199 // only accept 1 connection.
200 //
201 ret = listen(mListenSocket, 1);
202 if (ret == -1)
203 {
204 DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO);
205 }
206
207 otCliInit(
208 gInstance,
209 [](void *aContext, const char *aFormat, va_list aArguments) -> int {
210 return static_cast<Daemon *>(aContext)->OutputFormatV(aFormat, aArguments);
211 },
212 this);
213
214 Mainloop::Manager::Get().Add(*this);
215
216 exit:
217 return;
218 }
219
TearDown(void)220 void Daemon::TearDown(void)
221 {
222 Mainloop::Manager::Get().Remove(*this);
223
224 if (mSessionSocket != -1)
225 {
226 close(mSessionSocket);
227 mSessionSocket = -1;
228 }
229
230 if (mListenSocket != -1)
231 {
232 close(mListenSocket);
233 mListenSocket = -1;
234 }
235
236 if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE)
237 {
238 Filename sockfile;
239
240 GetFilename(sockfile, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
241 otLogDebgPlat("Removing daemon socket: %s", sockfile);
242 (void)unlink(sockfile);
243 }
244
245 if (mDaemonLock != -1)
246 {
247 (void)flock(mDaemonLock, LOCK_UN);
248 close(mDaemonLock);
249 mDaemonLock = -1;
250 }
251 }
252
Update(otSysMainloopContext & aContext)253 void Daemon::Update(otSysMainloopContext &aContext)
254 {
255 if (mListenSocket != -1)
256 {
257 FD_SET(mListenSocket, &aContext.mReadFdSet);
258 FD_SET(mListenSocket, &aContext.mErrorFdSet);
259
260 if (aContext.mMaxFd < mListenSocket)
261 {
262 aContext.mMaxFd = mListenSocket;
263 }
264 }
265
266 if (mSessionSocket != -1)
267 {
268 FD_SET(mSessionSocket, &aContext.mReadFdSet);
269 FD_SET(mSessionSocket, &aContext.mErrorFdSet);
270
271 if (aContext.mMaxFd < mSessionSocket)
272 {
273 aContext.mMaxFd = mSessionSocket;
274 }
275 }
276
277 return;
278 }
279
Process(const otSysMainloopContext & aContext)280 void Daemon::Process(const otSysMainloopContext &aContext)
281 {
282 ssize_t rval;
283
284 VerifyOrExit(mListenSocket != -1);
285
286 if (FD_ISSET(mListenSocket, &aContext.mErrorFdSet))
287 {
288 DieNowWithMessage("daemon socket error", OT_EXIT_FAILURE);
289 }
290 else if (FD_ISSET(mListenSocket, &aContext.mReadFdSet))
291 {
292 InitializeSessionSocket();
293 }
294
295 VerifyOrExit(mSessionSocket != -1);
296
297 if (FD_ISSET(mSessionSocket, &aContext.mErrorFdSet))
298 {
299 close(mSessionSocket);
300 mSessionSocket = -1;
301 }
302 else if (FD_ISSET(mSessionSocket, &aContext.mReadFdSet))
303 {
304 uint8_t buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
305
306 // leave 1 byte for the null terminator
307 rval = read(mSessionSocket, buffer, sizeof(buffer) - 1);
308
309 if (rval > 0)
310 {
311 buffer[rval] = '\0';
312 otLogInfoPlat("> %s", reinterpret_cast<const char *>(buffer));
313 otCliInputLine(reinterpret_cast<char *>(buffer));
314 otCliOutputFormat("> ");
315 }
316 else
317 {
318 if (rval < 0)
319 {
320 otLogWarnPlat("Daemon read: %s", strerror(errno));
321 }
322 close(mSessionSocket);
323 mSessionSocket = -1;
324 }
325 }
326
327 exit:
328 return;
329 }
330
Get(void)331 Daemon &Daemon::Get(void)
332 {
333 static Daemon sInstance;
334
335 return sInstance;
336 }
337
338 } // namespace Posix
339 } // namespace ot
340 #endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
341