1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * svccfg(1) interpreter and command execution engine. 30 */ 31 32 #include <sys/mman.h> 33 #include <sys/stat.h> 34 #include <sys/types.h> 35 #include <assert.h> 36 #include <errno.h> 37 #include <libintl.h> 38 #include <libtecla.h> 39 #include <md5.h> 40 #include <string.h> 41 #include <stdlib.h> 42 #include <unistd.h> 43 44 #include "manifest_hash.h" 45 #include "svccfg.h" 46 47 #define MS_PER_US 1000 48 49 engine_state_t *est; 50 51 /* 52 * Replacement lex(1) character retrieval routines. 53 */ 54 int 55 engine_cmd_getc(engine_state_t *E) 56 { 57 if (E->sc_cmd_file != NULL) 58 return (getc(E->sc_cmd_file)); 59 60 if (E->sc_cmd_flags & SC_CMD_EOF) 61 return (EOF); 62 63 if (E->sc_cmd_bufoff < E->sc_cmd_bufsz) 64 return (*(E->sc_cmd_buf + E->sc_cmd_bufoff++)); 65 66 if (!(E->sc_cmd_flags & SC_CMD_IACTIVE)) { 67 E->sc_cmd_flags |= SC_CMD_EOF; 68 69 return (EOF); 70 } else { 71 #ifdef NATIVE_BUILD 72 return (EOF); 73 #else 74 extern int parens; 75 76 if (parens <= 0) { 77 E->sc_cmd_flags |= SC_CMD_EOF; 78 return (EOF); 79 } 80 81 for (;;) { 82 E->sc_cmd_buf = gl_get_line(E->sc_gl, "> ", NULL, -1); 83 if (E->sc_cmd_buf != NULL) 84 break; 85 86 switch (gl_return_status(E->sc_gl)) { 87 case GLR_SIGNAL: 88 gl_abandon_line(E->sc_gl); 89 continue; 90 91 case GLR_EOF: 92 E->sc_cmd_flags |= SC_CMD_EOF; 93 return (EOF); 94 95 case GLR_ERROR: 96 uu_die(gettext("Error reading terminal: %s.\n"), 97 gl_error_message(E->sc_gl, NULL, 0)); 98 /* NOTREACHED */ 99 100 default: 101 #ifndef NDEBUG 102 (void) fprintf(stderr, "%s:%d: gl_get_line() " 103 "returned unexpected value %d.\n", __FILE__, 104 __LINE__, gl_return_status(E->sc_gl)); 105 #endif 106 abort(); 107 } 108 } 109 110 E->sc_cmd_bufsz = strlen(E->sc_cmd_buf); 111 E->sc_cmd_bufoff = 1; 112 113 return (E->sc_cmd_buf[0]); 114 #endif /* NATIVE_BUILD */ 115 } 116 } 117 118 int 119 engine_cmd_ungetc(engine_state_t *E, char c) 120 { 121 if (E->sc_cmd_file != NULL) 122 return (ungetc(c, E->sc_cmd_file)); 123 124 if (E->sc_cmd_buf != NULL) 125 *(E->sc_cmd_buf + --E->sc_cmd_bufoff) = c; 126 127 return (c); 128 } 129 130 /*ARGSUSED*/ 131 void 132 engine_cmd_nputs(engine_state_t *E, char *c, size_t n) 133 { 134 /* our lexer shouldn't need this state */ 135 exit(11); 136 } 137 138 int 139 engine_exec(char *cmd) 140 { 141 est->sc_cmd_buf = cmd; 142 est->sc_cmd_bufsz = strlen(cmd) + 1; 143 est->sc_cmd_bufoff = 0; 144 145 (void) yyparse(); 146 147 return (0); 148 } 149 150 #ifndef NATIVE_BUILD 151 /* ARGSUSED */ 152 static 153 CPL_CHECK_FN(check_xml) 154 { 155 const char *ext; 156 157 if (strlen(pathname) < 4) 158 return (0); 159 160 ext = pathname + strlen(pathname) - 4; 161 162 return (strcmp(ext, ".xml") == 0 ? 1 : 0); 163 } 164 165 static const char * const whitespace = " \t"; 166 167 static 168 CPL_MATCH_FN(complete_single_xml_file_arg) 169 { 170 const char *arg1 = data; 171 int arg1end_i, ret; 172 CplFileConf *cfc; 173 174 arg1end_i = arg1 + strcspn(arg1, whitespace) - line; 175 if (arg1end_i < word_end) 176 return (0); 177 178 cfc = new_CplFileConf(); 179 if (cfc == NULL) { 180 cpl_record_error(cpl, "Out of memory."); 181 return (1); 182 } 183 184 cfc_set_check_fn(cfc, check_xml, NULL); 185 186 ret = cpl_file_completions(cpl, cfc, line, word_end); 187 188 (void) del_CplFileConf(cfc); 189 return (ret); 190 } 191 192 static struct cmd_info { 193 const char *name; 194 uint32_t flags; 195 CplMatchFn *complete_args_f; 196 } cmds[] = { 197 { "validate", CS_GLOBAL, complete_single_xml_file_arg }, 198 { "import", CS_GLOBAL, complete_single_xml_file_arg }, 199 { "export", CS_GLOBAL, NULL }, 200 { "archive", CS_GLOBAL, NULL }, 201 { "apply", CS_GLOBAL, complete_single_xml_file_arg }, 202 { "extract", CS_GLOBAL, NULL }, 203 { "repository", CS_GLOBAL, NULL }, 204 { "inventory", CS_GLOBAL, complete_single_xml_file_arg }, 205 { "set", CS_GLOBAL, NULL }, 206 { "end", CS_GLOBAL, NULL }, 207 { "exit", CS_GLOBAL, NULL }, 208 { "quit", CS_GLOBAL, NULL }, 209 { "help", CS_GLOBAL, NULL }, 210 { "delete", CS_GLOBAL, NULL }, 211 { "select", CS_GLOBAL, complete_select }, 212 { "unselect", CS_SVC | CS_INST | CS_SNAP, NULL }, 213 { "list", CS_SCOPE | CS_SVC | CS_SNAP, NULL }, 214 { "add", CS_SCOPE | CS_SVC, NULL }, 215 { "listpg", CS_SVC | CS_INST | CS_SNAP, NULL }, 216 { "addpg", CS_SVC | CS_INST, NULL }, 217 { "delpg", CS_SVC | CS_INST, NULL }, 218 { "listprop", CS_SVC | CS_INST | CS_SNAP, NULL }, 219 { "setprop", CS_SVC | CS_INST, NULL }, 220 { "delprop", CS_SVC | CS_INST, NULL }, 221 { "editprop", CS_SVC | CS_INST, NULL }, 222 { "listsnap", CS_INST | CS_SNAP, NULL }, 223 { "selectsnap", CS_INST | CS_SNAP, NULL }, 224 { "revert", CS_INST | CS_SNAP, NULL }, 225 { NULL } 226 }; 227 228 int 229 add_cmd_matches(WordCompletion *cpl, const char *line, int word_end, 230 uint32_t scope) 231 { 232 int word_start, err; 233 size_t len; 234 const char *bol; 235 struct cmd_info *cip; 236 237 word_start = strspn(line, whitespace); 238 len = word_end - word_start; 239 bol = line + word_end - len; 240 241 for (cip = cmds; cip->name != NULL; ++cip) { 242 if ((cip->flags & scope) == 0) 243 continue; 244 245 if (strncmp(cip->name, bol, len) == 0) { 246 err = cpl_add_completion(cpl, line, word_start, 247 word_end, cip->name + len, "", " "); 248 if (err != 0) 249 return (err); 250 } 251 } 252 253 return (0); 254 } 255 256 /* 257 * Suggest completions. We must first determine if the cursor is in command 258 * position or in argument position. If the former, complete_command() finds 259 * matching commands. If the latter, we tail-call the command-specific 260 * argument-completion routine in the cmds table. 261 */ 262 /* ARGSUSED */ 263 static 264 CPL_MATCH_FN(complete) 265 { 266 const char *arg0, *arg1; 267 size_t arg0len; 268 struct cmd_info *cip; 269 270 arg0 = line + strspn(line, whitespace); 271 arg0len = strcspn(arg0, whitespace); 272 if ((arg0 + arg0len) - line >= word_end || 273 (arg0[arg0len] != ' ' && arg0[arg0len] != '\t')) 274 return (complete_command(cpl, (void *)arg0, line, word_end)); 275 276 arg1 = arg0 + arg0len; 277 arg1 += strspn(arg1, whitespace); 278 279 for (cip = cmds; cip->name != NULL; ++cip) { 280 if (strlen(cip->name) != arg0len) 281 continue; 282 283 if (strncmp(cip->name, arg0, arg0len) != 0) 284 continue; 285 286 if (cip->complete_args_f == NULL) 287 break; 288 289 return (cip->complete_args_f(cpl, (void *)arg1, line, 290 word_end)); 291 } 292 293 return (0); 294 } 295 #endif /* NATIVE_BUILD */ 296 297 int 298 engine_interp() 299 { 300 #ifdef NATIVE_BUILD 301 uu_die("native build does not support interactive mode."); 302 #else 303 char *selfmri; 304 size_t sfsz; 305 int r; 306 307 extern int parens; 308 309 (void) sigset(SIGINT, SIG_IGN); 310 311 est->sc_gl = new_GetLine(512, 8000); 312 if (est->sc_gl == NULL) 313 uu_die(gettext("Out of memory.\n")); 314 315 /* The longest string is "[snapname]fmri[:instname]> ". */ 316 sfsz = 1 + max_scf_name_len + 1 + max_scf_fmri_len + 2 + 317 max_scf_name_len + 1 + 2 + 1; 318 selfmri = safe_malloc(sfsz); 319 320 r = gl_customize_completion(est->sc_gl, NULL, complete); 321 assert(r == 0); 322 323 for (;;) { 324 lscf_get_selection_str(selfmri, sfsz - 2); 325 (void) strcat(selfmri, "> "); 326 est->sc_cmd_buf = gl_get_line(est->sc_gl, selfmri, NULL, -1); 327 328 if (est->sc_cmd_buf == NULL) { 329 switch (gl_return_status(est->sc_gl)) { 330 case GLR_SIGNAL: 331 gl_abandon_line(est->sc_gl); 332 continue; 333 334 case GLR_EOF: 335 break; 336 337 case GLR_ERROR: 338 uu_die(gettext("Error reading terminal: %s.\n"), 339 gl_error_message(est->sc_gl, NULL, 0)); 340 /* NOTREACHED */ 341 342 default: 343 #ifndef NDEBUG 344 (void) fprintf(stderr, "%s:%d: gl_get_line() " 345 "returned unexpected value %d.\n", __FILE__, 346 __LINE__, gl_return_status(est->sc_gl)); 347 #endif 348 abort(); 349 } 350 351 break; 352 } 353 354 parens = 0; 355 est->sc_cmd_bufsz = strlen(est->sc_cmd_buf); 356 est->sc_cmd_bufoff = 0; 357 est->sc_cmd_flags = SC_CMD_IACTIVE; 358 359 (void) yyparse(); 360 } 361 362 free(selfmri); 363 est->sc_gl = del_GetLine(est->sc_gl); /* returns NULL */ 364 365 #endif /* NATIVE_BUILD */ 366 return (0); 367 } 368 369 int 370 engine_source(const char *name, boolean_t dont_exit) 371 { 372 engine_state_t *old = est; 373 struct stat st; 374 int ret; 375 376 est = uu_zalloc(sizeof (engine_state_t)); 377 378 /* first, copy the stuff set up in engine_init */ 379 est->sc_repo_pid = old->sc_repo_pid; 380 if (old->sc_repo_filename != NULL) 381 est->sc_repo_filename = safe_strdup(old->sc_repo_filename); 382 if (old->sc_repo_doordir != NULL) 383 est->sc_repo_doordir = safe_strdup(old->sc_repo_doordir); 384 if (old->sc_repo_doorname != NULL) 385 est->sc_repo_doorname = safe_strdup(old->sc_repo_doorname); 386 if (old->sc_repo_server != NULL) 387 est->sc_repo_server = safe_strdup(old->sc_repo_server); 388 389 /* set up the new guy */ 390 est->sc_cmd_lineno = 1; 391 392 if (dont_exit) 393 est->sc_cmd_flags |= SC_CMD_DONT_EXIT; 394 395 if (strcmp(name, "-") == 0) { 396 est->sc_cmd_file = stdin; 397 est->sc_cmd_filename = "<stdin>"; 398 } else { 399 errno = 0; 400 est->sc_cmd_filename = name; 401 est->sc_cmd_file = fopen(name, "r"); 402 if (est->sc_cmd_file == NULL) { 403 if (errno == 0) 404 semerr(gettext("No free stdio streams.\n")); 405 else 406 semerr(gettext("Could not open %s"), name); 407 408 ret = -1; 409 goto fail; 410 } 411 412 do 413 ret = fstat(fileno(est->sc_cmd_file), &st); 414 while (ret != 0 && errno == EINTR); 415 if (ret != 0) { 416 (void) fclose(est->sc_cmd_file); 417 est->sc_cmd_file = NULL; /* for semerr() */ 418 419 semerr(gettext("Could not stat %s"), name); 420 421 ret = -1; 422 goto fail; 423 } 424 425 if (!S_ISREG(st.st_mode)) { 426 (void) fclose(est->sc_cmd_file); 427 est->sc_cmd_file = NULL; /* for semerr() */ 428 429 semerr(gettext("%s is not a regular file.\n"), name); 430 431 ret = -1; 432 goto fail; 433 } 434 } 435 436 (void) yyparse(); 437 438 if (est->sc_cmd_file != stdin) 439 (void) fclose(est->sc_cmd_file); 440 441 ret = 0; 442 443 fail: 444 if (est->sc_repo_pid != old->sc_repo_pid) 445 lscf_cleanup(); /* clean up any new repository */ 446 447 if (est->sc_repo_filename != NULL) 448 free((void *)est->sc_repo_filename); 449 if (est->sc_repo_doordir != NULL) 450 free((void *)est->sc_repo_doordir); 451 if (est->sc_repo_doorname != NULL) 452 free((void *)est->sc_repo_doorname); 453 if (est->sc_repo_server != NULL) 454 free((void *)est->sc_repo_server); 455 free(est); 456 457 est = old; 458 459 return (ret); 460 } 461 462 /* 463 * Initialize svccfg state. We recognize four environment variables: 464 * 465 * SVCCFG_REPOSITORY Create a private instance of svc.configd(1M) to answer 466 * requests for the specified repository file. 467 * SVCCFG_DOOR_PATH Directory for door creation. 468 * 469 * SVCCFG_DOOR Rendezvous via an alternative repository door. 470 * 471 * SVCCFG_CONFIGD_PATH Resolvable path to alternative svc.configd(1M) binary. 472 */ 473 void 474 engine_init() 475 { 476 const char *cp; 477 478 est = uu_zalloc(sizeof (engine_state_t)); 479 480 est->sc_cmd_lineno = 1; 481 est->sc_repo_pid = -1; 482 483 cp = getenv("SVCCFG_REPOSITORY"); 484 est->sc_repo_filename = cp ? safe_strdup(cp) : NULL; 485 486 cp = getenv("SVCCFG_DOOR_PATH"); 487 est->sc_repo_doordir = cp ? cp : "/var/run"; 488 489 cp = getenv("SVCCFG_DOOR"); 490 if (cp != NULL) { 491 if (est->sc_repo_filename != NULL) { 492 uu_warn(gettext("SVCCFG_DOOR unused when " 493 "SVCCFG_REPOSITORY specified\n")); 494 } else { 495 est->sc_repo_doorname = safe_strdup(cp); 496 } 497 } 498 499 cp = getenv("SVCCFG_CONFIGD_PATH"); 500 est->sc_repo_server = cp ? cp : "/lib/svc/bin/svc.configd"; 501 } 502 503 int 504 engine_import(uu_list_t *args) 505 { 506 int ret, argc, i, o; 507 bundle_t *b; 508 char *file, *pname; 509 uchar_t hash[MHASH_SIZE]; 510 char **argv; 511 string_list_t *slp; 512 boolean_t verify = B_FALSE; 513 uint_t flags = SCI_GENERALLAST; 514 515 argc = uu_list_numnodes(args); 516 if (argc < 1) 517 return (-2); 518 519 argv = calloc(argc + 1, sizeof (char *)); 520 if (argv == NULL) 521 uu_die(gettext("Out of memory.\n")); 522 523 for (slp = uu_list_first(args), i = 0; 524 slp != NULL; 525 slp = uu_list_next(args, slp), ++i) 526 argv[i] = slp->str; 527 528 argv[i] = NULL; 529 530 opterr = 0; 531 optind = 0; /* Remember, no argv[0]. */ 532 for (;;) { 533 o = getopt(argc, argv, "nV"); 534 if (o == -1) 535 break; 536 537 switch (o) { 538 case 'n': 539 flags |= SCI_NOREFRESH; 540 break; 541 542 case 'V': 543 verify = B_TRUE; 544 break; 545 546 case '?': 547 free(argv); 548 return (-2); 549 550 default: 551 bad_error("getopt", o); 552 } 553 } 554 555 argc -= optind; 556 if (argc != 1) { 557 free(argv); 558 return (-2); 559 } 560 561 file = argv[optind]; 562 free(argv); 563 564 lscf_prep_hndl(); 565 566 ret = mhash_test_file(g_hndl, file, 0, &pname, hash); 567 if (ret != MHASH_NEWFILE) 568 return (ret); 569 570 /* Load */ 571 b = internal_bundle_new(); 572 573 if (lxml_get_bundle_file(b, file, 0) != 0) { 574 internal_bundle_free(b); 575 return (-1); 576 } 577 578 /* Import */ 579 if (lscf_bundle_import(b, file, flags) != 0) { 580 internal_bundle_free(b); 581 return (-1); 582 } 583 584 internal_bundle_free(b); 585 586 if (g_verbose) 587 warn(gettext("Successful import.\n")); 588 589 if (pname) { 590 char *errstr; 591 592 if (mhash_store_entry(g_hndl, pname, hash, &errstr)) { 593 if (errstr) 594 semerr(errstr); 595 else 596 semerr(gettext("Unknown error from " 597 "mhash_store_entry()\n")); 598 } 599 600 free(pname); 601 } 602 603 /* Verify */ 604 if (verify) 605 warn(gettext("import -V not implemented.\n")); 606 607 return (0); 608 } 609 610 int 611 engine_apply(const char *file) 612 { 613 int ret; 614 bundle_t *b; 615 char *pname; 616 uchar_t hash[MHASH_SIZE]; 617 618 lscf_prep_hndl(); 619 620 ret = mhash_test_file(g_hndl, file, 1, &pname, hash); 621 if (ret != MHASH_NEWFILE) 622 return (ret); 623 624 b = internal_bundle_new(); 625 626 if (lxml_get_bundle_file(b, file, 1) != 0) { 627 internal_bundle_free(b); 628 return (-1); 629 } 630 631 if (lscf_bundle_apply(b) != 0) { 632 internal_bundle_free(b); 633 return (-1); 634 } 635 636 internal_bundle_free(b); 637 638 if (pname) { 639 char *errstr; 640 if (mhash_store_entry(g_hndl, pname, hash, &errstr)) 641 semerr(errstr); 642 643 free(pname); 644 } 645 646 return (0); 647 } 648 649 int 650 engine_set(uu_list_t *args) 651 { 652 uu_list_walk_t *walk; 653 string_list_t *slp; 654 655 if (uu_list_first(args) == NULL) { 656 /* Display current options. */ 657 if (!g_verbose) 658 (void) fputs("no", stdout); 659 (void) puts("verbose"); 660 661 return (0); 662 } 663 664 walk = uu_list_walk_start(args, UU_DEFAULT); 665 if (walk == NULL) 666 uu_die(gettext("Couldn't read arguments")); 667 668 /* Use getopt? */ 669 for (slp = uu_list_walk_next(walk); 670 slp != NULL; 671 slp = uu_list_walk_next(walk)) { 672 if (slp->str[0] == '-') { 673 char *op; 674 675 for (op = &slp->str[1]; *op != '\0'; ++op) { 676 switch (*op) { 677 case 'v': 678 g_verbose = 1; 679 break; 680 681 case 'V': 682 g_verbose = 0; 683 break; 684 685 default: 686 warn(gettext("Unknown option -%c.\n"), 687 *op); 688 } 689 } 690 } else { 691 warn(gettext("No non-flag arguments defined.\n")); 692 } 693 } 694 695 return (0); 696 } 697 698 void 699 help(int com) 700 { 701 int i; 702 703 if (com == 0) { 704 warn(gettext("General commands: help set repository end\n" 705 "Manifest commands: inventory validate import export " 706 "archive\n" 707 "Profile commands: apply extract\n" 708 "Entity commands: list select unselect add delete\n" 709 "Snapshot commands: listsnap selectsnap revert\n" 710 "Property group commands: listpg addpg delpg\n" 711 "Property commands: listprop setprop delprop editprop\n" 712 "Property value commands: addpropvalue delpropvalue " 713 "setenv unsetenv\n")); 714 return; 715 } 716 717 for (i = 0; help_messages[i].message != NULL; ++i) { 718 if (help_messages[i].token == com) { 719 warn(gettext("Usage: %s\n"), 720 gettext(help_messages[i].message)); 721 return; 722 } 723 } 724 725 warn(gettext("Unknown command.\n")); 726 } 727