--- old/usr/src/cmd/boot/scripts/boot-archive-update.xml Thu Feb 26 16:21:25 2009 +++ new/usr/src/cmd/boot/scripts/boot-archive-update.xml Thu Feb 26 16:21:24 2009 @@ -1,7 +1,7 @@ /dev/null 2>&1 ;exit 1" 1 2 15 --- old/usr/src/cmd/initpkg/umountall.sh Thu Feb 26 16:21:30 2009 +++ new/usr/src/cmd/initpkg/umountall.sh Thu Feb 26 16:21:29 2009 @@ -20,7 +20,7 @@ # CDDL HEADER END # # -# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T @@ -169,15 +169,7 @@ ZONENAME=`zonename` -# Check and if needed sync the boot archive before unmounting everything. # -if [ -z "${RFLAG}${NFLAG}${HFLAG}${FSType}" -a "$ZONENAME" = "global" \ - -a -x /sbin/bootadm ] ; then - /sbin/bootadm -a update_all -fi - - -# # If we are in deferred activation patching, and the caller is # svc.startd, then exit without unmounting any of the remaining # file systems since the call path is from shutdown. Note that --- old/usr/src/cmd/svc/shell/smf_include.sh Thu Feb 26 16:21:31 2009 +++ new/usr/src/cmd/svc/shell/smf_include.sh Thu Feb 26 16:21:31 2009 @@ -20,7 +20,7 @@ # CDDL HEADER END # # -# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -160,6 +160,11 @@ # every second until the contract is empty. This will catch # races between fork(2) and pkill(1). # +# Note that time in this routine is tracked (after being input +# via TIMEOUT) in 10ths of a second. This is because we want +# to sleep for short periods of time, and expr(1) is too dumb +# to do non-integer math. +# # Returns 1 if the contract is invalid. # Returns 2 if WAIT is "1", TIMEOUT is > 0, and TIMEOUT expires. # Returns 0 on success. @@ -171,6 +176,9 @@ [ -z "$time_to_wait" ] && time_to_wait=0 + # convert to 10ths. + time_to_wait=`expr $time_to_wait * 10` + # Verify contract id is valid using pgrep /usr/bin/pgrep -c $1 > /dev/null 2>&1 ret=$? @@ -195,17 +203,26 @@ # If contract does not empty, keep killing the contract to catch # any child processes missed because they were forking - /usr/bin/sleep 5 /usr/bin/pgrep -c $1 > /dev/null 2>&1 while [ $? -eq 0 ] ; do - - time_waited=`/usr/bin/expr $time_waited + 5` - - # Return if TIMEOUT was passed, and it has expired + # Return 2 if TIMEOUT was passed, and it has expired [ "$time_to_wait" -gt 0 -a $time_waited -ge $time_to_wait ] && \ return 2 - /usr/bin/pkill -$2 -c $1 - /usr/bin/sleep 5 + + # + # At five second intervals, issue the kill again. Note that + # the sleep time constant (in tenths) must be a factor of 50 + # for the remainder trick to work. i.e. sleeping 2 tenths is + # fine, but 27 tenths is not. + # + remainder=`/usr/bin/expr $time_waited % 50` + if [ $time_waited -gt 0 -a $remainder -eq 0 ]; then + /usr/bin/pkill -$2 -c $1 + fi + + # Wait two tenths, and go again. + /usr/bin/sleep 0.2 + time_waited=`/usr/bin/expr $time_waited + 2` /usr/bin/pgrep -c $1 > /dev/null 2>&1 done --- old/usr/src/cmd/svc/startd/fork.c Thu Feb 26 16:21:33 2009 +++ new/usr/src/cmd/svc/startd/fork.c Thu Feb 26 16:21:32 2009 @@ -19,7 +19,7 @@ * CDDL HEADER END */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #include #include #include +#include #include "configd_exit.h" #include "protocol.h" @@ -678,4 +680,111 @@ perror("exec"); exit(0); +} + +extern char **environ; + +/* + * A local variation on system(3c) which accepts a timeout argument. This + * allows us to better ensure that the system will actually shut down. + * + * gracetime specifies an amount of time in seconds which the routine must wait + * after the command exits, to allow for asynchronous effects (like sent + * signals) to take effect. This can be zero. + */ +void +fork_with_timeout(const char *cmd, uint_t gracetime, uint_t timeout) +{ + int err = 0; + pid_t pid; + char *argv[4]; + posix_spawnattr_t attr; + posix_spawn_file_actions_t factions; + + sigset_t mask, savemask; + uint_t msec_timeout; + uint_t msec_spent = 0; + uint_t msec_gracetime; + int status; + + msec_timeout = timeout * 1000; + msec_gracetime = gracetime * 1000; + + /* + * See also system(3c) in libc. This is very similar, except + * that we avoid some unneeded complexity. + */ + err = posix_spawnattr_init(&attr); + if (err == 0) + err = posix_spawnattr_setflags(&attr, + POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF | + POSIX_SPAWN_NOSIGCHLD_NP | POSIX_SPAWN_WAITPID_NP | + POSIX_SPAWN_NOEXECERR_NP); + + /* + * We choose to close fd's above 2, a deviation from system. + */ + if (err == 0) + err = posix_spawn_file_actions_init(&factions); + if (err == 0) + err = posix_spawn_file_actions_addclosefrom_np(&factions, + STDERR_FILENO + 1); + + (void) sigemptyset(&mask); + (void) sigaddset(&mask, SIGCHLD); + (void) thr_sigsetmask(SIG_BLOCK, &mask, &savemask); + + argv[0] = "/bin/sh"; + argv[1] = "-c"; + argv[2] = (char *)cmd; + argv[3] = NULL; + + if (err == 0) + err = posix_spawn(&pid, "/bin/sh", &factions, &attr, + (char *const *)argv, (char *const *)environ); + + (void) posix_spawnattr_destroy(&attr); + (void) posix_spawn_file_actions_destroy(&factions); + + if (err) { + uu_warn("Failed to spawn %s: %s\n", cmd, strerror(err)); + } else { + for (;;) { + int w; + w = waitpid(pid, &status, WNOHANG); + if (w == -1 && errno != EINTR) + break; + if (w > 0) { + /* + * Command succeeded, so give it gracetime + * seconds for it to have an effect. + */ + if (status == 0 && msec_gracetime != 0) + (void) poll(NULL, 0, msec_gracetime); + break; + } + + (void) poll(NULL, 0, 100); + msec_spent += 100; + /* + * If we timed out, kill off the process, then try to + * wait for it-- it's possible that we could accumulate + * a zombie here since we don't allow waitpid to hang, + * but it's better to let that happen and continue to + * make progress. + */ + if (msec_spent >= msec_timeout) { + uu_warn("'%s' timed out after %d " + "seconds. Killing.\n", cmd, + timeout); + (void) kill(pid, SIGTERM); + (void) poll(NULL, 0, 100); + (void) kill(pid, SIGKILL); + (void) poll(NULL, 0, 100); + (void) waitpid(pid, &status, WNOHANG); + break; + } + } + } + (void) thr_sigsetmask(SIG_BLOCK, &savemask, NULL); } --- old/usr/src/cmd/svc/startd/graph.c Thu Feb 26 16:21:34 2009 +++ new/usr/src/cmd/svc/startd/graph.c Thu Feb 26 16:21:33 2009 @@ -178,6 +178,11 @@ static boolean_t go_to_level1 = B_FALSE; /* + * Tracks when we started halting. + */ +static time_t halting_time = 0; + +/* * This tracks the legacy runlevel to ensure we signal init and manage * utmpx entries correctly. */ @@ -3441,11 +3446,44 @@ return (0); } + static void +kill_user_procs(void) +{ + (void) fputs("svc.startd: Killing user processes.\n", stdout); + + /* + * Despite its name, killall's role is to get select user processes-- + * basically those representing terminal-based logins-- to die. Victims + * are located by killall in the utmp database. Since these are most + * often shell based logins, and many shells mask SIGTERM (but are + * responsive to SIGHUP) we first HUP and then shortly thereafter + * kill -9. + */ + (void) fork_with_timeout("/usr/sbin/killall HUP", 1, 5); + (void) fork_with_timeout("/usr/sbin/killall KILL", 1, 5); + + /* + * Note the selection of user id's 0, 1 and 15, subsequently + * inverted by -v. 15 is reserved for dladmd. Yes, this is a + * kludge-- a better policy is needed. + * + * Note that fork_with_timeout will only wait out the 1 second + * "grace time" if pkill actually returns 0. So if there are + * no matches, this will run to completion much more quickly. + */ + (void) fork_with_timeout("/usr/bin/pkill -TERM -v -u 0,1,15", 1, 5); + (void) fork_with_timeout("/usr/bin/pkill -KILL -v -u 0,1,15", 1, 5); +} + +static void do_uadmin(void) { - int fd, left; + int fd; struct statvfs vfs; + time_t now; + struct tm nowtm; + char down_buf[256], time_buf[256]; const char * const resetting = "/etc/svc/volatile/resetting"; @@ -3458,31 +3496,88 @@ /* Kill dhcpagent if we're not using nfs for root */ if ((statvfs("/", &vfs) == 0) && (strncmp(vfs.f_basetype, "nfs", sizeof ("nfs") - 1) != 0)) - (void) system("/usr/bin/pkill -x -u 0 dhcpagent"); + fork_with_timeout("/usr/bin/pkill -x -u 0 dhcpagent", 0, 5); - (void) system("/usr/sbin/killall"); - left = 5; - while (left > 0) - left = sleep(left); + /* + * Call sync(2) now, before we kill off user processes. This takes + * advantage of the several seconds of pause we have before the + * killalls are done. Time we can make good use of to get pages + * moving out to disk. + * + * Inside non-global zones, we don't bother, and it's better not to + * anyway, since sync(2) can have system-wide impact. + */ + if (getzoneid() == 0) + sync(); - (void) system("/usr/sbin/killall 9"); - left = 10; - while (left > 0) - left = sleep(left); + kill_user_procs(); - sync(); - sync(); - sync(); + /* + * Note that this must come after the killing of user procs, since + * killall relies on utmpx, and this command affects the contents of + * said file. + */ + if (access("/usr/lib/acct/closewtmp", X_OK) == 0) + fork_with_timeout("/usr/lib/acct/closewtmp", 0, 5); - (void) system("/sbin/umountall -l"); - (void) system("/sbin/umount /tmp >/dev/null 2>&1"); - (void) system("/sbin/umount /var/adm >/dev/null 2>&1"); - (void) system("/sbin/umount /var/run >/dev/null 2>&1"); - (void) system("/sbin/umount /var >/dev/null 2>&1"); - (void) system("/sbin/umount /usr >/dev/null 2>&1"); + /* + * For patches which may be installed as the system is shutting + * down, we need to ensure, one more time, that the boot archive + * really is up to date. + */ + if (getzoneid() == 0 && access("/usr/sbin/bootadm", X_OK) == 0) + fork_with_timeout("/usr/sbin/bootadm -ea update_all", 0, 3600); - uu_warn("The system is down.\n"); + fork_with_timeout("/sbin/umountall -l", 0, 5); + fork_with_timeout("/sbin/umount /tmp /var/adm /var/run /var " + ">/dev/null 2>&1", 0, 5); + /* + * Try to get to consistency for whatever UFS filesystems are left. + * This is pretty expensive, so we save it for the end in the hopes of + * minimizing what it must do. The other option would be to start in + * parallel with the killall's, but lockfs tends to throw out much more + * than is needed, and so subsequent commands (like umountall) take a + * long time to get going again. + * + * Inside of zones, we don't bother, since we're not about to terminate + * the whole OS instance. + * + * On systems using only ZFS, this call to lockfs -fa is a no-op. + */ + if (getzoneid() == 0) { + if (access("/usr/sbin/lockfs", X_OK) == 0) + fork_with_timeout("/usr/sbin/lockfs -fa", 0, 30); + + sync(); /* once more, with feeling */ + } + + fork_with_timeout("/sbin/umount /usr >/dev/null 2>&1", 0, 5); + + /* + * Construct and emit the last words from userland: + * " The system is down. Shutdown took seconds." + * + * Normally we'd use syslog, but with /var and other things + * potentially gone, try to minimize the external dependencies. + */ + now = time(NULL); + (void) localtime_r(&now, &nowtm); + + if (strftime(down_buf, sizeof (down_buf), + "%b %e %T The system is down.", &nowtm) == 0) { + (void) strlcpy(down_buf, "The system is down.", + sizeof (down_buf)); + } + + if (halting_time != 0 && halting_time <= now) { + (void) snprintf(time_buf, sizeof (time_buf), + " Shutdown took %lu seconds.", now - halting_time); + } else { + time_buf[0] = '\0'; + } + (void) printf("%s%s\n", down_buf, time_buf); + (void) uadmin(A_SHUTDOWN, halting, NULL); uu_warn("uadmin() failed"); @@ -3681,24 +3776,9 @@ MUTEX_LOCK(&single_user_thread_lock); single_user_thread_count++; - if (!booting_to_single_user) { - /* - * From rcS.sh: Look for ttymon, in.telnetd, in.rlogind and - * processes in their process groups so they can be terminated. - */ - (void) fputs("svc.startd: Killing user processes: ", stdout); - (void) system("/usr/sbin/killall"); - (void) system("/usr/sbin/killall 9"); - (void) system("/usr/bin/pkill -TERM -v -u 0,1"); + if (!booting_to_single_user) + kill_user_procs(); - left = 5; - while (left > 0) - left = sleep(left); - - (void) system("/usr/bin/pkill -KILL -v -u 0,1"); - (void) puts("done."); - } - if (go_single_user_mode || booting_to_single_user) { msg = "SINGLE USER MODE\n"; } else { @@ -4972,16 +5052,19 @@ break; case '0': + halting_time = time(NULL); fork_rc_script(rl, stop, B_TRUE); halting = AD_HALT; goto uadmin; case '5': + halting_time = time(NULL); fork_rc_script(rl, stop, B_TRUE); halting = AD_POWEROFF; goto uadmin; case '6': + halting_time = time(NULL); fork_rc_script(rl, stop, B_TRUE); halting = AD_BOOT; goto uadmin; --- old/usr/src/cmd/svc/startd/method.c Thu Feb 26 16:21:36 2009 +++ new/usr/src/cmd/svc/startd/method.c Thu Feb 26 16:21:35 2009 @@ -19,12 +19,10 @@ * CDDL HEADER END */ /* - * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * method.c - method execution functions * @@ -955,6 +953,7 @@ */ if (type == METHOD_STOP && (!instance_is_transient_style(inst)) && !(contract_is_empty(inst->ri_i.i_primary_ctid))) { + int times = 0; if (timeout != METHOD_TIMEOUT_INFINITE) timeout_insert(inst, inst->ri_i.i_primary_ctid, @@ -961,7 +960,16 @@ timeout); for (;;) { - (void) poll(NULL, 0, 100); + /* + * Check frequently at first, then back off. This + * keeps startd from idling while shutting down. + */ + if (times < 20) { + (void) poll(NULL, 0, 5); + times++; + } else { + (void) poll(NULL, 0, 100); + } if (contract_is_empty(inst->ri_i.i_primary_ctid)) break; } --- old/usr/src/cmd/svc/startd/startd.h Thu Feb 26 16:21:37 2009 +++ new/usr/src/cmd/svc/startd/startd.h Thu Feb 26 16:21:37 2009 @@ -588,6 +588,7 @@ void *fork_configd_thread(void *); pid_t startd_fork1(int *); +void fork_with_timeout(const char *, uint_t, uint_t); /* graph.c */ void graph_init(void);