xref: /xnu-8796.101.5/osfmk/kern/coalition.c (revision aca3beaa3dfbd42498b42c5e5ce20a938e6554e5)
1 /*
2  * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #include <kern/kern_types.h>
30 #include <mach/mach_types.h>
31 #include <mach/boolean.h>
32 
33 #include <kern/coalition.h>
34 #include <kern/exc_resource.h>
35 #include <kern/host.h>
36 #include <kern/ledger.h>
37 #include <kern/mach_param.h> /* for TASK_CHUNK */
38 #if MONOTONIC
39 #include <kern/monotonic.h>
40 #endif /* MONOTONIC */
41 #include <kern/policy_internal.h>
42 #include <kern/task.h>
43 #include <kern/smr_hash.h>
44 #include <kern/thread_group.h>
45 #include <kern/zalloc.h>
46 #include <vm/vm_pageout.h>
47 
48 #include <libkern/OSAtomic.h>
49 
50 #include <mach/coalition_notification_server.h>
51 #include <mach/host_priv.h>
52 #include <mach/host_special_ports.h>
53 
54 #include <os/log.h>
55 
56 #include <sys/errno.h>
57 
58 /*
59  * BSD interface functions
60  */
61 size_t coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, size_t list_sz);
62 coalition_t task_get_coalition(task_t task, int type);
63 boolean_t coalition_is_leader(task_t task, coalition_t coal);
64 task_t coalition_get_leader(coalition_t coal);
65 int coalition_get_task_count(coalition_t coal);
66 uint64_t coalition_get_page_count(coalition_t coal, int *ntasks);
67 int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
68     int *pid_list, int list_sz);
69 
70 /* defined in task.c */
71 extern ledger_template_t task_ledger_template;
72 
73 /*
74  * Templates; task template is copied due to potential allocation limits on
75  * task ledgers.
76  */
77 ledger_template_t coalition_task_ledger_template = NULL;
78 ledger_template_t coalition_ledger_template = NULL;
79 
80 extern int      proc_selfpid(void);
81 /*
82  * Coalition zone needs limits. We expect there will be as many coalitions as
83  * tasks (same order of magnitude), so use the task zone's limits.
84  * */
85 #define CONFIG_COALITION_MAX CONFIG_TASK_MAX
86 #define COALITION_CHUNK TASK_CHUNK
87 
88 #if DEBUG || DEVELOPMENT
89 TUNABLE_WRITEABLE(int, unrestrict_coalition_syscalls, "unrestrict_coalition_syscalls", 0);
90 #else
91 #define unrestrict_coalition_syscalls false
92 #endif
93 
94 static LCK_GRP_DECLARE(coalitions_lck_grp, "coalition");
95 
96 /* coalitions_list_lock protects coalition_hash, next_coalition_id. */
97 static LCK_MTX_DECLARE(coalitions_list_lock, &coalitions_lck_grp);
98 static uint64_t coalition_next_id = 1;
99 static struct smr_hash coalition_hash;
100 
101 coalition_t init_coalition[COALITION_NUM_TYPES];
102 coalition_t corpse_coalition[COALITION_NUM_TYPES];
103 
104 static const char *
coal_type_str(int type)105 coal_type_str(int type)
106 {
107 	switch (type) {
108 	case COALITION_TYPE_RESOURCE:
109 		return "RESOURCE";
110 	case COALITION_TYPE_JETSAM:
111 		return "JETSAM";
112 	default:
113 		return "<unknown>";
114 	}
115 }
116 
117 struct coalition_type {
118 	int type;
119 	int has_default;
120 	/*
121 	 * init
122 	 * pre-condition: coalition just allocated (unlocked), unreferenced,
123 	 *                type field set
124 	 */
125 	kern_return_t (*init)(coalition_t coal, boolean_t privileged, boolean_t efficient);
126 
127 	/*
128 	 * dealloc
129 	 * pre-condition: coalition unlocked
130 	 * pre-condition: coalition refcount=0, active_count=0,
131 	 *                termrequested=1, terminated=1, reaped=1
132 	 */
133 	void          (*dealloc)(coalition_t coal);
134 
135 	/*
136 	 * adopt_task
137 	 * pre-condition: coalition locked
138 	 * pre-condition: coalition !repead and !terminated
139 	 */
140 	kern_return_t (*adopt_task)(coalition_t coal, task_t task);
141 
142 	/*
143 	 * remove_task
144 	 * pre-condition: coalition locked
145 	 * pre-condition: task has been removed from coalition's task list
146 	 */
147 	kern_return_t (*remove_task)(coalition_t coal, task_t task);
148 
149 	/*
150 	 * set_taskrole
151 	 * pre-condition: coalition locked
152 	 * pre-condition: task added to coalition's task list,
153 	 *                active_count >= 1 (at least the given task is active)
154 	 */
155 	kern_return_t (*set_taskrole)(coalition_t coal, task_t task, int role);
156 
157 	/*
158 	 * get_taskrole
159 	 * pre-condition: coalition locked
160 	 * pre-condition: task added to coalition's task list,
161 	 *                active_count >= 1 (at least the given task is active)
162 	 */
163 	int (*get_taskrole)(coalition_t coal, task_t task);
164 
165 	/*
166 	 * iterate_tasks
167 	 * pre-condition: coalition locked
168 	 */
169 	void (*iterate_tasks)(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t));
170 };
171 
172 /*
173  * COALITION_TYPE_RESOURCE
174  */
175 
176 static kern_return_t i_coal_resource_init(coalition_t coal, boolean_t privileged, boolean_t efficient);
177 static void          i_coal_resource_dealloc(coalition_t coal);
178 static kern_return_t i_coal_resource_adopt_task(coalition_t coal, task_t task);
179 static kern_return_t i_coal_resource_remove_task(coalition_t coal, task_t task);
180 static kern_return_t i_coal_resource_set_taskrole(coalition_t coal,
181     task_t task, int role);
182 static int           i_coal_resource_get_taskrole(coalition_t coal, task_t task);
183 static void          i_coal_resource_iterate_tasks(coalition_t coal, void *ctx,
184     void (*callback)(coalition_t, void *, task_t));
185 
186 /*
187  * Ensure COALITION_NUM_THREAD_QOS_TYPES defined in mach/coalition.h still
188  * matches THREAD_QOS_LAST defined in mach/thread_policy.h
189  */
190 static_assert(COALITION_NUM_THREAD_QOS_TYPES == THREAD_QOS_LAST);
191 
192 struct i_resource_coalition {
193 	/*
194 	 * This keeps track of resource utilization of tasks that are no longer active
195 	 * in the coalition and is updated when a task is removed from the coalition.
196 	 */
197 	ledger_t ledger;
198 	uint64_t bytesread;
199 	uint64_t byteswritten;
200 	uint64_t energy;
201 	uint64_t gpu_time;
202 	uint64_t logical_immediate_writes;
203 	uint64_t logical_deferred_writes;
204 	uint64_t logical_invalidated_writes;
205 	uint64_t logical_metadata_writes;
206 	uint64_t logical_immediate_writes_to_external;
207 	uint64_t logical_deferred_writes_to_external;
208 	uint64_t logical_invalidated_writes_to_external;
209 	uint64_t logical_metadata_writes_to_external;
210 	uint64_t cpu_time_eqos[COALITION_NUM_THREAD_QOS_TYPES];      /* cpu time per effective QoS class */
211 	uint64_t cpu_time_rqos[COALITION_NUM_THREAD_QOS_TYPES];      /* cpu time per requested QoS class */
212 	uint64_t cpu_instructions;
213 	uint64_t cpu_cycles;
214 	struct recount_coalition co_recount;
215 
216 	uint64_t task_count;      /* tasks that have started in this coalition */
217 	uint64_t dead_task_count; /* tasks that have exited in this coalition;
218 	                           *  subtract from task_count to get count
219 	                           *  of "active" tasks */
220 	/*
221 	 * Count the length of time this coalition had at least one active task.
222 	 * This can be a 'denominator' to turn e.g. cpu_time to %cpu.
223 	 * */
224 	uint64_t last_became_nonempty_time;
225 	uint64_t time_nonempty;
226 
227 	queue_head_t tasks;         /* List of active tasks in the coalition */
228 	/*
229 	 * This ledger is used for triggering resource exception. For the tracked resources, this is updated
230 	 * when the member tasks' resource usage changes.
231 	 */
232 	ledger_t resource_monitor_ledger;
233 #if CONFIG_PHYS_WRITE_ACCT
234 	uint64_t fs_metadata_writes;
235 #endif /* CONFIG_PHYS_WRITE_ACCT */
236 };
237 
238 /*
239  * COALITION_TYPE_JETSAM
240  */
241 
242 static kern_return_t i_coal_jetsam_init(coalition_t coal, boolean_t privileged, boolean_t efficient);
243 static void          i_coal_jetsam_dealloc(coalition_t coal);
244 static kern_return_t i_coal_jetsam_adopt_task(coalition_t coal, task_t task);
245 static kern_return_t i_coal_jetsam_remove_task(coalition_t coal, task_t task);
246 static kern_return_t i_coal_jetsam_set_taskrole(coalition_t coal,
247     task_t task, int role);
248 int           i_coal_jetsam_get_taskrole(coalition_t coal, task_t task);
249 static void          i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx,
250     void (*callback)(coalition_t, void *, task_t));
251 
252 struct i_jetsam_coalition {
253 	task_t       leader;
254 	queue_head_t extensions;
255 	queue_head_t services;
256 	queue_head_t other;
257 	struct thread_group *thread_group;
258 	bool swap_enabled;
259 };
260 
261 
262 /*
263  * main coalition structure
264  */
265 struct coalition {
266 	uint64_t id;                /* monotonically increasing */
267 	uint32_t type;
268 	uint32_t role;              /* default task role (background, adaptive, interactive, etc) */
269 	os_ref_atomic_t ref_count;  /* Number of references to the memory containing this struct */
270 	uint32_t active_count;      /* Number of members of (tasks in) the
271 	                             *  coalition, plus vouchers referring
272 	                             *  to the coalition */
273 	uint32_t focal_task_count;   /* Number of TASK_FOREGROUND_APPLICATION tasks in the coalition */
274 	uint32_t nonfocal_task_count; /* Number of TASK_BACKGROUND_APPLICATION tasks in the coalition */
275 
276 	/* coalition flags */
277 	uint32_t privileged : 1;    /* Members of this coalition may create
278 	                             *  and manage coalitions and may posix_spawn
279 	                             *  processes into selected coalitions */
280 	/* ast? */
281 	/* voucher */
282 	uint32_t termrequested : 1; /* launchd has requested termination when coalition becomes empty */
283 	uint32_t terminated : 1;    /* coalition became empty and spawns are now forbidden */
284 	uint32_t reaped : 1;        /* reaped, invisible to userspace, but waiting for ref_count to go to zero */
285 	uint32_t notified : 1;      /* no-more-processes notification was sent via special port */
286 	uint32_t efficient : 1;     /* launchd has marked the coalition as efficient */
287 #if DEVELOPMENT || DEBUG
288 	uint32_t should_notify : 1; /* should this coalition send notifications (default: yes) */
289 #endif
290 
291 	struct smrq_slink link;     /* global list of coalitions */
292 
293 	lck_mtx_t lock;             /* Coalition lock. */
294 
295 	/* put coalition type-specific structures here */
296 	union {
297 		struct i_resource_coalition  r;
298 		struct i_jetsam_coalition    j;
299 	};
300 };
301 
302 os_refgrp_decl(static, coal_ref_grp, "coalitions", NULL);
303 #define coal_ref_init(coal, c)      os_ref_init_count_raw(&(coal)->ref_count, &coal_ref_grp, c)
304 #define coal_ref_count(coal)        os_ref_get_count_raw(&(coal)->ref_count)
305 #define coal_ref_try_retain(coal)   os_ref_retain_try_raw(&(coal)->ref_count, &coal_ref_grp)
306 #define coal_ref_retain(coal)       os_ref_retain_raw(&(coal)->ref_count, &coal_ref_grp)
307 #define coal_ref_release(coal)      os_ref_release_raw(&(coal)->ref_count, &coal_ref_grp)
308 #define coal_ref_release_live(coal) os_ref_release_live_raw(&(coal)->ref_count, &coal_ref_grp)
309 
310 #define COALITION_HASH_SIZE_MIN   16
311 
312 static bool
coal_hash_obj_try_get(void * coal)313 coal_hash_obj_try_get(void *coal)
314 {
315 	return coal_ref_try_retain((struct coalition *)coal);
316 }
317 
318 SMRH_TRAITS_DEFINE_SCALAR(coal_hash_traits, struct coalition, id, link,
319     .domain = &smr_system,
320     .obj_try_get = coal_hash_obj_try_get,
321     );
322 
323 /*
324  * register different coalition types:
325  * these must be kept in the order specified in coalition.h
326  */
327 static const struct coalition_type s_coalition_types[COALITION_NUM_TYPES] = {
328 	{
329 		COALITION_TYPE_RESOURCE,
330 		1,
331 		i_coal_resource_init,
332 		i_coal_resource_dealloc,
333 		i_coal_resource_adopt_task,
334 		i_coal_resource_remove_task,
335 		i_coal_resource_set_taskrole,
336 		i_coal_resource_get_taskrole,
337 		i_coal_resource_iterate_tasks,
338 	}, {
339 		COALITION_TYPE_JETSAM,
340 		1,
341 		i_coal_jetsam_init,
342 		i_coal_jetsam_dealloc,
343 		i_coal_jetsam_adopt_task,
344 		i_coal_jetsam_remove_task,
345 		i_coal_jetsam_set_taskrole,
346 		i_coal_jetsam_get_taskrole,
347 		i_coal_jetsam_iterate_tasks,
348 	},
349 };
350 
351 static KALLOC_TYPE_DEFINE(coalition_zone, struct coalition, KT_PRIV_ACCT);
352 
353 #define coal_call(coal, func, ...) \
354 	(s_coalition_types[(coal)->type].func)(coal, ## __VA_ARGS__)
355 
356 
357 #define coalition_lock(c)   lck_mtx_lock(&(c)->lock)
358 #define coalition_unlock(c) lck_mtx_unlock(&(c)->lock)
359 
360 /*
361  * Define the coalition type to track focal tasks.
362  * On embedded, track them using jetsam coalitions since they have associated thread
363  * groups which reflect this property as a flag (and pass it down to CLPC).
364  * On non-embedded platforms, since not all coalitions have jetsam coalitions
365  * track focal counts on the resource coalition.
366  */
367 #if !XNU_TARGET_OS_OSX
368 #define COALITION_FOCAL_TASKS_ACCOUNTING  COALITION_TYPE_JETSAM
369 #else /* !XNU_TARGET_OS_OSX */
370 #define COALITION_FOCAL_TASKS_ACCOUNTING  COALITION_TYPE_RESOURCE
371 #endif /* !XNU_TARGET_OS_OSX */
372 
373 
374 /*
375  *
376  * Coalition ledger implementation
377  *
378  */
379 
380 struct coalition_ledger_indices coalition_ledgers =
381 {.logical_writes = -1, };
382 void __attribute__((noinline)) SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor);
383 
384 ledger_t
coalition_ledger_get_from_task(task_t task)385 coalition_ledger_get_from_task(task_t task)
386 {
387 	ledger_t ledger = LEDGER_NULL;
388 	coalition_t coal = task->coalition[COALITION_TYPE_RESOURCE];
389 
390 	if (coal != NULL && (!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]))) {
391 		ledger = coal->r.resource_monitor_ledger;
392 		ledger_reference(ledger);
393 	}
394 	return ledger;
395 }
396 
397 
398 enum {
399 	COALITION_IO_LEDGER_ENABLE,
400 	COALITION_IO_LEDGER_DISABLE
401 };
402 
403 void
coalition_io_monitor_ctl(struct coalition * coalition,uint32_t flags,int64_t limit)404 coalition_io_monitor_ctl(struct coalition *coalition, uint32_t flags, int64_t limit)
405 {
406 	ledger_t ledger = coalition->r.resource_monitor_ledger;
407 
408 	if (flags == COALITION_IO_LEDGER_ENABLE) {
409 		/* Configure the logical I/O ledger */
410 		ledger_set_limit(ledger, coalition_ledgers.logical_writes, (limit * 1024 * 1024), 0);
411 		ledger_set_period(ledger, coalition_ledgers.logical_writes, (COALITION_LEDGER_MONITOR_INTERVAL_SECS * NSEC_PER_SEC));
412 	} else if (flags == COALITION_IO_LEDGER_DISABLE) {
413 		ledger_disable_refill(ledger, coalition_ledgers.logical_writes);
414 		ledger_disable_callback(ledger, coalition_ledgers.logical_writes);
415 	}
416 }
417 
418 int
coalition_ledger_set_logical_writes_limit(struct coalition * coalition,int64_t limit)419 coalition_ledger_set_logical_writes_limit(struct coalition *coalition, int64_t limit)
420 {
421 	int error = 0;
422 
423 	/*  limit = -1 will be used to disable the limit and the callback */
424 	if (limit > COALITION_MAX_LOGICAL_WRITES_LIMIT || limit == 0 || limit < -1) {
425 		error = EINVAL;
426 		goto out;
427 	}
428 
429 	coalition_lock(coalition);
430 	if (limit == -1) {
431 		coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_DISABLE, limit);
432 	} else {
433 		coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_ENABLE, limit);
434 	}
435 	coalition_unlock(coalition);
436 out:
437 	return error;
438 }
439 
440 void __attribute__((noinline))
SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor)441 SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO(int flavor)
442 {
443 	int pid = proc_selfpid();
444 	ledger_amount_t new_limit;
445 	task_t task = current_task();
446 	struct ledger_entry_info lei;
447 	kern_return_t kr;
448 	ledger_t ledger;
449 	struct coalition *coalition = task->coalition[COALITION_TYPE_RESOURCE];
450 
451 	assert(coalition != NULL);
452 	ledger = coalition->r.resource_monitor_ledger;
453 
454 	switch (flavor) {
455 	case FLAVOR_IO_LOGICAL_WRITES:
456 		ledger_get_entry_info(ledger, coalition_ledgers.logical_writes, &lei);
457 		trace_resource_violation(RMON_LOGWRITES_VIOLATED, &lei);
458 		break;
459 	default:
460 		goto Exit;
461 	}
462 
463 	os_log(OS_LOG_DEFAULT, "Coalition [%lld] caught causing excessive I/O (flavor: %d). Task I/O: %lld MB. [Limit : %lld MB per %lld secs]. Triggered by process [%d]\n",
464 	    coalition->id, flavor, (lei.lei_balance / (1024 * 1024)), (lei.lei_limit / (1024 * 1024)),
465 	    (lei.lei_refill_period / NSEC_PER_SEC), pid);
466 
467 	kr = send_resource_violation(send_disk_writes_violation, task, &lei, kRNFlagsNone);
468 	if (kr) {
469 		os_log(OS_LOG_DEFAULT, "ERROR %#x returned from send_resource_violation(disk_writes, ...)\n", kr);
470 	}
471 
472 	/*
473 	 * Continue to monitor the coalition after it hits the initital limit, but increase
474 	 * the limit exponentially so that we don't spam the listener.
475 	 */
476 	new_limit = (lei.lei_limit / 1024 / 1024) * 4;
477 	coalition_lock(coalition);
478 	if (new_limit > COALITION_MAX_LOGICAL_WRITES_LIMIT) {
479 		coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_DISABLE, -1);
480 	} else {
481 		coalition_io_monitor_ctl(coalition, COALITION_IO_LEDGER_ENABLE, new_limit);
482 	}
483 	coalition_unlock(coalition);
484 
485 Exit:
486 	return;
487 }
488 
489 void
coalition_io_rate_exceeded(int warning,const void * param0,__unused const void * param1)490 coalition_io_rate_exceeded(int warning, const void *param0, __unused const void *param1)
491 {
492 	if (warning == 0) {
493 		SENDING_NOTIFICATION__THIS_COALITION_IS_CAUSING_TOO_MUCH_IO((int)param0);
494 	}
495 }
496 
497 void
init_coalition_ledgers(void)498 init_coalition_ledgers(void)
499 {
500 	ledger_template_t t;
501 	assert(coalition_ledger_template == NULL);
502 
503 	if ((t = ledger_template_create("Per-coalition ledgers")) == NULL) {
504 		panic("couldn't create coalition ledger template");
505 	}
506 
507 	coalition_ledgers.logical_writes = ledger_entry_add(t, "logical_writes", "res", "bytes");
508 
509 	if (coalition_ledgers.logical_writes < 0) {
510 		panic("couldn't create entries for coaliton ledger template");
511 	}
512 
513 	ledger_set_callback(t, coalition_ledgers.logical_writes, coalition_io_rate_exceeded, (void *)FLAVOR_IO_LOGICAL_WRITES, NULL);
514 	ledger_template_complete(t);
515 
516 	coalition_task_ledger_template = ledger_template_copy(task_ledger_template, "Coalition task ledgers");
517 
518 	if (coalition_task_ledger_template == NULL) {
519 		panic("couldn't create coalition task ledger template");
520 	}
521 
522 	ledger_template_complete(coalition_task_ledger_template);
523 
524 	coalition_ledger_template = t;
525 }
526 
527 void
coalition_io_ledger_update(task_t task,int32_t flavor,boolean_t is_credit,uint32_t io_size)528 coalition_io_ledger_update(task_t task, int32_t flavor, boolean_t is_credit, uint32_t io_size)
529 {
530 	ledger_t ledger;
531 	coalition_t coal = task->coalition[COALITION_TYPE_RESOURCE];
532 
533 	assert(coal != NULL);
534 	ledger = coal->r.resource_monitor_ledger;
535 	if (LEDGER_VALID(ledger)) {
536 		if (flavor == FLAVOR_IO_LOGICAL_WRITES) {
537 			if (is_credit) {
538 				ledger_credit(ledger, coalition_ledgers.logical_writes, io_size);
539 			} else {
540 				ledger_debit(ledger, coalition_ledgers.logical_writes, io_size);
541 			}
542 		}
543 	}
544 }
545 
546 static void
coalition_notify_user(uint64_t id,uint32_t flags)547 coalition_notify_user(uint64_t id, uint32_t flags)
548 {
549 	mach_port_t user_port;
550 	kern_return_t kr;
551 
552 	kr = host_get_coalition_port(host_priv_self(), &user_port);
553 	if ((kr != KERN_SUCCESS) || !IPC_PORT_VALID(user_port)) {
554 		return;
555 	}
556 
557 	coalition_notification(user_port, id, flags);
558 	ipc_port_release_send(user_port);
559 }
560 
561 /*
562  *
563  * COALITION_TYPE_RESOURCE
564  *
565  */
566 static kern_return_t
i_coal_resource_init(coalition_t coal,boolean_t privileged,boolean_t efficient)567 i_coal_resource_init(coalition_t coal, boolean_t privileged, boolean_t efficient)
568 {
569 #pragma unused(privileged, efficient)
570 
571 	assert(coal && coal->type == COALITION_TYPE_RESOURCE);
572 
573 	recount_coalition_init(&coal->r.co_recount);
574 	coal->r.ledger = ledger_instantiate(coalition_task_ledger_template,
575 	    LEDGER_CREATE_ACTIVE_ENTRIES);
576 	if (coal->r.ledger == NULL) {
577 		return KERN_RESOURCE_SHORTAGE;
578 	}
579 
580 	coal->r.resource_monitor_ledger = ledger_instantiate(coalition_ledger_template,
581 	    LEDGER_CREATE_ACTIVE_ENTRIES);
582 	if (coal->r.resource_monitor_ledger == NULL) {
583 		return KERN_RESOURCE_SHORTAGE;
584 	}
585 
586 	queue_init(&coal->r.tasks);
587 
588 	return KERN_SUCCESS;
589 }
590 
591 static void
i_coal_resource_dealloc(coalition_t coal)592 i_coal_resource_dealloc(coalition_t coal)
593 {
594 	assert(coal && coal->type == COALITION_TYPE_RESOURCE);
595 
596 	recount_coalition_deinit(&coal->r.co_recount);
597 	ledger_dereference(coal->r.ledger);
598 	ledger_dereference(coal->r.resource_monitor_ledger);
599 }
600 
601 static kern_return_t
i_coal_resource_adopt_task(coalition_t coal,task_t task)602 i_coal_resource_adopt_task(coalition_t coal, task_t task)
603 {
604 	struct i_resource_coalition *cr;
605 
606 	assert(coal && coal->type == COALITION_TYPE_RESOURCE);
607 	assert(queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
608 
609 	cr = &coal->r;
610 	cr->task_count++;
611 
612 	if (cr->task_count < cr->dead_task_count) {
613 		panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
614 		    __func__, coal, coal->id, coal_type_str(coal->type),
615 		    cr->task_count, cr->dead_task_count);
616 	}
617 
618 	/* If moving from 0->1 active tasks */
619 	if (cr->task_count - cr->dead_task_count == 1) {
620 		cr->last_became_nonempty_time = mach_absolute_time();
621 	}
622 
623 	/* put the task on the coalition's list of tasks */
624 	enqueue_tail(&cr->tasks, &task->task_coalition[COALITION_TYPE_RESOURCE]);
625 
626 	coal_dbg("Added PID:%d to id:%llu, task_count:%llu, dead_count:%llu, nonempty_time:%llu",
627 	    task_pid(task), coal->id, cr->task_count, cr->dead_task_count,
628 	    cr->last_became_nonempty_time);
629 
630 	return KERN_SUCCESS;
631 }
632 
633 static kern_return_t
i_coal_resource_remove_task(coalition_t coal,task_t task)634 i_coal_resource_remove_task(coalition_t coal, task_t task)
635 {
636 	struct i_resource_coalition *cr;
637 
638 	assert(coal && coal->type == COALITION_TYPE_RESOURCE);
639 	assert(task->coalition[COALITION_TYPE_RESOURCE] == coal);
640 	assert(!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
641 
642 	/*
643 	 * handle resource coalition accounting rollup for dead tasks
644 	 */
645 	cr = &coal->r;
646 
647 	cr->dead_task_count++;
648 
649 	if (cr->task_count < cr->dead_task_count) {
650 		panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
651 		    __func__, coal, coal->id, coal_type_str(coal->type), cr->task_count, cr->dead_task_count);
652 	}
653 
654 	/* If moving from 1->0 active tasks */
655 	if (cr->task_count - cr->dead_task_count == 0) {
656 		uint64_t last_time_nonempty = mach_absolute_time() - cr->last_became_nonempty_time;
657 		cr->last_became_nonempty_time = 0;
658 		cr->time_nonempty += last_time_nonempty;
659 	}
660 
661 	/* Do not roll up for exec'd task or exec copy task */
662 	if (!task_is_exec_copy(task) && !task_did_exec(task)) {
663 		ledger_rollup(cr->ledger, task->ledger);
664 		cr->bytesread += task->task_io_stats->disk_reads.size;
665 		cr->byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
666 #if defined(__x86_64__)
667 		cr->gpu_time += task_gpu_utilisation(task);
668 #endif /* defined(__x86_64__) */
669 
670 		cr->logical_immediate_writes += task->task_writes_counters_internal.task_immediate_writes;
671 		cr->logical_deferred_writes += task->task_writes_counters_internal.task_deferred_writes;
672 		cr->logical_invalidated_writes += task->task_writes_counters_internal.task_invalidated_writes;
673 		cr->logical_metadata_writes += task->task_writes_counters_internal.task_metadata_writes;
674 		cr->logical_immediate_writes_to_external += task->task_writes_counters_external.task_immediate_writes;
675 		cr->logical_deferred_writes_to_external += task->task_writes_counters_external.task_deferred_writes;
676 		cr->logical_invalidated_writes_to_external += task->task_writes_counters_external.task_invalidated_writes;
677 		cr->logical_metadata_writes_to_external += task->task_writes_counters_external.task_metadata_writes;
678 #if CONFIG_PHYS_WRITE_ACCT
679 		cr->fs_metadata_writes += task->task_fs_metadata_writes;
680 #endif /* CONFIG_PHYS_WRITE_ACCT */
681 		task_update_cpu_time_qos_stats(task, cr->cpu_time_eqos, cr->cpu_time_rqos);
682 		recount_coalition_rollup_task(&cr->co_recount, &task->tk_recount);
683 	}
684 
685 	/* remove the task from the coalition's list */
686 	remqueue(&task->task_coalition[COALITION_TYPE_RESOURCE]);
687 	queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
688 
689 	coal_dbg("removed PID:%d from id:%llu, task_count:%llu, dead_count:%llu",
690 	    task_pid(task), coal->id, cr->task_count, cr->dead_task_count);
691 
692 	return KERN_SUCCESS;
693 }
694 
695 static kern_return_t
i_coal_resource_set_taskrole(__unused coalition_t coal,__unused task_t task,__unused int role)696 i_coal_resource_set_taskrole(__unused coalition_t coal,
697     __unused task_t task, __unused int role)
698 {
699 	return KERN_SUCCESS;
700 }
701 
702 static int
i_coal_resource_get_taskrole(__unused coalition_t coal,__unused task_t task)703 i_coal_resource_get_taskrole(__unused coalition_t coal, __unused task_t task)
704 {
705 	task_t t;
706 
707 	assert(coal && coal->type == COALITION_TYPE_RESOURCE);
708 
709 	qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
710 		if (t == task) {
711 			return COALITION_TASKROLE_UNDEF;
712 		}
713 	}
714 
715 	return -1;
716 }
717 
718 static void
i_coal_resource_iterate_tasks(coalition_t coal,void * ctx,void (* callback)(coalition_t,void *,task_t))719 i_coal_resource_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
720 {
721 	task_t t;
722 	assert(coal && coal->type == COALITION_TYPE_RESOURCE);
723 
724 	qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE])
725 	callback(coal, ctx, t);
726 }
727 
728 #if CONFIG_PHYS_WRITE_ACCT
729 extern uint64_t kernel_pm_writes;
730 #endif /* CONFIG_PHYS_WRITE_ACCT */
731 
732 kern_return_t
coalition_resource_usage_internal(coalition_t coal,struct coalition_resource_usage * cru_out)733 coalition_resource_usage_internal(coalition_t coal, struct coalition_resource_usage *cru_out)
734 {
735 	kern_return_t kr;
736 	ledger_amount_t credit, debit;
737 	int i;
738 
739 	if (coal->type != COALITION_TYPE_RESOURCE) {
740 		return KERN_INVALID_ARGUMENT;
741 	}
742 
743 	/* Return KERN_INVALID_ARGUMENT for Corpse coalition */
744 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
745 		if (coal == corpse_coalition[i]) {
746 			return KERN_INVALID_ARGUMENT;
747 		}
748 	}
749 
750 	ledger_t sum_ledger = ledger_instantiate(coalition_task_ledger_template, LEDGER_CREATE_ACTIVE_ENTRIES);
751 	if (sum_ledger == LEDGER_NULL) {
752 		return KERN_RESOURCE_SHORTAGE;
753 	}
754 
755 	coalition_lock(coal);
756 
757 	/*
758 	 * Start with the coalition's ledger, which holds the totals from all
759 	 * the dead tasks.
760 	 */
761 	ledger_rollup(sum_ledger, coal->r.ledger);
762 	uint64_t bytesread = coal->r.bytesread;
763 	uint64_t byteswritten = coal->r.byteswritten;
764 	uint64_t gpu_time = coal->r.gpu_time;
765 	uint64_t logical_immediate_writes = coal->r.logical_immediate_writes;
766 	uint64_t logical_deferred_writes = coal->r.logical_deferred_writes;
767 	uint64_t logical_invalidated_writes = coal->r.logical_invalidated_writes;
768 	uint64_t logical_metadata_writes = coal->r.logical_metadata_writes;
769 	uint64_t logical_immediate_writes_to_external = coal->r.logical_immediate_writes_to_external;
770 	uint64_t logical_deferred_writes_to_external = coal->r.logical_deferred_writes_to_external;
771 	uint64_t logical_invalidated_writes_to_external = coal->r.logical_invalidated_writes_to_external;
772 	uint64_t logical_metadata_writes_to_external = coal->r.logical_metadata_writes_to_external;
773 #if CONFIG_PHYS_WRITE_ACCT
774 	uint64_t fs_metadata_writes = coal->r.fs_metadata_writes;
775 #endif /* CONFIG_PHYS_WRITE_ACCT */
776 	int64_t cpu_time_billed_to_me = 0;
777 	int64_t cpu_time_billed_to_others = 0;
778 	int64_t energy_billed_to_me = 0;
779 	int64_t energy_billed_to_others = 0;
780 	struct recount_usage stats_sum = { 0 };
781 	struct recount_usage stats_perf_only = { 0 };
782 	recount_coalition_usage_perf_only(&coal->r.co_recount, &stats_sum,
783 	    &stats_perf_only);
784 	uint64_t cpu_time_eqos[COALITION_NUM_THREAD_QOS_TYPES] = { 0 };
785 	uint64_t cpu_time_rqos[COALITION_NUM_THREAD_QOS_TYPES] = { 0 };
786 	/*
787 	 * Add to that all the active tasks' ledgers. Tasks cannot deallocate
788 	 * out from under us, since we hold the coalition lock.
789 	 */
790 	task_t task;
791 	qe_foreach_element(task, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
792 		/*
793 		 * Rolling up stats for exec copy task or exec'd task will lead to double accounting.
794 		 * Cannot take task lock after taking coaliton lock
795 		 */
796 		if (task_is_exec_copy(task) || task_did_exec(task)) {
797 			continue;
798 		}
799 
800 		ledger_rollup(sum_ledger, task->ledger);
801 		bytesread += task->task_io_stats->disk_reads.size;
802 		byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
803 #if defined(__x86_64__)
804 		gpu_time += task_gpu_utilisation(task);
805 #endif /* defined(__x86_64__) */
806 
807 		logical_immediate_writes += task->task_writes_counters_internal.task_immediate_writes;
808 		logical_deferred_writes += task->task_writes_counters_internal.task_deferred_writes;
809 		logical_invalidated_writes += task->task_writes_counters_internal.task_invalidated_writes;
810 		logical_metadata_writes += task->task_writes_counters_internal.task_metadata_writes;
811 		logical_immediate_writes_to_external += task->task_writes_counters_external.task_immediate_writes;
812 		logical_deferred_writes_to_external += task->task_writes_counters_external.task_deferred_writes;
813 		logical_invalidated_writes_to_external += task->task_writes_counters_external.task_invalidated_writes;
814 		logical_metadata_writes_to_external += task->task_writes_counters_external.task_metadata_writes;
815 #if CONFIG_PHYS_WRITE_ACCT
816 		fs_metadata_writes += task->task_fs_metadata_writes;
817 #endif /* CONFIG_PHYS_WRITE_ACCT */
818 
819 		task_update_cpu_time_qos_stats(task, cpu_time_eqos, cpu_time_rqos);
820 		recount_task_usage_perf_only(task, &stats_sum, &stats_perf_only);
821 	}
822 
823 	kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_me, (int64_t *)&cpu_time_billed_to_me);
824 	if (kr != KERN_SUCCESS || cpu_time_billed_to_me < 0) {
825 		cpu_time_billed_to_me = 0;
826 	}
827 
828 	kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_others, (int64_t *)&cpu_time_billed_to_others);
829 	if (kr != KERN_SUCCESS || cpu_time_billed_to_others < 0) {
830 		cpu_time_billed_to_others = 0;
831 	}
832 
833 	kr = ledger_get_balance(sum_ledger, task_ledgers.energy_billed_to_me, (int64_t *)&energy_billed_to_me);
834 	if (kr != KERN_SUCCESS || energy_billed_to_me < 0) {
835 		energy_billed_to_me = 0;
836 	}
837 
838 	kr = ledger_get_balance(sum_ledger, task_ledgers.energy_billed_to_others, (int64_t *)&energy_billed_to_others);
839 	if (kr != KERN_SUCCESS || energy_billed_to_others < 0) {
840 		energy_billed_to_others = 0;
841 	}
842 
843 	/* collect information from the coalition itself */
844 	cru_out->tasks_started = coal->r.task_count;
845 	cru_out->tasks_exited = coal->r.dead_task_count;
846 
847 	uint64_t time_nonempty = coal->r.time_nonempty;
848 	uint64_t last_became_nonempty_time = coal->r.last_became_nonempty_time;
849 
850 	coalition_unlock(coal);
851 
852 	/* Copy the totals out of sum_ledger */
853 	kr = ledger_get_entries(sum_ledger, task_ledgers.cpu_time,
854 	    &credit, &debit);
855 	if (kr != KERN_SUCCESS) {
856 		credit = 0;
857 	}
858 	cru_out->cpu_time = credit;
859 	cru_out->cpu_time_billed_to_me = (uint64_t)cpu_time_billed_to_me;
860 	cru_out->cpu_time_billed_to_others = (uint64_t)cpu_time_billed_to_others;
861 	cru_out->energy_billed_to_me = (uint64_t)energy_billed_to_me;
862 	cru_out->energy_billed_to_others = (uint64_t)energy_billed_to_others;
863 
864 	kr = ledger_get_entries(sum_ledger, task_ledgers.interrupt_wakeups,
865 	    &credit, &debit);
866 	if (kr != KERN_SUCCESS) {
867 		credit = 0;
868 	}
869 	cru_out->interrupt_wakeups = credit;
870 
871 	kr = ledger_get_entries(sum_ledger, task_ledgers.platform_idle_wakeups,
872 	    &credit, &debit);
873 	if (kr != KERN_SUCCESS) {
874 		credit = 0;
875 	}
876 	cru_out->platform_idle_wakeups = credit;
877 
878 	cru_out->bytesread = bytesread;
879 	cru_out->byteswritten = byteswritten;
880 	cru_out->gpu_time = gpu_time;
881 	cru_out->logical_immediate_writes = logical_immediate_writes;
882 	cru_out->logical_deferred_writes = logical_deferred_writes;
883 	cru_out->logical_invalidated_writes = logical_invalidated_writes;
884 	cru_out->logical_metadata_writes = logical_metadata_writes;
885 	cru_out->logical_immediate_writes_to_external = logical_immediate_writes_to_external;
886 	cru_out->logical_deferred_writes_to_external = logical_deferred_writes_to_external;
887 	cru_out->logical_invalidated_writes_to_external = logical_invalidated_writes_to_external;
888 	cru_out->logical_metadata_writes_to_external = logical_metadata_writes_to_external;
889 #if CONFIG_PHYS_WRITE_ACCT
890 	cru_out->fs_metadata_writes = fs_metadata_writes;
891 #else
892 	cru_out->fs_metadata_writes = 0;
893 #endif /* CONFIG_PHYS_WRITE_ACCT */
894 	cru_out->cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES;
895 	memcpy(cru_out->cpu_time_eqos, cpu_time_eqos, sizeof(cru_out->cpu_time_eqos));
896 
897 	cru_out->cpu_ptime = stats_perf_only.ru_system_time_mach +
898 	    stats_perf_only.ru_user_time_mach;
899 #if CONFIG_PERVASIVE_CPI
900 	cru_out->cpu_cycles = stats_sum.ru_cycles;
901 	cru_out->cpu_instructions = stats_sum.ru_instructions;
902 	cru_out->cpu_pinstructions = stats_perf_only.ru_instructions;
903 	cru_out->cpu_pcycles = stats_perf_only.ru_cycles;
904 #endif // CONFIG_PERVASIVE_CPI
905 
906 	ledger_dereference(sum_ledger);
907 	sum_ledger = LEDGER_NULL;
908 
909 #if CONFIG_PERVASIVE_ENERGY
910 	cru_out->energy = stats_sum.ru_energy_nj;
911 #endif /* CONFIG_PERVASIVE_ENERGY */
912 
913 #if CONFIG_PHYS_WRITE_ACCT
914 	// kernel_pm_writes are only recorded under kernel_task coalition
915 	if (coalition_id(coal) == COALITION_ID_KERNEL) {
916 		cru_out->pm_writes = kernel_pm_writes;
917 	} else {
918 		cru_out->pm_writes = 0;
919 	}
920 #else
921 	cru_out->pm_writes = 0;
922 #endif /* CONFIG_PHYS_WRITE_ACCT */
923 
924 	if (last_became_nonempty_time) {
925 		time_nonempty += mach_absolute_time() - last_became_nonempty_time;
926 	}
927 	absolutetime_to_nanoseconds(time_nonempty, &cru_out->time_nonempty);
928 
929 	return KERN_SUCCESS;
930 }
931 
932 /*
933  *
934  * COALITION_TYPE_JETSAM
935  *
936  */
937 static kern_return_t
i_coal_jetsam_init(coalition_t coal,boolean_t privileged,boolean_t efficient)938 i_coal_jetsam_init(coalition_t coal, boolean_t privileged, boolean_t efficient)
939 {
940 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
941 	(void)privileged;
942 	(void)efficient;
943 
944 	coal->j.leader = TASK_NULL;
945 	queue_head_init(coal->j.extensions);
946 	queue_head_init(coal->j.services);
947 	queue_head_init(coal->j.other);
948 
949 #if CONFIG_THREAD_GROUPS
950 	switch (coal->role) {
951 	case COALITION_ROLE_SYSTEM:
952 		coal->j.thread_group = thread_group_find_by_id_and_retain(THREAD_GROUP_SYSTEM);
953 		break;
954 	case COALITION_ROLE_BACKGROUND:
955 		coal->j.thread_group = thread_group_find_by_id_and_retain(THREAD_GROUP_BACKGROUND);
956 		break;
957 	default:
958 		coal->j.thread_group = thread_group_create_and_retain(efficient ? THREAD_GROUP_FLAGS_EFFICIENT : THREAD_GROUP_FLAGS_DEFAULT);
959 	}
960 	assert(coal->j.thread_group != NULL);
961 #endif
962 	return KERN_SUCCESS;
963 }
964 
965 static void
i_coal_jetsam_dealloc(__unused coalition_t coal)966 i_coal_jetsam_dealloc(__unused coalition_t coal)
967 {
968 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
969 
970 	/* the coalition should be completely clear at this point */
971 	assert(queue_empty(&coal->j.extensions));
972 	assert(queue_empty(&coal->j.services));
973 	assert(queue_empty(&coal->j.other));
974 	assert(coal->j.leader == TASK_NULL);
975 
976 #if CONFIG_THREAD_GROUPS
977 	/* disassociate from the thread group */
978 	assert(coal->j.thread_group != NULL);
979 	thread_group_release(coal->j.thread_group);
980 	coal->j.thread_group = NULL;
981 #endif
982 }
983 
984 static kern_return_t
i_coal_jetsam_adopt_task(coalition_t coal,task_t task)985 i_coal_jetsam_adopt_task(coalition_t coal, task_t task)
986 {
987 	struct i_jetsam_coalition *cj;
988 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
989 
990 	cj = &coal->j;
991 
992 	assert(queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
993 
994 	/* put each task initially in the "other" list */
995 	enqueue_tail(&cj->other, &task->task_coalition[COALITION_TYPE_JETSAM]);
996 	coal_dbg("coalition %lld adopted PID:%d as UNDEF",
997 	    coal->id, task_pid(task));
998 
999 	return KERN_SUCCESS;
1000 }
1001 
1002 static kern_return_t
i_coal_jetsam_remove_task(coalition_t coal,task_t task)1003 i_coal_jetsam_remove_task(coalition_t coal, task_t task)
1004 {
1005 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
1006 	assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1007 
1008 	coal_dbg("removing PID:%d from coalition id:%lld",
1009 	    task_pid(task), coal->id);
1010 
1011 	if (task == coal->j.leader) {
1012 		coal->j.leader = NULL;
1013 		coal_dbg("    PID:%d was the leader!", task_pid(task));
1014 	} else {
1015 		assert(!queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
1016 	}
1017 
1018 	/* remove the task from the specific coalition role queue */
1019 	remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
1020 	queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
1021 
1022 	return KERN_SUCCESS;
1023 }
1024 
1025 static kern_return_t
i_coal_jetsam_set_taskrole(coalition_t coal,task_t task,int role)1026 i_coal_jetsam_set_taskrole(coalition_t coal, task_t task, int role)
1027 {
1028 	struct i_jetsam_coalition *cj;
1029 	queue_t q = NULL;
1030 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
1031 	assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1032 
1033 	cj = &coal->j;
1034 
1035 	switch (role) {
1036 	case COALITION_TASKROLE_LEADER:
1037 		coal_dbg("setting PID:%d as LEADER of %lld",
1038 		    task_pid(task), coal->id);
1039 		if (cj->leader != TASK_NULL) {
1040 			/* re-queue the exiting leader onto the "other" list */
1041 			coal_dbg("    re-queue existing leader (%d) as OTHER",
1042 			    task_pid(cj->leader));
1043 			re_queue_tail(&cj->other, &cj->leader->task_coalition[COALITION_TYPE_JETSAM]);
1044 		}
1045 		/*
1046 		 * remove the task from the "other" list
1047 		 * (where it was put by default)
1048 		 */
1049 		remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
1050 		queue_chain_init(task->task_coalition[COALITION_TYPE_JETSAM]);
1051 
1052 		/* set the coalition leader */
1053 		cj->leader = task;
1054 		break;
1055 	case COALITION_TASKROLE_XPC:
1056 		coal_dbg("setting PID:%d as XPC in %lld",
1057 		    task_pid(task), coal->id);
1058 		q = (queue_t)&cj->services;
1059 		break;
1060 	case COALITION_TASKROLE_EXT:
1061 		coal_dbg("setting PID:%d as EXT in %lld",
1062 		    task_pid(task), coal->id);
1063 		q = (queue_t)&cj->extensions;
1064 		break;
1065 	case COALITION_TASKROLE_NONE:
1066 		/*
1067 		 * Tasks with a role of "none" should fall through to an
1068 		 * undefined role so long as the task is currently a member
1069 		 * of the coalition. This scenario can happen if a task is
1070 		 * killed (usually via jetsam) during exec.
1071 		 */
1072 		if (task->coalition[COALITION_TYPE_JETSAM] != coal) {
1073 			panic("%s: task %p attempting to set role %d "
1074 			    "in coalition %p to which it does not belong!", __func__, task, role, coal);
1075 		}
1076 		OS_FALLTHROUGH;
1077 	case COALITION_TASKROLE_UNDEF:
1078 		coal_dbg("setting PID:%d as UNDEF in %lld",
1079 		    task_pid(task), coal->id);
1080 		q = (queue_t)&cj->other;
1081 		break;
1082 	default:
1083 		panic("%s: invalid role(%d) for task", __func__, role);
1084 		return KERN_INVALID_ARGUMENT;
1085 	}
1086 
1087 	if (q != NULL) {
1088 		re_queue_tail(q, &task->task_coalition[COALITION_TYPE_JETSAM]);
1089 	}
1090 
1091 	return KERN_SUCCESS;
1092 }
1093 
1094 int
i_coal_jetsam_get_taskrole(coalition_t coal,task_t task)1095 i_coal_jetsam_get_taskrole(coalition_t coal, task_t task)
1096 {
1097 	struct i_jetsam_coalition *cj;
1098 	task_t t;
1099 
1100 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
1101 	assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
1102 
1103 	cj = &coal->j;
1104 
1105 	if (task == cj->leader) {
1106 		return COALITION_TASKROLE_LEADER;
1107 	}
1108 
1109 	qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM]) {
1110 		if (t == task) {
1111 			return COALITION_TASKROLE_XPC;
1112 		}
1113 	}
1114 
1115 	qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM]) {
1116 		if (t == task) {
1117 			return COALITION_TASKROLE_EXT;
1118 		}
1119 	}
1120 
1121 	qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM]) {
1122 		if (t == task) {
1123 			return COALITION_TASKROLE_UNDEF;
1124 		}
1125 	}
1126 
1127 	/* task not in the coalition?! */
1128 	return COALITION_TASKROLE_NONE;
1129 }
1130 
1131 static void
i_coal_jetsam_iterate_tasks(coalition_t coal,void * ctx,void (* callback)(coalition_t,void *,task_t))1132 i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
1133 {
1134 	struct i_jetsam_coalition *cj;
1135 	task_t t;
1136 
1137 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
1138 
1139 	cj = &coal->j;
1140 
1141 	if (cj->leader) {
1142 		callback(coal, ctx, cj->leader);
1143 	}
1144 
1145 	qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM])
1146 	callback(coal, ctx, t);
1147 
1148 	qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM])
1149 	callback(coal, ctx, t);
1150 
1151 	qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM])
1152 	callback(coal, ctx, t);
1153 }
1154 
1155 
1156 /*
1157  *
1158  * Main Coalition implementation
1159  *
1160  */
1161 
1162 /*
1163  * coalition_create_internal
1164  * Returns: New coalition object, referenced for the caller and unlocked.
1165  * Condition: coalitions_list_lock must be UNLOCKED.
1166  */
1167 kern_return_t
coalition_create_internal(int type,int role,boolean_t privileged,boolean_t efficient,coalition_t * out,uint64_t * coalition_id)1168 coalition_create_internal(int type, int role, boolean_t privileged, boolean_t efficient, coalition_t *out, uint64_t *coalition_id)
1169 {
1170 	kern_return_t kr;
1171 	struct coalition *new_coal;
1172 	uint64_t cid;
1173 
1174 	if (type < 0 || type > COALITION_TYPE_MAX) {
1175 		return KERN_INVALID_ARGUMENT;
1176 	}
1177 
1178 	new_coal = zalloc_flags(coalition_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL);
1179 
1180 	new_coal->type = type;
1181 	new_coal->role = role;
1182 
1183 	/* initialize type-specific resources */
1184 	kr = coal_call(new_coal, init, privileged, efficient);
1185 	if (kr != KERN_SUCCESS) {
1186 		zfree(coalition_zone, new_coal);
1187 		return kr;
1188 	}
1189 
1190 	/* One for caller, one for coalitions list */
1191 	coal_ref_init(new_coal, 2);
1192 
1193 	new_coal->privileged = privileged ? TRUE : FALSE;
1194 	new_coal->efficient = efficient ? TRUE : FALSE;
1195 #if DEVELOPMENT || DEBUG
1196 	new_coal->should_notify = 1;
1197 #endif
1198 
1199 	lck_mtx_init(&new_coal->lock, &coalitions_lck_grp, LCK_ATTR_NULL);
1200 
1201 	lck_mtx_lock(&coalitions_list_lock);
1202 	new_coal->id = cid = coalition_next_id++;
1203 
1204 	smr_hash_serialized_insert(&coalition_hash, &new_coal->link,
1205 	    &coal_hash_traits);
1206 
1207 #if CONFIG_THREAD_GROUPS
1208 	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_NEW),
1209 	    new_coal->id, new_coal->type,
1210 	    (new_coal->type == COALITION_TYPE_JETSAM && new_coal->j.thread_group) ?
1211 	    thread_group_get_id(new_coal->j.thread_group) : 0);
1212 
1213 #else
1214 	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_NEW),
1215 	    new_coal->id, new_coal->type);
1216 #endif
1217 
1218 	if (smr_hash_serialized_should_grow(&coalition_hash, 1, 4)) {
1219 		/* grow if more more than 4 elements per 1 bucket */
1220 		smr_hash_grow_and_unlock(&coalition_hash,
1221 		    &coalitions_list_lock, &coal_hash_traits);
1222 	} else {
1223 		lck_mtx_unlock(&coalitions_list_lock);
1224 	}
1225 
1226 	coal_dbg("id:%llu, type:%s", cid, coal_type_str(type));
1227 
1228 	if (coalition_id != NULL) {
1229 		*coalition_id = cid;
1230 	}
1231 
1232 	*out = new_coal;
1233 	return KERN_SUCCESS;
1234 }
1235 
1236 static void
coalition_free(void * coal)1237 coalition_free(void *coal)
1238 {
1239 	zfree(coalition_zone, coal);
1240 }
1241 
1242 static __attribute__((noinline)) void
coalition_retire(coalition_t coal)1243 coalition_retire(coalition_t coal)
1244 {
1245 	coalition_lock(coal);
1246 
1247 	coal_dbg("id:%llu type:%s active_count:%u%s",
1248 	    coal->id, coal_type_str(coal->type), coal->active_count,
1249 	    rc <= 0 ? ", will deallocate now" : "");
1250 
1251 	assert(coal->termrequested);
1252 	assert(coal->terminated);
1253 	assert(coal->active_count == 0);
1254 	assert(coal->reaped);
1255 	assert(coal->focal_task_count == 0);
1256 	assert(coal->nonfocal_task_count == 0);
1257 #if CONFIG_THREAD_GROUPS
1258 	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_FREE),
1259 	    coal->id, coal->type,
1260 	    coal->type == COALITION_TYPE_JETSAM ?
1261 	    coal->j.thread_group : 0);
1262 #else
1263 	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_FREE),
1264 	    coal->id, coal->type);
1265 #endif
1266 
1267 	coal_call(coal, dealloc);
1268 
1269 	coalition_unlock(coal);
1270 
1271 	lck_mtx_destroy(&coal->lock, &coalitions_lck_grp);
1272 
1273 	smr_global_retire(coal, sizeof(*coal), coalition_free);
1274 }
1275 
1276 /*
1277  * coalition_release
1278  * Condition: coalition must be UNLOCKED.
1279  * */
1280 void
coalition_release(coalition_t coal)1281 coalition_release(coalition_t coal)
1282 {
1283 	if (coal_ref_release(coal) > 0) {
1284 		return;
1285 	}
1286 
1287 	coalition_retire(coal);
1288 }
1289 
1290 /*
1291  * coalition_find_by_id
1292  * Returns: Coalition object with specified id, referenced.
1293  */
1294 coalition_t
coalition_find_by_id(uint64_t cid)1295 coalition_find_by_id(uint64_t cid)
1296 {
1297 	smrh_key_t key = SMRH_SCALAR_KEY(cid);
1298 
1299 	if (cid == 0) {
1300 		return COALITION_NULL;
1301 	}
1302 
1303 	return smr_hash_get(&coalition_hash, key, &coal_hash_traits);
1304 }
1305 
1306 /*
1307  * coalition_find_and_activate_by_id
1308  * Returns: Coalition object with specified id, referenced, and activated.
1309  * This is the function to use when putting a 'new' thing into a coalition,
1310  * like posix_spawn of an XPC service by launchd.
1311  * See also coalition_extend_active.
1312  */
1313 coalition_t
coalition_find_and_activate_by_id(uint64_t cid)1314 coalition_find_and_activate_by_id(uint64_t cid)
1315 {
1316 	coalition_t coal = coalition_find_by_id(cid);
1317 
1318 	if (coal == COALITION_NULL) {
1319 		return COALITION_NULL;
1320 	}
1321 
1322 	coalition_lock(coal);
1323 
1324 	if (coal->reaped || coal->terminated) {
1325 		/* Too late to put something new into this coalition, it's
1326 		 * already on its way out the door */
1327 		coalition_unlock(coal);
1328 		coalition_release(coal);
1329 		return COALITION_NULL;
1330 	}
1331 
1332 	coal->active_count++;
1333 
1334 #if COALITION_DEBUG
1335 	uint32_t rc = coal_ref_count(coal);
1336 	uint32_t ac = coal->active_count;
1337 #endif
1338 	coalition_unlock(coal);
1339 
1340 	coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u",
1341 	    coal->id, coal_type_str(coal->type), rc, ac);
1342 
1343 	return coal;
1344 }
1345 
1346 uint64_t
coalition_id(coalition_t coal)1347 coalition_id(coalition_t coal)
1348 {
1349 	assert(coal != COALITION_NULL);
1350 	return coal->id;
1351 }
1352 
1353 void
task_coalition_ids(task_t task,uint64_t ids[COALITION_NUM_TYPES])1354 task_coalition_ids(task_t task, uint64_t ids[COALITION_NUM_TYPES])
1355 {
1356 	int i;
1357 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1358 		if (task->coalition[i]) {
1359 			ids[i] = task->coalition[i]->id;
1360 		} else {
1361 			ids[i] = 0;
1362 		}
1363 	}
1364 }
1365 
1366 void
task_coalition_roles(task_t task,int roles[COALITION_NUM_TYPES])1367 task_coalition_roles(task_t task, int roles[COALITION_NUM_TYPES])
1368 {
1369 	int i;
1370 	memset(roles, 0, COALITION_NUM_TYPES * sizeof(roles[0]));
1371 
1372 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1373 		if (task->coalition[i]) {
1374 			coalition_lock(task->coalition[i]);
1375 			roles[i] = coal_call(task->coalition[i],
1376 			    get_taskrole, task);
1377 			coalition_unlock(task->coalition[i]);
1378 		} else {
1379 			roles[i] = COALITION_TASKROLE_NONE;
1380 		}
1381 	}
1382 }
1383 
1384 int
task_coalition_role_for_type(task_t task,int coalition_type)1385 task_coalition_role_for_type(task_t task, int coalition_type)
1386 {
1387 	coalition_t coal;
1388 	int role;
1389 	if (coalition_type >= COALITION_NUM_TYPES) {
1390 		panic("Attempt to call task_coalition_role_for_type with invalid coalition_type: %d\n", coalition_type);
1391 	}
1392 	coal = task->coalition[coalition_type];
1393 	if (coal == NULL) {
1394 		return COALITION_TASKROLE_NONE;
1395 	}
1396 	coalition_lock(coal);
1397 	role = coal_call(coal, get_taskrole, task);
1398 	coalition_unlock(coal);
1399 	return role;
1400 }
1401 
1402 int
coalition_type(coalition_t coal)1403 coalition_type(coalition_t coal)
1404 {
1405 	return coal->type;
1406 }
1407 
1408 boolean_t
coalition_term_requested(coalition_t coal)1409 coalition_term_requested(coalition_t coal)
1410 {
1411 	return coal->termrequested;
1412 }
1413 
1414 boolean_t
coalition_is_terminated(coalition_t coal)1415 coalition_is_terminated(coalition_t coal)
1416 {
1417 	return coal->terminated;
1418 }
1419 
1420 boolean_t
coalition_is_reaped(coalition_t coal)1421 coalition_is_reaped(coalition_t coal)
1422 {
1423 	return coal->reaped;
1424 }
1425 
1426 boolean_t
coalition_is_privileged(coalition_t coal)1427 coalition_is_privileged(coalition_t coal)
1428 {
1429 	return coal->privileged || unrestrict_coalition_syscalls;
1430 }
1431 
1432 boolean_t
task_is_in_privileged_coalition(task_t task,int type)1433 task_is_in_privileged_coalition(task_t task, int type)
1434 {
1435 	if (type < 0 || type > COALITION_TYPE_MAX) {
1436 		return FALSE;
1437 	}
1438 	if (unrestrict_coalition_syscalls) {
1439 		return TRUE;
1440 	}
1441 	if (!task->coalition[type]) {
1442 		return FALSE;
1443 	}
1444 	return task->coalition[type]->privileged;
1445 }
1446 
1447 void
task_coalition_update_gpu_stats(task_t task,uint64_t gpu_ns_delta)1448 task_coalition_update_gpu_stats(task_t task, uint64_t gpu_ns_delta)
1449 {
1450 	coalition_t coal;
1451 
1452 	assert(task != TASK_NULL);
1453 	if (gpu_ns_delta == 0) {
1454 		return;
1455 	}
1456 
1457 	coal = task->coalition[COALITION_TYPE_RESOURCE];
1458 	assert(coal != COALITION_NULL);
1459 
1460 	coalition_lock(coal);
1461 	coal->r.gpu_time += gpu_ns_delta;
1462 	coalition_unlock(coal);
1463 }
1464 
1465 boolean_t
task_coalition_adjust_focal_count(task_t task,int count,uint32_t * new_count)1466 task_coalition_adjust_focal_count(task_t task, int count, uint32_t *new_count)
1467 {
1468 	coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1469 	if (coal == COALITION_NULL) {
1470 		return FALSE;
1471 	}
1472 
1473 	*new_count = os_atomic_add(&coal->focal_task_count, count, relaxed);
1474 	assert(*new_count != UINT32_MAX);
1475 	return TRUE;
1476 }
1477 
1478 uint32_t
task_coalition_focal_count(task_t task)1479 task_coalition_focal_count(task_t task)
1480 {
1481 	coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1482 	if (coal == COALITION_NULL) {
1483 		return 0;
1484 	}
1485 
1486 	return coal->focal_task_count;
1487 }
1488 
1489 boolean_t
task_coalition_adjust_nonfocal_count(task_t task,int count,uint32_t * new_count)1490 task_coalition_adjust_nonfocal_count(task_t task, int count, uint32_t *new_count)
1491 {
1492 	coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1493 	if (coal == COALITION_NULL) {
1494 		return FALSE;
1495 	}
1496 
1497 	*new_count = os_atomic_add(&coal->nonfocal_task_count, count, relaxed);
1498 	assert(*new_count != UINT32_MAX);
1499 	return TRUE;
1500 }
1501 
1502 uint32_t
task_coalition_nonfocal_count(task_t task)1503 task_coalition_nonfocal_count(task_t task)
1504 {
1505 	coalition_t coal = task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING];
1506 	if (coal == COALITION_NULL) {
1507 		return 0;
1508 	}
1509 
1510 	return coal->nonfocal_task_count;
1511 }
1512 
1513 #if CONFIG_THREAD_GROUPS
1514 struct thread_group *
task_coalition_get_thread_group(task_t task)1515 task_coalition_get_thread_group(task_t task)
1516 {
1517 	coalition_t coal = task->coalition[COALITION_TYPE_JETSAM];
1518 	/* return system thread group for non-jetsam coalitions */
1519 	if (coal == COALITION_NULL) {
1520 		return init_coalition[COALITION_TYPE_JETSAM]->j.thread_group;
1521 	}
1522 	return coal->j.thread_group;
1523 }
1524 
1525 
1526 struct thread_group *
kdp_coalition_get_thread_group(coalition_t coal)1527 kdp_coalition_get_thread_group(coalition_t coal)
1528 {
1529 	if (coal->type != COALITION_TYPE_JETSAM) {
1530 		return NULL;
1531 	}
1532 	assert(coal->j.thread_group != NULL);
1533 	return coal->j.thread_group;
1534 }
1535 
1536 struct thread_group *
coalition_get_thread_group(coalition_t coal)1537 coalition_get_thread_group(coalition_t coal)
1538 {
1539 	if (coal->type != COALITION_TYPE_JETSAM) {
1540 		return NULL;
1541 	}
1542 	assert(coal->j.thread_group != NULL);
1543 	return thread_group_retain(coal->j.thread_group);
1544 }
1545 
1546 void
coalition_set_thread_group(coalition_t coal,struct thread_group * tg)1547 coalition_set_thread_group(coalition_t coal, struct thread_group *tg)
1548 {
1549 	assert(coal != COALITION_NULL);
1550 	assert(tg != NULL);
1551 
1552 	if (coal->type != COALITION_TYPE_JETSAM) {
1553 		return;
1554 	}
1555 	struct thread_group *old_tg = coal->j.thread_group;
1556 	assert(old_tg != NULL);
1557 	coal->j.thread_group = tg;
1558 
1559 	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_THREAD_GROUP_SET),
1560 	    coal->id, coal->type, thread_group_get_id(tg));
1561 
1562 	thread_group_release(old_tg);
1563 }
1564 
1565 void
task_coalition_thread_group_focal_update(task_t task)1566 task_coalition_thread_group_focal_update(task_t task)
1567 {
1568 	assert(task->coalition[COALITION_FOCAL_TASKS_ACCOUNTING] != COALITION_NULL);
1569 	thread_group_flags_update_lock();
1570 	uint32_t focal_count = task_coalition_focal_count(task);
1571 	if (focal_count) {
1572 		thread_group_set_flags_locked(task_coalition_get_thread_group(task), THREAD_GROUP_FLAGS_UI_APP);
1573 	} else {
1574 		thread_group_clear_flags_locked(task_coalition_get_thread_group(task), THREAD_GROUP_FLAGS_UI_APP);
1575 	}
1576 	thread_group_flags_update_unlock();
1577 }
1578 
1579 void
task_coalition_thread_group_application_set(task_t task)1580 task_coalition_thread_group_application_set(task_t task)
1581 {
1582 	/*
1583 	 * Setting the "Application" flag on the thread group is a one way transition.
1584 	 * Once a coalition has a single task with an application apptype, the
1585 	 * thread group associated with the coalition is tagged as Application.
1586 	 */
1587 	thread_group_flags_update_lock();
1588 	thread_group_set_flags_locked(task_coalition_get_thread_group(task), THREAD_GROUP_FLAGS_APPLICATION);
1589 	thread_group_flags_update_unlock();
1590 }
1591 
1592 #endif
1593 
1594 void
coalition_for_each_task(coalition_t coal,void * ctx,void (* callback)(coalition_t,void *,task_t))1595 coalition_for_each_task(coalition_t coal, void *ctx,
1596     void (*callback)(coalition_t, void *, task_t))
1597 {
1598 	assert(coal != COALITION_NULL);
1599 
1600 	coal_dbg("iterating tasks in coalition %p id:%llu type:%s, active_count:%u",
1601 	    coal, coal->id, coal_type_str(coal->type), coal->active_count);
1602 
1603 	coalition_lock(coal);
1604 
1605 	coal_call(coal, iterate_tasks, ctx, callback);
1606 
1607 	coalition_unlock(coal);
1608 }
1609 
1610 
1611 void
coalition_remove_active(coalition_t coal)1612 coalition_remove_active(coalition_t coal)
1613 {
1614 	coalition_lock(coal);
1615 
1616 	assert(!coalition_is_reaped(coal));
1617 	assert(coal->active_count > 0);
1618 
1619 	coal->active_count--;
1620 
1621 	boolean_t do_notify = FALSE;
1622 	uint64_t notify_id = 0;
1623 	uint32_t notify_flags = 0;
1624 	if (coal->termrequested && coal->active_count == 0) {
1625 		/* We only notify once, when active_count reaches zero.
1626 		 * We just decremented, so if it reached zero, we mustn't have
1627 		 * notified already.
1628 		 */
1629 		assert(!coal->terminated);
1630 		coal->terminated = TRUE;
1631 
1632 		assert(!coal->notified);
1633 
1634 		coal->notified = TRUE;
1635 #if DEVELOPMENT || DEBUG
1636 		do_notify = coal->should_notify;
1637 #else
1638 		do_notify = TRUE;
1639 #endif
1640 		notify_id = coal->id;
1641 		notify_flags = 0;
1642 	}
1643 
1644 #if COALITION_DEBUG
1645 	uint64_t cid = coal->id;
1646 	uint32_t rc = coal_ref_count(coal);
1647 	int      ac = coal->active_count;
1648 	int      ct = coal->type;
1649 #endif
1650 	coalition_unlock(coal);
1651 
1652 	coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u,%s",
1653 	    cid, coal_type_str(ct), rc, ac, do_notify ? " NOTIFY" : " ");
1654 
1655 	if (do_notify) {
1656 		coalition_notify_user(notify_id, notify_flags);
1657 	}
1658 }
1659 
1660 /* Used for kernel_task, launchd, launchd's early boot tasks... */
1661 kern_return_t
coalitions_adopt_init_task(task_t task)1662 coalitions_adopt_init_task(task_t task)
1663 {
1664 	kern_return_t kr;
1665 	kr = coalitions_adopt_task(init_coalition, task);
1666 	if (kr != KERN_SUCCESS) {
1667 		panic("failed to adopt task %p into default coalition: %d", task, kr);
1668 	}
1669 	return kr;
1670 }
1671 
1672 /* Used for forked corpses. */
1673 kern_return_t
coalitions_adopt_corpse_task(task_t task)1674 coalitions_adopt_corpse_task(task_t task)
1675 {
1676 	kern_return_t kr;
1677 	kr = coalitions_adopt_task(corpse_coalition, task);
1678 	if (kr != KERN_SUCCESS) {
1679 		panic("failed to adopt task %p into corpse coalition: %d", task, kr);
1680 	}
1681 	return kr;
1682 }
1683 
1684 /*
1685  * coalition_adopt_task_internal
1686  * Condition: Coalition must be referenced and unlocked. Will fail if coalition
1687  * is already terminated.
1688  */
1689 static kern_return_t
coalition_adopt_task_internal(coalition_t coal,task_t task)1690 coalition_adopt_task_internal(coalition_t coal, task_t task)
1691 {
1692 	kern_return_t kr;
1693 
1694 	if (task->coalition[coal->type]) {
1695 		return KERN_ALREADY_IN_SET;
1696 	}
1697 
1698 	coalition_lock(coal);
1699 
1700 	if (coal->reaped || coal->terminated) {
1701 		coalition_unlock(coal);
1702 		return KERN_TERMINATED;
1703 	}
1704 
1705 	kr = coal_call(coal, adopt_task, task);
1706 	if (kr != KERN_SUCCESS) {
1707 		goto out_unlock;
1708 	}
1709 
1710 	coal->active_count++;
1711 
1712 	coal_ref_retain(coal);
1713 
1714 	task->coalition[coal->type] = coal;
1715 
1716 out_unlock:
1717 #if COALITION_DEBUG
1718 	(void)coal; /* need expression after label */
1719 	uint64_t cid = coal->id;
1720 	uint32_t rc = coal_ref_count(coal);
1721 	uint32_t ct = coal->type;
1722 #endif
1723 	if (get_task_uniqueid(task) != UINT64_MAX) {
1724 		/* On 32-bit targets, uniqueid will get truncated to 32 bits */
1725 		KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_ADOPT),
1726 		    coal->id, get_task_uniqueid(task));
1727 	}
1728 
1729 	coalition_unlock(coal);
1730 
1731 	coal_dbg("task:%d, id:%llu type:%s ref_count:%u, kr=%d",
1732 	    task_pid(task), cid, coal_type_str(ct), rc, kr);
1733 	return kr;
1734 }
1735 
1736 static kern_return_t
coalition_remove_task_internal(task_t task,int type)1737 coalition_remove_task_internal(task_t task, int type)
1738 {
1739 	kern_return_t kr;
1740 
1741 	coalition_t coal = task->coalition[type];
1742 
1743 	if (!coal) {
1744 		return KERN_SUCCESS;
1745 	}
1746 
1747 	assert(coal->type == (uint32_t)type);
1748 
1749 	coalition_lock(coal);
1750 
1751 	kr = coal_call(coal, remove_task, task);
1752 
1753 #if COALITION_DEBUG
1754 	uint64_t cid = coal->id;
1755 	uint32_t rc = coal_ref_count(coal);
1756 	int      ac = coal->active_count;
1757 	int      ct = coal->type;
1758 #endif
1759 	KDBG_RELEASE(MACHDBG_CODE(DBG_MACH_COALITION, MACH_COALITION_REMOVE),
1760 	    coal->id, get_task_uniqueid(task));
1761 	coalition_unlock(coal);
1762 
1763 	coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u, kr=%d",
1764 	    cid, coal_type_str(ct), rc, ac, kr);
1765 
1766 	coalition_remove_active(coal);
1767 
1768 	return kr;
1769 }
1770 
1771 /*
1772  * coalitions_adopt_task
1773  * Condition: All coalitions must be referenced and unlocked.
1774  * Will fail if any coalition is already terminated.
1775  */
1776 kern_return_t
coalitions_adopt_task(coalition_t * coals,task_t task)1777 coalitions_adopt_task(coalition_t *coals, task_t task)
1778 {
1779 	int i;
1780 	kern_return_t kr;
1781 
1782 	if (!coals || coals[COALITION_TYPE_RESOURCE] == COALITION_NULL) {
1783 		return KERN_INVALID_ARGUMENT;
1784 	}
1785 
1786 	/* verify that the incoming coalitions are what they say they are */
1787 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1788 		if (coals[i] && coals[i]->type != (uint32_t)i) {
1789 			return KERN_INVALID_ARGUMENT;
1790 		}
1791 	}
1792 
1793 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1794 		kr = KERN_SUCCESS;
1795 		if (coals[i]) {
1796 			kr = coalition_adopt_task_internal(coals[i], task);
1797 		}
1798 		if (kr != KERN_SUCCESS) {
1799 			/* dis-associate any coalitions that just adopted this task */
1800 			while (--i >= 0) {
1801 				if (task->coalition[i]) {
1802 					coalition_remove_task_internal(task, i);
1803 				}
1804 			}
1805 			break;
1806 		}
1807 	}
1808 	return kr;
1809 }
1810 
1811 /*
1812  * coalitions_remove_task
1813  * Condition: task must be referenced and UNLOCKED; all task's coalitions must be UNLOCKED
1814  */
1815 kern_return_t
coalitions_remove_task(task_t task)1816 coalitions_remove_task(task_t task)
1817 {
1818 	kern_return_t kr;
1819 	int i;
1820 
1821 	task_lock(task);
1822 	if (!task_is_coalition_member(task)) {
1823 		task_unlock(task);
1824 		return KERN_SUCCESS;
1825 	}
1826 
1827 	task_clear_coalition_member(task);
1828 	task_unlock(task);
1829 
1830 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1831 		kr = coalition_remove_task_internal(task, i);
1832 		assert(kr == KERN_SUCCESS);
1833 	}
1834 
1835 	return kr;
1836 }
1837 
1838 /*
1839  * task_release_coalitions
1840  * helper function to release references to all coalitions in which
1841  * 'task' is a member.
1842  */
1843 void
task_release_coalitions(task_t task)1844 task_release_coalitions(task_t task)
1845 {
1846 	int i;
1847 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1848 		if (task->coalition[i]) {
1849 			coalition_release(task->coalition[i]);
1850 		} else if (i == COALITION_TYPE_RESOURCE) {
1851 			panic("deallocating task %p was not a member of a resource coalition", task);
1852 		}
1853 	}
1854 }
1855 
1856 /*
1857  * coalitions_set_roles
1858  * for each type of coalition, if the task is a member of a coalition of
1859  * that type (given in the coalitions parameter) then set the role of
1860  * the task within that that coalition.
1861  */
1862 kern_return_t
coalitions_set_roles(coalition_t coalitions[COALITION_NUM_TYPES],task_t task,int roles[COALITION_NUM_TYPES])1863 coalitions_set_roles(coalition_t coalitions[COALITION_NUM_TYPES],
1864     task_t task, int roles[COALITION_NUM_TYPES])
1865 {
1866 	kern_return_t kr = KERN_SUCCESS;
1867 	int i;
1868 
1869 	for (i = 0; i < COALITION_NUM_TYPES; i++) {
1870 		if (!coalitions[i]) {
1871 			continue;
1872 		}
1873 		coalition_lock(coalitions[i]);
1874 		kr = coal_call(coalitions[i], set_taskrole, task, roles[i]);
1875 		coalition_unlock(coalitions[i]);
1876 		assert(kr == KERN_SUCCESS);
1877 	}
1878 
1879 	return kr;
1880 }
1881 
1882 /*
1883  * coalition_terminate_internal
1884  * Condition: Coalition must be referenced and UNLOCKED.
1885  */
1886 kern_return_t
coalition_request_terminate_internal(coalition_t coal)1887 coalition_request_terminate_internal(coalition_t coal)
1888 {
1889 	assert(coal->type >= 0 && coal->type <= COALITION_TYPE_MAX);
1890 
1891 	if (coal == init_coalition[coal->type]) {
1892 		return KERN_DEFAULT_SET;
1893 	}
1894 
1895 	coalition_lock(coal);
1896 
1897 	if (coal->reaped) {
1898 		coalition_unlock(coal);
1899 		return KERN_INVALID_NAME;
1900 	}
1901 
1902 	if (coal->terminated || coal->termrequested) {
1903 		coalition_unlock(coal);
1904 		return KERN_TERMINATED;
1905 	}
1906 
1907 	coal->termrequested = TRUE;
1908 
1909 	boolean_t do_notify = FALSE;
1910 	uint64_t note_id = 0;
1911 	uint32_t note_flags = 0;
1912 
1913 	if (coal->active_count == 0) {
1914 		/*
1915 		 * We only notify once, when active_count reaches zero.
1916 		 * We just set termrequested to zero. If the active count
1917 		 * was already at zero (tasks died before we could request
1918 		 * a termination notification), we should notify.
1919 		 */
1920 		assert(!coal->terminated);
1921 		coal->terminated = TRUE;
1922 
1923 		assert(!coal->notified);
1924 
1925 		coal->notified = TRUE;
1926 #if DEVELOPMENT || DEBUG
1927 		do_notify = coal->should_notify;
1928 #else
1929 		do_notify = TRUE;
1930 #endif
1931 		note_id = coal->id;
1932 		note_flags = 0;
1933 	}
1934 
1935 	coalition_unlock(coal);
1936 
1937 	if (do_notify) {
1938 		coalition_notify_user(note_id, note_flags);
1939 	}
1940 
1941 	return KERN_SUCCESS;
1942 }
1943 
1944 /*
1945  * coalition_reap_internal
1946  * Condition: Coalition must be referenced and UNLOCKED.
1947  */
1948 kern_return_t
coalition_reap_internal(coalition_t coal)1949 coalition_reap_internal(coalition_t coal)
1950 {
1951 	assert(coal->type <= COALITION_TYPE_MAX);
1952 
1953 	if (coal == init_coalition[coal->type]) {
1954 		return KERN_DEFAULT_SET;
1955 	}
1956 
1957 	coalition_lock(coal);
1958 	if (coal->reaped) {
1959 		coalition_unlock(coal);
1960 		return KERN_TERMINATED;
1961 	}
1962 	if (!coal->terminated) {
1963 		coalition_unlock(coal);
1964 		return KERN_FAILURE;
1965 	}
1966 	assert(coal->termrequested);
1967 	if (coal->active_count > 0) {
1968 		coalition_unlock(coal);
1969 		return KERN_FAILURE;
1970 	}
1971 
1972 	coal->reaped = TRUE;
1973 
1974 	coalition_unlock(coal);
1975 
1976 	lck_mtx_lock(&coalitions_list_lock);
1977 	smr_hash_serialized_remove(&coalition_hash, &coal->link,
1978 	    &coal_hash_traits);
1979 	if (smr_hash_serialized_should_shrink(&coalition_hash,
1980 	    COALITION_HASH_SIZE_MIN, 2, 1)) {
1981 		/* shrink if more than 2 buckets per 1 element */
1982 		smr_hash_shrink_and_unlock(&coalition_hash,
1983 		    &coalitions_list_lock, &coal_hash_traits);
1984 	} else {
1985 		lck_mtx_unlock(&coalitions_list_lock);
1986 	}
1987 
1988 	/* Release the list's reference and launchd's reference. */
1989 	coal_ref_release_live(coal);
1990 	coalition_release(coal);
1991 
1992 	return KERN_SUCCESS;
1993 }
1994 
1995 #if DEVELOPMENT || DEBUG
1996 int
coalition_should_notify(coalition_t coal)1997 coalition_should_notify(coalition_t coal)
1998 {
1999 	int should;
2000 	if (!coal) {
2001 		return -1;
2002 	}
2003 	coalition_lock(coal);
2004 	should = coal->should_notify;
2005 	coalition_unlock(coal);
2006 
2007 	return should;
2008 }
2009 
2010 void
coalition_set_notify(coalition_t coal,int notify)2011 coalition_set_notify(coalition_t coal, int notify)
2012 {
2013 	if (!coal) {
2014 		return;
2015 	}
2016 	coalition_lock(coal);
2017 	coal->should_notify = !!notify;
2018 	coalition_unlock(coal);
2019 }
2020 #endif
2021 
2022 void
coalitions_init(void)2023 coalitions_init(void)
2024 {
2025 	kern_return_t kr;
2026 	int i;
2027 	const struct coalition_type *ctype;
2028 
2029 	smr_hash_init(&coalition_hash, COALITION_HASH_SIZE_MIN);
2030 
2031 	init_task_ledgers();
2032 
2033 	init_coalition_ledgers();
2034 
2035 	for (i = 0, ctype = &s_coalition_types[0]; i < COALITION_NUM_TYPES; ctype++, i++) {
2036 		/* verify the entry in the global coalition types array */
2037 		if (ctype->type != i ||
2038 		    !ctype->init ||
2039 		    !ctype->dealloc ||
2040 		    !ctype->adopt_task ||
2041 		    !ctype->remove_task) {
2042 			panic("%s: Malformed coalition type %s(%d) in slot for type:%s(%d)",
2043 			    __func__, coal_type_str(ctype->type), ctype->type, coal_type_str(i), i);
2044 		}
2045 		if (!ctype->has_default) {
2046 			continue;
2047 		}
2048 		kr = coalition_create_internal(ctype->type, COALITION_ROLE_SYSTEM, TRUE, FALSE, &init_coalition[ctype->type], NULL);
2049 		if (kr != KERN_SUCCESS) {
2050 			panic("%s: could not create init %s coalition: kr:%d",
2051 			    __func__, coal_type_str(i), kr);
2052 		}
2053 		if (i == COALITION_TYPE_RESOURCE) {
2054 			assert(COALITION_ID_KERNEL == init_coalition[ctype->type]->id);
2055 		}
2056 		kr = coalition_create_internal(ctype->type, COALITION_ROLE_SYSTEM, FALSE, FALSE, &corpse_coalition[ctype->type], NULL);
2057 		if (kr != KERN_SUCCESS) {
2058 			panic("%s: could not create corpse %s coalition: kr:%d",
2059 			    __func__, coal_type_str(i), kr);
2060 		}
2061 	}
2062 
2063 	/* "Leak" our reference to the global object */
2064 }
2065 
2066 /*
2067  * BSD Kernel interface functions
2068  *
2069  */
2070 static void
coalition_fill_procinfo(struct coalition * coal,struct procinfo_coalinfo * coalinfo)2071 coalition_fill_procinfo(struct coalition *coal,
2072     struct procinfo_coalinfo *coalinfo)
2073 {
2074 	coalinfo->coalition_id = coal->id;
2075 	coalinfo->coalition_type = coal->type;
2076 	coalinfo->coalition_tasks = coalition_get_task_count(coal);
2077 }
2078 
2079 
2080 size_t
coalitions_get_list(int type,struct procinfo_coalinfo * coal_list,size_t list_sz)2081 coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, size_t list_sz)
2082 {
2083 	size_t ncoals = 0;
2084 	struct coalition *coal;
2085 
2086 	lck_mtx_lock(&coalitions_list_lock);
2087 	smr_hash_foreach(coal, &coalition_hash, &coal_hash_traits) {
2088 		if (!coal->reaped && (type < 0 || type == (int)coal->type)) {
2089 			if (coal_list && ncoals < list_sz) {
2090 				coalition_fill_procinfo(coal, &coal_list[ncoals]);
2091 			}
2092 			++ncoals;
2093 		}
2094 	}
2095 	lck_mtx_unlock(&coalitions_list_lock);
2096 
2097 	return ncoals;
2098 }
2099 
2100 /*
2101  * Return the coaltion of the given type to which the task belongs.
2102  */
2103 coalition_t
task_get_coalition(task_t task,int coal_type)2104 task_get_coalition(task_t task, int coal_type)
2105 {
2106 	coalition_t c;
2107 
2108 	if (task == NULL || coal_type > COALITION_TYPE_MAX) {
2109 		return COALITION_NULL;
2110 	}
2111 
2112 	c = task->coalition[coal_type];
2113 	assert(c == COALITION_NULL || (int)c->type == coal_type);
2114 	return c;
2115 }
2116 
2117 /*
2118  * Report if the given task is the leader of the given jetsam coalition.
2119  */
2120 boolean_t
coalition_is_leader(task_t task,coalition_t coal)2121 coalition_is_leader(task_t task, coalition_t coal)
2122 {
2123 	boolean_t ret = FALSE;
2124 
2125 	if (coal != COALITION_NULL) {
2126 		coalition_lock(coal);
2127 
2128 		ret = (coal->type == COALITION_TYPE_JETSAM && coal->j.leader == task);
2129 
2130 		coalition_unlock(coal);
2131 	}
2132 
2133 	return ret;
2134 }
2135 
2136 kern_return_t
coalition_iterate_stackshot(coalition_iterate_fn_t callout,void * arg,uint32_t coalition_type)2137 coalition_iterate_stackshot(coalition_iterate_fn_t callout, void *arg, uint32_t coalition_type)
2138 {
2139 	coalition_t coal;
2140 	int i = 0;
2141 
2142 	smr_hash_foreach(coal, &coalition_hash, &coal_hash_traits) {
2143 		if (coal == NULL || !ml_validate_nofault((vm_offset_t)coal, sizeof(struct coalition))) {
2144 			return KERN_FAILURE;
2145 		}
2146 
2147 		if (coalition_type == coal->type) {
2148 			callout(arg, i++, coal);
2149 		}
2150 	}
2151 
2152 	return KERN_SUCCESS;
2153 }
2154 
2155 task_t
kdp_coalition_get_leader(coalition_t coal)2156 kdp_coalition_get_leader(coalition_t coal)
2157 {
2158 	if (!coal) {
2159 		return TASK_NULL;
2160 	}
2161 
2162 	if (coal->type == COALITION_TYPE_JETSAM) {
2163 		return coal->j.leader;
2164 	}
2165 	return TASK_NULL;
2166 }
2167 
2168 task_t
coalition_get_leader(coalition_t coal)2169 coalition_get_leader(coalition_t coal)
2170 {
2171 	task_t leader = TASK_NULL;
2172 
2173 	if (!coal) {
2174 		return TASK_NULL;
2175 	}
2176 
2177 	coalition_lock(coal);
2178 	if (coal->type != COALITION_TYPE_JETSAM) {
2179 		goto out_unlock;
2180 	}
2181 
2182 	leader = coal->j.leader;
2183 	if (leader != TASK_NULL) {
2184 		task_reference(leader);
2185 	}
2186 
2187 out_unlock:
2188 	coalition_unlock(coal);
2189 	return leader;
2190 }
2191 
2192 
2193 int
coalition_get_task_count(coalition_t coal)2194 coalition_get_task_count(coalition_t coal)
2195 {
2196 	int ntasks = 0;
2197 	struct queue_entry *qe;
2198 	if (!coal) {
2199 		return 0;
2200 	}
2201 
2202 	coalition_lock(coal);
2203 	switch (coal->type) {
2204 	case COALITION_TYPE_RESOURCE:
2205 		qe_foreach(qe, &coal->r.tasks)
2206 		ntasks++;
2207 		break;
2208 	case COALITION_TYPE_JETSAM:
2209 		if (coal->j.leader) {
2210 			ntasks++;
2211 		}
2212 		qe_foreach(qe, &coal->j.other)
2213 		ntasks++;
2214 		qe_foreach(qe, &coal->j.extensions)
2215 		ntasks++;
2216 		qe_foreach(qe, &coal->j.services)
2217 		ntasks++;
2218 		break;
2219 	default:
2220 		break;
2221 	}
2222 	coalition_unlock(coal);
2223 
2224 	return ntasks;
2225 }
2226 
2227 
2228 static uint64_t
i_get_list_footprint(queue_t list,int type,int * ntasks)2229 i_get_list_footprint(queue_t list, int type, int *ntasks)
2230 {
2231 	task_t task;
2232 	uint64_t bytes = 0;
2233 
2234 	qe_foreach_element(task, list, task_coalition[type]) {
2235 		bytes += get_task_phys_footprint(task);
2236 		coal_dbg("    [%d] task_pid:%d, type:%d, footprint:%lld",
2237 		    *ntasks, task_pid(task), type, bytes);
2238 		*ntasks += 1;
2239 	}
2240 
2241 	return bytes;
2242 }
2243 
2244 uint64_t
coalition_get_page_count(coalition_t coal,int * ntasks)2245 coalition_get_page_count(coalition_t coal, int *ntasks)
2246 {
2247 	uint64_t bytes = 0;
2248 	int num_tasks = 0;
2249 
2250 	if (ntasks) {
2251 		*ntasks = 0;
2252 	}
2253 	if (!coal) {
2254 		return bytes;
2255 	}
2256 
2257 	coalition_lock(coal);
2258 
2259 	switch (coal->type) {
2260 	case COALITION_TYPE_RESOURCE:
2261 		bytes += i_get_list_footprint(&coal->r.tasks, COALITION_TYPE_RESOURCE, &num_tasks);
2262 		break;
2263 	case COALITION_TYPE_JETSAM:
2264 		if (coal->j.leader) {
2265 			bytes += get_task_phys_footprint(coal->j.leader);
2266 			num_tasks = 1;
2267 		}
2268 		bytes += i_get_list_footprint(&coal->j.extensions, COALITION_TYPE_JETSAM, &num_tasks);
2269 		bytes += i_get_list_footprint(&coal->j.services, COALITION_TYPE_JETSAM, &num_tasks);
2270 		bytes += i_get_list_footprint(&coal->j.other, COALITION_TYPE_JETSAM, &num_tasks);
2271 		break;
2272 	default:
2273 		break;
2274 	}
2275 
2276 	coalition_unlock(coal);
2277 
2278 	if (ntasks) {
2279 		*ntasks = num_tasks;
2280 	}
2281 
2282 	return bytes / PAGE_SIZE_64;
2283 }
2284 
2285 struct coal_sort_s {
2286 	int pid;
2287 	int usr_order;
2288 	uint64_t bytes;
2289 };
2290 
2291 /*
2292  * return < 0 for a < b
2293  *          0 for a == b
2294  *        > 0 for a > b
2295  */
2296 typedef int (*cmpfunc_t)(const void *a, const void *b);
2297 
2298 extern void
2299 qsort(void *a, size_t n, size_t es, cmpfunc_t cmp);
2300 
2301 static int
dflt_cmp(const void * a,const void * b)2302 dflt_cmp(const void *a, const void *b)
2303 {
2304 	const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2305 	const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2306 
2307 	/*
2308 	 * if both A and B are equal, use a memory descending sort
2309 	 */
2310 	if (csA->usr_order == csB->usr_order) {
2311 		return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
2312 	}
2313 
2314 	/* otherwise, return the relationship between user specified orders */
2315 	return csA->usr_order - csB->usr_order;
2316 }
2317 
2318 static int
mem_asc_cmp(const void * a,const void * b)2319 mem_asc_cmp(const void *a, const void *b)
2320 {
2321 	const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2322 	const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2323 
2324 	return (int)((int64_t)csA->bytes - (int64_t)csB->bytes);
2325 }
2326 
2327 static int
mem_dec_cmp(const void * a,const void * b)2328 mem_dec_cmp(const void *a, const void *b)
2329 {
2330 	const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2331 	const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2332 
2333 	return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
2334 }
2335 
2336 static int
usr_asc_cmp(const void * a,const void * b)2337 usr_asc_cmp(const void *a, const void *b)
2338 {
2339 	const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2340 	const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2341 
2342 	return csA->usr_order - csB->usr_order;
2343 }
2344 
2345 static int
usr_dec_cmp(const void * a,const void * b)2346 usr_dec_cmp(const void *a, const void *b)
2347 {
2348 	const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
2349 	const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
2350 
2351 	return csB->usr_order - csA->usr_order;
2352 }
2353 
2354 /* avoid dynamic allocation in this path */
2355 #define MAX_SORTED_PIDS  80
2356 
2357 static int
coalition_get_sort_list(coalition_t coal,int sort_order,queue_t list,struct coal_sort_s * sort_array,int array_sz)2358 coalition_get_sort_list(coalition_t coal, int sort_order, queue_t list,
2359     struct coal_sort_s *sort_array, int array_sz)
2360 {
2361 	int ntasks = 0;
2362 	task_t task;
2363 
2364 	assert(sort_array != NULL);
2365 
2366 	if (array_sz <= 0) {
2367 		return 0;
2368 	}
2369 
2370 	if (!list) {
2371 		/*
2372 		 * this function will only be called with a NULL
2373 		 * list for JETSAM-type coalitions, and is intended
2374 		 * to investigate the leader process
2375 		 */
2376 		if (coal->type != COALITION_TYPE_JETSAM ||
2377 		    coal->j.leader == TASK_NULL) {
2378 			return 0;
2379 		}
2380 		sort_array[0].pid = task_pid(coal->j.leader);
2381 		switch (sort_order) {
2382 		case COALITION_SORT_DEFAULT:
2383 			sort_array[0].usr_order = 0;
2384 			OS_FALLTHROUGH;
2385 		case COALITION_SORT_MEM_ASC:
2386 		case COALITION_SORT_MEM_DEC:
2387 			sort_array[0].bytes = get_task_phys_footprint(coal->j.leader);
2388 			break;
2389 		case COALITION_SORT_USER_ASC:
2390 		case COALITION_SORT_USER_DEC:
2391 			sort_array[0].usr_order = 0;
2392 			break;
2393 		default:
2394 			break;
2395 		}
2396 		return 1;
2397 	}
2398 
2399 	qe_foreach_element(task, list, task_coalition[coal->type]) {
2400 		if (ntasks >= array_sz) {
2401 			printf("WARNING: more than %d pids in coalition %llu\n",
2402 			    MAX_SORTED_PIDS, coal->id);
2403 			break;
2404 		}
2405 
2406 		sort_array[ntasks].pid = task_pid(task);
2407 
2408 		switch (sort_order) {
2409 		case COALITION_SORT_DEFAULT:
2410 			sort_array[ntasks].usr_order = 0;
2411 			OS_FALLTHROUGH;
2412 		case COALITION_SORT_MEM_ASC:
2413 		case COALITION_SORT_MEM_DEC:
2414 			sort_array[ntasks].bytes = get_task_phys_footprint(task);
2415 			break;
2416 		case COALITION_SORT_USER_ASC:
2417 		case COALITION_SORT_USER_DEC:
2418 			sort_array[ntasks].usr_order = 0;
2419 			break;
2420 		default:
2421 			break;
2422 		}
2423 
2424 		ntasks++;
2425 	}
2426 
2427 	return ntasks;
2428 }
2429 
2430 int
coalition_get_pid_list(coalition_t coal,uint32_t rolemask,int sort_order,int * pid_list,int list_sz)2431 coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
2432     int *pid_list, int list_sz)
2433 {
2434 	struct i_jetsam_coalition *cj;
2435 	int ntasks = 0;
2436 	cmpfunc_t cmp_func = NULL;
2437 	struct coal_sort_s sort_array[MAX_SORTED_PIDS] = { {0, 0, 0} }; /* keep to < 2k */
2438 
2439 	if (!coal ||
2440 	    !(rolemask & COALITION_ROLEMASK_ALLROLES) ||
2441 	    !pid_list || list_sz < 1) {
2442 		coal_dbg("Invalid parameters: coal:%p, type:%d, rolemask:0x%x, "
2443 		    "pid_list:%p, list_sz:%d", coal, coal ? coal->type : -1,
2444 		    rolemask, pid_list, list_sz);
2445 		return -EINVAL;
2446 	}
2447 
2448 	switch (sort_order) {
2449 	case COALITION_SORT_NOSORT:
2450 		cmp_func = NULL;
2451 		break;
2452 	case COALITION_SORT_DEFAULT:
2453 		cmp_func = dflt_cmp;
2454 		break;
2455 	case COALITION_SORT_MEM_ASC:
2456 		cmp_func = mem_asc_cmp;
2457 		break;
2458 	case COALITION_SORT_MEM_DEC:
2459 		cmp_func = mem_dec_cmp;
2460 		break;
2461 	case COALITION_SORT_USER_ASC:
2462 		cmp_func = usr_asc_cmp;
2463 		break;
2464 	case COALITION_SORT_USER_DEC:
2465 		cmp_func = usr_dec_cmp;
2466 		break;
2467 	default:
2468 		return -ENOTSUP;
2469 	}
2470 
2471 	coalition_lock(coal);
2472 
2473 	if (coal->type == COALITION_TYPE_RESOURCE) {
2474 		ntasks += coalition_get_sort_list(coal, sort_order, &coal->r.tasks,
2475 		    sort_array, MAX_SORTED_PIDS);
2476 		goto unlock_coal;
2477 	}
2478 
2479 	cj = &coal->j;
2480 
2481 	if (rolemask & COALITION_ROLEMASK_UNDEF) {
2482 		ntasks += coalition_get_sort_list(coal, sort_order, &cj->other,
2483 		    sort_array + ntasks,
2484 		    MAX_SORTED_PIDS - ntasks);
2485 	}
2486 
2487 	if (rolemask & COALITION_ROLEMASK_XPC) {
2488 		ntasks += coalition_get_sort_list(coal, sort_order, &cj->services,
2489 		    sort_array + ntasks,
2490 		    MAX_SORTED_PIDS - ntasks);
2491 	}
2492 
2493 	if (rolemask & COALITION_ROLEMASK_EXT) {
2494 		ntasks += coalition_get_sort_list(coal, sort_order, &cj->extensions,
2495 		    sort_array + ntasks,
2496 		    MAX_SORTED_PIDS - ntasks);
2497 	}
2498 
2499 	if (rolemask & COALITION_ROLEMASK_LEADER) {
2500 		ntasks += coalition_get_sort_list(coal, sort_order, NULL,
2501 		    sort_array + ntasks,
2502 		    MAX_SORTED_PIDS - ntasks);
2503 	}
2504 
2505 unlock_coal:
2506 	coalition_unlock(coal);
2507 
2508 	/* sort based on the chosen criterion (no sense sorting 1 item) */
2509 	if (cmp_func && ntasks > 1) {
2510 		qsort(sort_array, ntasks, sizeof(struct coal_sort_s), cmp_func);
2511 	}
2512 
2513 	for (int i = 0; i < ntasks; i++) {
2514 		if (i >= list_sz) {
2515 			break;
2516 		}
2517 		coal_dbg(" [%d] PID:%d, footprint:%lld, usr_order:%d",
2518 		    i, sort_array[i].pid, sort_array[i].bytes,
2519 		    sort_array[i].usr_order);
2520 		pid_list[i] = sort_array[i].pid;
2521 	}
2522 
2523 	return ntasks;
2524 }
2525 
2526 static void
mark_coalition_member_as_swappable(__unused coalition_t coal,__unused void * ctx,task_t task)2527 mark_coalition_member_as_swappable(__unused coalition_t coal, __unused void *ctx, task_t task)
2528 {
2529 	vm_task_set_selfdonate_pages(task, true);
2530 }
2531 
2532 void
coalition_mark_swappable(coalition_t coal)2533 coalition_mark_swappable(coalition_t coal)
2534 {
2535 	struct i_jetsam_coalition *cj = NULL;
2536 
2537 	coalition_lock(coal);
2538 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
2539 
2540 	cj = &coal->j;
2541 	cj->swap_enabled = true;
2542 
2543 	i_coal_jetsam_iterate_tasks(coal, NULL, mark_coalition_member_as_swappable);
2544 
2545 	coalition_unlock(coal);
2546 }
2547 
2548 bool
coalition_is_swappable(coalition_t coal)2549 coalition_is_swappable(coalition_t coal)
2550 {
2551 	struct i_jetsam_coalition *cj = NULL;
2552 
2553 	coalition_lock(coal);
2554 	assert(coal && coal->type == COALITION_TYPE_JETSAM);
2555 
2556 	cj = &coal->j;
2557 	bool enabled = cj->swap_enabled;
2558 
2559 	coalition_unlock(coal);
2560 
2561 	return enabled;
2562 }
2563