blob: 9ea927454744f5cf511168ba407f88d3b9666cde [file] [log] [blame]
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -08001#include <stdio.h>
2#include <stdlib.h>
3#include <unistd.h>
4#include <fcntl.h>
5#include <stdarg.h>
6#include <string.h>
7#include <stddef.h>
8#include <ctype.h>
9
10#include "init.h"
11#include "property_service.h"
Colin Crossca7648d2010-04-13 19:29:51 -070012#include "parser.h"
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080013
San Mehat4e221f02010-02-25 14:19:50 -080014#include <cutils/iosched_policy.h>
15
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080016#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
17#include <sys/_system_properties.h>
18
19static list_declare(service_list);
20static list_declare(action_list);
21static list_declare(action_queue);
22
23#define RAW(x...) log_write(6, x)
24
25void DUMP(void)
26{
27#if 0
28 struct service *svc;
29 struct action *act;
30 struct command *cmd;
31 struct listnode *node;
32 struct listnode *node2;
33 struct socketinfo *si;
34 int n;
35
36 list_for_each(node, &service_list) {
37 svc = node_to_item(node, struct service, slist);
38 RAW("service %s\n", svc->name);
39 RAW(" class '%s'\n", svc->classname);
40 RAW(" exec");
41 for (n = 0; n < svc->nargs; n++) {
42 RAW(" '%s'", svc->args[n]);
43 }
44 RAW("\n");
45 for (si = svc->sockets; si; si = si->next) {
46 RAW(" socket %s %s 0%o\n", si->name, si->type, si->perm);
47 }
48 }
49
50 list_for_each(node, &action_list) {
51 act = node_to_item(node, struct action, alist);
52 RAW("on %s\n", act->name);
53 list_for_each(node2, &act->commands) {
54 cmd = node_to_item(node2, struct command, clist);
55 RAW(" %p", cmd->func);
56 for (n = 0; n < cmd->nargs; n++) {
57 RAW(" %s", cmd->args[n]);
58 }
59 RAW("\n");
60 }
61 RAW("\n");
62 }
63#endif
64}
65
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080066#define T_EOF 0
67#define T_TEXT 1
68#define T_NEWLINE 2
69
70struct parse_state
71{
72 char *ptr;
73 char *text;
74 int line;
75 int nexttoken;
76 void *context;
77 void (*parse_line)(struct parse_state *state, int nargs, char **args);
78 const char *filename;
79};
80
81static void *parse_service(struct parse_state *state, int nargs, char **args);
82static void parse_line_service(struct parse_state *state, int nargs, char **args);
83
84static void *parse_action(struct parse_state *state, int nargs, char **args);
85static void parse_line_action(struct parse_state *state, int nargs, char **args);
86
87void parse_error(struct parse_state *state, const char *fmt, ...)
88{
89 va_list ap;
90 char buf[128];
91 int off;
92
93 snprintf(buf, 128, "%s: %d: ", state->filename, state->line);
94 buf[127] = 0;
95 off = strlen(buf);
96
97 va_start(ap, fmt);
98 vsnprintf(buf + off, 128 - off, fmt, ap);
99 va_end(ap);
100 buf[127] = 0;
101 ERROR("%s", buf);
102}
103
104#define SECTION 0x01
105#define COMMAND 0x02
106#define OPTION 0x04
107
108#include "keywords.h"
109
110#define KEYWORD(symbol, flags, nargs, func) \
111 [ K_##symbol ] = { #symbol, func, nargs + 1, flags, },
112
113struct {
114 const char *name;
115 int (*func)(int nargs, char **args);
116 unsigned char nargs;
117 unsigned char flags;
118} keyword_info[KEYWORD_COUNT] = {
119 [ K_UNKNOWN ] = { "unknown", 0, 0, 0 },
120#include "keywords.h"
121};
122#undef KEYWORD
123
124#define kw_is(kw, type) (keyword_info[kw].flags & (type))
125#define kw_name(kw) (keyword_info[kw].name)
126#define kw_func(kw) (keyword_info[kw].func)
127#define kw_nargs(kw) (keyword_info[kw].nargs)
128
129int lookup_keyword(const char *s)
130{
131 switch (*s++) {
132 case 'c':
San Mehat7c44fe52009-08-26 16:39:25 -0700133 if (!strcmp(s, "opy")) return K_copy;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800134 if (!strcmp(s, "apability")) return K_capability;
Jay Freeman (saurik)e7cb1372008-11-17 06:41:10 +0000135 if (!strcmp(s, "hdir")) return K_chdir;
136 if (!strcmp(s, "hroot")) return K_chroot;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800137 if (!strcmp(s, "lass")) return K_class;
138 if (!strcmp(s, "lass_start")) return K_class_start;
139 if (!strcmp(s, "lass_stop")) return K_class_stop;
140 if (!strcmp(s, "onsole")) return K_console;
141 if (!strcmp(s, "hown")) return K_chown;
142 if (!strcmp(s, "hmod")) return K_chmod;
143 if (!strcmp(s, "ritical")) return K_critical;
144 break;
145 case 'd':
146 if (!strcmp(s, "isabled")) return K_disabled;
147 if (!strcmp(s, "omainname")) return K_domainname;
148 if (!strcmp(s, "evice")) return K_device;
149 break;
150 case 'e':
151 if (!strcmp(s, "xec")) return K_exec;
152 if (!strcmp(s, "xport")) return K_export;
153 break;
154 case 'g':
155 if (!strcmp(s, "roup")) return K_group;
156 break;
157 case 'h':
158 if (!strcmp(s, "ostname")) return K_hostname;
159 break;
160 case 'i':
San Mehat4e221f02010-02-25 14:19:50 -0800161 if (!strcmp(s, "oprio")) return K_ioprio;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800162 if (!strcmp(s, "fup")) return K_ifup;
163 if (!strcmp(s, "nsmod")) return K_insmod;
164 if (!strcmp(s, "mport")) return K_import;
165 break;
166 case 'k':
167 if (!strcmp(s, "eycodes")) return K_keycodes;
168 break;
169 case 'l':
170 if (!strcmp(s, "oglevel")) return K_loglevel;
171 break;
172 case 'm':
173 if (!strcmp(s, "kdir")) return K_mkdir;
174 if (!strcmp(s, "ount")) return K_mount;
175 break;
176 case 'o':
177 if (!strcmp(s, "n")) return K_on;
178 if (!strcmp(s, "neshot")) return K_oneshot;
179 if (!strcmp(s, "nrestart")) return K_onrestart;
180 break;
181 case 'r':
182 if (!strcmp(s, "estart")) return K_restart;
183 break;
184 case 's':
185 if (!strcmp(s, "ervice")) return K_service;
186 if (!strcmp(s, "etenv")) return K_setenv;
187 if (!strcmp(s, "etkey")) return K_setkey;
188 if (!strcmp(s, "etprop")) return K_setprop;
189 if (!strcmp(s, "etrlimit")) return K_setrlimit;
190 if (!strcmp(s, "ocket")) return K_socket;
191 if (!strcmp(s, "tart")) return K_start;
192 if (!strcmp(s, "top")) return K_stop;
193 if (!strcmp(s, "ymlink")) return K_symlink;
194 if (!strcmp(s, "ysclktz")) return K_sysclktz;
195 break;
196 case 't':
197 if (!strcmp(s, "rigger")) return K_trigger;
198 break;
199 case 'u':
200 if (!strcmp(s, "ser")) return K_user;
201 break;
202 case 'w':
203 if (!strcmp(s, "rite")) return K_write;
204 break;
205 }
206 return K_UNKNOWN;
207}
208
209void parse_line_no_op(struct parse_state *state, int nargs, char **args)
210{
211}
212
213int next_token(struct parse_state *state)
214{
215 char *x = state->ptr;
216 char *s;
217
218 if (state->nexttoken) {
219 int t = state->nexttoken;
220 state->nexttoken = 0;
221 return t;
222 }
223
224 for (;;) {
225 switch (*x) {
226 case 0:
227 state->ptr = x;
228 return T_EOF;
229 case '\n':
230 state->line++;
231 x++;
232 state->ptr = x;
233 return T_NEWLINE;
234 case ' ':
235 case '\t':
236 case '\r':
237 x++;
238 continue;
239 case '#':
240 while (*x && (*x != '\n')) x++;
241 state->line++;
242 state->ptr = x;
243 return T_NEWLINE;
244 default:
245 goto text;
246 }
247 }
248
249textdone:
250 state->ptr = x;
251 *s = 0;
252 return T_TEXT;
253text:
254 state->text = s = x;
255textresume:
256 for (;;) {
257 switch (*x) {
258 case 0:
259 goto textdone;
260 case ' ':
261 case '\t':
262 case '\r':
263 x++;
264 goto textdone;
265 case '\n':
266 state->nexttoken = T_NEWLINE;
267 x++;
268 goto textdone;
269 case '"':
270 x++;
271 for (;;) {
272 switch (*x) {
273 case 0:
274 /* unterminated quoted thing */
275 state->ptr = x;
276 return T_EOF;
277 case '"':
278 x++;
279 goto textresume;
280 default:
281 *s++ = *x++;
282 }
283 }
284 break;
285 case '\\':
286 x++;
287 switch (*x) {
288 case 0:
289 goto textdone;
290 case 'n':
291 *s++ = '\n';
292 break;
293 case 'r':
294 *s++ = '\r';
295 break;
296 case 't':
297 *s++ = '\t';
298 break;
299 case '\\':
300 *s++ = '\\';
301 break;
302 case '\r':
303 /* \ <cr> <lf> -> line continuation */
304 if (x[1] != '\n') {
305 x++;
306 continue;
307 }
308 case '\n':
309 /* \ <lf> -> line continuation */
310 state->line++;
311 x++;
312 /* eat any extra whitespace */
313 while((*x == ' ') || (*x == '\t')) x++;
314 continue;
315 default:
316 /* unknown escape -- just copy */
317 *s++ = *x++;
318 }
319 continue;
320 default:
321 *s++ = *x++;
322 }
323 }
324 return T_EOF;
325}
326
327void parse_line(int nargs, char **args)
328{
329 int n;
330 int id = lookup_keyword(args[0]);
331 printf("%s(%d)", args[0], id);
332 for (n = 1; n < nargs; n++) {
333 printf(" '%s'", args[n]);
334 }
335 printf("\n");
336}
337
338void parse_new_section(struct parse_state *state, int kw,
339 int nargs, char **args)
340{
341 printf("[ %s %s ]\n", args[0],
342 nargs > 1 ? args[1] : "");
343 switch(kw) {
344 case K_service:
345 state->context = parse_service(state, nargs, args);
346 if (state->context) {
347 state->parse_line = parse_line_service;
348 return;
349 }
350 break;
351 case K_on:
352 state->context = parse_action(state, nargs, args);
353 if (state->context) {
354 state->parse_line = parse_line_action;
355 return;
356 }
357 break;
358 }
359 state->parse_line = parse_line_no_op;
360}
361
362static void parse_config(const char *fn, char *s)
363{
364 struct parse_state state;
San Mehatf24e2522009-05-19 13:30:46 -0700365 char *args[SVC_MAXARGS];
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800366 int nargs;
367
368 nargs = 0;
369 state.filename = fn;
370 state.line = 1;
371 state.ptr = s;
372 state.nexttoken = 0;
373 state.parse_line = parse_line_no_op;
374 for (;;) {
375 switch (next_token(&state)) {
376 case T_EOF:
377 state.parse_line(&state, 0, 0);
378 return;
379 case T_NEWLINE:
380 if (nargs) {
381 int kw = lookup_keyword(args[0]);
382 if (kw_is(kw, SECTION)) {
383 state.parse_line(&state, 0, 0);
384 parse_new_section(&state, kw, nargs, args);
385 } else {
386 state.parse_line(&state, nargs, args);
387 }
388 nargs = 0;
389 }
390 break;
391 case T_TEXT:
San Mehatf24e2522009-05-19 13:30:46 -0700392 if (nargs < SVC_MAXARGS) {
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800393 args[nargs++] = state.text;
394 }
395 break;
396 }
397 }
398}
399
400int parse_config_file(const char *fn)
401{
402 char *data;
403 data = read_file(fn, 0);
404 if (!data) return -1;
405
406 parse_config(fn, data);
407 DUMP();
408 return 0;
409}
410
411static int valid_name(const char *name)
412{
413 if (strlen(name) > 16) {
414 return 0;
415 }
416 while (*name) {
417 if (!isalnum(*name) && (*name != '_') && (*name != '-')) {
418 return 0;
419 }
420 name++;
421 }
422 return 1;
423}
424
425struct service *service_find_by_name(const char *name)
426{
427 struct listnode *node;
428 struct service *svc;
429 list_for_each(node, &service_list) {
430 svc = node_to_item(node, struct service, slist);
431 if (!strcmp(svc->name, name)) {
432 return svc;
433 }
434 }
435 return 0;
436}
437
438struct service *service_find_by_pid(pid_t pid)
439{
440 struct listnode *node;
441 struct service *svc;
442 list_for_each(node, &service_list) {
443 svc = node_to_item(node, struct service, slist);
444 if (svc->pid == pid) {
445 return svc;
446 }
447 }
448 return 0;
449}
450
451struct service *service_find_by_keychord(int keychord_id)
452{
453 struct listnode *node;
454 struct service *svc;
455 list_for_each(node, &service_list) {
456 svc = node_to_item(node, struct service, slist);
457 if (svc->keychord_id == keychord_id) {
458 return svc;
459 }
460 }
461 return 0;
462}
463
464void service_for_each(void (*func)(struct service *svc))
465{
466 struct listnode *node;
467 struct service *svc;
468 list_for_each(node, &service_list) {
469 svc = node_to_item(node, struct service, slist);
470 func(svc);
471 }
472}
473
474void service_for_each_class(const char *classname,
475 void (*func)(struct service *svc))
476{
477 struct listnode *node;
478 struct service *svc;
479 list_for_each(node, &service_list) {
480 svc = node_to_item(node, struct service, slist);
481 if (!strcmp(svc->classname, classname)) {
482 func(svc);
483 }
484 }
485}
486
487void service_for_each_flags(unsigned matchflags,
488 void (*func)(struct service *svc))
489{
490 struct listnode *node;
491 struct service *svc;
492 list_for_each(node, &service_list) {
493 svc = node_to_item(node, struct service, slist);
494 if (svc->flags & matchflags) {
495 func(svc);
496 }
497 }
498}
499
500void action_for_each_trigger(const char *trigger,
501 void (*func)(struct action *act))
502{
503 struct listnode *node;
504 struct action *act;
505 list_for_each(node, &action_list) {
506 act = node_to_item(node, struct action, alist);
507 if (!strcmp(act->name, trigger)) {
508 func(act);
509 }
510 }
511}
512
513void queue_property_triggers(const char *name, const char *value)
514{
515 struct listnode *node;
516 struct action *act;
517 list_for_each(node, &action_list) {
518 act = node_to_item(node, struct action, alist);
519 if (!strncmp(act->name, "property:", strlen("property:"))) {
520 const char *test = act->name + strlen("property:");
521 int name_length = strlen(name);
522
523 if (!strncmp(name, test, name_length) &&
524 test[name_length] == '=' &&
525 !strcmp(test + name_length + 1, value)) {
526 action_add_queue_tail(act);
527 }
528 }
529 }
530}
531
532void queue_all_property_triggers()
533{
534 struct listnode *node;
535 struct action *act;
536 list_for_each(node, &action_list) {
537 act = node_to_item(node, struct action, alist);
538 if (!strncmp(act->name, "property:", strlen("property:"))) {
539 /* parse property name and value
540 syntax is property:<name>=<value> */
541 const char* name = act->name + strlen("property:");
542 const char* equals = strchr(name, '=');
543 if (equals) {
Mike Lockwoodb3779552009-05-08 14:27:42 -0400544 char prop_name[PROP_NAME_MAX + 1];
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800545 const char* value;
546 int length = equals - name;
547 if (length > PROP_NAME_MAX) {
548 ERROR("property name too long in trigger %s", act->name);
549 } else {
550 memcpy(prop_name, name, length);
551 prop_name[length] = 0;
552
553 /* does the property exist, and match the trigger value? */
Mike Lockwoodb3779552009-05-08 14:27:42 -0400554 value = property_get(prop_name);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800555 if (value && !strcmp(equals + 1, value)) {
556 action_add_queue_tail(act);
557 }
558 }
559 }
560 }
561 }
562}
563
564void action_add_queue_tail(struct action *act)
565{
566 list_add_tail(&action_queue, &act->qlist);
567}
568
569struct action *action_remove_queue_head(void)
570{
571 if (list_empty(&action_queue)) {
572 return 0;
573 } else {
574 struct listnode *node = list_head(&action_queue);
575 struct action *act = node_to_item(node, struct action, qlist);
576 list_remove(node);
577 return act;
578 }
579}
580
581static void *parse_service(struct parse_state *state, int nargs, char **args)
582{
583 struct service *svc;
584 if (nargs < 3) {
585 parse_error(state, "services must have a name and a program\n");
586 return 0;
587 }
588 if (!valid_name(args[1])) {
589 parse_error(state, "invalid service name '%s'\n", args[1]);
590 return 0;
591 }
592
593 svc = service_find_by_name(args[1]);
594 if (svc) {
595 parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]);
596 return 0;
597 }
598
599 nargs -= 2;
600 svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs);
601 if (!svc) {
602 parse_error(state, "out of memory\n");
603 return 0;
604 }
605 svc->name = args[1];
606 svc->classname = "default";
607 memcpy(svc->args, args + 2, sizeof(char*) * nargs);
608 svc->args[nargs] = 0;
609 svc->nargs = nargs;
610 svc->onrestart.name = "onrestart";
611 list_init(&svc->onrestart.commands);
612 list_add_tail(&service_list, &svc->slist);
613 return svc;
614}
615
616static void parse_line_service(struct parse_state *state, int nargs, char **args)
617{
618 struct service *svc = state->context;
619 struct command *cmd;
620 int i, kw, kw_nargs;
621
622 if (nargs == 0) {
623 return;
624 }
625
San Mehat4e221f02010-02-25 14:19:50 -0800626 svc->ioprio_class = IoSchedClass_NONE;
627
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800628 kw = lookup_keyword(args[0]);
629 switch (kw) {
630 case K_capability:
631 break;
632 case K_class:
633 if (nargs != 2) {
634 parse_error(state, "class option requires a classname\n");
635 } else {
636 svc->classname = args[1];
637 }
638 break;
639 case K_console:
640 svc->flags |= SVC_CONSOLE;
641 break;
642 case K_disabled:
643 svc->flags |= SVC_DISABLED;
644 break;
San Mehat4e221f02010-02-25 14:19:50 -0800645 case K_ioprio:
646 if (nargs != 3) {
647 parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n");
648 } else {
649 svc->ioprio_pri = strtoul(args[2], 0, 8);
650
651 if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) {
652 parse_error(state, "priority value must be range 0 - 7\n");
653 break;
654 }
655
656 if (!strcmp(args[1], "rt")) {
657 svc->ioprio_class = IoSchedClass_RT;
658 } else if (!strcmp(args[1], "be")) {
659 svc->ioprio_class = IoSchedClass_BE;
660 } else if (!strcmp(args[1], "idle")) {
661 svc->ioprio_class = IoSchedClass_IDLE;
662 } else {
663 parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n");
664 }
665 }
666 break;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800667 case K_group:
668 if (nargs < 2) {
669 parse_error(state, "group option requires a group id\n");
670 } else if (nargs > NR_SVC_SUPP_GIDS + 2) {
671 parse_error(state, "group option accepts at most %d supp. groups\n",
672 NR_SVC_SUPP_GIDS);
673 } else {
674 int n;
675 svc->gid = decode_uid(args[1]);
676 for (n = 2; n < nargs; n++) {
677 svc->supp_gids[n-2] = decode_uid(args[n]);
678 }
679 svc->nr_supp_gids = n - 2;
680 }
681 break;
682 case K_keycodes:
683 if (nargs < 2) {
684 parse_error(state, "keycodes option requires atleast one keycode\n");
685 } else {
686 svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0]));
687 if (!svc->keycodes) {
688 parse_error(state, "could not allocate keycodes\n");
689 } else {
690 svc->nkeycodes = nargs - 1;
691 for (i = 1; i < nargs; i++) {
692 svc->keycodes[i - 1] = atoi(args[i]);
693 }
694 }
695 }
696 break;
697 case K_oneshot:
698 svc->flags |= SVC_ONESHOT;
699 break;
700 case K_onrestart:
701 nargs--;
702 args++;
703 kw = lookup_keyword(args[0]);
704 if (!kw_is(kw, COMMAND)) {
705 parse_error(state, "invalid command '%s'\n", args[0]);
706 break;
707 }
708 kw_nargs = kw_nargs(kw);
709 if (nargs < kw_nargs) {
710 parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1,
711 kw_nargs > 2 ? "arguments" : "argument");
712 break;
713 }
714
715 cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
716 cmd->func = kw_func(kw);
717 cmd->nargs = nargs;
718 memcpy(cmd->args, args, sizeof(char*) * nargs);
719 list_add_tail(&svc->onrestart.commands, &cmd->clist);
720 break;
721 case K_critical:
722 svc->flags |= SVC_CRITICAL;
723 break;
724 case K_setenv: { /* name value */
725 struct svcenvinfo *ei;
726 if (nargs < 2) {
727 parse_error(state, "setenv option requires name and value arguments\n");
728 break;
729 }
730 ei = calloc(1, sizeof(*ei));
731 if (!ei) {
732 parse_error(state, "out of memory\n");
733 break;
734 }
735 ei->name = args[1];
736 ei->value = args[2];
737 ei->next = svc->envvars;
738 svc->envvars = ei;
739 break;
740 }
741 case K_socket: {/* name type perm [ uid gid ] */
742 struct socketinfo *si;
743 if (nargs < 4) {
744 parse_error(state, "socket option requires name, type, perm arguments\n");
745 break;
746 }
747 if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")) {
748 parse_error(state, "socket type must be 'dgram' or 'stream'\n");
749 break;
750 }
751 si = calloc(1, sizeof(*si));
752 if (!si) {
753 parse_error(state, "out of memory\n");
754 break;
755 }
756 si->name = args[1];
757 si->type = args[2];
758 si->perm = strtoul(args[3], 0, 8);
759 if (nargs > 4)
760 si->uid = decode_uid(args[4]);
761 if (nargs > 5)
762 si->gid = decode_uid(args[5]);
763 si->next = svc->sockets;
764 svc->sockets = si;
765 break;
766 }
767 case K_user:
768 if (nargs != 2) {
769 parse_error(state, "user option requires a user id\n");
770 } else {
771 svc->uid = decode_uid(args[1]);
772 }
773 break;
774 default:
775 parse_error(state, "invalid option '%s'\n", args[0]);
776 }
777}
778
779static void *parse_action(struct parse_state *state, int nargs, char **args)
780{
781 struct action *act;
782 if (nargs < 2) {
783 parse_error(state, "actions must have a trigger\n");
784 return 0;
785 }
786 if (nargs > 2) {
787 parse_error(state, "actions may not have extra parameters\n");
788 return 0;
789 }
790 act = calloc(1, sizeof(*act));
791 act->name = args[1];
792 list_init(&act->commands);
793 list_add_tail(&action_list, &act->alist);
794 /* XXX add to hash */
795 return act;
796}
797
798static void parse_line_action(struct parse_state* state, int nargs, char **args)
799{
800 struct command *cmd;
801 struct action *act = state->context;
802 int (*func)(int nargs, char **args);
803 int kw, n;
804
805 if (nargs == 0) {
806 return;
807 }
808
809 kw = lookup_keyword(args[0]);
810 if (!kw_is(kw, COMMAND)) {
811 parse_error(state, "invalid command '%s'\n", args[0]);
812 return;
813 }
814
815 n = kw_nargs(kw);
816 if (nargs < n) {
817 parse_error(state, "%s requires %d %s\n", args[0], n - 1,
818 n > 2 ? "arguments" : "argument");
819 return;
820 }
821 cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs);
822 cmd->func = kw_func(kw);
823 cmd->nargs = nargs;
824 memcpy(cmd->args, args, sizeof(char*) * nargs);
825 list_add_tail(&act->commands, &cmd->clist);
826}