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