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