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