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 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 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 { "delhash", CS_GLOBAL, complete_single_xml_file_arg }, 219 { "listprop", CS_SVC | CS_INST | CS_SNAP, NULL }, 220 { "setprop", CS_SVC | CS_INST, NULL }, 221 { "delprop", CS_SVC | CS_INST, NULL }, 222 { "editprop", CS_SVC | CS_INST, NULL }, 223 { "listsnap", CS_INST | CS_SNAP, NULL }, 224 { "selectsnap", CS_INST | CS_SNAP, NULL }, 225 { "revert", CS_INST | CS_SNAP, NULL }, 226 { "refresh", CS_INST, NULL }, 227 { NULL } 228 }; 229 230 int 231 add_cmd_matches(WordCompletion *cpl, const char *line, int word_end, 232 uint32_t scope) 233 { 234 int word_start, err; 235 size_t len; 236 const char *bol; 237 struct cmd_info *cip; 238 239 word_start = strspn(line, whitespace); 240 len = word_end - word_start; 241 bol = line + word_end - len; 242 243 for (cip = cmds; cip->name != NULL; ++cip) { 244 if ((cip->flags & scope) == 0) 245 continue; 246 247 if (strncmp(cip->name, bol, len) == 0) { 248 err = cpl_add_completion(cpl, line, word_start, 249 word_end, cip->name + len, "", " "); 250 if (err != 0) 251 return (err); 252 } 253 } 254 255 return (0); 256 } 257 258 /* 259 * Suggest completions. We must first determine if the cursor is in command 260 * position or in argument position. If the former, complete_command() finds 261 * matching commands. If the latter, we tail-call the command-specific 262 * argument-completion routine in the cmds table. 263 */ 264 /* ARGSUSED */ 265 static 266 CPL_MATCH_FN(complete) 267 { 268 const char *arg0, *arg1; 269 size_t arg0len; 270 struct cmd_info *cip; 271 272 arg0 = line + strspn(line, whitespace); 273 arg0len = strcspn(arg0, whitespace); 274 if ((arg0 + arg0len) - line >= word_end || 275 (arg0[arg0len] != ' ' && arg0[arg0len] != '\t')) 276 return (complete_command(cpl, (void *)arg0, line, word_end)); 277 278 arg1 = arg0 + arg0len; 279 arg1 += strspn(arg1, whitespace); 280 281 for (cip = cmds; cip->name != NULL; ++cip) { 282 if (strlen(cip->name) != arg0len) 283 continue; 284 285 if (strncmp(cip->name, arg0, arg0len) != 0) 286 continue; 287 288 if (cip->complete_args_f == NULL) 289 break; 290 291 return (cip->complete_args_f(cpl, (void *)arg1, line, 292 word_end)); 293 } 294 295 return (0); 296 } 297 #endif /* NATIVE_BUILD */ 298 299 int 300 engine_interp() 301 { 302 #ifdef NATIVE_BUILD 303 uu_die("native build does not support interactive mode."); 304 #else 305 char *selfmri; 306 size_t sfsz; 307 int r; 308 309 extern int parens; 310 311 (void) sigset(SIGINT, SIG_IGN); 312 313 est->sc_gl = new_GetLine(512, 8000); 314 if (est->sc_gl == NULL) 315 uu_die(gettext("Out of memory.\n")); 316 317 /* The longest string is "[snapname]fmri[:instname]> ". */ 318 sfsz = 1 + max_scf_name_len + 1 + max_scf_fmri_len + 2 + 319 max_scf_name_len + 1 + 2 + 1; 320 selfmri = safe_malloc(sfsz); 321 322 r = gl_customize_completion(est->sc_gl, NULL, complete); 323 assert(r == 0); 324 325 for (;;) { 326 lscf_get_selection_str(selfmri, sfsz - 2); 327 (void) strcat(selfmri, "> "); 328 est->sc_cmd_buf = gl_get_line(est->sc_gl, selfmri, NULL, -1); 329 330 if (est->sc_cmd_buf == NULL) { 331 switch (gl_return_status(est->sc_gl)) { 332 case GLR_SIGNAL: 333 gl_abandon_line(est->sc_gl); 334 continue; 335 336 case GLR_EOF: 337 break; 338 339 case GLR_ERROR: 340 uu_die(gettext("Error reading terminal: %s.\n"), 341 gl_error_message(est->sc_gl, NULL, 0)); 342 /* NOTREACHED */ 343 344 default: 345 #ifndef NDEBUG 346 (void) fprintf(stderr, "%s:%d: gl_get_line() " 347 "returned unexpected value %d.\n", __FILE__, 348 __LINE__, gl_return_status(est->sc_gl)); 349 #endif 350 abort(); 351 } 352 353 break; 354 } 355 356 parens = 0; 357 est->sc_cmd_bufsz = strlen(est->sc_cmd_buf); 358 est->sc_cmd_bufoff = 0; 359 est->sc_cmd_flags = SC_CMD_IACTIVE; 360 361 (void) yyparse(); 362 } 363 364 free(selfmri); 365 est->sc_gl = del_GetLine(est->sc_gl); /* returns NULL */ 366 367 #endif /* NATIVE_BUILD */ 368 return (0); 369 } 370 371 int 372 engine_source(const char *name, boolean_t dont_exit) 373 { 374 engine_state_t *old = est; 375 struct stat st; 376 int ret; 377 378 est = uu_zalloc(sizeof (engine_state_t)); 379 380 /* first, copy the stuff set up in engine_init */ 381 est->sc_repo_pid = old->sc_repo_pid; 382 if (old->sc_repo_filename != NULL) 383 est->sc_repo_filename = safe_strdup(old->sc_repo_filename); 384 if (old->sc_repo_doordir != NULL) 385 est->sc_repo_doordir = safe_strdup(old->sc_repo_doordir); 386 if (old->sc_repo_doorname != NULL) 387 est->sc_repo_doorname = safe_strdup(old->sc_repo_doorname); 388 if (old->sc_repo_server != NULL) 389 est->sc_repo_server = safe_strdup(old->sc_repo_server); 390 391 /* set up the new guy */ 392 est->sc_cmd_lineno = 1; 393 394 if (dont_exit) 395 est->sc_cmd_flags |= SC_CMD_DONT_EXIT; 396 397 if (strcmp(name, "-") == 0) { 398 est->sc_cmd_file = stdin; 399 est->sc_cmd_filename = "<stdin>"; 400 } else { 401 errno = 0; 402 est->sc_cmd_filename = name; 403 est->sc_cmd_file = fopen(name, "r"); 404 if (est->sc_cmd_file == NULL) { 405 if (errno == 0) 406 semerr(gettext("No free stdio streams.\n")); 407 else 408 semerr(gettext("Could not open %s"), name); 409 410 ret = -1; 411 goto fail; 412 } 413 414 do { 415 ret = fstat(fileno(est->sc_cmd_file), &st); 416 } while (ret != 0 && errno == EINTR); 417 if (ret != 0) { 418 (void) fclose(est->sc_cmd_file); 419 est->sc_cmd_file = NULL; /* for semerr() */ 420 421 semerr(gettext("Could not stat %s"), name); 422 423 ret = -1; 424 goto fail; 425 } 426 427 if (!S_ISREG(st.st_mode)) { 428 (void) fclose(est->sc_cmd_file); 429 est->sc_cmd_file = NULL; /* for semerr() */ 430 431 semerr(gettext("%s is not a regular file.\n"), name); 432 433 ret = -1; 434 goto fail; 435 } 436 } 437 438 (void) yyparse(); 439 440 if (est->sc_cmd_file != stdin) 441 (void) fclose(est->sc_cmd_file); 442 443 ret = 0; 444 445 fail: 446 if (est->sc_repo_pid != old->sc_repo_pid) 447 lscf_cleanup(); /* clean up any new repository */ 448 449 if (est->sc_repo_filename != NULL) 450 free((void *)est->sc_repo_filename); 451 if (est->sc_repo_doordir != NULL) 452 free((void *)est->sc_repo_doordir); 453 if (est->sc_repo_doorname != NULL) 454 free((void *)est->sc_repo_doorname); 455 if (est->sc_repo_server != NULL) 456 free((void *)est->sc_repo_server); 457 free(est); 458 459 est = old; 460 461 return (ret); 462 } 463 464 /* 465 * Initialize svccfg state. We recognize four environment variables: 466 * 467 * SVCCFG_REPOSITORY Create a private instance of svc.configd(1M) to answer 468 * requests for the specified repository file. 469 * SVCCFG_DOOR_PATH Directory for door creation. 470 * 471 * SVCCFG_DOOR Rendezvous via an alternative repository door. 472 * 473 * SVCCFG_CONFIGD_PATH Resolvable path to alternative svc.configd(1M) binary. 474 */ 475 void 476 engine_init() 477 { 478 const char *cp; 479 480 est = uu_zalloc(sizeof (engine_state_t)); 481 482 est->sc_cmd_lineno = 1; 483 est->sc_repo_pid = -1; 484 485 cp = getenv("SVCCFG_REPOSITORY"); 486 est->sc_repo_filename = cp ? safe_strdup(cp) : NULL; 487 488 cp = getenv("SVCCFG_DOOR_PATH"); 489 est->sc_repo_doordir = cp ? cp : "/var/run"; 490 491 cp = getenv("SVCCFG_DOOR"); 492 if (cp != NULL) { 493 if (est->sc_repo_filename != NULL) { 494 uu_warn(gettext("SVCCFG_DOOR unused when " 495 "SVCCFG_REPOSITORY specified\n")); 496 } else { 497 est->sc_repo_doorname = safe_strdup(cp); 498 } 499 } 500 501 cp = getenv("SVCCFG_CONFIGD_PATH"); 502 est->sc_repo_server = cp ? cp : "/lib/svc/bin/svc.configd"; 503 } 504 505 int 506 engine_import(uu_list_t *args) 507 { 508 int ret, argc, i, o; 509 bundle_t *b; 510 char *file, *pname; 511 uchar_t hash[MHASH_SIZE]; 512 char **argv; 513 string_list_t *slp; 514 boolean_t verify = B_FALSE; 515 uint_t flags = SCI_GENERALLAST; 516 517 argc = uu_list_numnodes(args); 518 if (argc < 1) 519 return (-2); 520 521 argv = calloc(argc + 1, sizeof (char *)); 522 if (argv == NULL) 523 uu_die(gettext("Out of memory.\n")); 524 525 for (slp = uu_list_first(args), i = 0; 526 slp != NULL; 527 slp = uu_list_next(args, slp), ++i) 528 argv[i] = slp->str; 529 530 argv[i] = NULL; 531 532 opterr = 0; 533 optind = 0; /* Remember, no argv[0]. */ 534 for (;;) { 535 o = getopt(argc, argv, "nV"); 536 if (o == -1) 537 break; 538 539 switch (o) { 540 case 'n': 541 flags |= SCI_NOREFRESH; 542 break; 543 544 case 'V': 545 verify = B_TRUE; 546 break; 547 548 case '?': 549 free(argv); 550 return (-2); 551 552 default: 553 bad_error("getopt", o); 554 } 555 } 556 557 argc -= optind; 558 if (argc != 1) { 559 free(argv); 560 return (-2); 561 } 562 563 file = argv[optind]; 564 free(argv); 565 566 lscf_prep_hndl(); 567 568 ret = mhash_test_file(g_hndl, file, 0, &pname, hash); 569 if (ret != MHASH_NEWFILE) 570 return (ret); 571 572 /* Load */ 573 b = internal_bundle_new(); 574 575 if (lxml_get_bundle_file(b, file, SVCCFG_OP_IMPORT) != 0) { 576 internal_bundle_free(b); 577 return (-1); 578 } 579 580 /* Import */ 581 if (lscf_bundle_import(b, file, flags) != 0) { 582 internal_bundle_free(b); 583 return (-1); 584 } 585 586 internal_bundle_free(b); 587 588 if (g_verbose) 589 warn(gettext("Successful import.\n")); 590 591 if (pname) { 592 char *errstr; 593 594 if (mhash_store_entry(g_hndl, pname, hash, &errstr)) { 595 if (errstr) 596 semerr(errstr); 597 else 598 semerr(gettext("Unknown error from " 599 "mhash_store_entry()\n")); 600 } 601 602 free(pname); 603 } 604 605 /* Verify */ 606 if (verify) 607 warn(gettext("import -V not implemented.\n")); 608 609 return (0); 610 } 611 612 int 613 engine_apply(const char *file) 614 { 615 int ret; 616 bundle_t *b; 617 char *pname; 618 uchar_t hash[MHASH_SIZE]; 619 620 lscf_prep_hndl(); 621 622 ret = mhash_test_file(g_hndl, file, 1, &pname, hash); 623 if (ret != MHASH_NEWFILE) 624 return (ret); 625 626 b = internal_bundle_new(); 627 628 if (lxml_get_bundle_file(b, file, SVCCFG_OP_APPLY) != 0) { 629 internal_bundle_free(b); 630 return (-1); 631 } 632 633 if (lscf_bundle_apply(b, file) != 0) { 634 internal_bundle_free(b); 635 return (-1); 636 } 637 638 internal_bundle_free(b); 639 640 if (pname) { 641 char *errstr; 642 if (mhash_store_entry(g_hndl, pname, hash, &errstr)) 643 semerr(errstr); 644 645 free(pname); 646 } 647 648 return (0); 649 } 650 651 int 652 engine_restore(const char *file) 653 { 654 bundle_t *b; 655 656 lscf_prep_hndl(); 657 658 b = internal_bundle_new(); 659 660 if (lxml_get_bundle_file(b, file, SVCCFG_OP_RESTORE) != 0) { 661 internal_bundle_free(b); 662 return (-1); 663 } 664 665 if (lscf_bundle_import(b, file, SCI_NOSNAP) != 0) { 666 internal_bundle_free(b); 667 return (-1); 668 } 669 670 internal_bundle_free(b); 671 672 return (0); 673 } 674 675 int 676 engine_set(uu_list_t *args) 677 { 678 uu_list_walk_t *walk; 679 string_list_t *slp; 680 681 if (uu_list_first(args) == NULL) { 682 /* Display current options. */ 683 if (!g_verbose) 684 (void) fputs("no", stdout); 685 (void) puts("verbose"); 686 687 return (0); 688 } 689 690 walk = uu_list_walk_start(args, UU_DEFAULT); 691 if (walk == NULL) 692 uu_die(gettext("Couldn't read arguments")); 693 694 /* Use getopt? */ 695 for (slp = uu_list_walk_next(walk); 696 slp != NULL; 697 slp = uu_list_walk_next(walk)) { 698 if (slp->str[0] == '-') { 699 char *op; 700 701 for (op = &slp->str[1]; *op != '\0'; ++op) { 702 switch (*op) { 703 case 'v': 704 g_verbose = 1; 705 break; 706 707 case 'V': 708 g_verbose = 0; 709 break; 710 711 default: 712 warn(gettext("Unknown option -%c.\n"), 713 *op); 714 } 715 } 716 } else { 717 warn(gettext("No non-flag arguments defined.\n")); 718 } 719 } 720 721 return (0); 722 } 723 724 void 725 help(int com) 726 { 727 int i; 728 729 if (com == 0) { 730 warn(gettext("General commands: help set repository end\n" 731 "Manifest commands: inventory validate import export " 732 "archive\n" 733 "Profile commands: apply extract\n" 734 "Entity commands: list select unselect add delete\n" 735 "Snapshot commands: listsnap selectsnap revert\n" 736 "Instance commands: refresh\n" 737 "Property group commands: listpg addpg delpg\n" 738 "Property commands: listprop setprop delprop editprop\n" 739 "Property value commands: addpropvalue delpropvalue " 740 "setenv unsetenv\n")); 741 return; 742 } 743 744 for (i = 0; help_messages[i].message != NULL; ++i) { 745 if (help_messages[i].token == com) { 746 warn(gettext("Usage: %s\n"), 747 gettext(help_messages[i].message)); 748 return; 749 } 750 } 751 752 warn(gettext("Unknown command.\n")); 753 } 754