1 /* pk-cmd.c - Poke commands.  */
2 
3 /* Copyright (C) 2019, 2020, 2021 Jose E. Marchesi */
4 
5 /* This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <limits.h>
27 #include <fcntl.h>
28 #include <assert.h>
29 #include <glob.h> /* For tilde-expansion.  */
30 #include <xalloc.h>
31 #include <xstrndup.h>
32 #include <ctype.h>
33 
34 #include "poke.h"
35 #include "pk-cmd.h"
36 #include "pk-utils.h"
37 
38 /* Table of supported commands.  */
39 
40 extern const struct pk_cmd ios_cmd; /* pk-cmd-ios.c */
41 extern const struct pk_cmd file_cmd; /* pk-cmd-ios.c  */
42 extern const struct pk_cmd mem_cmd; /* pk-cmd-ios.c */
43 #ifdef HAVE_LIBNBD
44 extern const struct pk_cmd nbd_cmd; /* pk-cmd-ios.c */
45 #endif
46 extern const struct pk_cmd close_cmd; /* pk-cmd-file.c */
47 extern const struct pk_cmd load_cmd; /* pk-cmd-file.c */
48 extern const struct pk_cmd source_cmd; /* pk-cmd-ios.c */
49 extern const struct pk_cmd info_cmd; /* pk-cmd-info.c  */
50 extern const struct pk_cmd exit_cmd; /* pk-cmd-misc.c  */
51 extern const struct pk_cmd quit_cmd; /* pk-cmd-misc.c  */
52 extern const struct pk_cmd version_cmd; /* pk-cmd-misc.c */
53 extern const struct pk_cmd doc_cmd; /* pk-cmd-misc.c */
54 extern const struct pk_cmd jmd_cmd; /* pk-cmd-misc.c */
55 extern const struct pk_cmd help_cmd; /* pk-cmd-help.c */
56 extern const struct pk_cmd vm_cmd; /* pk-cmd-vm.c  */
57 extern const struct pk_cmd set_cmd; /* pk-cmd-set.c */
58 extern const struct pk_cmd editor_cmd; /* pk-cmd-editor.c */
59 extern const struct pk_cmd map_cmd; /* pk-cmd-map.c */
60 
61 const struct pk_cmd null_cmd = {};
62 
63 static const struct pk_cmd *dot_cmds[] =
64   {
65     &ios_cmd,
66     &file_cmd,
67     &exit_cmd,
68     &quit_cmd,
69     &version_cmd,
70     &doc_cmd,
71     &jmd_cmd,
72     &info_cmd,
73     &close_cmd,
74     &load_cmd,
75     &source_cmd,
76     &help_cmd,
77     &vm_cmd,
78     &set_cmd,
79     &map_cmd,
80     &editor_cmd,
81     &mem_cmd,
82 #ifdef HAVE_LIBNBD
83     &nbd_cmd,
84 #endif
85     &null_cmd
86   };
87 
88 /* Convenience macros and functions for parsing.  */
89 
90 static inline const char *
skip_blanks(const char * p)91 skip_blanks (const char *p)
92 {
93   while (isblank (*p))
94     p++;
95   return p;
96 }
97 
98 static inline int
pk_atoi(const char ** p,int64_t * number)99 pk_atoi (const char **p, int64_t *number)
100 {
101   long int li;
102   char *end;
103 
104   errno = 0;
105   li = strtoll (*p, &end, 0);
106   if ((errno != 0 && li == 0)
107       || end == *p)
108     return 0;
109 
110   *number = li;
111   *p = end;
112   return 1;
113 }
114 
115 /* Little implementation of prefix trees, or tries.  This is used in
116    order to support calling to commands and subcommands using
117    unambiguous prefixes.  It is also a pretty efficient way to decode
118    command names.  */
119 
120 struct pk_trie
121 {
122   char c;
123   struct pk_trie *parent;
124   int num_children;
125   struct pk_trie *children[256];
126   const struct pk_cmd *cmd;
127 };
128 
129 static struct pk_trie *
pk_trie_new(char c,struct pk_trie * parent)130 pk_trie_new (char c, struct pk_trie *parent)
131 {
132   struct pk_trie *trie;
133   size_t i;
134 
135   trie = xmalloc (sizeof (struct pk_trie));
136   trie->c = c;
137   trie->parent = parent;
138   trie->cmd = NULL;
139   trie->num_children = 0;
140   for (i = 0; i < 256; i++)
141     trie->children[i] = NULL;
142 
143   return trie;
144 }
145 
146 static void
pk_trie_free(struct pk_trie * trie)147 pk_trie_free (struct pk_trie *trie)
148 {
149   int i;
150 
151   if (trie == NULL)
152     return;
153 
154   for (i = 0; i < 256; i++)
155     pk_trie_free (trie->children[i]);
156 
157   free (trie);
158   return;
159 }
160 
161 static void
pk_trie_expand_cmds(struct pk_trie * root,struct pk_trie * trie)162 pk_trie_expand_cmds (struct pk_trie *root,
163                      struct pk_trie *trie)
164 {
165   if (trie->cmd != NULL)
166     {
167       struct pk_trie *t;
168       t = trie->parent;
169       while (t != root && t->num_children == 1)
170         {
171           t->cmd = trie->cmd;
172           t = t->parent;
173         }
174     }
175   else
176     {
177       size_t i;
178       for (i = 0; i < 256; i++)
179         {
180           if (trie->children[i] != NULL)
181             pk_trie_expand_cmds (root, trie->children[i]);
182         }
183     }
184 }
185 
186 static struct pk_trie *
pk_trie_from_cmds(const struct pk_cmd * cmds[])187 pk_trie_from_cmds (const struct pk_cmd *cmds[])
188 {
189   size_t i;
190   struct pk_trie *root;
191   struct pk_trie *t;
192   const struct pk_cmd *cmd;
193 
194   root = pk_trie_new (' ', NULL);
195   t = root;
196 
197   for (i = 0, cmd = cmds[0];
198        cmd->name != NULL;
199        cmd = cmds[++i])
200     {
201       const char *p;
202 
203       for (p = cmd->name; *p != '\0'; p++)
204         {
205           int c = *p;
206 
207           if (t->children[c] == NULL)
208             {
209               t->num_children++;
210               t->children[c] = pk_trie_new (c, t);
211             }
212           t = t->children[c];
213         }
214 
215       /* Note this assumes no commands with empty names.  */
216       t->cmd = cmd;
217       t = root;
218     }
219 
220   pk_trie_expand_cmds (root, root);
221   return root;
222 }
223 
224 static const struct pk_cmd *
pk_trie_get_cmd(struct pk_trie * trie,const char * str)225 pk_trie_get_cmd (struct pk_trie *trie, const char *str)
226 {
227   const char *pc;
228 
229   for (pc = str; *pc; pc++)
230     {
231       int n = *pc;
232 
233       if (trie->children[n] == NULL)
234         return NULL;
235 
236       trie = trie->children[n];
237     }
238 
239   return trie->cmd;
240 }
241 
242 #if 0
243 static void
244 pk_print_trie (int indent, struct pk_trie *trie)
245 {
246   size_t i;
247 
248   for (i = 0; i < indent; i++)
249     printf (" ");
250   printf ("TRIE:: '%c' cmd='%s'\n",
251           trie->c, trie->cmd != NULL ? trie->cmd->name : "NULL");
252 
253   for (i =0 ; i < 256; i++)
254     if (trie->children[i] != NULL)
255       pk_print_trie (indent + 2, trie->children[i]);
256 }
257 #endif
258 
259 /* Routines to execute a command.  */
260 
261 #define MAX_CMD_NAME 18
262 
263 static int
pk_cmd_exec_1(const char * str,struct pk_trie * cmds_trie,char * prefix)264 pk_cmd_exec_1 (const char *str, struct pk_trie *cmds_trie, char *prefix)
265 {
266 #define GOTO_USAGE()                                                           \
267   do {                                                                         \
268     besilent = 0;                                                              \
269     ret = 0;                                                                   \
270     goto usage;                                                                \
271   } while (0)
272   int ret = 1;
273   char cmd_name[MAX_CMD_NAME];
274   const char *p;
275   const struct pk_cmd *cmd;
276   int argc = 0;
277   struct pk_cmd_arg argv[8];
278   uint64_t uflags;
279   const char *a;
280   int besilent = 0;
281 
282   /* Skip blanks, and return if the command is composed by only blank
283      characters.  */
284   p = skip_blanks (str);
285   if (*p == '\0')
286     return 0;
287 
288   /* Get the command name.  */
289   memset (cmd_name, 0, MAX_CMD_NAME);
290   for (int i = 0; isalnum (*p) || *p == '_' || *p == '-' || *p == ':';)
291     {
292       if (i >= MAX_CMD_NAME - 1)
293         {
294           pk_printf (_("%s: command not found.\n"), cmd_name);
295           return 0;
296         }
297       cmd_name[i++] = *(p++);
298     }
299 
300   /* Look for the command in the prefix table.  */
301   cmd = pk_trie_get_cmd (cmds_trie, cmd_name);
302   if (cmd == NULL)
303     {
304       if (prefix != NULL)
305         pk_printf ("%s ", prefix);
306       pk_printf (_("%s: command not found.\n"), cmd_name);
307       return 0;
308     }
309 
310   /* Process user flags.  */
311   uflags = 0;
312   if (*p == '/')
313     {
314       p++;
315       while (isalpha (*p))
316         {
317           int fi;
318           for (fi = 0; cmd->uflags[fi]; fi++)
319             if (cmd->uflags[fi] == *p)
320               {
321                 uflags |= 1 << fi;
322                 break;
323               }
324 
325           if (cmd->uflags[fi] == '\0')
326             {
327               pk_printf (_("%s: invalid flag `%c'\n"), cmd_name, *p);
328               return 0;
329             }
330 
331           p++;
332         }
333     }
334 
335   /* If this command has subcommands, process them and be done.  */
336   if (cmd->subtrie != NULL)
337     {
338       p = skip_blanks (p);
339       if (*p == '\0')
340         GOTO_USAGE();
341       return pk_cmd_exec_1 (p, *cmd->subtrie, cmd_name);
342     }
343 
344   /* Parse arguments.  */
345   a = cmd->arg_fmt;
346   while (*a != '\0')
347     {
348       /* Handle an argument. */
349       int match = 0;
350 
351       p = skip_blanks (p);
352       if (*a == '?' && ((*p == ',' || *p == '\0')))
353         {
354           if (*p == ',')
355             p++;
356           argv[argc].type = PK_CMD_ARG_NULL;
357           match = 1;
358         }
359       else
360         {
361           if (*a == '?')
362             a++;
363 
364           /* Try the different options, in order, until one succeeds or
365              the next argument or the end of the input is found.  */
366           while (*a != ',' && *a != '\0')
367             {
368               const char *beg = p;
369 
370               switch (*a)
371                 {
372                 case 'i':
373                 case 'n':
374                   /* Parse an integer or natural.  */
375                   p = skip_blanks (p);
376                   if (pk_atoi (&p, &(argv[argc].val.integer))
377                       && (*a == 'i' || argv[argc].val.integer >= 0))
378                     {
379                       p = skip_blanks (p);
380                       if (*p == ',' || *p == '\0')
381                         {
382                           argv[argc].type = PK_CMD_ARG_INT;
383                           match = 1;
384                         }
385                     }
386 
387                   break;
388                 case 't':
389                   /* Parse a #N tag.  */
390                   p = skip_blanks (p);
391                   if (*p == '#'
392                       && p++
393                       && pk_atoi (&p, &(argv[argc].val.tag))
394                       && argv[argc].val.tag >= 0)
395                     {
396                       if (*p == ',' || *p == '\0' || isblank (*p))
397                         {
398                           argv[argc].type = PK_CMD_ARG_TAG;
399                           match = 1;
400                         }
401                     }
402 
403                   break;
404                 case 's':
405                   {
406                     /* Parse a string.  */
407 
408                     const char *end;
409                     char *str;
410                     size_t size;
411 
412                     p = skip_blanks (p);
413                     /* Note how commas are allowed in the value of the
414                        string argument if the argument is the last in
415                        the argument list.  This is checked using
416                        a[1].  */
417                     for (end = p;
418                          *end != '\0' && (a[1] == '\0' || *end != ',');
419                          end++)
420                       ;
421 
422                     size = end - p;
423                     str = xstrndup (p, size);
424                     p = end;
425 
426                     /* Trim trailing space.  */
427                     if (size)
428                       {
429                         char *e = str + size - 1;
430                         while (e > str && isspace ((unsigned char) *e))
431                           *e-- = '\0';
432                       }
433 
434                     argv[argc].type = PK_CMD_ARG_STR;
435                     argv[argc].val.str = str;
436                     match = 1;
437                     break;
438                   }
439                 case 'f':
440                   {
441                     glob_t exp_result;
442                     char *fname;
443                     char *filename;
444 
445                     if (p[0] == '\0')
446                       GOTO_USAGE();
447 
448                     fname = xstrdup (p);
449                     pk_str_trim (&fname);
450                     switch (glob (fname, GLOB_TILDE,
451                                   NULL /* errfunc */,
452                                   &exp_result))
453                       {
454                       case 0: /* Successful.  */
455                         if (exp_result.gl_pathc != 1)
456                           {
457                             free (fname);
458                             globfree (&exp_result);
459                             GOTO_USAGE();
460                           }
461 
462                         filename = xstrdup (exp_result.gl_pathv[0]);
463                         globfree (&exp_result);
464                         break;
465                       default:
466                         filename = xstrdup (fname);
467                         break;
468                       }
469 
470                     free (fname);
471 
472                     argv[argc].type = PK_CMD_ARG_STR;
473                     argv[argc].val.str = filename;
474                     match = 1;
475 
476                     p += strlen (p);
477                     break;
478                   }
479                 default:
480                   /* This should NOT happen.  */
481                   assert (0);
482                 }
483 
484               if (match)
485                 break;
486 
487               /* Rewind input and try next option.  */
488               p = beg;
489               a++;
490             }
491         }
492 
493       /* Boo, could not find valid input for this argument.  */
494       if (!match)
495         GOTO_USAGE();
496 
497       if (*p == ',')
498         p++;
499 
500       /* Skip any further options for this argument.  */
501       while (*a != ',' && *a != '\0')
502         a++;
503       if (*a == ',')
504         a++;
505 
506       /* Ok, next argument!  */
507       argc++;
508     }
509 
510   /* Make sure there is no trailer contents in the input.  */
511   p = skip_blanks (p);
512   if (*p != '\0')
513     GOTO_USAGE();
514 
515   /* Process command flags.  */
516   if (cmd->flags & PK_CMD_F_REQ_IO
517       && pk_ios_cur (poke_compiler) == NULL)
518     {
519       pk_puts (_("This command requires an IO space.  Use the `file' command.\n"));
520       return 0;
521     }
522 
523   if (cmd->flags & PK_CMD_F_REQ_W)
524     {
525       pk_ios cur_io = pk_ios_cur (poke_compiler);
526       if (cur_io == NULL
527           || !(pk_ios_flags (cur_io) & PK_IOS_F_READ))
528         {
529           pk_puts (_("This command requires a writable IO space."));
530           return 0;
531         }
532     }
533 
534   /* Call the command handler, passing the arguments.  */
535   ret = (*cmd->handler) (argc, argv, uflags);
536 
537   besilent = 1;
538   usage:
539   /* Free arguments occupying memory.  */
540   for (int i = 0; i < argc; ++i)
541     {
542       if (argv[i].type == PK_CMD_ARG_STR)
543         free (argv[i].val.str);
544     }
545 
546   if (!besilent)
547     pk_printf (_("Usage: %s\n"), cmd->usage);
548 
549   return ret;
550 #undef GOTO_USAGE
551 }
552 
553 extern const struct pk_cmd *info_cmds[]; /* pk-cmd-info.c  */
554 extern struct pk_trie *info_trie; /* pk-cmd-info.c  */
555 
556 extern const struct pk_cmd *vm_cmds[]; /* pk-cmd-vm.c  */
557 extern struct pk_trie *vm_trie;  /* pk-cmd-vm.c  */
558 
559 extern const struct pk_cmd *vm_disas_cmds[];  /* pk-cmd-vm.c */
560 extern struct pk_trie *vm_disas_trie; /* pk-cmd-vm.c */
561 
562 extern const struct pk_cmd *vm_profile_cmds[]; /* pk-cmd-vm.c */
563 extern struct pk_trie *vm_profile_trie; /* pk-cmd-vm.c */
564 
565 extern const struct pk_cmd *set_cmds[]; /* pk-cmd-set.c */
566 extern struct pk_trie *set_trie; /* pk-cmd-set.c */
567 
568 extern const struct pk_cmd *map_cmds[]; /* pk-cmd-map.c */
569 extern struct pk_trie *map_trie; /* pk-cmd-map.c */
570 
571 extern const struct pk_cmd *map_entry_cmds[]; /* pk-cmd-map.c  */
572 extern struct pk_trie *map_entry_trie; /* pk-cmd-map.c  */
573 
574 static struct pk_trie *cmds_trie;
575 
576 #define IS_COMMAND(input, cmd) \
577   (strncmp ((input), (cmd), sizeof (cmd) - 1) == 0 \
578    && ((input)[sizeof (cmd) - 1] == ' ' || (input)[sizeof (cmd) - 1] == '\t'))
579 
580 int
pk_cmd_exec(const char * str)581 pk_cmd_exec (const char *str)
582 {
583   /* If the first non-blank character in STR is a dot ('.'), then this
584      is a poke command.  Dispatch it with pk_cmd_exec_1.  Otherwise,
585      compile a Poke declaration or a statement and execute it.  */
586 
587   const char *cmd = skip_blanks (str);
588 
589   if (*cmd == '.')
590     return pk_cmd_exec_1 (cmd + 1, cmds_trie, NULL);
591   else
592     {
593       const char *ecmd = cmd, *end;
594       char *cmd_alloc = NULL;
595       int what; /* 0 -> declaration, 1 -> statement */
596       int retval = 1;
597 
598       if (IS_COMMAND(ecmd, "fun"))
599         what = 0;
600       else
601         {
602           if (IS_COMMAND(ecmd, "var")
603            || IS_COMMAND(ecmd, "type")
604            || IS_COMMAND(ecmd, "unit"))
605             what = 0;
606           else
607             what = 1;
608 
609           cmd_alloc = pk_str_concat (cmd, ";", NULL);
610           if (!cmd_alloc)
611             pk_fatal (_("out of memory"));
612 
613           ecmd = cmd_alloc;
614         }
615 
616       pk_set_lexical_cuckolding_p (poke_compiler, 1);
617       if (what == 0)
618         {
619           /* Declaration.  */
620           if (pk_compile_buffer (poke_compiler, ecmd, &end) != PK_OK)
621             {
622               retval = 0;
623               goto cleanup;
624             }
625         }
626       else
627         {
628           /* Statement.  */
629           pk_val val;
630 
631           if (pk_compile_statement (poke_compiler, ecmd, &end, &val) != PK_OK)
632             {
633               retval = 0;
634               goto cleanup;
635             }
636 
637           if (val != PK_NULL)
638             {
639               pk_print_val (poke_compiler, val);
640               pk_puts ("\n");
641             }
642         }
643       pk_set_lexical_cuckolding_p (poke_compiler, 0);
644 
645     cleanup:
646       free (cmd_alloc);
647       return retval;
648     }
649 }
650 #undef IS_COMMAND
651 
652 
653 static int
is_blank_line(const char * line)654 is_blank_line (const char *line)
655 {
656   const char *c = line;
657   while (*c != '\0' && (*c == ' ' || *c == '\t'))
658     c++;
659   return (*c == '\0');
660 }
661 
662 
663 int
pk_cmd_exec_script(const char * filename)664 pk_cmd_exec_script (const char *filename)
665 {
666   FILE *fp = fopen (filename, "r");
667 
668   if (fp == NULL)
669     {
670       perror (filename);
671       return 1;
672     }
673 
674   /* Read commands from FD, one per line, and execute them.  Lines
675      starting with the '#' character are comments, and ignored.
676      Likewise, empty lines are also ignored.  */
677 
678   char *line = NULL;
679   size_t line_len = 0;
680   while (1)
681     {
682       int ret;
683 
684       /* Read a line from the file.  */
685       errno = 0;
686       ssize_t n = getline (&line, &line_len, fp);
687 
688       if (n == -1)
689         {
690           if (errno != 0)
691             perror (filename);
692           break;
693         }
694 
695       if (line[n - 1] == '\n')
696         line[n - 1] = '\0';
697 
698       /* If the line is empty, or it starts with '#', or it contains
699          just blank characters, just ignore it.  */
700       if (!(line[0] == '#' || line[0] == '\0' || is_blank_line (line)))
701         {
702           /* Execute the line.  */
703           ret = pk_cmd_exec (line);
704           if (!ret)
705             goto error;
706         }
707     }
708 
709   free (line);
710   fclose (fp);
711   return 1;
712 
713  error:
714   free (line);
715   fclose (fp);
716   return 0;
717 }
718 
719 void
pk_cmd_init(void)720 pk_cmd_init (void)
721 {
722   cmds_trie = pk_trie_from_cmds (dot_cmds);
723   info_trie = pk_trie_from_cmds (info_cmds);
724   vm_trie = pk_trie_from_cmds (vm_cmds);
725   vm_disas_trie = pk_trie_from_cmds (vm_disas_cmds);
726   vm_profile_trie = pk_trie_from_cmds (vm_profile_cmds);
727   set_trie = pk_trie_from_cmds (set_cmds);
728   map_trie = pk_trie_from_cmds (map_cmds);
729   map_entry_trie = pk_trie_from_cmds (map_entry_cmds);
730 
731   /* Compile commands written in Poke.  */
732   if (!pk_load (poke_compiler, "pk-cmd"))
733     pk_fatal ("unable to load the pk-cmd module");
734 }
735 
736 void
pk_cmd_shutdown(void)737 pk_cmd_shutdown (void)
738 {
739   pk_trie_free (cmds_trie);
740   pk_trie_free (info_trie);
741   pk_trie_free (vm_trie);
742   pk_trie_free (vm_disas_trie);
743   pk_trie_free (vm_profile_trie);
744   pk_trie_free (set_trie);
745   pk_trie_free (map_trie);
746   pk_trie_free (map_entry_trie);
747 }
748 
749 
750 /*  Return the name of the next dot command that matches the first
751     LEN characters of TEXT.
752     Returns the name of the next command in the set, or NULL if there
753     are no more.  The returned value must be freed by the caller.  */
754 char *
pk_cmd_get_next_match(const char * text,size_t len)755 pk_cmd_get_next_match (const char *text, size_t len)
756 {
757   static int idx = 0;
758 
759   if (len > 0 && text[0] != '.')
760     return NULL;
761 
762   /* Dot commands */
763   for (const struct pk_cmd **c = dot_cmds + idx++;
764        *c != &null_cmd;
765        c++)
766     {
767       if (len == 0 || strncmp ((*c)->name, text + 1, len - 1) == 0)
768         return pk_str_concat (".", (*c)->name, NULL);
769     }
770   idx = 0;
771 
772   return NULL;
773 }
774 
775 
776 /* Search for a command which matches cmdname.
777  Returns NULL if no such command exists.  */
778 const struct pk_cmd *
pk_cmd_find(const char * cmdname)779 pk_cmd_find (const char *cmdname)
780 {
781   if (cmdname != NULL)
782     {
783       const struct pk_cmd **c;
784       for (c = dot_cmds; *c != &null_cmd; ++c)
785         {
786           /* Check if the command name matches.
787              +1 to skip the leading '.' */
788           if (STREQ ((*c)->name, cmdname + 1))
789             return *c;
790         }
791     }
792   return NULL;
793 }
794