parser.c revision ed8a7d84428ec945c48b6b53dc5a3a18fabaf683
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 break; 208 } 209 return K_UNKNOWN; 210} 211 212void parse_line_no_op(struct parse_state *state, int nargs, char **args) 213{ 214} 215 216int next_token(struct parse_state *state) 217{ 218 char *x = state->ptr; 219 char *s; 220 221 if (state->nexttoken) { 222 int t = state->nexttoken; 223 state->nexttoken = 0; 224 return t; 225 } 226 227 for (;;) { 228 switch (*x) { 229 case 0: 230 state->ptr = x; 231 return T_EOF; 232 case '\n': 233 state->line++; 234 x++; 235 state->ptr = x; 236 return T_NEWLINE; 237 case ' ': 238 case '\t': 239 case '\r': 240 x++; 241 continue; 242 case '#': 243 while (*x && (*x != '\n')) x++; 244 state->line++; 245 state->ptr = x; 246 return T_NEWLINE; 247 default: 248 goto text; 249 } 250 } 251 252textdone: 253 state->ptr = x; 254 *s = 0; 255 return T_TEXT; 256text: 257 state->text = s = x; 258textresume: 259 for (;;) { 260 switch (*x) { 261 case 0: 262 goto textdone; 263 case ' ': 264 case '\t': 265 case '\r': 266 x++; 267 goto textdone; 268 case '\n': 269 state->nexttoken = T_NEWLINE; 270 x++; 271 goto textdone; 272 case '"': 273 x++; 274 for (;;) { 275 switch (*x) { 276 case 0: 277 /* unterminated quoted thing */ 278 state->ptr = x; 279 return T_EOF; 280 case '"': 281 x++; 282 goto textresume; 283 default: 284 *s++ = *x++; 285 } 286 } 287 break; 288 case '\\': 289 x++; 290 switch (*x) { 291 case 0: 292 goto textdone; 293 case 'n': 294 *s++ = '\n'; 295 break; 296 case 'r': 297 *s++ = '\r'; 298 break; 299 case 't': 300 *s++ = '\t'; 301 break; 302 case '\\': 303 *s++ = '\\'; 304 break; 305 case '\r': 306 /* \ <cr> <lf> -> line continuation */ 307 if (x[1] != '\n') { 308 x++; 309 continue; 310 } 311 case '\n': 312 /* \ <lf> -> line continuation */ 313 state->line++; 314 x++; 315 /* eat any extra whitespace */ 316 while((*x == ' ') || (*x == '\t')) x++; 317 continue; 318 default: 319 /* unknown escape -- just copy */ 320 *s++ = *x++; 321 } 322 continue; 323 default: 324 *s++ = *x++; 325 } 326 } 327 return T_EOF; 328} 329 330void parse_line(int nargs, char **args) 331{ 332 int n; 333 int id = lookup_keyword(args[0]); 334 printf("%s(%d)", args[0], id); 335 for (n = 1; n < nargs; n++) { 336 printf(" '%s'", args[n]); 337 } 338 printf("\n"); 339} 340 341void parse_new_section(struct parse_state *state, int kw, 342 int nargs, char **args) 343{ 344 printf("[ %s %s ]\n", args[0], 345 nargs > 1 ? args[1] : ""); 346 switch(kw) { 347 case K_service: 348 state->context = parse_service(state, nargs, args); 349 if (state->context) { 350 state->parse_line = parse_line_service; 351 return; 352 } 353 break; 354 case K_on: 355 state->context = parse_action(state, nargs, args); 356 if (state->context) { 357 state->parse_line = parse_line_action; 358 return; 359 } 360 break; 361 } 362 state->parse_line = parse_line_no_op; 363} 364 365static void parse_config(const char *fn, char *s) 366{ 367 struct parse_state state; 368 char *args[SVC_MAXARGS]; 369 int nargs; 370 371 nargs = 0; 372 state.filename = fn; 373 state.line = 1; 374 state.ptr = s; 375 state.nexttoken = 0; 376 state.parse_line = parse_line_no_op; 377 for (;;) { 378 switch (next_token(&state)) { 379 case T_EOF: 380 state.parse_line(&state, 0, 0); 381 return; 382 case T_NEWLINE: 383 if (nargs) { 384 int kw = lookup_keyword(args[0]); 385 if (kw_is(kw, SECTION)) { 386 state.parse_line(&state, 0, 0); 387 parse_new_section(&state, kw, nargs, args); 388 } else { 389 state.parse_line(&state, nargs, args); 390 } 391 nargs = 0; 392 } 393 break; 394 case T_TEXT: 395 if (nargs < SVC_MAXARGS) { 396 args[nargs++] = state.text; 397 } 398 break; 399 } 400 } 401} 402 403int parse_config_file(const char *fn) 404{ 405 char *data; 406 data = read_file(fn, 0); 407 if (!data) return -1; 408 409 parse_config(fn, data); 410 DUMP(); 411 return 0; 412} 413 414static int valid_name(const char *name) 415{ 416 if (strlen(name) > 16) { 417 return 0; 418 } 419 while (*name) { 420 if (!isalnum(*name) && (*name != '_') && (*name != '-')) { 421 return 0; 422 } 423 name++; 424 } 425 return 1; 426} 427 428struct service *service_find_by_name(const char *name) 429{ 430 struct listnode *node; 431 struct service *svc; 432 list_for_each(node, &service_list) { 433 svc = node_to_item(node, struct service, slist); 434 if (!strcmp(svc->name, name)) { 435 return svc; 436 } 437 } 438 return 0; 439} 440 441struct service *service_find_by_pid(pid_t pid) 442{ 443 struct listnode *node; 444 struct service *svc; 445 list_for_each(node, &service_list) { 446 svc = node_to_item(node, struct service, slist); 447 if (svc->pid == pid) { 448 return svc; 449 } 450 } 451 return 0; 452} 453 454struct service *service_find_by_keychord(int keychord_id) 455{ 456 struct listnode *node; 457 struct service *svc; 458 list_for_each(node, &service_list) { 459 svc = node_to_item(node, struct service, slist); 460 if (svc->keychord_id == keychord_id) { 461 return svc; 462 } 463 } 464 return 0; 465} 466 467void service_for_each(void (*func)(struct service *svc)) 468{ 469 struct listnode *node; 470 struct service *svc; 471 list_for_each(node, &service_list) { 472 svc = node_to_item(node, struct service, slist); 473 func(svc); 474 } 475} 476 477void service_for_each_class(const char *classname, 478 void (*func)(struct service *svc)) 479{ 480 struct listnode *node; 481 struct service *svc; 482 list_for_each(node, &service_list) { 483 svc = node_to_item(node, struct service, slist); 484 if (!strcmp(svc->classname, classname)) { 485 func(svc); 486 } 487 } 488} 489 490void service_for_each_flags(unsigned matchflags, 491 void (*func)(struct service *svc)) 492{ 493 struct listnode *node; 494 struct service *svc; 495 list_for_each(node, &service_list) { 496 svc = node_to_item(node, struct service, slist); 497 if (svc->flags & matchflags) { 498 func(svc); 499 } 500 } 501} 502 503void action_for_each_trigger(const char *trigger, 504 void (*func)(struct action *act)) 505{ 506 struct listnode *node; 507 struct action *act; 508 list_for_each(node, &action_list) { 509 act = node_to_item(node, struct action, alist); 510 if (!strcmp(act->name, trigger)) { 511 func(act); 512 } 513 } 514} 515 516void queue_property_triggers(const char *name, const char *value) 517{ 518 struct listnode *node; 519 struct action *act; 520 list_for_each(node, &action_list) { 521 act = node_to_item(node, struct action, alist); 522 if (!strncmp(act->name, "property:", strlen("property:"))) { 523 const char *test = act->name + strlen("property:"); 524 int name_length = strlen(name); 525 526 if (!strncmp(name, test, name_length) && 527 test[name_length] == '=' && 528 !strcmp(test + name_length + 1, value)) { 529 action_add_queue_tail(act); 530 } 531 } 532 } 533} 534 535void queue_all_property_triggers() 536{ 537 struct listnode *node; 538 struct action *act; 539 list_for_each(node, &action_list) { 540 act = node_to_item(node, struct action, alist); 541 if (!strncmp(act->name, "property:", strlen("property:"))) { 542 /* parse property name and value 543 syntax is property:<name>=<value> */ 544 const char* name = act->name + strlen("property:"); 545 const char* equals = strchr(name, '='); 546 if (equals) { 547 char prop_name[PROP_NAME_MAX + 1]; 548 const char* value; 549 int length = equals - name; 550 if (length > PROP_NAME_MAX) { 551 ERROR("property name too long in trigger %s", act->name); 552 } else { 553 memcpy(prop_name, name, length); 554 prop_name[length] = 0; 555 556 /* does the property exist, and match the trigger value? */ 557 value = property_get(prop_name); 558 if (value && !strcmp(equals + 1, value)) { 559 action_add_queue_tail(act); 560 } 561 } 562 } 563 } 564 } 565} 566 567void queue_builtin_action(int (*func)(int nargs, char **args), char *name) 568{ 569 struct action *act; 570 struct command *cmd; 571 572 act = calloc(1, sizeof(*act)); 573 act->name = name; 574 list_init(&act->commands); 575 576 cmd = calloc(1, sizeof(*cmd)); 577 cmd->func = func; 578 cmd->args[0] = name; 579 list_add_tail(&act->commands, &cmd->clist); 580 581 list_add_tail(&action_list, &act->alist); 582 action_add_queue_tail(act); 583} 584 585void action_add_queue_tail(struct action *act) 586{ 587 list_add_tail(&action_queue, &act->qlist); 588} 589 590struct action *action_remove_queue_head(void) 591{ 592 if (list_empty(&action_queue)) { 593 return 0; 594 } else { 595 struct listnode *node = list_head(&action_queue); 596 struct action *act = node_to_item(node, struct action, qlist); 597 list_remove(node); 598 return act; 599 } 600} 601 602int action_queue_empty() 603{ 604 return list_empty(&action_queue); 605} 606 607static void *parse_service(struct parse_state *state, int nargs, char **args) 608{ 609 struct service *svc; 610 if (nargs < 3) { 611 parse_error(state, "services must have a name and a program\n"); 612 return 0; 613 } 614 if (!valid_name(args[1])) { 615 parse_error(state, "invalid service name '%s'\n", args[1]); 616 return 0; 617 } 618 619 svc = service_find_by_name(args[1]); 620 if (svc) { 621 parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]); 622 return 0; 623 } 624 625 nargs -= 2; 626 svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs); 627 if (!svc) { 628 parse_error(state, "out of memory\n"); 629 return 0; 630 } 631 svc->name = args[1]; 632 svc->classname = "default"; 633 memcpy(svc->args, args + 2, sizeof(char*) * nargs); 634 svc->args[nargs] = 0; 635 svc->nargs = nargs; 636 svc->onrestart.name = "onrestart"; 637 list_init(&svc->onrestart.commands); 638 list_add_tail(&service_list, &svc->slist); 639 return svc; 640} 641 642static void parse_line_service(struct parse_state *state, int nargs, char **args) 643{ 644 struct service *svc = state->context; 645 struct command *cmd; 646 int i, kw, kw_nargs; 647 648 if (nargs == 0) { 649 return; 650 } 651 652 svc->ioprio_class = IoSchedClass_NONE; 653 654 kw = lookup_keyword(args[0]); 655 switch (kw) { 656 case K_capability: 657 break; 658 case K_class: 659 if (nargs != 2) { 660 parse_error(state, "class option requires a classname\n"); 661 } else { 662 svc->classname = args[1]; 663 } 664 break; 665 case K_console: 666 svc->flags |= SVC_CONSOLE; 667 break; 668 case K_disabled: 669 svc->flags |= SVC_DISABLED; 670 break; 671 case K_ioprio: 672 if (nargs != 3) { 673 parse_error(state, "ioprio optin usage: ioprio <rt|be|idle> <ioprio 0-7>\n"); 674 } else { 675 svc->ioprio_pri = strtoul(args[2], 0, 8); 676 677 if (svc->ioprio_pri < 0 || svc->ioprio_pri > 7) { 678 parse_error(state, "priority value must be range 0 - 7\n"); 679 break; 680 } 681 682 if (!strcmp(args[1], "rt")) { 683 svc->ioprio_class = IoSchedClass_RT; 684 } else if (!strcmp(args[1], "be")) { 685 svc->ioprio_class = IoSchedClass_BE; 686 } else if (!strcmp(args[1], "idle")) { 687 svc->ioprio_class = IoSchedClass_IDLE; 688 } else { 689 parse_error(state, "ioprio option usage: ioprio <rt|be|idle> <0-7>\n"); 690 } 691 } 692 break; 693 case K_group: 694 if (nargs < 2) { 695 parse_error(state, "group option requires a group id\n"); 696 } else if (nargs > NR_SVC_SUPP_GIDS + 2) { 697 parse_error(state, "group option accepts at most %d supp. groups\n", 698 NR_SVC_SUPP_GIDS); 699 } else { 700 int n; 701 svc->gid = decode_uid(args[1]); 702 for (n = 2; n < nargs; n++) { 703 svc->supp_gids[n-2] = decode_uid(args[n]); 704 } 705 svc->nr_supp_gids = n - 2; 706 } 707 break; 708 case K_keycodes: 709 if (nargs < 2) { 710 parse_error(state, "keycodes option requires atleast one keycode\n"); 711 } else { 712 svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0])); 713 if (!svc->keycodes) { 714 parse_error(state, "could not allocate keycodes\n"); 715 } else { 716 svc->nkeycodes = nargs - 1; 717 for (i = 1; i < nargs; i++) { 718 svc->keycodes[i - 1] = atoi(args[i]); 719 } 720 } 721 } 722 break; 723 case K_oneshot: 724 svc->flags |= SVC_ONESHOT; 725 break; 726 case K_onrestart: 727 nargs--; 728 args++; 729 kw = lookup_keyword(args[0]); 730 if (!kw_is(kw, COMMAND)) { 731 parse_error(state, "invalid command '%s'\n", args[0]); 732 break; 733 } 734 kw_nargs = kw_nargs(kw); 735 if (nargs < kw_nargs) { 736 parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1, 737 kw_nargs > 2 ? "arguments" : "argument"); 738 break; 739 } 740 741 cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); 742 cmd->func = kw_func(kw); 743 cmd->nargs = nargs; 744 memcpy(cmd->args, args, sizeof(char*) * nargs); 745 list_add_tail(&svc->onrestart.commands, &cmd->clist); 746 break; 747 case K_critical: 748 svc->flags |= SVC_CRITICAL; 749 break; 750 case K_setenv: { /* name value */ 751 struct svcenvinfo *ei; 752 if (nargs < 2) { 753 parse_error(state, "setenv option requires name and value arguments\n"); 754 break; 755 } 756 ei = calloc(1, sizeof(*ei)); 757 if (!ei) { 758 parse_error(state, "out of memory\n"); 759 break; 760 } 761 ei->name = args[1]; 762 ei->value = args[2]; 763 ei->next = svc->envvars; 764 svc->envvars = ei; 765 break; 766 } 767 case K_socket: {/* name type perm [ uid gid ] */ 768 struct socketinfo *si; 769 if (nargs < 4) { 770 parse_error(state, "socket option requires name, type, perm arguments\n"); 771 break; 772 } 773 if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")) { 774 parse_error(state, "socket type must be 'dgram' or 'stream'\n"); 775 break; 776 } 777 si = calloc(1, sizeof(*si)); 778 if (!si) { 779 parse_error(state, "out of memory\n"); 780 break; 781 } 782 si->name = args[1]; 783 si->type = args[2]; 784 si->perm = strtoul(args[3], 0, 8); 785 if (nargs > 4) 786 si->uid = decode_uid(args[4]); 787 if (nargs > 5) 788 si->gid = decode_uid(args[5]); 789 si->next = svc->sockets; 790 svc->sockets = si; 791 break; 792 } 793 case K_user: 794 if (nargs != 2) { 795 parse_error(state, "user option requires a user id\n"); 796 } else { 797 svc->uid = decode_uid(args[1]); 798 } 799 break; 800 default: 801 parse_error(state, "invalid option '%s'\n", args[0]); 802 } 803} 804 805static void *parse_action(struct parse_state *state, int nargs, char **args) 806{ 807 struct action *act; 808 if (nargs < 2) { 809 parse_error(state, "actions must have a trigger\n"); 810 return 0; 811 } 812 if (nargs > 2) { 813 parse_error(state, "actions may not have extra parameters\n"); 814 return 0; 815 } 816 act = calloc(1, sizeof(*act)); 817 act->name = args[1]; 818 list_init(&act->commands); 819 list_add_tail(&action_list, &act->alist); 820 /* XXX add to hash */ 821 return act; 822} 823 824static void parse_line_action(struct parse_state* state, int nargs, char **args) 825{ 826 struct command *cmd; 827 struct action *act = state->context; 828 int (*func)(int nargs, char **args); 829 int kw, n; 830 831 if (nargs == 0) { 832 return; 833 } 834 835 kw = lookup_keyword(args[0]); 836 if (!kw_is(kw, COMMAND)) { 837 parse_error(state, "invalid command '%s'\n", args[0]); 838 return; 839 } 840 841 n = kw_nargs(kw); 842 if (nargs < n) { 843 parse_error(state, "%s requires %d %s\n", args[0], n - 1, 844 n > 2 ? "arguments" : "argument"); 845 return; 846 } 847 cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); 848 cmd->func = kw_func(kw); 849 cmd->nargs = nargs; 850 memcpy(cmd->args, args, sizeof(char*) * nargs); 851 list_add_tail(&act->commands, &cmd->clist); 852} 853