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