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