1 /*
2  *  Copyright (c) 2019, 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 "platform/openthread-posix-config.h"
30 
31 #include "cli/cli_config.h"
32 
33 #include <openthread/platform/toolchain.h>
34 
35 #ifndef HAVE_LIBEDIT
36 #define HAVE_LIBEDIT 0
37 #endif
38 
39 #ifndef HAVE_LIBREADLINE
40 #define HAVE_LIBREADLINE 0
41 #endif
42 
43 #define OPENTHREAD_USE_READLINE (HAVE_LIBEDIT || HAVE_LIBREADLINE)
44 
45 #include <assert.h>
46 #include <getopt.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sys/socket.h>
51 #include <sys/un.h>
52 #include <time.h>
53 #include <unistd.h>
54 
55 #if HAVE_LIBEDIT
56 #include <editline/readline.h>
57 #elif HAVE_LIBREADLINE
58 #include <readline/history.h>
59 #include <readline/readline.h>
60 #endif
61 
62 #include "common/code_utils.hpp"
63 
64 #include "platform-posix.h"
65 
66 namespace {
67 
68 struct Config
69 {
70     const char *mNetifName;
71 };
72 
73 enum
74 {
75     kLineBufferSize = OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
76 };
77 
78 static_assert(kLineBufferSize >= sizeof("> "), "kLineBufferSize is too small");
79 static_assert(kLineBufferSize >= sizeof("Done\r\n"), "kLineBufferSize is too small");
80 static_assert(kLineBufferSize >= sizeof("Error "), "kLineBufferSize is too small");
81 
82 int sSessionFd = -1;
83 
QuitOnExit(const char * aBuffer)84 void QuitOnExit(const char *aBuffer)
85 {
86     constexpr char kExit[] = "exit";
87 
88     while (*aBuffer == ' ' || *aBuffer == '\t')
89     {
90         ++aBuffer;
91     }
92 
93     VerifyOrExit(strstr(aBuffer, kExit) == aBuffer);
94 
95     aBuffer += sizeof(kExit) - 1;
96 
97     while (*aBuffer == ' ' || *aBuffer == '\t')
98     {
99         ++aBuffer;
100     }
101 
102     switch (*aBuffer)
103     {
104     case '\0':
105     case '\r':
106     case '\n':
107         exit(OT_EXIT_SUCCESS);
108         break;
109     default:
110         break;
111     }
112 
113 exit:
114     return;
115 }
116 
117 #if OPENTHREAD_USE_READLINE
InputCallback(char * aLine)118 void InputCallback(char *aLine)
119 {
120     if (aLine != nullptr)
121     {
122         QuitOnExit(aLine);
123         add_history(aLine);
124         dprintf(sSessionFd, "%s\n", aLine);
125         free(aLine);
126     }
127     else
128     {
129         exit(OT_EXIT_SUCCESS);
130     }
131 }
132 #endif // OPENTHREAD_USE_READLINE
133 
DoWrite(int aFile,const void * aBuffer,size_t aSize)134 bool DoWrite(int aFile, const void *aBuffer, size_t aSize)
135 {
136     bool ret = true;
137 
138     while (aSize)
139     {
140         ssize_t rval = write(aFile, aBuffer, aSize);
141         if (rval <= 0)
142         {
143             VerifyOrExit((rval == -1) && (errno == EINTR), perror("write"); ret = false);
144         }
145         else
146         {
147             aBuffer = reinterpret_cast<const uint8_t *>(aBuffer) + rval;
148             aSize -= static_cast<size_t>(rval);
149         }
150     }
151 
152 exit:
153     return ret;
154 }
155 
ConnectSession(const Config & aConfig)156 int ConnectSession(const Config &aConfig)
157 {
158     int ret;
159 
160     if (sSessionFd != -1)
161     {
162         close(sSessionFd);
163     }
164 
165     sSessionFd = socket(AF_UNIX, SOCK_STREAM, 0);
166     VerifyOrExit(sSessionFd != -1, ret = -1);
167 
168     {
169         struct sockaddr_un sockname;
170 
171         memset(&sockname, 0, sizeof(struct sockaddr_un));
172         sockname.sun_family = AF_UNIX;
173         ret = snprintf(sockname.sun_path, sizeof(sockname.sun_path), OPENTHREAD_POSIX_DAEMON_SOCKET_NAME,
174                        aConfig.mNetifName);
175         VerifyOrExit(ret >= 0 && static_cast<size_t>(ret) < sizeof(sockname.sun_path), {
176             errno = EINVAL;
177             ret   = -1;
178         });
179 
180         ret = connect(sSessionFd, reinterpret_cast<const struct sockaddr *>(&sockname), sizeof(struct sockaddr_un));
181     }
182 
183 exit:
184     return ret;
185 }
186 
ReconnectSession(Config & aConfig)187 bool ReconnectSession(Config &aConfig)
188 {
189     bool     ok    = false;
190     uint32_t delay = 0; // 100ms
191 
192     for (int i = 0; i < 6; i++) // delay for 3.1s in total
193     {
194         int rval;
195 
196         usleep(delay);
197         delay = delay > 0 ? delay * 2 : 100000;
198 
199         rval = ConnectSession(aConfig);
200 
201         VerifyOrExit(rval == -1, ok = true);
202 
203         // Exit immediately if the sock file is not found
204         VerifyOrExit(errno != ENOENT);
205     }
206 
207 exit:
208     return ok;
209 }
210 
211 enum
212 {
213     kOptInterfaceName = 'I',
214     kOptHelp          = 'h',
215 };
216 
217 const struct option kOptions[] = {
218     {"interface-name", required_argument, NULL, kOptInterfaceName},
219     {"help", required_argument, NULL, kOptHelp},
220 };
221 
PrintUsage(const char * aProgramName,FILE * aStream,int aExitCode)222 void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode)
223 {
224     fprintf(aStream,
225             "Syntax:\n"
226             "    %s [Options] [--] ...\n"
227             "Options:\n"
228             "    -h  --help                    Display this usage information.\n"
229             "    -I  --interface-name name     Thread network interface name.\n",
230             aProgramName);
231     exit(aExitCode);
232 }
233 
ShouldEscape(char aChar)234 static bool ShouldEscape(char aChar)
235 {
236     return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') || (aChar == '\n') || (aChar == '\\');
237 }
238 
ParseArg(int & aArgCount,char ** & aArgVector)239 Config ParseArg(int &aArgCount, char **&aArgVector)
240 {
241     Config config = {"wpan0"};
242 
243     optind = 1;
244 
245     for (int index, option; (option = getopt_long(aArgCount, aArgVector, "+I:h", kOptions, &index)) != -1;)
246     {
247         switch (option)
248         {
249         case kOptInterfaceName:
250             config.mNetifName = optarg;
251             break;
252         case kOptHelp:
253             PrintUsage(aArgVector[0], stdout, OT_EXIT_SUCCESS);
254             break;
255         default:
256             PrintUsage(aArgVector[0], stderr, OT_EXIT_FAILURE);
257             break;
258         }
259     }
260 
261     aArgCount -= optind;
262     aArgVector += optind;
263 
264     return config;
265 }
266 
267 } // namespace
268 
main(int argc,char * argv[])269 int main(int argc, char *argv[])
270 {
271     bool   isInteractive = true;
272     bool   isFinished    = false;
273     bool   isBeginOfLine = true;
274     char   lineBuffer[kLineBufferSize];
275     size_t lineBufferWritePos = 0;
276     int    ret;
277     Config config;
278 
279     config = ParseArg(argc, argv);
280 
281     VerifyOrExit(ConnectSession(config) != -1, perror("connect session failed"); ret = OT_EXIT_FAILURE);
282 
283     if (argc > 0)
284     {
285         char   buffer[kLineBufferSize];
286         size_t count = 0;
287 
288         for (int i = 0; i < argc; i++)
289         {
290             for (const char *c = argv[i]; *c && count < sizeof(buffer);)
291             {
292                 if (ShouldEscape(*c))
293                 {
294                     buffer[count++] = '\\';
295 
296                     VerifyOrExit(count < sizeof(buffer), ret = OT_EXIT_INVALID_ARGUMENTS);
297                 }
298 
299                 buffer[count++] = *c++;
300             }
301 
302             VerifyOrExit(count < sizeof(buffer), ret = OT_EXIT_INVALID_ARGUMENTS);
303             buffer[count++] = ' ';
304         }
305 
306         // ignore the trailing space
307         if (--count)
308         {
309             VerifyOrExit(DoWrite(sSessionFd, buffer, count), ret = OT_EXIT_FAILURE);
310         }
311 
312         isInteractive = false;
313     }
314 #if OPENTHREAD_USE_READLINE
315     else
316     {
317         rl_instream           = stdin;
318         rl_outstream          = stdout;
319         rl_inhibit_completion = true;
320         rl_callback_handler_install("> ", InputCallback);
321         rl_already_prompted = 1;
322     }
323 #endif
324 
325     while (!isFinished)
326     {
327         char   buffer[kLineBufferSize];
328         fd_set readFdSet;
329         int    maxFd = sSessionFd;
330 
331         FD_ZERO(&readFdSet);
332 
333         FD_SET(sSessionFd, &readFdSet);
334 
335         if (isInteractive)
336         {
337             FD_SET(STDIN_FILENO, &readFdSet);
338             if (STDIN_FILENO > maxFd)
339             {
340                 maxFd = STDIN_FILENO;
341             }
342         }
343 
344         ret = select(maxFd + 1, &readFdSet, nullptr, nullptr, nullptr);
345 
346         VerifyOrExit(ret != -1, perror("select"); ret = OT_EXIT_FAILURE);
347 
348         if (ret == 0)
349         {
350             ExitNow(ret = OT_EXIT_SUCCESS);
351         }
352 
353         if (isInteractive && FD_ISSET(STDIN_FILENO, &readFdSet))
354         {
355 #if OPENTHREAD_USE_READLINE
356             rl_callback_read_char();
357 #else
358             VerifyOrExit(fgets(buffer, sizeof(buffer), stdin) != nullptr, ret = OT_EXIT_FAILURE);
359 
360             QuitOnExit(buffer);
361             VerifyOrExit(DoWrite(sSessionFd, buffer, strlen(buffer)), ret = OT_EXIT_FAILURE);
362 #endif
363         }
364 
365         if (FD_ISSET(sSessionFd, &readFdSet))
366         {
367             ssize_t rval = read(sSessionFd, buffer, sizeof(buffer));
368             VerifyOrExit(rval != -1, perror("read"); ret = OT_EXIT_FAILURE);
369 
370             if (rval == 0)
371             {
372                 // daemon closed sSessionFd
373                 if (isInteractive && ReconnectSession(config))
374                 {
375                     continue;
376                 }
377 
378                 ExitNow(ret = isInteractive ? OT_EXIT_FAILURE : OT_EXIT_SUCCESS);
379             }
380 
381             if (isInteractive)
382             {
383                 VerifyOrExit(DoWrite(STDOUT_FILENO, buffer, static_cast<size_t>(rval)), ret = OT_EXIT_FAILURE);
384             }
385             else
386             {
387                 for (ssize_t i = 0; i < rval; i++)
388                 {
389                     char c = buffer[i];
390 
391                     lineBuffer[lineBufferWritePos++] = c;
392                     if (c == '\n' || lineBufferWritePos >= sizeof(lineBuffer) - 1)
393                     {
394                         char * line = lineBuffer;
395                         size_t len  = lineBufferWritePos;
396 
397                         // read one line successfully or line buffer is full
398                         line[len] = '\0';
399 
400                         if (isBeginOfLine && strncmp("> ", lineBuffer, 2) == 0)
401                         {
402                             line += 2;
403                             len -= 2;
404                         }
405 
406                         VerifyOrExit(DoWrite(STDOUT_FILENO, line, len), ret = OT_EXIT_FAILURE);
407 
408                         if (isBeginOfLine && (strncmp("Done\n", line, 5) == 0 || strncmp("Done\r\n", line, 6) == 0 ||
409                                               strncmp("Error ", line, 6) == 0))
410                         {
411                             isFinished = true;
412                             ret        = OT_EXIT_SUCCESS;
413                             break;
414                         }
415 
416                         // reset for next line
417                         lineBufferWritePos = 0;
418                         isBeginOfLine      = c == '\n';
419                     }
420                 }
421             }
422         }
423     }
424 
425 exit:
426     if (sSessionFd != -1)
427     {
428 #if OPENTHREAD_USE_READLINE
429         if (isInteractive)
430         {
431             rl_callback_handler_remove();
432         }
433 #endif
434         close(sSessionFd);
435     }
436 
437     return ret;
438 }
439