1 /*
2  * Copyright (c) 2015, Freescale Semiconductor, Inc.
3  * Copyright 2016-2017 NXP
4  * All rights reserved.
5  *
6  * SPDX-License-Identifier: BSD-3-Clause
7  *
8  * POSIX getopt for Windows
9  * Code given out at the 1985 UNIFORUM conference in Dallas.
10  *
11  * From std-unix@ut-sally.UUCP (Moderator, John Quarterman) Sun Nov  3 14:34:15 1985
12  * Relay-Version: version B 2.10.3 4.3bsd-beta 6/6/85; site gatech.CSNET
13  * Posting-Version: version B 2.10.2 9/18/84; site ut-sally.UUCP
14  * Path: gatech!akgua!mhuxv!mhuxt!mhuxr!ulysses!allegra!mit-eddie!genrad!panda!talcott!harvard!seismo!ut-sally!std-unix
15  * From: std-unix@ut-sally.UUCP (Moderator, John Quarterman)
16  * Newsgroups: mod.std.unix
17  * Subject: public domain AT&T getopt source
18  * Message-ID: <3352@ut-sally.UUCP>
19  * Date: 3 Nov 85 19:34:15 GMT
20  * Date-Received: 4 Nov 85 12:25:09 GMT
21  * Organization: IEEE/P1003 Portable Operating System Environment Committee
22  * Lines: 91
23  * Approved: jsq@ut-sally.UUC
24  * Here's something you've all been waiting for:  the AT&T public domain
25  * source for getopt(3).  It is the code which was given out at the 1985
26  * UNIFORUM conference in Dallas.  I obtained it by electronic mail
27  * directly from AT&T.  The people there assure me that it is indeed
28  * in the public domain
29  * There is no manual page.  That is because the one they gave out at
30  * UNIFORUM was slightly different from the current System V Release 2
31  * manual page.  The difference apparently involved a note about the
32  * famous rules 5 and 6, recommending using white space between an option
33  * and its first argument, and not grouping options that have arguments.
34  * Getopt itself is currently lenient about both of these things White
35  * space is allowed, but not mandatory, and the last option in a group can
36  * have an argument.  That particular version of the man page evidently
37  * has no official existence, and my source at AT&T did not send a copy.
38  * The current SVR2 man page reflects the actual behavor of this getopt.
39  * However, I am not about to post a copy of anything licensed by AT&T.
40  */
41 
42 #include <assert.h>
43 #include "fsl_shell.h"
44 
45 /*******************************************************************************
46  * Definitions
47  ******************************************************************************/
48 #define KEY_ESC (0x1BU)
49 #define KET_DEL (0x7FU)
50 
51 /*******************************************************************************
52  * Prototypes
53  ******************************************************************************/
54 static int32_t HelpCommand(p_shell_context_t context, int32_t argc, char **argv); /*!< help command */
55 
56 static int32_t ExitCommand(p_shell_context_t context, int32_t argc, char **argv); /*!< exit command */
57 
58 static int32_t ParseLine(const char *cmd, uint32_t len, char *argv[SHELL_MAX_ARGS]); /*!< parse line command */
59 
60 static int32_t StrCompare(const char *str1, const char *str2, int32_t count); /*!< compare string command */
61 
62 static void ProcessCommand(p_shell_context_t context, const char *cmd); /*!< process a command */
63 
64 static void GetHistoryCommand(p_shell_context_t context, uint8_t hist_pos); /*!< get commands history */
65 
66 static void AutoComplete(p_shell_context_t context); /*!< auto complete command */
67 
68 static uint8_t GetChar(p_shell_context_t context); /*!< get a char from communication interface */
69 
70 static int32_t StrLen(const char *str); /*!< get string length */
71 
72 static char *StrCopy(char *dest, const char *src, int32_t count); /*!< string copy */
73 
74 /*******************************************************************************
75  * Variables
76  ******************************************************************************/
77 static const shell_command_context_t xHelpCommand = {"help", "\r\n\"help\": Lists all the registered commands\r\n",
78                                                      HelpCommand, 0};
79 
80 static const shell_command_context_t xExitCommand = {"exit", "\r\n\"exit\": Exit program\r\n", ExitCommand, 0};
81 
82 static shell_command_context_list_t g_RegisteredCommands;
83 
84 static char g_paramBuffer[SHELL_BUFFER_SIZE];
85 
86 /*******************************************************************************
87  * Code
88  ******************************************************************************/
SHELL_Init(p_shell_context_t context,send_data_cb_t send_cb,recv_data_cb_t recv_cb,printf_data_t shell_printf,char * prompt)89 void SHELL_Init(
90     p_shell_context_t context, send_data_cb_t send_cb, recv_data_cb_t recv_cb, printf_data_t shell_printf, char *prompt)
91 {
92     assert(send_cb != NULL);
93     assert(recv_cb != NULL);
94     assert(prompt != NULL);
95     assert(shell_printf != NULL);
96 
97     /* Memset for context */
98     memset(context, 0, sizeof(shell_context_struct));
99     context->send_data_func = send_cb;
100     context->recv_data_func = recv_cb;
101     context->printf_data_func = shell_printf;
102     context->prompt = prompt;
103 
104     SHELL_RegisterCommand(&xHelpCommand);
105     SHELL_RegisterCommand(&xExitCommand);
106 }
107 
SHELL_Main(p_shell_context_t context)108 int32_t SHELL_Main(p_shell_context_t context)
109 {
110     uint8_t ch;
111     int32_t i;
112 
113     if (!context)
114     {
115         return -1;
116     }
117 
118     context->exit = false;
119     context->printf_data_func("\r\nSHELL (build: %s)\r\n", __DATE__);
120     context->printf_data_func("Copyright (c) 2017 NXP Semiconductor\r\n");
121     context->printf_data_func(context->prompt);
122 
123     while (1)
124     {
125         if (context->exit)
126         {
127             break;
128         }
129         ch = GetChar(context);
130         /* If error occured when getting a char, continue to receive a new char. */
131         if ((uint8_t)(-1) == ch)
132         {
133             continue;
134         }
135         /* Special key */
136         if (ch == KEY_ESC)
137         {
138             context->stat = kSHELL_Special;
139             continue;
140         }
141         else if (context->stat == kSHELL_Special)
142         {
143             /* Function key */
144             if (ch == '[')
145             {
146                 context->stat = kSHELL_Function;
147                 continue;
148             }
149             context->stat = kSHELL_Normal;
150         }
151         else if (context->stat == kSHELL_Function)
152         {
153             context->stat = kSHELL_Normal;
154 
155             switch ((uint8_t)ch)
156             {
157                 /* History operation here */
158                 case 'A': /* Up key */
159                     GetHistoryCommand(context, context->hist_current);
160                     if (context->hist_current < (context->hist_count - 1))
161                     {
162                         context->hist_current++;
163                     }
164                     break;
165                 case 'B': /* Down key */
166                     GetHistoryCommand(context, context->hist_current);
167                     if (context->hist_current > 0)
168                     {
169                         context->hist_current--;
170                     }
171                     break;
172                 case 'D': /* Left key */
173                     if (context->c_pos)
174                     {
175                         context->printf_data_func("\b");
176                         context->c_pos--;
177                     }
178                     break;
179                 case 'C': /* Right key */
180                     if (context->c_pos < context->l_pos)
181                     {
182                         context->printf_data_func("%c", context->line[context->c_pos]);
183                         context->c_pos++;
184                     }
185                     break;
186                 default:
187                     break;
188             }
189             continue;
190         }
191         /* Handle tab key */
192         else if (ch == '\t')
193         {
194 #if SHELL_AUTO_COMPLETE
195             /* Move the cursor to the beginning of line */
196             for (i = 0; i < context->c_pos; i++)
197             {
198                 context->printf_data_func("\b");
199             }
200             /* Do auto complete */
201             AutoComplete(context);
202             /* Move position to end */
203             context->c_pos = context->l_pos = StrLen(context->line);
204 #endif
205             continue;
206         }
207 #if SHELL_SEARCH_IN_HIST
208         /* Search command in history */
209         else if ((ch == '`') && (context->l_pos == 0) && (context->line[0] == 0x00))
210         {
211         }
212 #endif
213         /* Handle backspace key */
214         else if ((ch == KET_DEL) || (ch == '\b'))
215         {
216             /* There must be at last one char */
217             if (context->c_pos == 0)
218             {
219                 continue;
220             }
221 
222             context->l_pos--;
223             context->c_pos--;
224 
225             if (context->l_pos > context->c_pos)
226             {
227                 memmove(&context->line[context->c_pos], &context->line[context->c_pos + 1],
228                         context->l_pos - context->c_pos);
229                 context->line[context->l_pos] = 0;
230                 context->printf_data_func("\b%s  \b", &context->line[context->c_pos]);
231 
232                 /* Reset position */
233                 for (i = context->c_pos; i <= context->l_pos; i++)
234                 {
235                     context->printf_data_func("\b");
236                 }
237             }
238             else /* Normal backspace operation */
239             {
240                 context->printf_data_func("\b \b");
241                 context->line[context->l_pos] = 0;
242             }
243             continue;
244         }
245         else
246         {
247         }
248 
249         /* Input too long */
250         if (context->l_pos >= (SHELL_BUFFER_SIZE - 1))
251         {
252             context->l_pos = 0;
253         }
254 
255         /* Handle end of line, break */
256         if ((ch == '\r') || (ch == '\n'))
257         {
258             static char endoflinechar = 0U;
259 
260             if ((endoflinechar != 0U) && (endoflinechar != ch))
261             {
262                 continue;
263             }
264             else
265             {
266                 endoflinechar = ch;
267                 context->printf_data_func("\r\n");
268                 /* If command line is NULL, will start a new transfer */
269                 if (0U == StrLen(context->line))
270                 {
271                     context->printf_data_func(context->prompt);
272                     continue;
273                 }
274                 ProcessCommand(context, context->line);
275                 /* Reset all params */
276                 context->c_pos = context->l_pos = 0;
277                 context->hist_current = 0;
278                 context->printf_data_func(context->prompt);
279                 memset(context->line, 0, sizeof(context->line));
280                 continue;
281             }
282         }
283 
284         /* Normal character */
285         if (context->c_pos < context->l_pos)
286         {
287             memmove(&context->line[context->c_pos + 1], &context->line[context->c_pos],
288                     context->l_pos - context->c_pos);
289             context->line[context->c_pos] = ch;
290             context->printf_data_func("%s", &context->line[context->c_pos]);
291             /* Move the cursor to new position */
292             for (i = context->c_pos; i < context->l_pos; i++)
293             {
294                 context->printf_data_func("\b");
295             }
296         }
297         else
298         {
299             context->line[context->l_pos] = ch;
300             context->printf_data_func("%c", ch);
301         }
302 
303         ch = 0;
304         context->l_pos++;
305         context->c_pos++;
306     }
307     return 0;
308 }
309 
HelpCommand(p_shell_context_t context,int32_t argc,char ** argv)310 static int32_t HelpCommand(p_shell_context_t context, int32_t argc, char **argv)
311 {
312     uint8_t i = 0;
313 
314     for (i = 0; i < g_RegisteredCommands.numberOfCommandInList; i++)
315     {
316         context->printf_data_func(g_RegisteredCommands.CommandList[i]->pcHelpString);
317     }
318     return 0;
319 }
320 
ExitCommand(p_shell_context_t context,int32_t argc,char ** argv)321 static int32_t ExitCommand(p_shell_context_t context, int32_t argc, char **argv)
322 {
323     /* Skip warning */
324     context->printf_data_func("\r\nSHELL exited\r\n");
325     context->exit = true;
326     return 0;
327 }
328 
ProcessCommand(p_shell_context_t context,const char * cmd)329 static void ProcessCommand(p_shell_context_t context, const char *cmd)
330 {
331     static const shell_command_context_t *tmpCommand = NULL;
332     static const char *tmpCommandString;
333     int32_t argc;
334     char *argv[SHELL_BUFFER_SIZE];
335     uint8_t flag = 1;
336     uint8_t tmpCommandLen;
337     uint8_t tmpLen;
338     uint8_t i = 0;
339 
340     tmpLen = StrLen(cmd);
341     argc = ParseLine(cmd, tmpLen, argv);
342 
343     if ((tmpCommand == NULL) && (argc > 0))
344     {
345         for (i = 0; i < g_RegisteredCommands.numberOfCommandInList; i++)
346         {
347             tmpCommand = g_RegisteredCommands.CommandList[i];
348             tmpCommandString = tmpCommand->pcCommand;
349             tmpCommandLen = StrLen(tmpCommandString);
350             /* Compare with space or end of string */
351             if ((cmd[tmpCommandLen] == ' ') || (cmd[tmpCommandLen] == 0x00))
352             {
353                 if (StrCompare(tmpCommandString, argv[0], tmpCommandLen) == 0)
354                 {
355                     /* support commands with optional number of parameters */
356                     if (tmpCommand->cExpectedNumberOfParameters == SHELL_OPTIONAL_PARAMS)
357                     {
358                         flag = 0;
359                     }
360                     else if ((tmpCommand->cExpectedNumberOfParameters == 0) && (argc == 1))
361                     {
362                         flag = 0;
363                     }
364                     else if (tmpCommand->cExpectedNumberOfParameters > 0)
365                     {
366                         if ((argc - 1) == tmpCommand->cExpectedNumberOfParameters)
367                         {
368                             flag = 0;
369                         }
370                     }
371                     else
372                     {
373                         flag = 1;
374                     }
375                     break;
376                 }
377             }
378         }
379     }
380 
381     if ((tmpCommand != NULL) && (flag == 1U))
382     {
383         context->printf_data_func(
384             "\r\nIncorrect command parameter(s).  Enter \"help\" to view a list of available commands.\r\n\r\n");
385         tmpCommand = NULL;
386     }
387     else if (tmpCommand != NULL)
388     {
389         tmpLen = StrLen(cmd);
390         /* Compare with last command. Push back to history buffer if different */
391         if (tmpLen != StrCompare(cmd, context->hist_buf[0], StrLen(cmd)))
392         {
393             for (i = SHELL_HIST_MAX - 1; i > 0; i--)
394             {
395                 memset(context->hist_buf[i], '\0', SHELL_BUFFER_SIZE);
396                 tmpLen = StrLen(context->hist_buf[i - 1]);
397                 StrCopy(context->hist_buf[i], context->hist_buf[i - 1], tmpLen);
398             }
399             memset(context->hist_buf[0], '\0', SHELL_BUFFER_SIZE);
400             tmpLen = StrLen(cmd);
401             StrCopy(context->hist_buf[0], cmd, tmpLen);
402             if (context->hist_count < SHELL_HIST_MAX)
403             {
404                 context->hist_count++;
405             }
406         }
407         tmpCommand->pFuncCallBack(context, argc, argv);
408         tmpCommand = NULL;
409     }
410     else
411     {
412         context->printf_data_func(
413             "\r\nCommand not recognised.  Enter 'help' to view a list of available commands.\r\n\r\n");
414         tmpCommand = NULL;
415     }
416 }
417 
GetHistoryCommand(p_shell_context_t context,uint8_t hist_pos)418 static void GetHistoryCommand(p_shell_context_t context, uint8_t hist_pos)
419 {
420     uint8_t i;
421     uint32_t tmp;
422 
423     if (context->hist_buf[0][0] == '\0')
424     {
425         context->hist_current = 0;
426         return;
427     }
428     if (hist_pos >= SHELL_HIST_MAX)
429     {
430         hist_pos = SHELL_HIST_MAX - 1;
431     }
432     tmp = StrLen(context->line);
433     /* Clear current if have */
434     if (tmp > 0)
435     {
436         memset(context->line, '\0', tmp);
437         for (i = 0; i < tmp; i++)
438         {
439             context->printf_data_func("\b \b");
440         }
441     }
442 
443     context->l_pos = StrLen(context->hist_buf[hist_pos]);
444     context->c_pos = context->l_pos;
445     StrCopy(context->line, context->hist_buf[hist_pos], context->l_pos);
446     context->printf_data_func(context->hist_buf[hist_pos]);
447 }
448 
AutoComplete(p_shell_context_t context)449 static void AutoComplete(p_shell_context_t context)
450 {
451     int32_t len;
452     int32_t minLen;
453     uint8_t i = 0;
454     const shell_command_context_t *tmpCommand = NULL;
455     const char *namePtr;
456     const char *cmdName;
457 
458     minLen = 0;
459     namePtr = NULL;
460 
461     if (!StrLen(context->line))
462     {
463         return;
464     }
465     context->printf_data_func("\r\n");
466     /* Empty tab, list all commands */
467     if (context->line[0] == '\0')
468     {
469         HelpCommand(context, 0, NULL);
470         return;
471     }
472     /* Do auto complete */
473     for (i = 0; i < g_RegisteredCommands.numberOfCommandInList; i++)
474     {
475         tmpCommand = g_RegisteredCommands.CommandList[i];
476         cmdName = tmpCommand->pcCommand;
477         if (StrCompare(context->line, cmdName, StrLen(context->line)) == 0)
478         {
479             if (minLen == 0)
480             {
481                 namePtr = cmdName;
482                 minLen = StrLen(namePtr);
483                 /* Show possible matches */
484                 context->printf_data_func("%s\r\n", cmdName);
485                 continue;
486             }
487             len = StrCompare(namePtr, cmdName, StrLen(namePtr));
488             if (len < 0)
489             {
490                 len = len * (-1);
491             }
492             if (len < minLen)
493             {
494                 minLen = len;
495             }
496         }
497     }
498     /* Auto complete string */
499     if (namePtr)
500     {
501         StrCopy(context->line, namePtr, minLen);
502     }
503     context->printf_data_func("%s%s", context->prompt, context->line);
504     return;
505 }
506 
StrCopy(char * dest,const char * src,int32_t count)507 static char *StrCopy(char *dest, const char *src, int32_t count)
508 {
509     char *ret = dest;
510     int32_t i = 0;
511 
512     for (i = 0; i < count; i++)
513     {
514         dest[i] = src[i];
515     }
516 
517     return ret;
518 }
519 
StrLen(const char * str)520 static int32_t StrLen(const char *str)
521 {
522     int32_t i = 0;
523 
524     while (*str)
525     {
526         str++;
527         i++;
528     }
529     return i;
530 }
531 
StrCompare(const char * str1,const char * str2,int32_t count)532 static int32_t StrCompare(const char *str1, const char *str2, int32_t count)
533 {
534     while (count--)
535     {
536         if (*str1++ != *str2++)
537         {
538             return *(unsigned char *)(str1 - 1) - *(unsigned char *)(str2 - 1);
539         }
540     }
541     return 0;
542 }
543 
ParseLine(const char * cmd,uint32_t len,char * argv[SHELL_MAX_ARGS])544 static int32_t ParseLine(const char *cmd, uint32_t len, char *argv[SHELL_MAX_ARGS])
545 {
546     uint32_t argc;
547     char *p;
548     uint32_t position;
549 
550     /* Init params */
551     memset(g_paramBuffer, '\0', len + 1);
552     StrCopy(g_paramBuffer, cmd, len);
553 
554     p = g_paramBuffer;
555     position = 0;
556     argc = 0;
557 
558     while (position < len)
559     {
560         /* Skip all blanks */
561         while (((char)(*p) == ' ') && (position < len))
562         {
563             *p = '\0';
564             p++;
565             position++;
566         }
567         /* Process begin of a string */
568         if (*p == '"')
569         {
570             p++;
571             position++;
572             argv[argc] = p;
573             argc++;
574             /* Skip this string */
575             while ((*p != '"') && (position < len))
576             {
577                 p++;
578                 position++;
579             }
580             /* Skip '"' */
581             *p = '\0';
582             p++;
583             position++;
584         }
585         else /* Normal char */
586         {
587             argv[argc] = p;
588             argc++;
589             while (((char)*p != ' ') && ((char)*p != '\t') && (position < len))
590             {
591                 p++;
592                 position++;
593             }
594         }
595     }
596     return argc;
597 }
598 
SHELL_RegisterCommand(const shell_command_context_t * command_context)599 int32_t SHELL_RegisterCommand(const shell_command_context_t *command_context)
600 {
601     int32_t result = 0;
602 
603     /* If have room  in command list */
604     if (g_RegisteredCommands.numberOfCommandInList < SHELL_MAX_CMD)
605     {
606         g_RegisteredCommands.CommandList[g_RegisteredCommands.numberOfCommandInList++] = command_context;
607     }
608     else
609     {
610         result = -1;
611     }
612     return result;
613 }
614 
GetChar(p_shell_context_t context)615 static uint8_t GetChar(p_shell_context_t context)
616 {
617     uint8_t ch;
618 
619 #if SHELL_USE_FILE_STREAM
620     ch = fgetc(context->STDIN);
621 #else
622     context->recv_data_func(&ch, 1U);
623 #endif
624     return ch;
625 }
626