xref: /illumos-gate/usr/src/cmd/svc/lsvcrun/lsvcrun.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 /*
26  * lsvcrun - run an rc?.d script, modifying appropriate data in the
27  * repository to reflect legacy behavior.
28  *
29  * We try to keep track of what we can for the legacy scripts via
30  * property groups under the smf/legacy_run service.  Each property
31  * group identifies a service, named in the form 'rc2_d_S10foo'.
32  *
33  * Each group has the following properties: name, the script name
34  * displayed by svcs(1); state_timestamp; contract, contract ID;
35  * inode, the inode of the script; and suffix, the suffix of the
36  * script name, e.g. 'foo'.
37  *
38  * In order to support rc scripts which delete themselves upon invocation we
39  * collect inode before running the script.
40  *
41  * When we run a K script, we try to identify and remove the
42  * property group by means of examining the inode and script
43  * suffix.  The inode check means more than one script with the
44  * same suffix will still work as intended in the common case.
45  *
46  * If we cannot find a property group, or one already exists
47  * when we try to add one, then we print a suitable warning.  These
48  * are warnings because there was no strict requirement that K
49  * and S scripts be matched up.
50  *
51  * In the face of these assumptions being proved wrong, we always
52  * make sure to execute the script anyway in an attempt to keep
53  * things working as they used to.  If we can't execute the script,
54  * we try to leave the repository in the state it was before.
55  */
56 
57 #include <sys/ctfs.h>
58 #include <sys/types.h>
59 #include <sys/wait.h>
60 #include <sys/stat.h>
61 #include <assert.h>
62 #include <ctype.h>
63 #include <errno.h>
64 #include <fcntl.h>
65 #include <fnmatch.h>
66 #include <libcontract.h>
67 #include <libcontract_priv.h>
68 #include <libintl.h>
69 #include <libscf.h>
70 #include <libscf_priv.h>
71 #include <libuutil.h>
72 #include <signal.h>
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <string.h>
76 #include <strings.h>
77 #include <time.h>
78 #include <unistd.h>
79 #include <limits.h>
80 
81 
82 /* Environment variables to pass on.  See clean_environment(). */
83 static char *evars_to_pass[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
84 	"LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "PATH", "TZ"
85 };
86 
87 #define	EVARS_TO_PASS_NUM						\
88 	(sizeof (evars_to_pass) / sizeof (*evars_to_pass))
89 
90 
91 static void
92 usage()
93 {
94 	(void) fprintf(stderr,
95 	    gettext("Usage: %s [-s] script {start | stop}\n"), uu_getpname());
96 	exit(UU_EXIT_USAGE);
97 }
98 
99 /*
100  * Pick out the script name and convert it for use as an SMF property
101  * group name.
102  */
103 static char *
104 start_pg_name(const char *path)
105 {
106 	char *out, *cp;
107 
108 	if (fnmatch("/etc/rc[0-6S].d/S*", path, FNM_PATHNAME) != 0) {
109 		uu_warn(gettext("couldn't parse name %s.\n"), path);
110 		return (NULL);
111 	}
112 
113 	out = strdup(path + sizeof ("/etc/") - 1);
114 
115 	if (out == NULL) {
116 		uu_warn(gettext("strdup() failed (%s).\n"), strerror(errno));
117 		return (NULL);
118 	}
119 
120 	/* Convert illegal characters to _. */
121 	for (cp = out; *cp != '\0'; ++cp) {
122 		/* locale problem? */
123 		if (!isalnum(*cp) && *cp != '-')
124 			*cp = '_';
125 	}
126 
127 	return (out);
128 }
129 
130 static char *
131 script_suffix(const char *path)
132 {
133 	const char *cp;
134 	char *out;
135 
136 	if (fnmatch("/etc/rc[0-6S].d/[SK]*", path, FNM_PATHNAME) != 0) {
137 		uu_warn(gettext("couldn't parse name %s.\n"), path);
138 		return (NULL);
139 	}
140 
141 	cp = path + sizeof ("/etc/rc0.d/S") - 1;
142 
143 	while (isdigit(*cp))
144 		cp++;
145 
146 	if (*cp == '\0') {
147 		uu_warn(gettext("couldn't parse name %s.\n"), path);
148 		return (NULL);
149 	}
150 
151 	out = strdup(cp);
152 	if (out == NULL)
153 		uu_warn(gettext("strdup() failed (%s).\n"), strerror(errno));
154 
155 	return (out);
156 }
157 
158 /*
159  * Convert a path to an acceptable SMF (service) name.
160  */
161 static char *
162 path_to_svc_name(const char *path)
163 {
164 	char *out, *cp;
165 
166 	out = strdup(path);
167 	if (out == NULL) {
168 		uu_warn(gettext("strdup() failed (%s).\n"), strerror(errno));
169 		return (NULL);
170 	}
171 
172 	/* Convert illegal characters to _. */
173 	for (cp = out; *cp != '\0'; ++cp) {
174 		/* locale problem? */
175 		if (!isalnum(*cp) && *cp != '-' && *cp != '/')
176 			*cp = '_';
177 	}
178 
179 	/* If the first character is _, use a instead. */
180 	if (*out == '_')
181 		*out = 'a';
182 
183 	return (out);
184 }
185 
186 static void
187 scferr(const char *func)
188 {
189 	uu_warn(gettext("%s failed (%s).  Repository will not be modified.\n"),
190 	    func, scf_strerror(scf_error()));
191 }
192 
193 static scf_propertygroup_t *
194 get_start_pg(const char *script, scf_handle_t *h, scf_service_t *svc,
195     boolean_t *ok)
196 {
197 	char *pg_name = NULL;
198 	scf_propertygroup_t *pg = NULL;
199 	scf_property_t *prop = NULL;
200 
201 	if ((pg_name = start_pg_name(script)) == NULL)
202 		return (NULL);
203 
204 	if ((pg = scf_pg_create(h)) == NULL) {
205 		scferr("scf_pg_create()");
206 		goto out;
207 	}
208 
209 add:
210 	if (scf_service_add_pg(svc, pg_name, SCF_GROUP_FRAMEWORK,
211 	    SCF_PG_FLAG_NONPERSISTENT, pg) == 0) {
212 		*ok = 1;
213 		free(pg_name);
214 		return (pg);
215 	}
216 
217 	switch (scf_error()) {
218 	case SCF_ERROR_INVALID_ARGUMENT:
219 		assert(0);
220 		abort();
221 		/* NOTREACHED */
222 
223 	case SCF_ERROR_EXISTS:
224 		break;
225 
226 	case SCF_ERROR_PERMISSION_DENIED:
227 		uu_die(gettext(
228 		    "Insufficient privilege to add repository properties; "
229 		    "not launching \"%s\".\n"), script);
230 		/* NOTREACHED */
231 
232 	default:
233 		scferr("scf_service_add_pg()");
234 		scf_pg_destroy(pg);
235 		pg = NULL;
236 		goto out;
237 	}
238 
239 	if (scf_service_get_pg(svc, pg_name, pg) != 0) {
240 		switch (scf_error()) {
241 		case SCF_ERROR_INVALID_ARGUMENT:
242 			assert(0);
243 			abort();
244 			/* NOTREACHED */
245 
246 		case SCF_ERROR_NOT_FOUND:
247 			goto add;
248 
249 		default:
250 			scferr("scf_service_get_pg()");
251 			scf_pg_destroy(pg);
252 			pg = NULL;
253 			goto out;
254 		}
255 	}
256 
257 	if ((prop = scf_property_create(h)) == NULL) {
258 		scferr("scf_property_create()");
259 		scf_pg_destroy(pg);
260 		pg = NULL;
261 		goto out;
262 	}
263 
264 	/*
265 	 * See if the pg has the name property.  If it has, that
266 	 * implies we successfully ran the same script before.  We
267 	 * should re-run it anyway, but not modify the existing pg;
268 	 * this might lose contract-control but there's not much we
269 	 * can do.
270 	 *
271 	 * If there's no name property, then we probably couldn't
272 	 * remove the pg fully after a script failed to run.
273 	 */
274 
275 	if (scf_pg_get_property(pg, SCF_LEGACY_PROPERTY_NAME, prop) == 0) {
276 		uu_warn(gettext("Service matching \"%s\" "
277 		    "seems to be running.\n"), script);
278 		scf_pg_destroy(pg);
279 		pg = NULL;
280 	} else if (scf_error() != SCF_ERROR_NOT_FOUND) {
281 		scferr("scf_pg_get_property()");
282 		scf_pg_destroy(pg);
283 		pg = NULL;
284 	} else {
285 		uu_warn(gettext("Service \"%s\" has an invalid property "
286 		    "group.\n"), script);
287 	}
288 
289 out:
290 	free(pg_name);
291 	scf_property_destroy(prop);
292 	return (pg);
293 }
294 
295 static scf_propertygroup_t *
296 pg_match(scf_handle_t *h, scf_service_t *svc, ino_t ino, const char *suffix)
297 {
298 	char buf[PATH_MAX];
299 	scf_iter_t *iter = NULL;
300 	scf_propertygroup_t *pg = NULL;
301 	scf_property_t *prop = NULL;
302 	scf_value_t *val = NULL;
303 
304 	if ((pg = scf_pg_create(h)) == NULL) {
305 		scferr("scf_pg_create()");
306 		goto err;
307 	}
308 
309 	if ((iter = scf_iter_create(h)) == NULL) {
310 		scferr("scf_iter_create()");
311 		goto err;
312 	}
313 
314 	if ((prop = scf_property_create(h)) == NULL) {
315 		scferr("scf_property_create()");
316 		goto err;
317 	}
318 
319 	if ((val = scf_value_create(h)) == NULL) {
320 		scferr("scf_value_create()");
321 		goto err;
322 	}
323 
324 	if (scf_iter_service_pgs_typed(iter, svc, SCF_GROUP_FRAMEWORK) !=
325 	    0) {
326 		scferr("scf_iter_service_pgs_typed()");
327 		goto err;
328 	}
329 
330 	while (scf_iter_next_pg(iter, pg) > 0) {
331 		int match = 1;
332 
333 		if (suffix != NULL) {
334 			ssize_t len;
335 
336 			if (scf_pg_get_property(pg, SCF_LEGACY_PROPERTY_SUFFIX,
337 			    prop) != 0)
338 				continue;
339 
340 			if (scf_property_get_value(prop, val) != 0)
341 				continue;
342 
343 			len = scf_value_get_astring(val, buf, sizeof (buf));
344 			if (len < 0) {
345 				scferr("scf_value_get_astring()");
346 				goto err;
347 			}
348 			if (len >= sizeof (buf))
349 				continue;
350 
351 			match = (strcmp(buf, suffix) == 0);
352 		}
353 
354 		if (ino != 0) {
355 			uint64_t pval;
356 
357 			if (scf_pg_get_property(pg, SCF_LEGACY_PROPERTY_INODE,
358 			    prop) != 0)
359 				continue;
360 
361 			if (scf_property_get_value(prop, val) != 0)
362 				continue;
363 
364 			if (scf_value_get_count(val, &pval) != 0)
365 				continue;
366 
367 			match = (ino == pval) && match;
368 		}
369 
370 		if (match)
371 			goto out;
372 	}
373 
374 err:
375 	scf_pg_destroy(pg);
376 	pg = NULL;
377 
378 out:
379 	scf_value_destroy(val);
380 	scf_iter_destroy(iter);
381 	scf_property_destroy(prop);
382 	return (pg);
383 }
384 
385 /*
386  * Try and find the property group matching the service this script
387  * stops.  First we look for a matching inode plus a matching suffix.
388  * This commonly succeeds, but if not, we just search for inode.
389  * Finally, we try for just the script suffix.
390  */
391 static scf_propertygroup_t *
392 get_stop_pg(const char *script, scf_handle_t *h, scf_service_t *svc,
393     boolean_t *ok)
394 {
395 	struct stat st;
396 	char *suffix;
397 	scf_propertygroup_t *pg;
398 
399 	if (stat(script, &st) != 0) {
400 		uu_warn(gettext("Couldn't stat %s (%s).\n"), script,
401 		    strerror(errno));
402 		return (NULL);
403 	}
404 
405 	if ((suffix = script_suffix(script)) == NULL) {
406 		pg = pg_match(h, svc, st.st_ino, NULL);
407 		if (pg != NULL)
408 			goto out;
409 		return (NULL);
410 	}
411 
412 	if ((pg = pg_match(h, svc, st.st_ino, suffix)) != NULL)
413 		goto out;
414 
415 	if ((pg = pg_match(h, svc, st.st_ino, NULL)) != NULL)
416 		goto out;
417 
418 	if ((pg = pg_match(h, svc, 0, suffix)) == NULL) {
419 		uu_warn(gettext("Service matching \"%s\" "
420 		    "doesn't seem to be running.\n"), script);
421 		free(suffix);
422 		return (NULL);
423 	}
424 
425 out:
426 	*ok = 1;
427 	free(suffix);
428 	return (pg);
429 }
430 
431 static scf_propertygroup_t *
432 get_script_pg(const char *script, boolean_t start_flag, boolean_t *ok)
433 {
434 	scf_handle_t *h = NULL;
435 	scf_scope_t *scope = NULL;
436 	scf_service_t *svc = NULL;
437 	scf_propertygroup_t *pg = NULL;
438 
439 	*ok = 0;
440 
441 	h = scf_handle_create(SCF_VERSION);
442 	if (h == NULL) {
443 		scferr("scf_handle_create()");
444 		goto out;
445 	}
446 
447 	if (scf_handle_bind(h) != 0) {
448 		if (scf_error() != SCF_ERROR_NO_SERVER) {
449 			scferr("scf_handle_bind()");
450 		} else {
451 			uu_warn(gettext(
452 			    "Could not connect to svc.configd.\n"));
453 		}
454 		goto out;
455 	}
456 
457 	if ((scope = scf_scope_create(h)) == NULL) {
458 		scferr("scf_scope_create()");
459 		goto out;
460 	}
461 
462 	if ((svc = scf_service_create(h)) == NULL) {
463 		scferr("scf_service_create()");
464 		goto out;
465 	}
466 
467 	if (scf_handle_get_scope(h, SCF_SCOPE_LOCAL, scope) != 0) {
468 		scferr("scf_handle_get_local_scope()");
469 		goto out;
470 	}
471 
472 	if (scf_scope_get_service(scope, SCF_LEGACY_SERVICE, svc) != 0) {
473 		if (scf_error() != SCF_ERROR_NOT_FOUND) {
474 			scferr("scf_scope_get_service()");
475 			goto out;
476 		}
477 
478 		if (scf_scope_add_service(scope, SCF_LEGACY_SERVICE, svc) !=
479 		    0) {
480 			scferr("scf_scope_add_service()");
481 			goto out;
482 		}
483 	}
484 
485 	if (start_flag)
486 		pg = get_start_pg(script, h, svc, ok);
487 	else
488 		pg = get_stop_pg(script, h, svc, ok);
489 
490 out:
491 	scf_service_destroy(svc);
492 	scf_scope_destroy(scope);
493 	return (pg);
494 }
495 
496 static int
497 prepare_contract(const char *script, const char *action)
498 {
499 	int fd;
500 	char *svc_name;
501 	char *svc_strbuf;
502 	int err = 0;
503 
504 	do {
505 		fd = open64(CTFS_ROOT "/process/template", O_RDWR);
506 	} while (fd < 0 && errno == EINTR);
507 	if (fd < 0) {
508 		uu_warn(gettext("Can not create contract"));
509 		return (-1);
510 	}
511 
512 	svc_strbuf = malloc(CT_PARAM_MAX_SIZE);
513 	if (svc_strbuf == NULL) {
514 		uu_warn(gettext("Can not allocate memory"));
515 		err = -1;
516 		goto cleanup;
517 	}
518 
519 	(void) strlcpy(svc_strbuf, SCF_FMRI_LEGACY_PREFIX, CT_PARAM_MAX_SIZE);
520 	svc_name = path_to_svc_name(script);
521 	(void) strlcat(svc_strbuf, svc_name ? svc_name : script,
522 	    CT_PARAM_MAX_SIZE);
523 	if (svc_name != NULL) {
524 		free(svc_name);
525 	}
526 
527 	if ((errno = ct_pr_tmpl_set_svc_fmri(fd, svc_strbuf)) != 0) {
528 		uu_warn(gettext("Can not set svc_fmri"));
529 		err = -1;
530 		goto cleanup;
531 	}
532 
533 	(void) strlcpy(svc_strbuf, action, CT_PARAM_MAX_SIZE);
534 	if ((errno = ct_pr_tmpl_set_svc_aux(fd, svc_strbuf)) != 0) {
535 		uu_warn(gettext("Can not set svc_aux"));
536 		err = -1;
537 		goto cleanup;
538 	}
539 
540 	/* Leave HWERR in fatal set. */
541 
542 	errno = ct_tmpl_activate(fd);
543 	if (errno != 0) {
544 		assert(errno == EPERM);
545 		uu_warn(gettext("Can not activate contract template"));
546 		err = -1;
547 		goto cleanup;
548 	}
549 
550 cleanup:
551 	if (svc_strbuf != NULL)
552 		free(svc_strbuf);
553 	(void) close(fd);
554 
555 	return (err);
556 }
557 
558 static void
559 cleanup_pg(scf_propertygroup_t *pg)
560 {
561 	scf_error_t err;
562 	char buf[80];
563 
564 	if (scf_pg_delete(pg) == 0)
565 		return;
566 
567 	err = scf_error();
568 
569 	if (scf_pg_to_fmri(pg, buf, sizeof (buf)) != 0)
570 		(void) strcpy(buf, "?");
571 
572 	uu_warn(gettext("Could not remove property group %s: %s.\n"), buf,
573 	    scf_strerror(err));
574 }
575 
576 /*
577  * Create a duplicate environment which only contains approved
578  * variables---those in evars_to_pass and those beginning with "_INIT_".
579  */
580 static char **
581 approved_env(char **env)
582 {
583 	char **newenv;
584 	int i, i_new, j;
585 
586 	for (i = 0; env[i] != NULL; ++i)
587 		;
588 
589 	newenv = malloc(sizeof (*newenv) * (i + 1));
590 	if (newenv == NULL)
591 		return (NULL);
592 
593 	i_new = 0;
594 
595 	for (i = 0; env[i] != NULL; ++i) {
596 		if (strncmp(env[i], "_INIT_", sizeof ("_INIT_") - 1) == 0) {
597 			newenv[i_new++] = env[i];
598 			continue;
599 		}
600 
601 		for (j = 0; j < EVARS_TO_PASS_NUM; ++j) {
602 			size_t l = strlen(evars_to_pass[j]);
603 
604 			if (env[i][l] == '=' &&
605 			    strncmp(env[i], evars_to_pass[j], l) == 0)
606 				newenv[i_new++] = env[i];
607 		}
608 	}
609 
610 	newenv[i_new] = NULL;
611 
612 	return (newenv);
613 }
614 
615 /*
616  * Create a duplicate environment which does not contain any SMF_ variables.
617  */
618 static char **
619 env_without_smf(char **env)
620 {
621 	char **newenv;
622 	int i, i_new;
623 
624 	for (i = 0; env[i] != NULL; ++i)
625 		;
626 
627 	newenv = malloc(sizeof (*newenv) * (i + 1));
628 	if (newenv == NULL)
629 		return (NULL);
630 
631 	i_new = 0;
632 
633 	for (i = 0; env[i] != NULL; ++i) {
634 		if (strncmp(env[i], "SMF_", sizeof ("SMF_") - 1) == 0)
635 			continue;
636 
637 		newenv[i_new++] = env[i];
638 	}
639 
640 	newenv[i_new] = NULL;
641 
642 	return (newenv);
643 }
644 
645 static int
646 add_new_property(scf_handle_t *h, scf_transaction_t *tx, const char *name,
647     scf_type_t ty, const void *val)
648 {
649 	scf_transaction_entry_t *e;
650 	scf_value_t *v;
651 	const char *func;
652 	const struct timeval *t;
653 	int r;
654 
655 	if ((e = scf_entry_create(h)) == NULL) {
656 		func = "scf_entry_create()";
657 		goto err;
658 	}
659 
660 	if ((v = scf_value_create(h)) == NULL) {
661 		func = "scf_value_create()";
662 		goto err;
663 	}
664 
665 	r = scf_transaction_property_new(tx, e, name, ty);
666 	if (r != 0) {
667 		func = "scf_transaction_property_new()";
668 		goto err;
669 	}
670 
671 	switch (ty) {
672 	case SCF_TYPE_COUNT:
673 		scf_value_set_count(v, (uint64_t)(uintptr_t)val);
674 		break;
675 
676 	case SCF_TYPE_TIME:
677 		t = val;
678 		r = scf_value_set_time(v, t->tv_sec, 1000 * t->tv_usec);
679 		assert(r == 0);
680 		break;
681 
682 	case SCF_TYPE_ASTRING:
683 		r = scf_value_set_astring(v, val);
684 		assert(r == 0);
685 		break;
686 
687 	default:
688 		assert(0);
689 		abort();
690 	}
691 
692 	if (scf_entry_add_value(e, v) == 0)
693 		return (0);
694 
695 	func = "scf_entry_add_value()";
696 
697 err:
698 	uu_warn(gettext("%s failed (%s).\n"), func, scf_strerror(scf_error()));
699 	return (-1);
700 }
701 
702 static void
703 set_legacy_service(scf_propertygroup_t *pg, const char *script, ino_t inode)
704 {
705 	scf_handle_t *h;
706 	const char *func;
707 	char *suffix;
708 	scf_transaction_t *tx;
709 	struct timeval tstamp;
710 	ctid_t ctid;
711 	char *svc_name = NULL;
712 	int ret;
713 
714 	h = scf_pg_handle(pg);
715 	if (h == NULL) {
716 		func = "scf_pg_handle()";
717 		goto scferr;
718 	}
719 
720 	ret = gettimeofday(&tstamp, NULL);
721 	assert(ret == 0);
722 
723 	if (errno = contract_latest(&ctid)) {
724 		uu_warn(gettext("Could not get contract"));
725 		goto err;
726 	}
727 
728 	tx = scf_transaction_create(h);
729 	if (tx == NULL) {
730 		func = "scf_transaction_create()";
731 		goto scferr;
732 	}
733 
734 	if (scf_transaction_start(tx, pg) != 0) {
735 		func = "scf_transaction_start()";
736 		goto scferr;
737 	}
738 
739 	/*
740 	 * We'd like to use the prettier svc_name, but if path_to_svc_name()
741 	 * fails, we can use the script name anyway.
742 	 */
743 	svc_name = path_to_svc_name(script);
744 
745 	if (add_new_property(h, tx, SCF_LEGACY_PROPERTY_NAME, SCF_TYPE_ASTRING,
746 	    (void *)(svc_name ? svc_name : script)) != 0)
747 		goto err;
748 
749 	if (add_new_property(h, tx, SCF_PROPERTY_STATE_TIMESTAMP,
750 	    SCF_TYPE_TIME, &tstamp) != 0)
751 		goto err;
752 
753 	if (add_new_property(h, tx, SCF_LEGACY_PROPERTY_INODE,
754 	    SCF_TYPE_COUNT, (void *)inode) != 0)
755 		goto err;
756 
757 	if ((suffix = script_suffix(script)) != NULL) {
758 		if (add_new_property(h, tx, SCF_LEGACY_PROPERTY_SUFFIX,
759 		    SCF_TYPE_ASTRING, (void *)suffix) != 0)
760 			goto err;
761 
762 		free(suffix);
763 	}
764 
765 	if (add_new_property(h, tx, SCF_PROPERTY_CONTRACT, SCF_TYPE_COUNT,
766 	    (void *)ctid) != 0)
767 		goto err;
768 
769 	for (;;) {
770 		switch (scf_transaction_commit(tx)) {
771 		case 1:
772 			free(svc_name);
773 			return;
774 
775 		case 0:
776 			if (scf_pg_update(pg) == -1) {
777 				func = "scf_pg_update()";
778 				goto scferr;
779 			}
780 			continue;
781 
782 		case -1:
783 			func = "scf_transaction_commit()";
784 			goto scferr;
785 
786 		default:
787 			assert(0);
788 			abort();
789 		}
790 	}
791 
792 scferr:
793 	uu_warn(gettext("%s failed (%s).\n"), func, scf_strerror(scf_error()));
794 err:
795 	uu_die(gettext("Could not commit property values to repository.\n"));
796 }
797 
798 int
799 main(int argc, char *argv[], char *envp[])
800 {
801 	const char *restarter, *script, *action;
802 	boolean_t source = 0;
803 	int o;
804 	boolean_t start_flag;
805 	char **newenv;
806 	pid_t pid;
807 	int pipefds[2];
808 	char c;
809 	int exitstatus;
810 	struct stat st;
811 
812 	scf_propertygroup_t *pg;
813 	boolean_t pg_ok;
814 
815 	(void) uu_setpname(argv[0]);
816 	uu_alt_exit(UU_PROFILE_LAUNCHER);
817 
818 	/* Make sure we were run by svc.startd. */
819 	if ((restarter = getenv("SMF_RESTARTER")) == NULL ||
820 	    strcmp(restarter, SCF_SERVICE_STARTD) != 0)
821 		uu_die(gettext("invocation outside smf(7) inappropriate\n"));
822 
823 	while ((o = getopt(argc, argv, "s")) != -1) {
824 		switch (o) {
825 		case 's':
826 			source = 1;
827 			break;
828 
829 		default:
830 			usage();
831 		}
832 	}
833 
834 	if (argc - optind != 2)
835 		usage();
836 
837 	script = argv[optind];
838 	action = argv[optind + 1];
839 
840 	if (strcmp(action, "start") == 0)
841 		start_flag = 1;
842 	else if (strcmp(action, "stop") == 0)
843 		start_flag = 0;
844 	else
845 		usage();
846 
847 	/*
848 	 * Look for the pg & exit if appropriate.  Also, if we're starting,
849 	 * add the pg now so we can exit before launching the script if we
850 	 * have insufficient repository privilege.
851 	 *
852 	 * If any other problem occurs, we carry on anyway.
853 	 */
854 	pg = get_script_pg(script, start_flag, &pg_ok);
855 
856 	/* Clean the environment.  Now so we can fail early. */
857 	if (!source)
858 		newenv = approved_env(envp);
859 	else
860 		newenv = env_without_smf(envp);
861 	if (newenv == NULL)
862 		uu_die(gettext(
863 		    "Could not create new environment: out of memory.\n"));
864 
865 	if (prepare_contract(script, action) == -1) {
866 		if (start_flag && pg != NULL)
867 			cleanup_pg(pg);
868 
869 		exit(UU_EXIT_FATAL);
870 	}
871 
872 	/* pipe to communicate exec success or failure */
873 	if (pipe(pipefds) != 0) {
874 		uu_warn(gettext("Could not create pipe"));
875 
876 		if (start_flag && pg != NULL)
877 			cleanup_pg(pg);
878 
879 		exit(UU_EXIT_FATAL);
880 	}
881 
882 	if (!pg_ok)
883 		(void) printf(gettext("Executing legacy init script \"%s\" "
884 		    "despite previous errors.\n"), script);
885 	else
886 		(void) printf(gettext("Executing legacy init script \"%s\".\n"),
887 		    script);
888 	(void) fflush(stdout);
889 
890 	if (stat(script, &st) != 0) {
891 		uu_warn(gettext("Couldn't stat %s (%s).\n"), script,
892 		    strerror(errno));
893 		st.st_ino = (ino_t)0;
894 	}
895 
896 	pid = fork();
897 	if (pid < 0) {
898 		uu_warn(gettext("Could not fork"));
899 
900 		if (start_flag && pg != NULL)
901 			cleanup_pg(pg);
902 
903 		exit(UU_EXIT_FATAL);
904 	}
905 
906 	if (pid == 0) {
907 		/* child */
908 
909 		const char *arg1, *arg2, *arg3;
910 
911 		(void) close(pipefds[0]);
912 		(void) fcntl(pipefds[1], F_SETFD, FD_CLOEXEC);
913 
914 		if (!source) {
915 			arg1 = "/bin/sh";
916 			arg2 = script;
917 			arg3 = action;
918 		} else {
919 			arg1 = "/bin/sh";
920 			arg2 = "-c";
921 			arg3 = script;
922 		}
923 
924 		(void) execle(arg1, arg1, arg2, arg3, NULL, newenv);
925 
926 		uu_warn(gettext("Could not exec \"%s %s %s\""), arg1,
927 		    arg2, arg3);
928 
929 
930 		/* Notify parent of the failure. */
931 		while (write(pipefds[1], &c, 1) != 1) {
932 			switch (errno) {
933 			case EAGAIN:
934 				(void) sleep(1);
935 
936 				/* FALLTHROUGH */
937 
938 			case EINTR:
939 				continue;
940 			}
941 
942 			uu_warn(gettext("Could not inform parent of error"));
943 			break;
944 		}
945 
946 		exit(UU_EXIT_FATAL);
947 	}
948 
949 	(void) close(pipefds[1]);
950 
951 	if (read(pipefds[0], &c, sizeof (c)) > 0) {
952 		if (!start_flag)
953 			uu_die(gettext("exec() failed; leaving properties.\n"));
954 		else {
955 			uu_warn(gettext("exec() failed.\n"));
956 			if (pg != NULL)
957 				cleanup_pg(pg);
958 			exit(UU_EXIT_FATAL);
959 		}
960 	}
961 
962 	while (waitpid(pid, &exitstatus, 0) == -1) {
963 		assert(errno == EINTR);
964 	}
965 
966 	if (WIFSIGNALED(exitstatus)) {
967 		char buf[SIG2STR_MAX];
968 		(void) sig2str(WTERMSIG(exitstatus), buf);
969 		(void) printf(gettext("Legacy init script \"%s\" failed due "
970 		    "to signal %s.\n"), script, buf);
971 	} else {
972 		(void) printf(gettext("Legacy init script \"%s\" exited with "
973 		    "return code %d.\n"), script, WEXITSTATUS(exitstatus));
974 	}
975 
976 	if (pg != NULL) {
977 		if (start_flag)
978 			set_legacy_service(pg, script, st.st_ino);
979 		else
980 			cleanup_pg(pg);
981 		scf_pg_destroy(pg);
982 	}
983 
984 	return (UU_EXIT_OK);
985 }
986