svccfg_engine.c revision 7475:9a5f7406e094
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
49engine_state_t *est;
50
51/*
52 * Replacement lex(1) character retrieval routines.
53 */
54int
55engine_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
118int
119engine_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*/
131void
132engine_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
138int
139engine_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 */
152static
153CPL_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
165static const char * const whitespace = " \t";
166
167static
168CPL_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
192static 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
230int
231add_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 */
265static
266CPL_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
299int
300engine_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
371int
372engine_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
445fail:
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 */
475void
476engine_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
505int
506engine_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
612int
613engine_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
651int
652engine_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
675int
676engine_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
724void
725help(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