fork.c revision 6073:47f6aa7a8077
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 2008 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 * fork.c - safe forking for svc.startd
30 *
31 * fork_configd() and fork_sulogin() are related, special cases that handle the
32 * spawning of specific client processes for svc.startd.
33 */
34
35#include <sys/contract/process.h>
36#include <sys/corectl.h>
37#include <sys/ctfs.h>
38#include <sys/stat.h>
39#include <sys/types.h>
40#include <sys/uio.h>
41#include <sys/wait.h>
42#include <assert.h>
43#include <errno.h>
44#include <fcntl.h>
45#include <libcontract.h>
46#include <libcontract_priv.h>
47#include <libscf_priv.h>
48#include <limits.h>
49#include <port.h>
50#include <signal.h>
51#include <stdarg.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56#include <utmpx.h>
57
58#include "configd_exit.h"
59#include "protocol.h"
60#include "startd.h"
61
62static	struct	utmpx	*utmpp;	/* pointer for getutxent() */
63
64pid_t
65startd_fork1(int *forkerr)
66{
67	pid_t p;
68
69	/*
70	 * prefork stack
71	 */
72	wait_prefork();
73
74	p = fork1();
75
76	if (p == -1 && forkerr != NULL)
77		*forkerr = errno;
78
79	/*
80	 * postfork stack
81	 */
82	wait_postfork(p);
83
84	return (p);
85}
86
87/*
88 * void fork_mount(char *, char *)
89 *   Run mount(1M) with the given options and mount point.  (mount(1M) has much
90 *   hidden knowledge; it's much less correct to reimplement that logic here to
91 *   save a fork(2)/exec(2) invocation.)
92 */
93int
94fork_mount(char *path, char *opts)
95{
96	pid_t pid;
97	uint_t tries = 0;
98	int status;
99
100	for (pid = fork1(); pid == -1; pid = fork1()) {
101		if (++tries > MAX_MOUNT_RETRIES)
102			return (-1);
103
104		(void) sleep(tries);
105	}
106
107	if (pid != 0) {
108		(void) waitpid(pid, &status, 0);
109
110		/*
111		 * If our mount(1M) invocation exited by peculiar means, or with
112		 * a non-zero status, our mount likelihood is low.
113		 */
114		if (!WIFEXITED(status) ||
115		    WEXITSTATUS(status) != 0)
116			return (-1);
117
118		return (0);
119	}
120
121	(void) execl("/sbin/mount", "mount", "-o", opts, path, NULL);
122
123	return (-1);
124}
125
126/*
127 * pid_t fork_common(...)
128 *   Common routine used by fork_sulogin and fork_configd to fork a
129 *   process in a contract with the provided terms.  Invokes
130 *   fork_sulogin (with its no-fork argument set) on errors.
131 */
132static pid_t
133fork_common(const char *name, const char *svc_fmri, int retries, ctid_t *ctidp,
134    uint_t inf, uint_t crit, uint_t fatal, uint_t param, uint64_t cookie)
135{
136	uint_t tries = 0;
137	int ctfd, err;
138	pid_t pid;
139
140	/*
141	 * Establish process contract terms.
142	 */
143	if ((ctfd = open64(CTFS_ROOT "/process/template", O_RDWR)) == -1) {
144		fork_sulogin(B_TRUE, "Could not open process contract template "
145		    "for %s: %s\n", name, strerror(errno));
146		/* NOTREACHED */
147	}
148
149	err = ct_tmpl_set_critical(ctfd, crit);
150	err |= ct_pr_tmpl_set_fatal(ctfd, fatal);
151	err |= ct_tmpl_set_informative(ctfd, inf);
152	err |= ct_pr_tmpl_set_param(ctfd, param);
153	err |= ct_tmpl_set_cookie(ctfd, cookie);
154	err |= ct_pr_tmpl_set_svc_fmri(ctfd, svc_fmri);
155	err |= ct_pr_tmpl_set_svc_aux(ctfd, name);
156	if (err) {
157		(void) close(ctfd);
158		fork_sulogin(B_TRUE, "Could not set %s process contract "
159		    "terms\n", name);
160		/* NOTREACHED */
161	}
162
163	if (err = ct_tmpl_activate(ctfd)) {
164		(void) close(ctfd);
165		fork_sulogin(B_TRUE, "Could not activate %s process contract "
166		    "template: %s\n", name, strerror(err));
167		/* NOTREACHED */
168	}
169
170	/*
171	 * Attempt to fork "retries" times.
172	 */
173	for (pid = fork1(); pid == -1; pid = fork1()) {
174		if (++tries > retries) {
175			/*
176			 * When we exit the sulogin session, init(1M)
177			 * will restart svc.startd(1M).
178			 */
179			err = errno;
180			(void) ct_tmpl_clear(ctfd);
181			(void) close(ctfd);
182			fork_sulogin(B_TRUE, "Could not fork to start %s: %s\n",
183			    name, strerror(err));
184			/* NOTREACHED */
185		}
186		(void) sleep(tries);
187	}
188
189	/*
190	 * Clean up, return pid and ctid.
191	 */
192	if (pid != 0 && (errno = contract_latest(ctidp)) != 0)
193		uu_die("Could not get new contract id for %s\n", name);
194	(void) ct_tmpl_clear(ctfd);
195	(void) close(ctfd);
196
197	return (pid);
198}
199
200/*
201 * void fork_sulogin(boolean_t, const char *, ...)
202 *   When we are invoked with the -s flag from boot (or run into an unfixable
203 *   situation), we run a private copy of sulogin.  When the sulogin session
204 *   is ended, we continue.  This is the last fallback action for system
205 *   maintenance.
206 *
207 *   If immediate is true, fork_sulogin() executes sulogin(1M) directly, without
208 *   forking.
209 *
210 *   Because fork_sulogin() is needed potentially before we daemonize, we leave
211 *   it outside the wait_register() framework.
212 */
213/*PRINTFLIKE2*/
214void
215fork_sulogin(boolean_t immediate, const char *format, ...)
216{
217	va_list args;
218	int fd_console;
219
220	(void) printf("Requesting System Maintenance Mode\n");
221
222	if (!booting_to_single_user)
223		(void) printf("(See /lib/svc/share/README for more "
224		    "information.)\n");
225
226	va_start(args, format);
227	(void) vprintf(format, args);
228	va_end(args);
229
230	if (!immediate) {
231		ctid_t	ctid;
232		pid_t	pid;
233
234		pid = fork_common("sulogin", SVC_SULOGIN_FMRI,
235		    MAX_SULOGIN_RETRIES, &ctid, CT_PR_EV_HWERR, 0,
236		    CT_PR_EV_HWERR, CT_PR_PGRPONLY, SULOGIN_COOKIE);
237
238		if (pid != 0) {
239			(void) waitpid(pid, NULL, 0);
240			contract_abandon(ctid);
241			return;
242		}
243		/* close all inherited fds */
244		closefrom(0);
245	} else {
246		(void) printf("Directly executing sulogin.\n");
247		/*
248		 * Can't call closefrom() in this MT section
249		 * so safely close a minimum set of fds.
250		 */
251		(void) close(STDIN_FILENO);
252		(void) close(STDOUT_FILENO);
253		(void) close(STDERR_FILENO);
254	}
255
256	(void) setpgrp();
257
258	/* open the console for sulogin */
259	if ((fd_console = open("/dev/console", O_RDWR)) >= 0) {
260		if (fd_console != STDIN_FILENO)
261			while (dup2(fd_console, STDIN_FILENO) < 0 &&
262			    errno == EINTR)
263				;
264		if (fd_console != STDOUT_FILENO)
265			while (dup2(fd_console, STDOUT_FILENO) < 0 &&
266			    errno == EINTR)
267				;
268		if (fd_console != STDERR_FILENO)
269			while (dup2(fd_console, STDERR_FILENO) < 0 &&
270			    errno == EINTR)
271				;
272		if (fd_console > STDERR_FILENO)
273			(void) close(fd_console);
274	}
275
276	setutxent();
277	while ((utmpp = getutxent()) != NULL) {
278		if (strcmp(utmpp->ut_user, "LOGIN") != 0) {
279			if (strcmp(utmpp->ut_line, "console") == 0) {
280				(void) kill(utmpp->ut_pid, 9);
281				break;
282			}
283		}
284	}
285
286	(void) execl("/sbin/sulogin", "sulogin", NULL);
287
288	uu_warn("Could not exec() sulogin");
289
290	exit(1);
291}
292
293#define	CONFIGD_PATH	"/lib/svc/bin/svc.configd"
294
295/*
296 * void fork_configd(int status)
297 *   We are interested in exit events (since the parent's exiting means configd
298 *   is ready to run and since the child's exiting indicates an error case) and
299 *   in empty events.  This means we have a unique template for initiating
300 *   configd.
301 */
302/*ARGSUSED*/
303void
304fork_configd(int exitstatus)
305{
306	pid_t pid;
307	ctid_t ctid = -1;
308	int err;
309	char path[PATH_MAX];
310
311retry:
312	log_framework(LOG_DEBUG, "fork_configd trying to start svc.configd\n");
313
314	/*
315	 * If we're retrying, we will have an old contract lying around
316	 * from the failure.  Since we're going to be creating a new
317	 * contract shortly, we abandon the old one now.
318	 */
319	if (ctid != -1)
320		contract_abandon(ctid);
321	ctid = -1;
322
323	pid = fork_common("svc.configd", SCF_SERVICE_CONFIGD,
324	    MAX_CONFIGD_RETRIES, &ctid, 0, CT_PR_EV_EXIT, 0,
325	    CT_PR_INHERIT | CT_PR_REGENT, CONFIGD_COOKIE);
326
327	if (pid != 0) {
328		int exitstatus;
329
330		st->st_configd_pid = pid;
331
332		if (waitpid(pid, &exitstatus, 0) == -1) {
333			fork_sulogin(B_FALSE, "waitpid on svc.configd "
334			    "failed: %s\n", strerror(errno));
335		} else if (WIFEXITED(exitstatus)) {
336			char *errstr;
337
338			/*
339			 * Examine exitstatus.  This will eventually get more
340			 * complicated, as we will want to teach startd how to
341			 * invoke configd with alternate repositories, etc.
342			 *
343			 * Note that exec(2) failure results in an exit status
344			 * of 1, resulting in the default clause below.
345			 */
346
347			/*
348			 * Assign readable strings to cases we don't handle, or
349			 * have error outcomes that cannot be eliminated.
350			 */
351			switch (WEXITSTATUS(exitstatus)) {
352			case CONFIGD_EXIT_BAD_ARGS:
353				errstr = "bad arguments";
354				break;
355
356			case CONFIGD_EXIT_DATABASE_BAD:
357				errstr = "database corrupt";
358				break;
359
360			case CONFIGD_EXIT_DATABASE_LOCKED:
361				errstr = "database locked";
362				break;
363			case CONFIGD_EXIT_INIT_FAILED:
364				errstr = "initialization failure";
365				break;
366			case CONFIGD_EXIT_DOOR_INIT_FAILED:
367				errstr = "door initialization failure";
368				break;
369			case CONFIGD_EXIT_DATABASE_INIT_FAILED:
370				errstr = "database initialization failure";
371				break;
372			case CONFIGD_EXIT_NO_THREADS:
373				errstr = "no threads available";
374				break;
375			case CONFIGD_EXIT_LOST_MAIN_DOOR:
376				errstr = "lost door server attachment";
377				break;
378			case 1:
379				errstr = "execution failure";
380				break;
381			default:
382				errstr = "unknown error";
383				break;
384			}
385
386			/*
387			 * Remedial actions for various configd failures.
388			 */
389			switch (WEXITSTATUS(exitstatus)) {
390			case CONFIGD_EXIT_OKAY:
391				break;
392
393			case CONFIGD_EXIT_DATABASE_LOCKED:
394				/* attempt remount of / read-write */
395				if (fs_is_read_only("/", NULL) == 1) {
396					if (fs_remount("/") == -1)
397						fork_sulogin(B_FALSE,
398						    "remount of root "
399						    "filesystem failed\n");
400
401					goto retry;
402				}
403				break;
404
405			default:
406				fork_sulogin(B_FALSE, "svc.configd exited "
407				    "with status %d (%s)\n",
408				    WEXITSTATUS(exitstatus), errstr);
409				goto retry;
410			}
411		} else if (WIFSIGNALED(exitstatus)) {
412			char signame[SIG2STR_MAX];
413
414			if (sig2str(WTERMSIG(exitstatus), signame))
415				(void) snprintf(signame, SIG2STR_MAX,
416				    "signum %d", WTERMSIG(exitstatus));
417
418			fork_sulogin(B_FALSE, "svc.configd signalled:"
419			    " %s\n", signame);
420
421			goto retry;
422		} else {
423			fork_sulogin(B_FALSE, "svc.configd non-exit "
424			    "condition: 0x%x\n", exitstatus);
425
426			goto retry;
427		}
428
429		/*
430		 * Announce that we have a valid svc.configd status.
431		 */
432		MUTEX_LOCK(&st->st_configd_live_lock);
433		st->st_configd_lives = 1;
434		err = pthread_cond_broadcast(&st->st_configd_live_cv);
435		assert(err == 0);
436		MUTEX_UNLOCK(&st->st_configd_live_lock);
437
438		log_framework(LOG_DEBUG, "fork_configd broadcasts configd is "
439		    "live\n");
440		return;
441	}
442
443	/*
444	 * Set our per-process core file path to leave core files in
445	 * /etc/svc/volatile directory, named after the PID to aid in debugging.
446	 */
447	(void) snprintf(path, sizeof (path),
448	    "/etc/svc/volatile/core.configd.%%p");
449
450	(void) core_set_process_path(path, strlen(path) + 1, getpid());
451
452	log_framework(LOG_DEBUG, "executing svc.configd\n");
453
454	(void) execl(CONFIGD_PATH, CONFIGD_PATH, NULL);
455
456	/*
457	 * Status code is used above to identify configd exec failure.
458	 */
459	exit(1);
460}
461
462void *
463fork_configd_thread(void *vctid)
464{
465	int fd, err;
466	ctid_t configd_ctid = (ctid_t)vctid;
467
468	if (configd_ctid == -1) {
469		log_framework(LOG_DEBUG,
470		    "fork_configd_thread starting svc.configd\n");
471		fork_configd(0);
472	} else {
473		/*
474		 * configd_ctid is known:  we broadcast and continue.
475		 * test contract for appropriate state by verifying that
476		 * there is one or more processes within it?
477		 */
478		log_framework(LOG_DEBUG,
479		    "fork_configd_thread accepting svc.configd with CTID %ld\n",
480		    configd_ctid);
481		MUTEX_LOCK(&st->st_configd_live_lock);
482		st->st_configd_lives = 1;
483		(void) pthread_cond_broadcast(&st->st_configd_live_cv);
484		MUTEX_UNLOCK(&st->st_configd_live_lock);
485	}
486
487	fd = open64(CTFS_ROOT "/process/pbundle", O_RDONLY);
488	if (fd == -1)
489		uu_die("process bundle open failed");
490
491	/*
492	 * Make sure we get all events (including those generated by configd
493	 * before this thread was started).
494	 */
495	err = ct_event_reset(fd);
496	assert(err == 0);
497
498	for (;;) {
499		int efd, sfd;
500		ct_evthdl_t ev;
501		uint32_t type;
502		ctevid_t evid;
503		ct_stathdl_t status;
504		ctid_t ctid;
505		uint64_t cookie;
506		pid_t pid;
507
508		if (err = ct_event_read_critical(fd, &ev)) {
509			assert(err != EINVAL && err != EAGAIN);
510			log_error(LOG_WARNING,
511			    "Error reading next contract event: %s",
512			    strerror(err));
513			continue;
514		}
515
516		evid = ct_event_get_evid(ev);
517		ctid = ct_event_get_ctid(ev);
518		type = ct_event_get_type(ev);
519
520		/* Fetch cookie. */
521		sfd = contract_open(ctid, "process", "status", O_RDONLY);
522		if (sfd < 0) {
523			ct_event_free(ev);
524			continue;
525		}
526
527		if (err = ct_status_read(sfd, CTD_COMMON, &status)) {
528			log_framework(LOG_WARNING, "Could not get status for "
529			    "contract %ld: %s\n", ctid, strerror(err));
530
531			ct_event_free(ev);
532			startd_close(sfd);
533			continue;
534		}
535
536		cookie = ct_status_get_cookie(status);
537
538		ct_status_free(status);
539
540		startd_close(sfd);
541
542		/*
543		 * Don't process events from contracts we aren't interested in.
544		 */
545		if (cookie != CONFIGD_COOKIE) {
546			ct_event_free(ev);
547			continue;
548		}
549
550		if (type == CT_PR_EV_EXIT) {
551			int exitstatus;
552
553			(void) ct_pr_event_get_pid(ev, &pid);
554			(void) ct_pr_event_get_exitstatus(ev,
555			    &exitstatus);
556
557			if (st->st_configd_pid != pid) {
558				/*
559				 * This is the child exiting, so we
560				 * abandon the contract and restart
561				 * configd.
562				 */
563				contract_abandon(ctid);
564				fork_configd(exitstatus);
565			}
566		}
567
568		efd = contract_open(ctid, "process", "ctl", O_WRONLY);
569		if (efd != -1) {
570			(void) ct_ctl_ack(efd, evid);
571			startd_close(efd);
572		}
573
574		ct_event_free(ev);
575
576	}
577
578	/*NOTREACHED*/
579	return (NULL);
580}
581
582void
583fork_rc_script(char rl, const char *arg, boolean_t wait)
584{
585	pid_t pid;
586	int tmpl, err, stat;
587	char path[20] = "/sbin/rc.", log[20] = "rc..log", timebuf[20];
588	time_t now;
589	struct tm ltime;
590	size_t sz;
591	char *pathenv;
592	char **nenv;
593
594	path[8] = rl;
595
596	tmpl = open64(CTFS_ROOT "/process/template", O_RDWR);
597	if (tmpl >= 0) {
598		err = ct_tmpl_set_critical(tmpl, 0);
599		assert(err == 0);
600
601		err = ct_tmpl_set_informative(tmpl, 0);
602		assert(err == 0);
603
604		err = ct_pr_tmpl_set_fatal(tmpl, 0);
605		assert(err == 0);
606
607		err = ct_tmpl_activate(tmpl);
608		assert(err == 0);
609
610		err = close(tmpl);
611		assert(err == 0);
612	} else {
613		uu_warn("Could not create contract template for %s.\n", path);
614	}
615
616	pid = startd_fork1(NULL);
617	if (pid < 0) {
618		return;
619	} else if (pid != 0) {
620		/* parent */
621		if (wait) {
622			do
623				err = waitpid(pid, &stat, 0);
624			while (err != 0 && errno == EINTR)
625				;
626
627			if (!WIFEXITED(stat)) {
628				log_framework(LOG_INFO,
629				    "%s terminated with waitpid() status %d.\n",
630				    path, stat);
631			} else if (WEXITSTATUS(stat) != 0) {
632				log_framework(LOG_INFO,
633				    "%s failed with status %d.\n", path,
634				    WEXITSTATUS(stat));
635			}
636		}
637
638		return;
639	}
640
641	/* child */
642
643	log[2] = rl;
644
645	setlog(log);
646
647	now = time(NULL);
648	sz = strftime(timebuf, sizeof (timebuf), "%b %e %T",
649	    localtime_r(&now, &ltime));
650	assert(sz != 0);
651
652	(void) fprintf(stderr, "%s Executing %s %s\n", timebuf, path, arg);
653
654	if (rl == 'S')
655		pathenv = "PATH=/sbin:/usr/sbin:/usr/bin";
656	else
657		pathenv = "PATH=/usr/sbin:/usr/bin";
658
659	nenv = set_smf_env(NULL, 0, pathenv, NULL, NULL);
660
661	(void) execle(path, path, arg, 0, nenv);
662
663	perror("exec");
664	exit(0);
665}
666