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