[launchd-changes] [23794] trunk

source_changes at macosforge.org source_changes at macosforge.org
Tue Feb 10 17:24:50 PST 2009


Revision: 23794
          http://trac.macosforge.org/projects/launchd/changeset/23794
Author:   dsorresso at apple.com
Date:     2009-02-10 17:24:50 -0800 (Tue, 10 Feb 2009)
Log Message:
-----------
<rdar://problem/6120453> Add SPI to disable/enable per-user launchd's
<rdar://problem/6424345> audit start up and shut down integration in launchd
<rdar://problem/6460582> Allow a session to be bootstrapped in on-demand mode
<rdar://problem/6549046> Roll back fix for <rdar://problem/5947376>
<rdar://problem/6549469> SnowLeopard: launchd console: gssd-agent errors
<rdar://problem/6551269> K3: 10a261: Black screen hang during restart test.

Modified Paths:
--------------
    trunk/launchd/src/config.h
    trunk/launchd/src/launchctl.c
    trunk/launchd/src/launchd.c
    trunk/launchd/src/launchd_core_logic.c
    trunk/launchd/src/launchd_core_logic.h
    trunk/launchd/src/launchd_runtime.c
    trunk/launchd/src/launchd_runtime.h
    trunk/launchd/src/launchd_unix_ipc.c
    trunk/launchd/src/launchproxy.c
    trunk/launchd/src/libvproc.c
    trunk/launchd/src/protocol_job_reply.defs
    trunk/launchd/src/protocol_vproc.defs
    trunk/launchd/src/vproc_priv.h
    trunk/launchd.xcodeproj/project.pbxproj

Modified: trunk/launchd/src/config.h
===================================================================
--- trunk/launchd/src/config.h	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/config.h	2009-02-11 01:24:50 UTC (rev 23794)
@@ -4,4 +4,5 @@
 #define HAVE_QUARANTINE TARGET_HAVE_QUARANTINE
 #define HAVE_SANDBOX TARGET_HAVE_SANDBOX
 #define HAVE_SECURITY !TARGET_HAVE_EMBEDDED_SECURITY
+#define HAVE_LIBAUDITD !TARGET_OS_EMBEDDED
 #endif /* __CONFIG_H__ */

Modified: trunk/launchd/src/launchctl.c
===================================================================
--- trunk/launchd/src/launchctl.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchctl.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -20,6 +20,7 @@
 
 static const char *const __rcs_file_version__ = "$Revision$";
 
+#include "config.h"
 #include "launch.h"
 #include "launch_priv.h"
 #include "bootstrap.h"
@@ -81,6 +82,13 @@
 #include <spawn.h>
 #include <sys/syslimits.h>
 
+#if HAVE_LIBAUDITD
+#include <bsm/auditd_lib.h>
+#ifndef	AUDITD_PLIST_FILE
+#define	AUDITD_PLIST_FILE "/System/Library/LaunchDaemons/com.apple.auditd.plist"
+#endif
+#endif
+
 extern char **environ;
 
 
@@ -1756,6 +1764,9 @@
 	int hnmib[] = { CTL_KERN, KERN_HOSTNAME };
 	struct kevent kev;
 	int kq;
+#if HAVE_LIBAUDITD
+	launch_data_t lda, ldb;
+#endif
 
 	do_sysversion_sysctl();
 
@@ -1862,10 +1873,23 @@
 	assumes(touch_file(_PATH_VARRUN "/.systemStarterRunning", DEFFILEMODE) != -1);
 #endif
 
+#if HAVE_LIBAUDITD
+	/*
+	 * Only start auditing if not "Disabled" in auditd plist.
+	 */
+	if ((lda = read_plist_file(AUDITD_PLIST_FILE, false, false)) != NULL && 
+		((ldb = launch_data_dict_lookup(lda, LAUNCH_JOBKEY_DISABLED)) == NULL ||
+		 job_disabled_logic(ldb) == false)) 
+	{
+		assumes(audit_quick_start() == 0);
+		launch_data_free(lda);	
+	}
+#else
 	if (path_check("/etc/security/rc.audit")) {
 		const char *audit_tool[] = { _PATH_BSHELL, "/etc/security/rc.audit", NULL };
 		assumes(fwexec(audit_tool, NULL) != -1);
 	}
+#endif
 
 	do_BootCache_magic(BOOTCACHE_START);
 
@@ -2079,7 +2103,7 @@
 
 		if (strcasecmp(session_type, VPROCMGR_SESSION_BACKGROUND) == 0) {
 			read_launchd_conf();
-#if HAVE_SECURITY
+#if 0 /* XXX PR-6456403 */
 			assumes(SessionCreate(sessionKeepCurrentBootstrap, 0) == 0);
 #endif
 		}

Modified: trunk/launchd/src/launchd.c
===================================================================
--- trunk/launchd/src/launchd.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchd.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -71,6 +71,10 @@
 #include <pthread.h>
 #include <util.h>
 
+#if HAVE_LIBAUDITD
+#include <bsm/auditd_lib.h>
+#endif
+
 #include "bootstrap.h"
 #include "vproc.h"
 #include "vproc_priv.h"
@@ -261,6 +265,12 @@
 void
 do_pid1_crash_diagnosis_mode(void)
 {
+	if( g_wsp ) {
+		kill(g_wsp, SIGKILL);
+		sleep(3);
+		g_wsp = 0;
+	}
+
 	while( g_shutdown_debugging && !do_pid1_crash_diagnosis_mode2() ) {
 		sleep(1);
 	}
@@ -269,7 +279,7 @@
 int
 basic_fork(void)
 {
-	int wstatus;
+	int wstatus = 0;
 	pid_t p;
 	
 	switch ((p = fork())) {
@@ -279,7 +289,14 @@
 		case 0:
 			return p;
 		default:
+		#if 0
+			/* If we attach with the debugger, the kernel reparenting could 
+			 * cause this to return prematurely.
+			 */
 			waitpid(p, &wstatus, 0);
+		#else
+			sleep(UINT_MAX);
+		#endif
 			if (WIFEXITED(wstatus)) {
 				if (WEXITSTATUS(wstatus) == EXIT_SUCCESS) {
 					return 1;
@@ -326,9 +343,9 @@
 	fprintf(stdout, "The PID 1 launchd has crashed. It has fork(2)ed itself for debugging.\n");
 	fprintf(stdout, "To debug the main thread of PID 1:\n");
 	fprintf(stdout, "    gdb attach %d\n", getppid());
-	fprintf(stdout, "To exit this shell, capture a sample of launchd and shut down:\n");
-	fprintf(stdout, "    exit\n");
-	fprintf(stdout, "A sample of PID 1 will be written to %s\n", PID1_CRASH_LOGFILE);
+	fprintf(stdout, "To exit this shell and shut down:\n");
+	fprintf(stdout, "    kill -9 1\n");
+	fprintf(stdout, "A sample of PID 1 has been written to %s\n", PID1_CRASH_LOGFILE);
 	fprintf(stdout, "\n");
 	fflush(stdout);
 	
@@ -347,8 +364,6 @@
 
 	crash_addr = si->si_addr;
 	crash_pid = si->si_pid;
-
-	do_pid1_crash_diagnosis_mode();
 	
 	unlink(PID1_CRASH_LOGFILE);
 
@@ -364,6 +379,8 @@
 		break;
 	}
 
+	do_pid1_crash_diagnosis_mode();
+
 	switch (sig) {
 	default:
 	case 0:
@@ -428,6 +445,12 @@
 	runtime_syslog(LOG_NOTICE, "%s%s began", term_who, pid1_magic ? "" : g_username);
 
 	launchd_assert(jobmgr_shutdown(root_jobmgr) != NULL);
+
+#if HAVE_LIBAUDITD
+	if( pid1_magic ) {
+		launchd_assumes(audit_quick_stop() == 0);
+	}
+#endif
 }
 
 void

Modified: trunk/launchd/src/launchd_core_logic.c
===================================================================
--- trunk/launchd/src/launchd_core_logic.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchd_core_logic.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -148,6 +148,14 @@
 static bool waiting4removal_new(job_t j, mach_port_t rp);
 static void waiting4removal_delete(job_t j, struct waiting_for_removal *w4r);
 
+struct waiting_for_exit {
+	LIST_ENTRY(waiting_for_exit) sle;
+	mach_port_t rp;
+};
+
+static bool waiting4exit_new(job_t j, mach_port_t rp);
+static void waiting4exit_delete(job_t j, struct waiting_for_exit *w4e);
+
 struct mspolicy {
 	SLIST_ENTRY(mspolicy) sle;
 	unsigned int		allow:1, per_pid:1, __junk:30;
@@ -363,7 +371,7 @@
 static void jobmgr_reap_bulk(jobmgr_t jm, struct kevent *kev);
 static void jobmgr_log_stray_children(jobmgr_t jm, bool kill_strays);
 static void jobmgr_kill_stray_child(jobmgr_t jm, pid_t p);
-static void jobmgr_remove(jobmgr_t jm, bool expect_real_jobs);
+static void jobmgr_remove(jobmgr_t jm);
 static void jobmgr_dispatch_all(jobmgr_t jm, bool newmounthack);
 static void jobmgr_dequeue_next_sample(jobmgr_t jm);
 static job_t jobmgr_init_session(jobmgr_t jm, const char *session_type, bool sflag);
@@ -371,6 +379,7 @@
 static job_t jobmgr_find_by_pid(jobmgr_t jm, pid_t p, bool create_anon);
 static jobmgr_t jobmgr_find_by_name(jobmgr_t jm, const char *where);
 static job_t job_mig_intran2(jobmgr_t jm, mach_port_t mport, pid_t upid);
+static job_t jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch, mach_port_t *mp);
 static void job_export_all2(jobmgr_t jm, launch_data_t where);
 static void jobmgr_callback(void *obj, struct kevent *kev);
 static void jobmgr_setup_env_from_other_jobs(jobmgr_t jm);
@@ -390,8 +399,11 @@
 	LIST_ENTRY(job_s) pid_hash_sle;
 	LIST_ENTRY(job_s) label_hash_sle;
 	LIST_ENTRY(job_s) global_env_sle;
+	LIST_ENTRY(job_s) suspended_peruser_sle;
 	STAILQ_ENTRY(job_s) pending_samples_sle;
 	SLIST_ENTRY(job_s) curious_jobs_sle;
+	LIST_HEAD(, job_s) suspended_perusers;
+	LIST_HEAD(, waiting_for_exit) exit_watchers;
 	SLIST_HEAD(, socketgroup) sockets;
 	SLIST_HEAD(, calendarinterval) cal_intervals;
 	SLIST_HEAD(, envitem) global_env;
@@ -444,6 +456,7 @@
 	uint64_t start_time;
 	uint32_t min_run_time;
 	uint32_t start_interval;
+	uint32_t peruser_suspend_count; /* The number of jobs that have disabled this per-user launchd. */
 #if 0
 	/* someday ... */
 	enum {
@@ -494,6 +507,7 @@
 	     	deny_job_creation			:1,	/* Don't let this job create new 'job_t' objects in launchd */
 	     	kill_via_shmem				:1,	/* man launchd.plist --> EnableTransactions */
 	     	sent_kill_via_shmem			:1,	/* We need to 'kill_via_shmem' once-and-only-once */
+			clean_kill					:1, /* The job was sent SIGKILL because it was clean. */
 			pending_sample				:1, /* This job needs to be sampled for some reason. */
 			kill_after_sample			:1, /* The job is to be killed after sampling. */
 			is_being_sampled			:1, /* We've spawned a sample tool to sample the job. */
@@ -505,6 +519,7 @@
 			jetsam_frontmost			:1, /* The job is considered "frontmost" by Jetsam. */
 			needs_kickoff				:1, /* The job is to be kept alive continuously, but it must be initially kicked off. */
 			is_bootstrapper				:1, /* The job is a bootstrapper. */
+			has_console					:1, /* The job owns the console. */
 			migratory					:1; /* The (anonymous) job called vprocmgr_switch_to_session(). */
 	mode_t mask;
 	pid_t tracing_pid;
@@ -598,6 +613,10 @@
 static void extract_rcsid_substr(const char *i, char *o, size_t osz);
 static void do_first_per_user_launchd_hack(void);
 static void simulate_pid1_crash(void);
+static pid_t spawn_sync(job_t j);
+static pid_t basic_spawn(job_t j, void (*what_to_do)(job_t));
+static void take_sample(job_t j);
+static void do_sync(job_t j);
 
 void eliminate_double_reboot(void);
 
@@ -613,6 +632,7 @@
 static bool did_first_per_user_launchd_BootCache_hack;
 #define JOB_BOOTCACHE_HACK_CHECK(j)	(unlikely(j->per_user && !did_first_per_user_launchd_BootCache_hack && (j->mach_uid >= 500) && (j->mach_uid != (uid_t)-2)))
 static job_t workaround_5477111;
+static pid_t s_update_pid = 0;
 
 /* process wide globals */
 mach_port_t inherited_bootstrap_port;
@@ -711,6 +731,7 @@
 	j->sent_signal_time = runtime_get_opaque_time();
 
 	if (newval < 0) {
+		j->clean_kill = true;
 		job_kill(j);
 	} else {
 		/*
@@ -892,11 +913,10 @@
 	}
 
 	LIST_FOREACH(ji, &jm->jobs, sle) {
-		why_active = job_active(ji);
-
-		job_log(ji, LOG_DEBUG | LOG_CONSOLE, "%s", why_active ? why_active : "Inactive");
+		if( (why_active = job_active(ji)) ) {
+			job_log(ji, LOG_DEBUG | LOG_CONSOLE, "%s", why_active);
+		}
 	}
-
 }
 
 static void
@@ -926,7 +946,7 @@
 }
 
 void
-jobmgr_remove(jobmgr_t jm, bool expect_real_jobs)
+jobmgr_remove(jobmgr_t jm)
 {
 	jobmgr_t jmi;
 	job_t ji;
@@ -934,22 +954,16 @@
 	jobmgr_log(jm, LOG_DEBUG, "Removing job manager.");
 	if (!jobmgr_assumes(jm, SLIST_EMPTY(&jm->submgrs))) {
 		while ((jmi = SLIST_FIRST(&jm->submgrs))) {
-			jobmgr_remove(jmi, false);
+			jobmgr_remove(jmi);
 		}
 	}
 
 	while( (ji = LIST_FIRST(&jm->jobs)) ) {
-		if( !expect_real_jobs && ji->p && !job_assumes(ji, ji->anonymous) ) {
-			job_log(ji, LOG_WARNING | LOG_CONSOLE, "%s() called incorrectly with non-anonymous job still active. Forcing removal.", __func__);
-			job_remove(ji, true);
-		} else {
-			if( !ji->anonymous && ji->p ) {
-				job_log(ji, LOG_WARNING | LOG_CONSOLE, "Job has overstayed its welcome. Forcing removal.");
-				job_remove(ji, true);
-			} else {
-				job_remove(ji, false);
-			}
+		if( !ji->anonymous && ji->p ) {
+			job_log(ji, LOG_WARNING | LOG_CONSOLE, "Job has overstayed its welcome. Forcing removal.");
+			ji->p = 0;
 		}
+		job_remove(ji);
 	}
 
 	if (jm->req_port) {
@@ -979,7 +993,7 @@
 }
 
 void
-job_remove(job_t j, bool force)
+job_remove(job_t j)
 {
 	struct waiting_for_removal *w4r;
 	struct calendarinterval *ci;
@@ -993,7 +1007,7 @@
 	if (unlikely(j->p)) {
 		if (j->anonymous) {
 			job_reap(j);
-		} else if( !force ) {
+		} else {
 			job_log(j, LOG_DEBUG, "Removal pended until the job exits");
 
 			if (!j->removal_pending) {
@@ -1256,7 +1270,7 @@
 
 out_bad:
 	if (jr) {
-		job_remove(jr, false);
+		job_remove(jr);
 	}
 	return NULL;
 }
@@ -1484,6 +1498,10 @@
 		j->argv[i] = NULL;
 	}
 
+	if( strcmp(j->label, "com.apple.WindowServer") == 0 ) {
+		j->has_console = true;
+	}
+
 	LIST_INSERT_HEAD(&jm->jobs, j, sle);
 	LIST_INSERT_HEAD(&label_hash[hash_label(j->label)], j, label_hash_sle);
 
@@ -2351,7 +2369,7 @@
 
 	if (unlikely(j && (j != workaround_5477111) && j->unload_at_mig_return)) {
 		job_log(j, LOG_NOTICE, "Unloading PID %u at MIG return.", j->p);
-		job_remove(j, false);
+		job_remove(j);
 	}
 
 	workaround_5477111 = NULL;
@@ -2509,15 +2527,6 @@
 		kevent_mod((uintptr_t)&j->exit_timeout, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
 	}
 
-	if (j->anonymous) {
-		total_anon_children--;
-		if( j->migratory ) {
-			runtime_del_ref();
-		}
-	} else {
-		runtime_del_ref();
-		total_children--;
-	}
 	LIST_REMOVE(j, pid_hash_sle);
 
 	if (j->wait_reply_port) {
@@ -2566,7 +2575,7 @@
 		int s = WTERMSIG(status);
 		if ((SIGKILL == s || SIGTERM == s) && !j->stopped) {
 			job_log(j, LOG_NOTICE, "Exited: %s", strsignal(s));
-		} else {
+		} else if( !j->stopped && !j->clean_kill ) {			
 			switch( s ) {
 				/* Signals which indicate a crash. */
 				case SIGILL		:
@@ -2607,6 +2616,33 @@
 		}
 	}
 	
+	job_t ji = NULL;
+	while( (ji = LIST_FIRST(&j->suspended_perusers)) ) {
+		job_log(j, LOG_ERR, "Job exited before resuming per-user launchd for UID %u. Will forcibly resume.", ji->mach_uid);
+		ji->peruser_suspend_count--;
+		job_dispatch(ji, false);
+		LIST_REMOVE(ji, suspended_peruser_sle);
+	}
+	
+	struct waiting_for_exit *w4e = NULL;
+	while( (w4e = LIST_FIRST(&j->exit_watchers)) ) {
+		waiting4exit_delete(j, w4e);
+	}
+	
+	if (j->anonymous) {
+		total_anon_children--;
+		if( j->migratory ) {
+			runtime_del_ref();
+		}
+	} else {
+		runtime_del_ref();
+		total_children--;
+	}
+	
+	if( j->has_console ) {
+		g_wsp = 0;
+	}
+	
 	if (j->hopefully_exits_first) {
 		j->mgr->hopefully_first_cnt--;
 	} else if (!j->anonymous && !j->hopefully_exits_last) {
@@ -2615,6 +2651,7 @@
 	j->last_exit_status = status;
 	j->sent_signal_time = 0;
 	j->sent_sigkill = false;
+	j->clean_kill = false;
 	j->sampling_complete = false;
 	j->sent_kill_via_shmem = false;
 	j->lastlookup = NULL;
@@ -2655,7 +2692,133 @@
 	}
 }
 
+/* Maybe someday... */
+pid_t
+spawn_sync(job_t j)
+{
+	pid_t p = 0;
+	char *sync_args[] = { "/bin/sync", NULL };
+	switch( (p = vfork()) ) {
+		case 0	:
+			execve("/bin/sync", sync_args, environ);
+			_exit(EXIT_FAILURE);
+		case -1	:
+			job_log(j, LOG_NOTICE, "vfork(2) failed: %d", errno);
+			break;
+		default	:
+			break;
+	}
+	
+	int r = -1;
+	if( p != -1 ) {
+		/* Let us know when the process is done. ONESHOT is implicit if we're just interested in NOTE_EXIT. */
+		if( !job_assumes(j, (r = kevent_mod(p, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j)) != -1) ) {
+			if( errno != ESRCH ) {
+				job_assumes(j, runtime_kill(p, SIGKILL) != -1);
+			}
+		}
+		
+		int status = 0;
+		if( r == -1 ) {
+			job_assumes(j, waitpid(p, &status, WNOHANG) != -1);
+		}
+	}
+	
+	return p;
+}
+
+pid_t
+basic_spawn(job_t j, void (*what_to_do)(job_t))
+{
+	pid_t p = 0;
+	thread_state_flavor_t f = 0;
+#if defined (__ppc__) || defined(__ppc64__)
+	f = PPC_THREAD_STATE64;
+#elif defined(__i386__) || defined(__x86_64__)
+	f = x86_THREAD_STATE;
+#elif defined(__arm__)
+	f = ARM_THREAD_STATE;
+#else
+	#error "unknown architecture"
+#endif
+	
+	int execpair[2] = { 0, 0 };
+	job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execpair) != -1);
+	
+	switch( (p = fork()) ) {
+		case 0	:
+			job_assumes(j, runtime_close(execpair[0]) != -1);
+			/* Handle exceptions directly. */
+			task_set_exception_ports(mach_task_self(), EXC_MASK_CRASH, runtime_get_kernel_port(), EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f);
+			
+			/* Wait for the parent to attach a kevent. */
+			read(_fd(execpair[1]), &p, sizeof(p));
+			what_to_do(j);
+			_exit(EXIT_FAILURE);
+		case -1	:
+			job_assumes(j, runtime_close(execpair[0]) != -1);
+			job_assumes(j, runtime_close(execpair[1]) != -1);
+			execpair[0] = -1;
+			execpair[1] = -1;
+			job_log(j, LOG_NOTICE | LOG_CONSOLE, "fork(2) failed: %d", errno);
+			break;
+		default	:
+			job_assumes(j, runtime_close(execpair[1]) != -1);
+			execpair[1] = -1;
+			break;
+	}
+	
+	int r = -1;
+	if( p != -1 ) {
+		/* Let us know when sample is done. ONESHOT is implicit if we're just interested in NOTE_EXIT. */
+		if( job_assumes(j, (r = kevent_mod(p, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j)) != -1) ) {
+			if( !job_assumes(j, write(execpair[0], &p, sizeof(p)) == sizeof(p)) ) {
+				job_assumes(j, kevent_mod(p, EVFILT_PROC, EV_DELETE, 0, 0, NULL) != -1);
+				job_assumes(j, runtime_kill(p, SIGKILL) != -1);
+				r = -1;
+				p = -1;
+			}
+		} else {
+			job_assumes(j, runtime_kill(p, SIGKILL) != -1);
+		}
+		
+		int status = 0;
+		if( r == -1 ) {
+			job_assumes(j, waitpid(p, &status, WNOHANG) != -1);
+		}
+	}
+	
+	if( execpair[0] != -1 ) {
+		job_assumes(j, runtime_close(execpair[0]) != -1);
+	}
+	
+	if( execpair[1] != -1 ) {
+		job_assumes(j, runtime_close(execpair[0]) != -1);
+	}
+	
+	return p;
+}
+
 void 
+take_sample(job_t j)
+{
+	char pidstr[32];
+	snprintf(pidstr, sizeof(pidstr), "%u", j->p);
+	char *sample_args[] = { "/usr/bin/sample", pidstr, "1", "-unsupportedShowArch", "-mayDie", "-file", j->mgr->sample_log_file, NULL };
+	
+	execve(sample_args[0], sample_args, environ);
+	_exit(EXIT_FAILURE);
+}
+
+void
+do_sync(job_t j __attribute__((unused)))
+{
+	char *sync_args[] = { "/bin/sync", NULL };
+	execve(sync_args[0], sync_args, environ);
+	_exit(EXIT_FAILURE);
+}
+
+void 
 jobmgr_dequeue_next_sample(jobmgr_t jm)
 {
 	if( STAILQ_EMPTY(&jm->pending_samples) ) {
@@ -2687,103 +2850,18 @@
 	snprintf(j->mgr->sample_log_file, sizeof(j->mgr->sample_log_file), SHUTDOWN_LOG_DIR "/%s-%u.sample.txt", j->label, j->p);
 	
 	if (job_assumes(j, unlink(jm->sample_log_file) != -1 || errno == ENOENT)) {
-		pid_t sp = 0;
-		char *sample_args[] = { "/usr/bin/sample", pidstr, "1", "-unsupportedShowArch", "-mayDie", "-file", j->mgr->sample_log_file, NULL };
-		thread_state_flavor_t f = 0;
-	#if defined (__ppc__) || defined(__ppc64__)
-		f = PPC_THREAD_STATE64;
-	#elif defined(__i386__) || defined(__x86_64__)
-		f = x86_THREAD_STATE;
-	#elif defined(__arm__)
-		f = ARM_THREAD_STATE;
-	#else
-		#error "unknown architecture"
-	#endif
+		pid_t sp = basic_spawn(j, take_sample);
 		
-	#if NEEDS_MULTI_THREADED_EXEC
-		posix_spawnattr_t psattr;
-		posix_spawnattr_init(psattr);
-		posix_spawnattr_setexceptionports_np(&psattr, EXC_MASK_CRASH, runtime_get_kernel_port(), EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES , f);
-		
-		if (!job_assumes(j, (errno = posix_spawnp(&sp, sample_args[0], NULL, &psattr, sample_args, environ)) == 0)) {
+		if( sp == -1 ) {
 			job_log(j, LOG_ERR | LOG_CONSOLE, "Sampling for job failed!");
 			STAILQ_REMOVE(&jm->pending_samples, j, job_s, pending_samples_sle);
+			j->sampling_complete = true;
 			jobmgr_dequeue_next_sample(jm);
 		} else {
 			j->tracing_pid = sp;
-
-			/* Let us know when sample is done. ONESHOT is implicit if we're just interested in NOTE_EXIT. */
-			job_assumes(j, kevent_mod(sp, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j) != -1);
-		}
-		
-		posix_spawnattr_destroy(&psattr);
-	#else
-		int execpair[2] = { 0, 0 };
-		job_assumes(j, socketpair(AF_UNIX, SOCK_STREAM, 0, execpair) != -1);
-		
-		switch( (sp = fork()) ) {
-			case 0	:
-				job_assumes(j, runtime_close(execpair[0]) != -1);
-				/* Handle sample's exceptions directly, since ReportCrash will not be able to. */
-				task_set_exception_ports(mach_task_self(), EXC_MASK_CRASH, runtime_get_kernel_port(), EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, f);
-				
-				/* Wait for the parent to attach a kevent. */
-				read(_fd(execpair[1]), &sp, sizeof(sp));
-				execve(sample_args[0], sample_args, environ);
-				job_log(j, LOG_NOTICE | LOG_CONSOLE, "Could not exec(2): %d", errno);
-				_exit(EXIT_FAILURE);
-			case -1	:
-				job_assumes(j, runtime_close(execpair[0]) != -1);
-				job_assumes(j, runtime_close(execpair[1]) != -1);
-				execpair[0] = -1;
-				execpair[1] = -1;
-				job_log(j, LOG_NOTICE | LOG_CONSOLE, "fork(2) failed: %d", errno);
-				break;
-			default	:
-				job_assumes(j, runtime_close(execpair[1]) != -1);
-				execpair[1] = -1;
-				break;
-		}
-		
-		int r = -1;
-		if( sp != -1 ) {
-			/* Let us know when sample is done. ONESHOT is implicit if we're just interested in NOTE_EXIT. */
-			if( job_assumes(j, (r = kevent_mod(sp, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, j)) != -1) ) {
-				if( job_assumes(j, write(execpair[0], &sp, sizeof(sp)) == sizeof(sp)) ) {
-					j->tracing_pid = sp;
-					j->is_being_sampled = true;
-				} else {
-					job_assumes(j, kevent_mod(sp, EVFILT_PROC, EV_DELETE, 0, 0, NULL) != -1);
-					job_assumes(j, runtime_kill(sp, SIGKILL) != -1);
-					r = -1;
-				}
-			} else {
-				job_assumes(j, runtime_kill(sp, SIGKILL) != -1);
-			}
-			
-			int status = 0;
-			if( r == -1 ) {
-				job_assumes(j, waitpid(sp, &status, WNOHANG) != -1);
-			}
-		}
-		
-		if( execpair[0] != -1 ) {
-			job_assumes(j, runtime_close(execpair[0]) != -1);
-		}
-		
-		if( execpair[1] != -1 ) {
-			job_assumes(j, runtime_close(execpair[0]) != -1);
-		}
-		
-		if( r == -1 ) {
-			job_log(j, LOG_ERR | LOG_CONSOLE, "Sampling for job failed!");
-			STAILQ_REMOVE(&jm->pending_samples, j, job_s, pending_samples_sle);
-			j->sampling_complete = true;
-			jobmgr_dequeue_next_sample(jm);
-		} else {
+			j->is_being_sampled = true;
 			job_log(j, LOG_DEBUG | LOG_CONSOLE, "Sampling job (sample PID: %i, file: %s).", sp, j->mgr->sample_log_file);
 		}
-	#endif
 	} else {
 		STAILQ_REMOVE(&jm->pending_samples, j, job_s, pending_samples_sle);
 		j->sampling_complete = true;
@@ -2823,9 +2901,13 @@
 	 */
 	if (!job_active(j)) {
 		if (job_useless(j)) {
-			job_remove(j, false);
+			job_remove(j);
 			return NULL;
-		} 
+		}
+		if( unlikely(j->per_user && j->peruser_suspend_count > 0) ) {
+			return NULL;
+		}
+		
 		if (kickstart || job_keepalive(j)) {
 			job_log(j, LOG_DEBUG, "Starting job (kickstart = %s)", kickstart ? "true" : "false");
 			job_start(j);
@@ -3011,6 +3093,14 @@
 	int fflags = kev->fflags;
 	
 	if( fflags & NOTE_EXIT ) {
+		if( s_update_pid == (pid_t)kev->ident ) {
+			int status = 0;
+			job_assumes(j, waitpid(s_update_pid, &status, 0) == 0);
+			job_log(j, LOG_NOTICE, "Reaping update job (PID %i, exit status %i)", s_update_pid, WEXITSTATUS(status));
+			
+			s_update_pid = 0;
+		}
+		
 		if( j->p == (pid_t)kev->ident && !j->anonymous && !j->is_being_sampled ) {
 			int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, j->p };
 			struct kinfo_proc kp;
@@ -3086,7 +3176,7 @@
 		job_reap(j);
 
 		if (j->anonymous) {
-			job_remove(j, false);
+			job_remove(j);
 			j = NULL;
 		} else {
 			j = job_dispatch(j, false);
@@ -3111,6 +3201,12 @@
 		job_log(j, LOG_DEBUG, "&j->start_interval == ident (%p)", ident);
 		j->start_pending = true;
 		job_dispatch(j, false);
+	} else if( do_sync == ident ) {
+		pid_t p = spawn_sync(j);
+		if( job_assumes(j, p != -1) ) {
+			job_log(j, LOG_NOTICE, "Starting update job (PID %i)", p);
+			s_update_pid = p;
+		}
 	} else if (&j->exit_timeout == ident) {
 		if( !job_assumes(j, j->p != 0) ) {
 			return;
@@ -3403,6 +3499,10 @@
 			}
 		}
 		
+		if( j->has_console ) {
+			g_wsp = c;
+		}
+		
 		runtime_add_ref();
 		total_children++;
 		LIST_INSERT_HEAD(&j->mgr->active_jobs[ACTIVE_JOB_HASH(c)], j, pid_hash_sle);
@@ -3822,9 +3922,24 @@
 	 */
 
 	if (likely(!j->no_init_groups)) {
+	#if 1
 		if (!job_assumes(j, initgroups(loginname, desired_gid) != -1)) {
 			_exit(EXIT_FAILURE);
 		}
+	#else
+		/* Do our own little initgroups(). We do this to guarantee that we're
+		 * always opted into dynamic group resolution in the kernel. initgroups(3)
+		 * does not make this guarantee.
+		 */
+		int groups[NGROUPS], ngroups;
+		
+		/* A failure here isn't fatal, and we'll still get data we can use. */
+		job_assumes(j, getgrouplist(j->username, desired_gid, groups, &ngroups) != -1);
+		
+		if( !job_assumes(j, syscall(SYS_initgroups, ngroups, groups, desired_uid) != -1) ) {
+			_exit(EXIT_FAILURE);
+		}
+	#endif
 	}
 
 	if (!job_assumes(j, setuid(desired_uid) != -1)) {
@@ -5269,109 +5384,115 @@
 jobmgr_t
 jobmgr_do_hopefully_first_shutdown_phase(jobmgr_t jm)
 {
-	jobmgr_t _jm = jm;
 	if( !jm->shutting_down ) {
-		return _jm;
+		return jm;
 	}
+
+	if( jm->killed_hopefully_first_jobs ) {
+		return NULL;
+	}
+
+	jobmgr_log(jm, LOG_DEBUG, "Doing first phase of garbage collection.");
 	
-	bool should_proceed = !jm->killed_hopefully_first_jobs;
-	
+	uint32_t unkilled_cnt = 0;
 	job_t ji = NULL, jn = NULL;
-	if( should_proceed ) {
-		jobmgr_log(jm, LOG_DEBUG, "Doing first phase of garbage collection.");
-		uint32_t unkilled_cnt = 0;
-		LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) {
-			if( jm->parentmgr ) {
-				job_log(ji, LOG_DEBUG, "Examining...");
-			}
-			if( ji->hopefully_exits_first ) {
-				bool active = job_active(ji);
-				if( active && !ji->stopped ) {
-					job_stop(ji);
-					
-					/* We may have sent SIGKILL to the job in job_stop(). */
-					unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
-				} else if( ji->stopped ) {
-					unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
-				} else if( !active ) {
-					job_remove(ji, false);
-				}
-			}
+	LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) {
+		if( !ji->hopefully_exits_first ) {
+			continue;
 		}
 		
-		/* If we've killed everyone, move on. */
-		if( unkilled_cnt == 0 ) {
-			jm->killed_hopefully_first_jobs = true;
-			_jm = NULL;
+		bool active = job_active(ji);
+		if( active && !ji->stopped ) {
+			/* If the job is active and we haven't told it to stop yet, stop it. */
+			job_stop(ji);
+			
+			/* We may have sent SIGKILL to the job in job_stop(). In this case,
+			 * "clean" jobs should exit immediately, so we shouldn't have to wait
+			 * for them.
+			 */
+			unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
+		} else if( ji->stopped ) {
+			/* If the job is active and has been told to stop, disregard it
+			 * after we've sent SIGKILL.
+			 */
+			unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
+		} else if( !active ) {
+			/* If the job was not active when shutdown began, remove it. */
+			job_remove(ji);
 		}
-	} else {
+	}
+	
+	/* If we've killed everyone, move on. */
+	if( unkilled_cnt == 0 ) {
 		jm->killed_hopefully_first_jobs = true;
-		_jm = NULL;
+		jm = NULL;
 	}
 	
-	return _jm;
+	return jm;
 }
 
 jobmgr_t
 jobmgr_do_normal_shutdown_phase(jobmgr_t jm)
 {
-	jobmgr_t _jm = jm;
 	if( !jm->shutting_down ) {
-		return _jm;
+		return jm;
 	}
 	
-	bool should_proceed = !jm->killed_normal_jobs;
+	if( jm->killed_normal_jobs ) {
+		return NULL;
+	}
 	
+	jobmgr_log(jm, LOG_DEBUG, "Doing second phase of garbage collection.");
+	
+	uint32_t unkilled_cnt = 0;
 	job_t ji = NULL, jn = NULL;
-	if( should_proceed ) {
-		jobmgr_log(jm, LOG_DEBUG, "Doing second phase of garbage collection.");
-		uint32_t unkilled_cnt = 0;
-		LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) {
-			if( jm->parentmgr ) {
-				job_log(ji, LOG_DEBUG, "Examining...");
-			}
-			
-			if( !(ji->hopefully_exits_first || ji->hopefully_exits_last) ) {
-				if( !ji->anonymous ) {
-					bool active = job_active(ji);
-					if( active && !ji->stopped ) {
-						job_stop(ji);
-						
-						/* We may have sent SIGKILL to the job in job_stop(). */
-						unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
-					} else if( ji->stopped ) {
-						unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
-					} else if( !active ) {
-						job_remove(ji, false);
-					}
-				} else if( ji->migratory && jm->parentmgr ) {
-					/* If a job has migrated into a sub-bootstrap, we want to 
-					 * keep the job manager around as long as the job is there.
-					 */
-					unkilled_cnt++;
-				}
-			}
+	LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) {
+		if( ji->migratory ) {
+			/* If we're shutting down, release the hold migratory jobs
+			 * have on us.
+			 */
+			job_remove(ji);			
 		}
 		
-		/* If we've killed everyone, move on. */
-		if( unkilled_cnt == 0 ) {
-			jm->killed_normal_jobs = true;
-			_jm = NULL;
+		if( ji->anonymous || ji->hopefully_exits_first || ji->hopefully_exits_last ) {
+			continue;
 		}
-	} else {
-		jm->killed_normal_jobs = true;
-		_jm = NULL;
+		
+		bool active = job_active(ji);
+		if( active && !ji->stopped ) {
+			/* If the job is active and we haven't told it to stop yet, stop it. */
+			job_stop(ji);
+			
+			/* We may have sent SIGKILL to the job in job_stop(). In this case,
+			 * "clean" jobs should exit immediately, so we shouldn't have to wait
+			 * for them.
+			 */
+			unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
+		} else if( ji->stopped ) {
+			/* If the job is active and has been told to stop, disregard it
+			 * after we've sent SIGKILL.
+			 */
+			unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
+		} else if( !active ) {
+			/* If the job was not active when shutdown began, remove it. */
+			job_remove(ji);
+		}
 	}
 	
-	return _jm;
+	/* If we've killed everyone, move on. */
+	if( unkilled_cnt == 0 ) {
+		jm->killed_hopefully_first_jobs = true;
+		jm = NULL;
+	}
+	
+	return jm;
 }
 
 jobmgr_t
 jobmgr_do_hopefully_last_shutdown_phase(jobmgr_t jm)
 {
-	jobmgr_t _jm = jm;
 	if( !jm->shutting_down ) {
-		return _jm;
+		return jm;
 	}
 	
 	if( jm == root_jobmgr ) {
@@ -5384,42 +5505,46 @@
 		killed_stray_jobs = true;
 	}
 	
-	bool should_proceed = !jm->killed_hopefully_last_jobs && total_children != 0;
+	if( jm->killed_hopefully_last_jobs || total_children == 0 ) {
+		return NULL;
+	}
 	
+	uint32_t unkilled_cnt = 0;
 	job_t ji = NULL, jn = NULL;
-	if( should_proceed ) {
-		jobmgr_log(jm, LOG_DEBUG, "Doing third phase of garbage collection.");
-		uint32_t unkilled_cnt = 0;
-		LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) {
-			if( jm->parentmgr ) {
-				job_log(ji, LOG_DEBUG, "Examining...");
-			}
-			if( ji->hopefully_exits_last ) {
-				bool active = job_active(ji);
-				if( active && !ji->stopped ) {
-					job_stop(ji);
-					
-					/* We may have sent SIGKILL to the job in job_stop(). */
-					unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
-				} else if( ji->stopped ) {
-					unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
-				} else if( !active ) {
-					job_remove(ji, false);
-				}
-			}
+	jobmgr_log(jm, LOG_DEBUG, "Doing third phase of garbage collection.");
+	LIST_FOREACH_SAFE( ji, &jm->jobs, sle, jn ) {
+		if( !ji->hopefully_exits_last ) {
+			continue;
 		}
 		
-		/* If we've killed everyone, move on. */
-		if( unkilled_cnt == 0 ) {
-			jm->killed_hopefully_last_jobs = true;
-			_jm = NULL;
+		bool active = job_active(ji);
+		if( active && !ji->stopped ) {
+			/* If the job is active and we haven't told it to stop yet, stop it. */
+			job_stop(ji);
+			
+			/* We may have sent SIGKILL to the job in job_stop(). In this case,
+			 * "clean" jobs should exit immediately, so we shouldn't have to wait
+			 * for them.
+			 */
+			unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
+		} else if( ji->stopped ) {
+			/* If the job is active and has been told to stop, disregard it
+			 * after we've sent SIGKILL.
+			 */
+			unkilled_cnt += !ji->sent_sigkill ? 1 : 0;
+		} else if( !active ) {
+			/* If the job was not active when shutdown began, remove it. */
+			job_remove(ji);
 		}
-	} else {
-		jm->killed_hopefully_last_jobs = true;
-		_jm = NULL;
 	}
 	
-	return _jm;
+	/* If we've killed everyone, move on. */
+	if( unkilled_cnt == 0 ) {
+		jm->killed_hopefully_first_jobs = true;
+		jm = NULL;
+	}
+	
+	return jm;
 }
 
 jobmgr_t
@@ -5439,22 +5564,12 @@
 		_jm = jobmgr_do_normal_shutdown_phase(jm) ? : jobmgr_do_hopefully_last_shutdown_phase(jm);
 	}
 	
-	if( !_jm ) {
+	if( !_jm && SLIST_EMPTY(&jm->submgrs) ) {
 		jobmgr_log(jm, LOG_NOTICE | LOG_CONSOLE, "Removing.");
 		jobmgr_log_stray_children(jm, false);
-		
-		job_t ji = NULL;
-		LIST_FOREACH( ji, &jm->jobs, sle ) {
-			if( pid1_magic && !jm->parentmgr ) {
-				if( (ji->anonymous || ji->sent_sigkill) && ji->p ) {
-					job_log(ji, LOG_NOTICE | LOG_CONSOLE, "Job should be gone but is not.");
-				}
-			} else if( !pid1_magic && !ji->anonymous ) {
-				job_log(ji, LOG_NOTICE | LOG_CONSOLE, "Job should be gone but is not.");
-			}
-		}
-		
-		jobmgr_remove(jm, total_children != 0);
+		jobmgr_remove(jm);
+	} else {
+		_jm = jm;
 	}
 	
 	return _jm;
@@ -5692,7 +5807,7 @@
 
 out_bad:
 	if (jmr) {
-		jobmgr_remove(jmr, false);
+		jobmgr_remove(jmr);
 	}
 	return NULL;
 }
@@ -5724,6 +5839,11 @@
 		#if TARGET_OS_EMBEDDED
 			bootstrapper->stderrpath = strdup(_PATH_CONSOLE);
 		#endif
+		
+		#if 0
+			/* Start the update job. */
+			jobmgr_assumes(jm, kevent_mod((uintptr_t)do_sync, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 30, bootstrapper) != -1);
+		#endif
 		}
 	}
 	
@@ -6530,7 +6650,7 @@
 			return BOOTSTRAP_NOT_PRIVILEGED;
 		}
 
-		job_remove(otherj, false);
+		job_remove(otherj);
 
 		if (do_block) {
 			job_log(j, LOG_DEBUG, "Blocking MIG return of job_remove(): %s", otherj->label);
@@ -6885,6 +7005,45 @@
 			g_shutdown_debugging = true;
 		}
 		break;
+		case VPROC_GSK_PERUSER_SUSPEND:
+			if( pid1_magic && ldc->euid == 0 ) {
+				mach_port_t junk = MACH_PORT_NULL;
+				job_t jpu = jobmgr_lookup_per_user_context_internal(j, (uid_t)inval, false, &junk);
+				if( jpu ) {
+					job_t ji = NULL;
+					LIST_FOREACH( ji, &j->suspended_perusers, suspended_peruser_sle ) {
+						if( (int64_t)(ji->mach_uid) == inval ) {
+							job_log(j, LOG_WARNING, "Job tried to suspend per-user launchd for UID %u twice.", ji->mach_uid);
+							break;
+						}
+					}
+					
+					if( ji == NULL ) {
+						jpu->peruser_suspend_count++;
+						LIST_INSERT_HEAD(&j->suspended_perusers, jpu, suspended_peruser_sle);					
+						job_stop(jpu);
+					}
+				}
+			}
+			break;
+		case VPROC_GSK_PERUSER_RESUME:
+			if( pid1_magic && ldc->euid == 0 ) {
+				job_t ji = NULL, jt = NULL;
+				LIST_FOREACH_SAFE( ji, &j->suspended_perusers, suspended_peruser_sle, jt ) {
+					if( (int64_t)(ji->mach_uid) == inval ) {
+						ji->peruser_suspend_count--;
+						LIST_REMOVE(ji, suspended_peruser_sle);
+						break;
+					}
+				}
+				
+				if( ji == NULL ) {
+					job_log(j, LOG_WARNING, "Job tried to resume per-user launchd for UID %llu that it did not suspend.", inval);
+				} else if( ji->peruser_suspend_count == 0 ) {
+					job_dispatch(ji, false);
+				}
+			}
+			break;
 	case 0:
 		break;
 	default:
@@ -7014,29 +7173,10 @@
 	return 0;
 }
 
-kern_return_t
-job_mig_lookup_per_user_context(job_t j, uid_t which_user, mach_port_t *up_cont)
+job_t
+jobmgr_lookup_per_user_context_internal(job_t j, uid_t which_user, bool dispatch, mach_port_t *mp)
 {
-	struct ldcred *ldc = runtime_get_caller_creds();
-	job_t ji;
-
-	if (!launchd_assumes(j != NULL)) {
-		return BOOTSTRAP_NO_MEMORY;
-	}
-
-	job_log(j, LOG_DEBUG, "Looking up per user launchd for UID: %u", which_user);
-
-	if (unlikely(!pid1_magic)) {
-		job_log(j, LOG_ERR, "Only PID 1 supports per user launchd lookups.");
-		return BOOTSTRAP_NOT_PRIVILEGED;
-	}
-
-	if (ldc->euid || ldc->uid) {
-		which_user = ldc->euid ?: ldc->uid;
-	}
-
-	*up_cont = MACH_PORT_NULL;
-
+	job_t ji = NULL;
 	LIST_FOREACH(ji, &root_jobmgr->jobs, sle) {
 		if (!ji->per_user) {
 			continue;
@@ -7052,42 +7192,65 @@
 		}
 		break;
 	}
-
-	if (unlikely(ji == NULL)) {
+	
+	if( unlikely(ji == NULL) ) {
 		struct machservice *ms;
 		char lbuf[1024];
-
+		
 		job_log(j, LOG_DEBUG, "Creating per user launchd job for UID: %u", which_user);
-
+		
 		sprintf(lbuf, "com.apple.launchd.peruser.%u", which_user);
-
+		
 		ji = job_new(root_jobmgr, lbuf, "/sbin/launchd", NULL);
-
-		if (ji == NULL) {
-			return BOOTSTRAP_NO_MEMORY;
+		
+		if( ji != NULL ) {
+			ji->mach_uid = which_user;
+			ji->per_user = true;
+			ji->kill_via_shmem = true;
+			
+			if ((ms = machservice_new(ji, lbuf, mp, false)) == NULL) {
+				job_remove(ji);
+				ji = NULL;
+			} else {
+				ms->per_user_hack = true;
+				ms->hide = true;
+				
+				ji = dispatch ? job_dispatch(ji, false) : ji;
+			}
 		}
-
-		ji->mach_uid = which_user;
-		ji->per_user = true;
-		ji->kill_via_shmem = true;
-
-		if ((ms = machservice_new(ji, lbuf, up_cont, false)) == NULL) {
-			job_remove(ji, false);
-			return BOOTSTRAP_NO_MEMORY;
-		}
-
-		ms->per_user_hack = true;
-		ms->hide = true;
-
-		ji = job_dispatch(ji, false);
 	} else {
+		*mp = machservice_port(SLIST_FIRST(&ji->machservices));
 		job_log(j, LOG_DEBUG, "Per user launchd job found for UID: %u", which_user);
 	}
+	
+	return ji;
+}
 
-	if (job_assumes(j, ji != NULL)) {
-		*up_cont = machservice_port(SLIST_FIRST(&ji->machservices));
+kern_return_t
+job_mig_lookup_per_user_context(job_t j, uid_t which_user, mach_port_t *up_cont)
+{
+	struct ldcred *ldc = runtime_get_caller_creds();
+	job_t jpu;
+	
+	if (!launchd_assumes(j != NULL)) {
+		return BOOTSTRAP_NO_MEMORY;
 	}
-
+	
+	job_log(j, LOG_DEBUG, "Looking up per user launchd for UID: %u", which_user);
+	
+	if (unlikely(!pid1_magic)) {
+		job_log(j, LOG_ERR, "Only PID 1 supports per user launchd lookups.");
+		return BOOTSTRAP_NOT_PRIVILEGED;
+	}
+	
+	if (ldc->euid || ldc->uid) {
+		which_user = ldc->euid ?: ldc->uid;
+	}
+	
+	*up_cont = MACH_PORT_NULL;
+	
+	jpu = jobmgr_lookup_per_user_context_internal(j, which_user, true, up_cont);
+	
 	return 0;
 }
 
@@ -7136,7 +7299,6 @@
 			job_log(j, LOG_WARNING, "Check-in of Mach service failed. Already active: %s", servicename);
 			return BOOTSTRAP_SERVICE_ACTIVE;
 		}
-		
 	}
 
 	job_checkin(j);
@@ -7726,11 +7888,14 @@
 	 */
 	if( flags & LAUNCH_GLOBAL_ON_DEMAND ) {
 		/* This is so awful. */
-		mach_port_t mp = MACH_PORT_NULL;
-		name_t target_name;
-		strlcpy(target_name, jmr->name, sizeof(target_name));
+		/* Remove the job from its current job manager. */
+		LIST_REMOVE(j, sle);
+		LIST_REMOVE(j, pid_hash_sle);
+
+		/* Put the job into the target job manager. */
+		LIST_INSERT_HEAD(&jmr->jobs, j, sle);
+		LIST_INSERT_HEAD(&jmr->active_jobs[ACTIVE_JOB_HASH(j->p)], j, pid_hash_sle);
 		
-		kr = job_mig_switch_to_session(j, MACH_PORT_NULL, target_name, &mp);
 		if( job_assumes(j, kr == KERN_SUCCESS) ) {
 			job_set_global_on_demand(j, true);
 		} else {
@@ -8164,6 +8329,27 @@
 }
 
 kern_return_t
+job_mig_wait2(job_t j, job_t target_j, mach_port_t srp, integer_t *status __attribute__((unused)))
+{
+	if( !launchd_assumes(j != NULL) ) {
+		return BOOTSTRAP_NO_MEMORY;
+	}
+	if( !launchd_assumes(target_j != NULL) ) {
+		return BOOTSTRAP_NO_MEMORY;
+	}
+	
+	if( target_j->p == 0 ) {
+		return BOOTSTRAP_SUCCESS;
+	}
+	
+	if( !job_assumes(j, waiting4exit_new(target_j, srp) == true) ) {
+		return BOOTSTRAP_NO_MEMORY;
+	}
+	
+	return MIG_NO_REPLY;
+}
+
+kern_return_t
 job_mig_uncork_fork(job_t j)
 {
 	if (!launchd_assumes(j != NULL)) {
@@ -8249,12 +8435,12 @@
 	}
 
 	if (!job_assumes(jr, jr->p)) {
-		job_remove(jr, false);
+		job_remove(jr);
 		return BOOTSTRAP_NO_MEMORY;
 	}
 
 	if (!job_setup_machport(jr)) {
-		job_remove(jr, false);
+		job_remove(jr);
 		return BOOTSTRAP_NO_MEMORY;
 	}
 
@@ -8412,6 +8598,29 @@
 	free(w4r);
 }
 
+bool 
+waiting4exit_new(job_t j, mach_port_t rp)
+{
+	struct waiting_for_exit *w4e = NULL;
+	if( !job_assumes(j, (w4e = malloc(sizeof(struct waiting_for_exit))) != NULL) ) {
+		return false;
+	}
+	
+	w4e->rp = rp;
+	LIST_INSERT_HEAD(&j->exit_watchers, w4e, sle);
+	
+	return true;
+}
+
+void
+waiting4exit_delete(job_t j, struct waiting_for_exit *w4e)
+{
+	job_assumes(j, job_mig_wait2_reply(w4e->rp, KERN_SUCCESS, j->last_exit_status) == KERN_SUCCESS);
+	LIST_REMOVE(w4e, sle);
+	
+	free(w4e);
+}
+
 size_t
 get_kern_max_proc(void)
 {

Modified: trunk/launchd/src/launchd_core_logic.h
===================================================================
--- trunk/launchd/src/launchd_core_logic.h	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchd_core_logic.h	2009-02-11 01:24:50 UTC (rev 23794)
@@ -47,7 +47,7 @@
 launch_data_t job_export(job_t j);
 void job_stop(job_t j);
 void job_checkin(job_t j);
-void job_remove(job_t j, bool force);
+void job_remove(job_t j);
 job_t job_import(launch_data_t pload);
 launch_data_t job_import_bulk(launch_data_t pload);
 job_t job_mig_intran(mach_port_t mp);

Modified: trunk/launchd/src/launchd_runtime.c
===================================================================
--- trunk/launchd/src/launchd_runtime.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchd_runtime.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -90,7 +90,6 @@
 static void mportset_callback(void);
 static kq_callback kqmportset_callback = (kq_callback)mportset_callback;
 static void *kqueue_demand_loop(void *arg);
-static void log_kevent_struct(int level, struct kevent *kev_base, int indx);
 
 boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
 static void record_caller_creds(mach_msg_header_t *mh);
@@ -134,6 +133,7 @@
 bool g_flat_mach_namespace = true;
 bool g_simulate_pid1_crash = false;
 bool g_use_gmalloc = false;
+pid_t g_wsp = 0;
 
 mach_port_t
 runtime_get_kernel_port(void)
@@ -536,11 +536,11 @@
 #if 0
 			if (launchd_assumes(kev.udata != NULL)) {
 #endif
-				log_kevent_struct(LOG_DEBUG, &kev, 0);
+				log_kevent_struct(LOG_DEBUG, &kev, i);
 				(*((kq_callback *)kev.udata))(kev.udata, &kev);
 #if 0
 			} else {
-				log_kevent_struct(LOG_ERR, &kev, 0);
+				log_kevent_struct(LOG_ERR, &kev, i);
 			}
 #endif
 			/* the callback may have tainted our ability to continue this for loop */
@@ -586,30 +586,29 @@
 	bulk_kev = kev;
 
 	if (launchd_assumes((bulk_kev_cnt = kevent(fd, NULL, 0, kev, BULK_KEV_MAX, &ts)) != -1)) {
+	#if 0	
 		for (i = 0; i < bulk_kev_cnt; i++) {
 			log_kevent_struct(LOG_DEBUG, kev, i);
 		}
+	#endif
 		for (i = 0; i < bulk_kev_cnt; i++) {
 			bulk_kev_i = i;
 			kevi = &kev[i];
 
 			if (kevi->filter) {
-#if 0
 				Dl_info dli;
 
 				/* Check if kevi->udata was either malloc(3)ed or is a valid function pointer. 
 				 * If neither, it's probably an invalid pointer and we should log it. 
 				 */
 				if (launchd_assumes(malloc_size(kevi->udata) || dladdr(kevi->udata, &dli))) {
-#endif
-				runtime_ktrace(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_START, kevi->ident, kevi->filter, kevi->fflags);
-				(*((kq_callback *)kevi->udata))(kevi->udata, kevi);
-				runtime_ktrace0(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_END);
-#if 0
+					runtime_ktrace(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_START, kevi->ident, kevi->filter, kevi->fflags);
+					(*((kq_callback *)kevi->udata))(kevi->udata, kevi);
+					runtime_ktrace0(RTKT_LAUNCHD_BSD_KEVENT|DBG_FUNC_END);
 				} else {
+					runtime_syslog(LOG_ERR, "The following kevent had invalid context data.");
 					log_kevent_struct(LOG_EMERG, kevi, i);
 				}
-#endif
 			}
 		}
 	}
@@ -619,8 +618,6 @@
 	return 0;
 }
 
-
-
 void
 launchd_runtime(void)
 {
@@ -852,6 +849,14 @@
 	if (flags & EV_ADD && !launchd_assumes(udata != NULL)) {
 		errno = EINVAL;
 		return -1;
+	} else if( (flags & EV_DELETE) && bulk_kev ) {
+		int i = 0;
+		for( i = bulk_kev_i + 1; i < bulk_kev_cnt; i++ ) {
+			if( bulk_kev[i].filter == filter && bulk_kev[i].ident == ident ) {
+				runtime_syslog(LOG_DEBUG, "Skipping PROC event for PID %lu", ident);
+				bulk_kev[i].filter = 0;
+			}
+		}
 	}
 
 	EV_SET(&kev, ident, filter, flags, fflags, data, udata);
@@ -1349,7 +1354,31 @@
 
 	offset = (void *)*outval;
 
+#if 0
+	if( !ourlogfile && !pid1_magic && shutdown_in_progress ) {
+		char logfile[NAME_MAX];
+		snprintf(logfile, sizeof(logfile), "/var/tmp/launchd-%s.shutdown.log", g_username);
+		
+		char logfile1[NAME_MAX];
+		snprintf(logfile1, sizeof(logfile1), "/var/tmp/launchd-%s.shutdown.log.1", g_username);
+		
+		rename(logfile, logfile1);
+		ourlogfile = fopen(logfile, "a");
+	}
+#endif
+
+	static int64_t shutdown_start = 0;
+	if( shutdown_start == 0 ) {
+		shutdown_start = runtime_get_wall_time();
+	}
+
 	while ((lm = STAILQ_FIRST(&logmsg_queue))) {
+		int64_t log_delta = lm->when - shutdown_start;
+		if( !pid1_magic && ourlogfile ) {
+			fprintf(ourlogfile, "%8lld%6u %-40s%6u %-40s %s\n", log_delta,
+					lm->from_pid, lm->from_name, lm->about_pid, lm->about_name, lm->msg);
+		}
+
 		lm->from_name_offset = lm->from_name - (char *)lm;
 		lm->about_name_offset = lm->about_name - (char *)lm;
 		lm->msg_offset = lm->msg - (char *)lm;
@@ -1361,6 +1390,10 @@
 
 		logmsg_remove(lm);
 	}
+	
+	if( ourlogfile ) {
+		fflush(ourlogfile);
+	}
 
 	return 0;
 }
@@ -1447,6 +1480,8 @@
 
 		logmsg_remove(lm);
 	}
+	
+	fflush(ourlogfile);
 }
 
 kern_return_t
@@ -1533,6 +1568,11 @@
 {
 	if (!pid1_magic) {
 	#if !TARGET_OS_EMBEDDED
+		if( _vproc_transaction_count() == 0 ) {
+			runtime_syslog(LOG_NOTICE, "Exiting cleanly.");
+		}
+		
+		runtime_closelog();
 		_vproc_transaction_end();
 	#endif
 	}

Modified: trunk/launchd/src/launchd_runtime.h
===================================================================
--- trunk/launchd/src/launchd_runtime.h	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchd_runtime.h	2009-02-11 01:24:50 UTC (rev 23794)
@@ -103,6 +103,7 @@
 extern char g_username[128];
 extern bool g_shutdown_debugging;
 extern bool g_use_gmalloc;
+extern pid_t g_wsp;
 
 mach_port_t runtime_get_kernel_port(void);
 extern boolean_t launchd_internal_demux(mach_msg_header_t *Request, mach_msg_header_t *Reply);
@@ -134,6 +135,7 @@
 
 int kevent_bulk_mod(struct kevent *kev, size_t kev_cnt);
 int kevent_mod(uintptr_t ident, short filter, u_short flags, u_int fflags, intptr_t data, void *udata);
+void log_kevent_struct(int level, struct kevent *kev_base, int indx);
 
 pid_t runtime_fork(mach_port_t bsport);
 

Modified: trunk/launchd/src/launchd_unix_ipc.c
===================================================================
--- trunk/launchd/src/launchd_unix_ipc.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchd_unix_ipc.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -393,7 +393,7 @@
 		resp = launch_data_new_errno(errno);
 	} else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) {
 		if ((j = job_find(launch_data_get_string(data))) != NULL) {
-			job_remove(j, false);
+			job_remove(j);
 			errno = 0;
 		}
 		resp = launch_data_new_errno(errno);

Modified: trunk/launchd/src/launchproxy.c
===================================================================
--- trunk/launchd/src/launchproxy.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/launchproxy.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -156,7 +156,7 @@
 		if ((r = accept((int)kev.ident, (struct sockaddr *)&ss, &slen)) == -1) {
 			if (errno == EWOULDBLOCK)
 				continue;
-			syslog(LOG_DEBUG, "accept(): %m");
+			syslog(LOG_WARNING, "accept(): %m");
 			goto out;
 		} else {
 			if (ss.ss_family == AF_INET || ss.ss_family == AF_INET6) {
@@ -181,6 +181,9 @@
 			switch (fork()) {
 			case -1:
 				syslog(LOG_WARNING, "fork(): %m");
+				if( errno != ENOMEM ) {
+					continue;
+				}
 				goto out;
 			case 0:
 				break;

Modified: trunk/launchd/src/libvproc.c
===================================================================
--- trunk/launchd/src/libvproc.c	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/libvproc.c	2009-02-11 01:24:50 UTC (rev 23794)
@@ -467,15 +467,7 @@
 		return (vproc_err_t)_vprocmgr_move_subset_to_user;
 	}
 
-	/* Don't do things like setting up the exception port if we're tainted. This is primarily for
-	 * loginwindow, which setuid(2)s to the logged-in user but still runs under the system Mach
-	 * bootstrap. So we want the system launchd to spawn a crash reporter to report loginwindow
-	 * crashes, not the per-user launchd, since that session will be wiped out of there is a loginwindow
-	 * crash.
-	 *
-	 * See rdar://problem/5947376 for more details.
-	 */
-	return !issetugid() ? _vproc_post_fork_ping() : NULL;
+	return _vproc_post_fork_ping();
 }
 
 vproc_err_t
@@ -831,6 +823,21 @@
 			/* Once you're in the transaction model, you're in for good. Like the Mafia. */
 			s_cached_transactions_enabled = 1;
 			break;
+		case VPROC_GSK_PERUSER_SUSPEND:
+			{
+				char peruser_label[NAME_MAX];
+				snprintf(peruser_label, NAME_MAX - 1, "com.apple.launchd.peruser.%u", (uid_t)*inval);
+				
+				vproc_t pu_vp = vprocmgr_lookup_vproc(peruser_label);
+				if( pu_vp ) {
+					int status = 0;
+					kern_return_t kr = vproc_mig_wait2(bootstrap_port, pu_vp->j_port, &status);
+					vproc_release(pu_vp);
+					
+					syslog(LOG_DEBUG, "%u's suspended launchd exited with status %i (kr = 0x%x).", (uid_t)*inval, status, kr);
+				}
+			}
+			break;
 		default:
 			break;
 		}

Modified: trunk/launchd/src/protocol_job_reply.defs
===================================================================
--- trunk/launchd/src/protocol_job_reply.defs	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/protocol_job_reply.defs	2009-02-11 01:24:50 UTC (rev 23794)
@@ -78,3 +78,26 @@
 		__r_port	: mach_port_move_send_once_t;
 		__result	: kern_return_t, RetCode;
 		__outval	: pointer_t);
+
+skip; /* log_forward */
+
+skip; /* kickstart */
+
+skip; /* embedded_wait */
+
+skip; /* lookup_children */
+
+skip; /* switch_to_session */
+
+skip; /* transaction_count_for_pid */
+				
+skip; /* pid_is_managed */
+
+skip; /* port_for_label */
+
+skip; /* init_session */
+
+simpleroutine job_mig_wait2_reply(
+		__r_port	: mach_port_move_send_once_t;
+		__result	: kern_return_t, RetCode;
+		__waitval	: integer_t);

Modified: trunk/launchd/src/protocol_vproc.defs
===================================================================
--- trunk/launchd/src/protocol_vproc.defs	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/protocol_vproc.defs	2009-02-11 01:24:50 UTC (rev 23794)
@@ -209,3 +209,9 @@
 routine init_session(
 				__bs_port			: job_t;
 				__session_name		: name_t);
+
+routine wait2(
+				__bs_port			: job_t;
+				__target_port		: job_t;
+sreplyport		__rport				: mach_port_make_send_once_t;
+out				__waitval			: integer_t);

Modified: trunk/launchd/src/vproc_priv.h
===================================================================
--- trunk/launchd/src/vproc_priv.h	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd/src/vproc_priv.h	2009-02-11 01:24:50 UTC (rev 23794)
@@ -62,6 +62,8 @@
 	VPROC_GSK_WEIRD_BOOTSTRAP,
 	VPROC_GSK_WAITFORDEBUGGER,
 	VPROC_GSK_SHUTDOWN_DEBUGGING,
+	VPROC_GSK_PERUSER_SUSPEND,
+	VPROC_GSK_PERUSER_RESUME,
 } vproc_gsk_t;
 
 typedef unsigned int vproc_flags_t;

Modified: trunk/launchd.xcodeproj/project.pbxproj
===================================================================
--- trunk/launchd.xcodeproj/project.pbxproj	2009-02-02 21:35:16 UTC (rev 23793)
+++ trunk/launchd.xcodeproj/project.pbxproj	2009-02-11 01:24:50 UTC (rev 23794)
@@ -51,9 +51,11 @@
 
 /* Begin PBXBuildFile section */
 		4B9EDCA20EAFC77E00A78496 /* DiskArbitration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B9EDCA10EAFC77E00A78496 /* DiskArbitration.framework */; };
+		7215DE4C0EFAF2EC00ABD81E /* libauditd.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */; };
 		721FBEBC0EA7AE2F0057462B /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 721FBEBB0EA7AE2F0057462B /* Security.framework */; };
 		726055EC0EA7EC2400D65FE7 /* mach_exc.defs in Sources */ = {isa = PBXBuildFile; fileRef = FC36291F0E9349410054F1A3 /* mach_exc.defs */; settings = {ATTRIBUTES = (Server, ); }; };
 		726056090EA7FCF200D65FE7 /* launchd_ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */; };
+		72AFE8090EFAF3D9004BDA46 /* libauditd.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */; };
 		72FDB15F0EA7D7B200B2AC84 /* launchd_ktrace.c in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */; };
 		72FDB1C00EA7E21C00B2AC84 /* protocol_job_forward.defs in Sources */ = {isa = PBXBuildFile; fileRef = 72FDB1BF0EA7E21C00B2AC84 /* protocol_job_forward.defs */; };
 		FC3627BA0E9343220054F1A3 /* StartupItems.c in Sources */ = {isa = PBXBuildFile; fileRef = FC59A0FD0E8C8ADF00D41150 /* StartupItems.c */; };
@@ -288,6 +290,7 @@
 
 /* Begin PBXFileReference section */
 		4B9EDCA10EAFC77E00A78496 /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = /System/Library/Frameworks/DiskArbitration.framework; sourceTree = "<absolute>"; };
+		7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libauditd.dylib; path = /usr/lib/libauditd.dylib; sourceTree = "<absolute>"; };
 		721FBEA50EA7ABC40057462B /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = config.h; path = launchd/src/config.h; sourceTree = "<group>"; };
 		721FBEBB0EA7AE2F0057462B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = "<absolute>"; };
 		72FDB15D0EA7D7B200B2AC84 /* launchd_ktrace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = launchd_ktrace.c; path = launchd/src/launchd_ktrace.c; sourceTree = "<group>"; };
@@ -363,6 +366,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				FC36292D0E934AA40054F1A3 /* libbsm.dylib in Frameworks */,
+				7215DE4C0EFAF2EC00ABD81E /* libauditd.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -380,6 +384,7 @@
 				FCC841CC0EA7138700C01666 /* IOKit.framework in Frameworks */,
 				FC3628080E9345E10054F1A3 /* CoreFoundation.framework in Frameworks */,
 				FCD713740E95DE49001B0111 /* libedit.dylib in Frameworks */,
+				72AFE8090EFAF3D9004BDA46 /* libauditd.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -471,6 +476,7 @@
 				4B9EDCA10EAFC77E00A78496 /* DiskArbitration.framework */,
 				721FBEBB0EA7AE2F0057462B /* Security.framework */,
 				FC36292C0E934AA40054F1A3 /* libbsm.dylib */,
+				7215DE4B0EFAF2EC00ABD81E /* libauditd.dylib */,
 				FCD713730E95DE49001B0111 /* libedit.dylib */,
 				FC3628070E9345E10054F1A3 /* CoreFoundation.framework */,
 				FC36283E0E93463C0054F1A3 /* IOKit.framework */,
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/launchd-changes/attachments/20090210/482ca41b/attachment-0001.html>


More information about the launchd-changes mailing list