+void
+proc_dirty_start(struct proc *p)
+{
+ proc_lock(p);
+ while (p->p_dirty & P_DIRTY_BUSY) {
+ msleep(&p->p_dirty, &p->p_mlock, 0, "proc_dirty_start", NULL);
+ }
+ p->p_dirty |= P_DIRTY_BUSY;
+ proc_unlock(p);
+}
+
+void
+proc_dirty_end(struct proc *p)
+{
+ proc_lock(p);
+ if (p->p_dirty & P_DIRTY_BUSY) {
+ p->p_dirty &= ~P_DIRTY_BUSY;
+ wakeup(&p->p_dirty);
+ }
+ proc_unlock(p);
+}
+
+static boolean_t
+proc_validate_track_flags(uint32_t pcontrol, struct proc *target_p) {
+ /* Check idle exit isn't specified independently */
+ if ((pcontrol & PROC_DIRTY_TRACK_MASK) == PROC_DIRTY_ALLOW_IDLE_EXIT) {
+ return false;
+ }
+
+ /* See that the process isn't marked for termination */
+ if (target_p->p_dirty & P_DIRTY_TERMINATED) {
+ return false;
+ }
+
+ return true;
+}
+
+int
+proc_dirtycontrol(int pid, int flavor, uint64_t arg, int32_t *retval) {
+ struct proc *target_p;
+ int error = 0;
+ uint32_t pcontrol = (uint32_t)arg;
+ kauth_cred_t my_cred, target_cred;
+ boolean_t self = FALSE;
+ boolean_t child = FALSE;
+ pid_t selfpid;
+
+ target_p = proc_find(pid);
+ if (target_p == PROC_NULL) {
+ return(ESRCH);
+ }
+
+ my_cred = kauth_cred_get();
+ target_cred = kauth_cred_proc_ref(target_p);
+
+ selfpid = proc_selfpid();
+ if (pid == selfpid) {
+ self = TRUE;
+ } else if (target_p->p_ppid == selfpid) {
+ child = TRUE;
+ }
+
+ switch (flavor) {
+ case PROC_DIRTYCONTROL_TRACK: {
+ /* Only allow the process itself, its parent, or root */
+ if ((self == FALSE) && (child == FALSE) && kauth_cred_issuser(kauth_cred_get()) != TRUE) {
+ error = EPERM;
+ goto out;
+ }
+
+ proc_dirty_start(target_p);
+
+ if (proc_validate_track_flags(pcontrol, target_p)) {
+ /* Cumulative, as per <rdar://problem/11159924> */
+ target_p->p_dirty |=
+ ((pcontrol & PROC_DIRTY_TRACK) ? P_DIRTY_TRACK : 0) |
+ ((pcontrol & PROC_DIRTY_ALLOW_IDLE_EXIT) ? P_DIRTY_ALLOW_IDLE_EXIT : 0);
+#if CONFIG_MEMORYSTATUS
+ if ((target_p->p_dirty & P_DIRTY_CAN_IDLE_EXIT) == P_DIRTY_CAN_IDLE_EXIT) {
+ memorystatus_on_track_dirty(pid, TRUE);
+ }
+#endif
+ } else {
+ error = EINVAL;
+ }
+
+ proc_dirty_end(target_p);
+ }
+ break;
+
+ case PROC_DIRTYCONTROL_SET: {
+ boolean_t kill = false;
+
+ /* Check privileges; use cansignal() here since the process could be terminated */
+ if (!cansignal(current_proc(), my_cred, target_p, SIGKILL, 0)) {
+ error = EPERM;
+ goto out;
+ }
+
+ proc_dirty_start(target_p);
+
+ if (!(target_p->p_dirty & P_DIRTY_TRACK)) {
+ /* Dirty tracking not enabled */
+ error = EINVAL;
+ } else if (pcontrol && (target_p->p_dirty & P_DIRTY_TERMINATED)) {
+ /*
+ * Process is set to be terminated and we're attempting to mark it dirty.
+ * Set for termination and marking as clean is OK - see <rdar://problem/10594349>.
+ */
+ error = EBUSY;
+ } else {
+ int flag = (self == TRUE) ? P_DIRTY : P_DIRTY_SHUTDOWN;
+ if (pcontrol && !(target_p->p_dirty & flag)) {
+ target_p->p_dirty |= flag;
+ } else if ((pcontrol == 0) && (target_p->p_dirty & flag)) {
+ if ((flag == P_DIRTY_SHUTDOWN) && (!target_p->p_dirty & P_DIRTY)) {
+ /* Clearing the dirty shutdown flag, and the process is otherwise clean - kill */
+ target_p->p_dirty |= P_DIRTY_TERMINATED;
+ kill = true;
+ } else if ((flag == P_DIRTY) && (target_p->p_dirty & P_DIRTY_TERMINATED)) {
+ /* Kill previously terminated processes if set clean */
+ kill = true;
+ }
+ target_p->p_dirty &= ~flag;
+ } else {
+ /* Already set */
+ error = EALREADY;
+ }
+ }
+#if CONFIG_MEMORYSTATUS
+ if ((error == 0) && ((target_p->p_dirty & P_DIRTY_CAN_IDLE_EXIT) == P_DIRTY_CAN_IDLE_EXIT)) {
+ memorystatus_on_dirty(pid, pcontrol ? TRUE : FALSE);
+ }
+#endif
+ proc_dirty_end(target_p);
+
+ if ((error == 0) && (kill == true)) {
+ psignal(target_p, SIGKILL);
+ }
+ }
+ break;
+
+ case PROC_DIRTYCONTROL_GET: {
+ /* No permissions check - dirty state is freely available */
+ if (retval) {
+ proc_dirty_start(target_p);
+
+ *retval = 0;
+ if (target_p->p_dirty & P_DIRTY_TRACK) {
+ *retval |= PROC_DIRTY_TRACKED;
+ if (target_p->p_dirty & P_DIRTY_ALLOW_IDLE_EXIT) {
+ *retval |= PROC_DIRTY_ALLOWS_IDLE_EXIT;
+ }
+ if (target_p->p_dirty & P_DIRTY) {
+ *retval |= PROC_DIRTY_IS_DIRTY;
+ }
+ }
+
+ proc_dirty_end(target_p);
+ } else {
+ error = EINVAL;
+ }
+ }
+ break;
+ }
+
+out:
+ proc_rele(target_p);
+ kauth_cred_unref(&target_cred);
+
+ return(error);
+}
+
+/*
+ * proc_terminate() provides support for sudden termination.
+ * SIGKILL is issued to tracked, clean processes; otherwise,
+ * SIGTERM is sent.
+ */
+
+int
+proc_terminate(int pid, int32_t *retval)
+{
+ int error = 0;
+ proc_t p;
+ kauth_cred_t uc = kauth_cred_get();
+ int sig;
+
+#if 0
+ /* XXX: Check if these are necessary */
+ AUDIT_ARG(pid, pid);
+ AUDIT_ARG(signum, sig);
+#endif
+
+ if (pid <= 0 || retval == NULL) {
+ return (EINVAL);
+ }
+
+ if ((p = proc_find(pid)) == NULL) {
+ return (ESRCH);
+ }
+
+#if 0
+ /* XXX: Check if these are necessary */
+ AUDIT_ARG(process, p);
+#endif
+
+ /* Check privileges; if SIGKILL can be issued, then SIGTERM is also OK */
+ if (!cansignal(current_proc(), uc, p, SIGKILL, 0)) {
+ error = EPERM;
+ goto out;
+ }
+
+ proc_dirty_start(p);
+
+ p->p_dirty |= P_DIRTY_TERMINATED;
+
+ if ((p->p_dirty & (P_DIRTY_TRACK|P_DIRTY_IS_DIRTY)) == P_DIRTY_TRACK) {
+ /* Clean; mark as terminated and issue SIGKILL */
+ sig = SIGKILL;
+ } else {
+ /* Dirty, terminated, or state tracking is unsupported; issue SIGTERM to allow cleanup */
+ sig = SIGTERM;
+ }
+
+ proc_dirty_end(p);
+
+ proc_removethrottle(p);
+
+ psignal(p, sig);
+ *retval = sig;
+
+out:
+ proc_rele(p);
+
+ return error;
+}
+
+void
+proc_removethrottle(proc_t p)
+
+{
+ /* remove throttled states in all threads; process is going to terminate soon */
+ proc_lock(p);
+
+ /* if already marked marked for proc_termiantion.. */
+ if ((p->p_lflag & P_LPTERMINATE) != 0) {
+ proc_unlock(p);
+ return;
+ }
+ p->p_lflag |= P_LPTERMINATE;
+ proc_unlock(p);
+
+ (void)proc_task_remove_throttle(p->task);
+
+}
+