]> git.saurik.com Git - apple/xnu.git/blame - osfmk/kern/coalition.c
xnu-3247.10.11.tar.gz
[apple/xnu.git] / osfmk / kern / coalition.c
CommitLineData
fe8ab488
A
1/*
2 * Copyright (c) 2013 Apple Computer, 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/host.h>
fe8ab488 35#include <kern/kalloc.h>
3e170ce0 36#include <kern/ledger.h>
fe8ab488
A
37#include <kern/mach_param.h> /* for TASK_CHUNK */
38#include <kern/task.h>
39#include <kern/zalloc.h>
40
41#include <libkern/OSAtomic.h>
42
43#include <mach/coalition_notification_server.h>
44#include <mach/host_priv.h>
45#include <mach/host_special_ports.h>
46
47#include <sys/errno.h>
48
3e170ce0
A
49/*
50 * BSD interface functions
51 */
52int coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz);
53boolean_t coalition_is_leader(task_t task, int coal_type, coalition_t *coal);
54int coalition_get_task_count(coalition_t coal);
55uint64_t coalition_get_page_count(coalition_t coal, int *ntasks);
56int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
57 int *pid_list, int list_sz);
58
fe8ab488
A
59/* defined in task.c */
60extern ledger_template_t task_ledger_template;
61
62/*
63 * Coalition zone needs limits. We expect there will be as many coalitions as
64 * tasks (same order of magnitude), so use the task zone's limits.
65 * */
66#define CONFIG_COALITION_MAX CONFIG_TASK_MAX
67#define COALITION_CHUNK TASK_CHUNK
68
69int unrestrict_coalition_syscalls;
70
71lck_attr_t coalitions_lck_attr;
72lck_grp_t coalitions_lck_grp;
73lck_grp_attr_t coalitions_lck_grp_attr;
74
75/* coalitions_list_lock protects coalition_count, coalitions queue, next_coalition_id. */
76decl_lck_mtx_data(static,coalitions_list_lock);
77static uint64_t coalition_count;
78static uint64_t coalition_next_id = 1;
3e170ce0 79static queue_head_t coalitions_q;
fe8ab488 80
3e170ce0 81coalition_t init_coalition[COALITION_NUM_TYPES];
fe8ab488
A
82
83zone_t coalition_zone;
84
3e170ce0
A
85static const char *coal_type_str(int type)
86{
87 switch(type) {
88 case COALITION_TYPE_RESOURCE:
89 return "RESOURCE";
90 case COALITION_TYPE_JETSAM:
91 return "JETSAM";
92 default:
93 return "<unknown>";
94 }
95}
96
97struct coalition_type {
98 int type;
99 int has_default;
100 /*
101 * init
102 * pre-condition: coalition just allocated (unlocked), unreferenced,
103 * type field set
104 */
105 kern_return_t (*init)(coalition_t coal, boolean_t privileged);
106
107 /*
108 * dealloc
109 * pre-condition: coalition unlocked
110 * pre-condition: coalition refcount=0, active_count=0,
111 * termrequested=1, terminated=1, reaped=1
112 */
113 void (*dealloc)(coalition_t coal);
114
115 /*
116 * adopt_task
117 * pre-condition: coalition locked
118 * pre-condition: coalition !repead and !terminated
119 */
120 kern_return_t (*adopt_task)(coalition_t coal, task_t task);
121
122 /*
123 * remove_task
124 * pre-condition: coalition locked
125 * pre-condition: task has been removed from coalition's task list
126 */
127 kern_return_t (*remove_task)(coalition_t coal, task_t task);
128
129 /*
130 * set_taskrole
131 * pre-condition: coalition locked
132 * pre-condition: task added to coalition's task list,
133 * active_count >= 1 (at least the given task is active)
134 */
135 kern_return_t (*set_taskrole)(coalition_t coal, task_t task, int role);
136
137 /*
138 * get_taskrole
139 * pre-condition: coalition locked
140 * pre-condition: task added to coalition's task list,
141 * active_count >= 1 (at least the given task is active)
142 */
143 int (*get_taskrole)(coalition_t coal, task_t task);
144
145 /*
146 * iterate_tasks
147 * pre-condition: coalition locked
148 */
149 void (*iterate_tasks)(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t));
150};
151
152/*
153 * COALITION_TYPE_RESOURCE
154 */
fe8ab488 155
3e170ce0
A
156static kern_return_t i_coal_resource_init(coalition_t coal, boolean_t privileged);
157static void i_coal_resource_dealloc(coalition_t coal);
158static kern_return_t i_coal_resource_adopt_task(coalition_t coal, task_t task);
159static kern_return_t i_coal_resource_remove_task(coalition_t coal, task_t task);
160static kern_return_t i_coal_resource_set_taskrole(coalition_t coal,
161 task_t task, int role);
162static int i_coal_resource_get_taskrole(coalition_t coal, task_t task);
163static void i_coal_resource_iterate_tasks(coalition_t coal, void *ctx,
164 void (*callback)(coalition_t, void *, task_t));
165
166struct i_resource_coalition {
fe8ab488
A
167 ledger_t ledger;
168 uint64_t bytesread;
169 uint64_t byteswritten;
170 uint64_t gpu_time;
171
3e170ce0
A
172 uint64_t task_count; /* tasks that have started in this coalition */
173 uint64_t dead_task_count; /* tasks that have exited in this coalition;
174 subtract from task_count to get count
175 of "active" tasks */
fe8ab488
A
176 /*
177 * Count the length of time this coalition had at least one active task.
178 * This can be a 'denominator' to turn e.g. cpu_time to %cpu.
179 * */
180 uint64_t last_became_nonempty_time;
181 uint64_t time_nonempty;
182
3e170ce0
A
183 queue_head_t tasks; /* List of active tasks in the coalition */
184};
fe8ab488 185
3e170ce0
A
186/*
187 * COALITION_TYPE_JETSAM
188 */
fe8ab488 189
3e170ce0
A
190static kern_return_t i_coal_jetsam_init(coalition_t coal, boolean_t privileged);
191static void i_coal_jetsam_dealloc(coalition_t coal);
192static kern_return_t i_coal_jetsam_adopt_task(coalition_t coal, task_t task);
193static kern_return_t i_coal_jetsam_remove_task(coalition_t coal, task_t task);
194static kern_return_t i_coal_jetsam_set_taskrole(coalition_t coal,
195 task_t task, int role);
196static int i_coal_jetsam_get_taskrole(coalition_t coal, task_t task);
197static void i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx,
198 void (*callback)(coalition_t, void *, task_t));
199
200struct i_jetsam_coalition {
201 task_t leader;
202 queue_head_t extensions;
203 queue_head_t services;
204 queue_head_t other;
205};
fe8ab488 206
fe8ab488 207
3e170ce0
A
208/*
209 * main coalition structure
210 */
211struct coalition {
212 uint64_t id; /* monotonically increasing */
213 uint32_t type;
214 uint32_t ref_count; /* Number of references to the memory containing this struct */
215 uint32_t active_count; /* Number of members of (tasks in) the
216 coalition, plus vouchers referring
217 to the coalition */
218 uint32_t focal_task_count; /* Number of TASK_FOREGROUND_APPLICATION tasks in the coalition */
219 uint32_t nonfocal_task_count; /* Number of TASK_BACKGROUND_APPLICATION tasks in the coalition */
220
221 /* coalition flags */
222 uint32_t privileged : 1; /* Members of this coalition may create
223 and manage coalitions and may posix_spawn
224 processes into selected coalitions */
fe8ab488 225 /* ast? */
fe8ab488 226 /* voucher */
3e170ce0
A
227 uint32_t termrequested : 1; /* launchd has requested termination when coalition becomes empty */
228 uint32_t terminated : 1; /* coalition became empty and spawns are now forbidden */
229 uint32_t reaped : 1; /* reaped, invisible to userspace, but waiting for ref_count to go to zero */
230 uint32_t notified : 1; /* no-more-processes notification was sent via special port */
231#if defined(DEVELOPMENT) || defined(DEBUG)
232 uint32_t should_notify : 1; /* should this coalition send notifications (default: yes) */
233#endif
fe8ab488 234
3e170ce0 235 queue_chain_t coalitions; /* global list of coalitions */
a1c7dba1 236
3e170ce0
A
237 decl_lck_mtx_data(,lock) /* Coalition lock. */
238
239 /* put coalition type-specific structures here */
240 union {
241 struct i_resource_coalition r;
242 struct i_jetsam_coalition j;
243 };
244};
245
246/*
247 * register different coalition types:
248 * these must be kept in the order specified in coalition.h
249 */
250static const struct coalition_type
251s_coalition_types[COALITION_NUM_TYPES] = {
252 {
253 COALITION_TYPE_RESOURCE,
254 1,
255 i_coal_resource_init,
256 i_coal_resource_dealloc,
257 i_coal_resource_adopt_task,
258 i_coal_resource_remove_task,
259 i_coal_resource_set_taskrole,
260 i_coal_resource_get_taskrole,
261 i_coal_resource_iterate_tasks,
262 },
263 {
264 COALITION_TYPE_JETSAM,
265 1,
266 i_coal_jetsam_init,
267 i_coal_jetsam_dealloc,
268 i_coal_jetsam_adopt_task,
269 i_coal_jetsam_remove_task,
270 i_coal_jetsam_set_taskrole,
271 i_coal_jetsam_get_taskrole,
272 i_coal_jetsam_iterate_tasks,
273 },
fe8ab488
A
274};
275
3e170ce0
A
276#define coal_call(coal, func, ...) \
277 (s_coalition_types[(coal)->type].func)(coal, ## __VA_ARGS__)
278
279
fe8ab488
A
280#define coalition_lock(c) do{ lck_mtx_lock(&c->lock); }while(0)
281#define coalition_unlock(c) do{ lck_mtx_unlock(&c->lock); }while(0)
282
283static void
284coalition_notify_user(uint64_t id, uint32_t flags)
285{
286 mach_port_t user_port;
287 kern_return_t kr;
288
289 kr = host_get_coalition_port(host_priv_self(), &user_port);
290 if ((kr != KERN_SUCCESS) || !IPC_PORT_VALID(user_port)) {
291 return;
292 }
293
294 coalition_notification(user_port, id, flags);
295}
296
297/*
3e170ce0
A
298 *
299 * COALITION_TYPE_RESOURCE
300 *
fe8ab488 301 */
3e170ce0
A
302static kern_return_t
303i_coal_resource_init(coalition_t coal, boolean_t privileged)
fe8ab488 304{
3e170ce0
A
305 (void)privileged;
306 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
307 coal->r.ledger = ledger_instantiate(task_ledger_template,
308 LEDGER_CREATE_ACTIVE_ENTRIES);
309 if (coal->r.ledger == NULL)
310 return KERN_RESOURCE_SHORTAGE;
311
312 queue_init(&coal->r.tasks);
313
314 return KERN_SUCCESS;
315}
316
317static void
318i_coal_resource_dealloc(coalition_t coal)
319{
320 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
321 ledger_dereference(coal->r.ledger);
322}
323
324static kern_return_t
325i_coal_resource_adopt_task(coalition_t coal, task_t task)
326{
327 struct i_resource_coalition *cr;
328
329 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
330 assert(queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
331
332 cr = &coal->r;
333 cr->task_count++;
334
335 if (cr->task_count < cr->dead_task_count) {
336 panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
337 __func__, coal, coal->id, coal_type_str(coal->type),
338 cr->task_count, cr->dead_task_count);
fe8ab488
A
339 }
340
3e170ce0
A
341 /* If moving from 0->1 active tasks */
342 if (cr->task_count - cr->dead_task_count == 1) {
343 cr->last_became_nonempty_time = mach_absolute_time();
fe8ab488 344 }
3e170ce0
A
345
346 /* put the task on the coalition's list of tasks */
347 enqueue_tail(&cr->tasks, &task->task_coalition[COALITION_TYPE_RESOURCE]);
348
349 coal_dbg("Added PID:%d to id:%llu, task_count:%llu, dead_count:%llu, nonempty_time:%llu",
350 task_pid(task), coal->id, cr->task_count, cr->dead_task_count,
351 cr->last_became_nonempty_time);
352
353 return KERN_SUCCESS;
354}
355
356static kern_return_t
357i_coal_resource_remove_task(coalition_t coal, task_t task)
358{
359 struct i_resource_coalition *cr;
360
361 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
362 assert(task->coalition[COALITION_TYPE_RESOURCE] == coal);
363 assert(!queue_empty(&task->task_coalition[COALITION_TYPE_RESOURCE]));
364
365 /*
366 * handle resource coalition accounting rollup for dead tasks
367 */
368 cr = &coal->r;
369
370 cr->dead_task_count++;
371
372 if (cr->task_count < cr->dead_task_count) {
373 panic("%s: coalition %p id:%llu type:%s task_count(%llu) < dead_task_count(%llu)",
374 __func__, coal, coal->id, coal_type_str(coal->type), cr->task_count, cr->dead_task_count);
375 }
376
377 /* If moving from 1->0 active tasks */
378 if (cr->task_count - cr->dead_task_count == 0) {
379 uint64_t last_time_nonempty = mach_absolute_time() - cr->last_became_nonempty_time;
380 cr->last_became_nonempty_time = 0;
381 cr->time_nonempty += last_time_nonempty;
382 }
383
384 ledger_rollup(cr->ledger, task->ledger);
385 cr->bytesread += task->task_io_stats->disk_reads.size;
386 cr->byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
387 cr->gpu_time += task_gpu_utilisation(task);
388
389 /* remove the task from the coalition's list */
390 remqueue(&task->task_coalition[COALITION_TYPE_RESOURCE]);
391 queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
392
393 coal_dbg("removed PID:%d from id:%llu, task_count:%llu, dead_count:%llu",
394 task_pid(task), coal->id, cr->task_count, cr->dead_task_count);
395
396 return KERN_SUCCESS;
397}
398
399static kern_return_t
400i_coal_resource_set_taskrole(__unused coalition_t coal,
401 __unused task_t task, __unused int role)
402{
403 return KERN_SUCCESS;
404}
405
406static int
407i_coal_resource_get_taskrole(__unused coalition_t coal, __unused task_t task)
408{
409 task_t t;
410
411 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
412
413 qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
414 if (t == task)
415 return COALITION_TASKROLE_UNDEF;
416 }
417
418 return -1;
419}
420
421static void
422i_coal_resource_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
423{
424 task_t t;
425 assert(coal && coal->type == COALITION_TYPE_RESOURCE);
426
427 qe_foreach_element(t, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE])
428 callback(coal, ctx, t);
fe8ab488
A
429}
430
431kern_return_t
432coalition_resource_usage_internal(coalition_t coal, struct coalition_resource_usage *cru_out)
433{
434 kern_return_t kr;
435 ledger_amount_t credit, debit;
436
3e170ce0
A
437 if (coal->type != COALITION_TYPE_RESOURCE)
438 return KERN_INVALID_ARGUMENT;
439
fe8ab488 440 ledger_t sum_ledger = ledger_instantiate(task_ledger_template, LEDGER_CREATE_ACTIVE_ENTRIES);
3e170ce0 441 if (sum_ledger == LEDGER_NULL)
fe8ab488 442 return KERN_RESOURCE_SHORTAGE;
fe8ab488
A
443
444 coalition_lock(coal);
445
446 /*
447 * Start with the coalition's ledger, which holds the totals from all
448 * the dead tasks.
449 */
3e170ce0
A
450 ledger_rollup(sum_ledger, coal->r.ledger);
451 uint64_t bytesread = coal->r.bytesread;
452 uint64_t byteswritten = coal->r.byteswritten;
453 uint64_t gpu_time = coal->r.gpu_time;
454 int64_t cpu_time_billed_to_me = 0;
455 int64_t cpu_time_billed_to_others = 0;
456
457 kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_me, (int64_t *)&cpu_time_billed_to_me);
458 if (kr != KERN_SUCCESS || cpu_time_billed_to_me < 0) {
459#if DEVELOPMENT || DEBUG
460 printf("ledger_get_balance failed or ledger negative in coalition_resource_usage_internal: %lld\n", cpu_time_billed_to_me);
461#endif /* DEVELOPMENT || DEBUG */
462 cpu_time_billed_to_me = 0;
463 }
464
465 kr = ledger_get_balance(sum_ledger, task_ledgers.cpu_time_billed_to_others, (int64_t *)&cpu_time_billed_to_others);
466 if (kr != KERN_SUCCESS || cpu_time_billed_to_others < 0) {
467#if DEVELOPMENT || DEBUG
468 printf("ledger_get_balance failed or ledger negative in coalition_resource_usage_internal: %lld\n", cpu_time_billed_to_others);
469#endif /* DEVELOPMENT || DEBUG */
470 cpu_time_billed_to_others = 0;
471 }
fe8ab488
A
472
473 /*
474 * Add to that all the active tasks' ledgers. Tasks cannot deallocate
475 * out from under us, since we hold the coalition lock.
3e170ce0
A
476 * Do not use the on-behalf of cpu time from ledger for live tasks, since
477 * it will not have cpu time for active linkages between tasks.
fe8ab488
A
478 */
479 task_t task;
3e170ce0 480 qe_foreach_element(task, &coal->r.tasks, task_coalition[COALITION_TYPE_RESOURCE]) {
fe8ab488
A
481 ledger_rollup(sum_ledger, task->ledger);
482 bytesread += task->task_io_stats->disk_reads.size;
483 byteswritten += task->task_io_stats->total_io.size - task->task_io_stats->disk_reads.size;
484 gpu_time += task_gpu_utilisation(task);
3e170ce0
A
485 cpu_time_billed_to_me += (int64_t)bank_billed_time(task->bank_context);
486 cpu_time_billed_to_others += (int64_t)bank_serviced_time(task->bank_context);
fe8ab488
A
487 }
488
489 /* collect information from the coalition itself */
3e170ce0
A
490 cru_out->tasks_started = coal->r.task_count;
491 cru_out->tasks_exited = coal->r.dead_task_count;
fe8ab488 492
3e170ce0
A
493 uint64_t time_nonempty = coal->r.time_nonempty;
494 uint64_t last_became_nonempty_time = coal->r.last_became_nonempty_time;
fe8ab488
A
495
496 coalition_unlock(coal);
497
498 /* Copy the totals out of sum_ledger */
499 kr = ledger_get_entries(sum_ledger, task_ledgers.cpu_time,
500 &credit, &debit);
501 if (kr != KERN_SUCCESS) {
502 credit = 0;
503 }
504 cru_out->cpu_time = credit;
3e170ce0
A
505 cru_out->cpu_time_billed_to_me = (uint64_t)cpu_time_billed_to_me;
506 cru_out->cpu_time_billed_to_others = (uint64_t)cpu_time_billed_to_others;
fe8ab488
A
507
508 kr = ledger_get_entries(sum_ledger, task_ledgers.interrupt_wakeups,
509 &credit, &debit);
510 if (kr != KERN_SUCCESS) {
511 credit = 0;
512 }
513 cru_out->interrupt_wakeups = credit;
514
515 kr = ledger_get_entries(sum_ledger, task_ledgers.platform_idle_wakeups,
516 &credit, &debit);
517 if (kr != KERN_SUCCESS) {
518 credit = 0;
519 }
520 cru_out->platform_idle_wakeups = credit;
521
522 cru_out->bytesread = bytesread;
523 cru_out->byteswritten = byteswritten;
524 cru_out->gpu_time = gpu_time;
525
526 ledger_dereference(sum_ledger);
527 sum_ledger = LEDGER_NULL;
528
529 if (last_became_nonempty_time) {
530 time_nonempty += mach_absolute_time() - last_became_nonempty_time;
531 }
532 absolutetime_to_nanoseconds(time_nonempty, &cru_out->time_nonempty);
533
534 return KERN_SUCCESS;
535}
536
3e170ce0
A
537/*
538 *
539 * COALITION_TYPE_JETSAM
540 *
541 */
542static kern_return_t
543i_coal_jetsam_init(coalition_t coal, boolean_t privileged)
544{
545 assert(coal && coal->type == COALITION_TYPE_JETSAM);
546 (void)privileged;
547
548 coal->j.leader= TASK_NULL;
549 queue_head_init(coal->j.extensions);
550 queue_head_init(coal->j.services);
551 queue_head_init(coal->j.other);
552
553 return KERN_SUCCESS;
554}
555
556static void
557i_coal_jetsam_dealloc(__unused coalition_t coal)
558{
559 assert(coal && coal->type == COALITION_TYPE_JETSAM);
560
561 /* the coalition should be completely clear at this point */
562 assert(queue_empty(&coal->j.extensions));
563 assert(queue_empty(&coal->j.services));
564 assert(queue_empty(&coal->j.other));
565 assert(coal->j.leader == TASK_NULL);
566}
567
568static kern_return_t
569i_coal_jetsam_adopt_task(coalition_t coal, task_t task)
570{
571 struct i_jetsam_coalition *cj;
572 assert(coal && coal->type == COALITION_TYPE_JETSAM);
573
574 cj = &coal->j;
575
576 assert(queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
577
578 /* put each task initially in the "other" list */
579 enqueue_tail(&cj->other, &task->task_coalition[COALITION_TYPE_JETSAM]);
580 coal_dbg("coalition %lld adopted PID:%d as UNDEF",
581 coal->id, task_pid(task));
582
583 return KERN_SUCCESS;
584}
585
586static kern_return_t
587i_coal_jetsam_remove_task(coalition_t coal, task_t task)
588{
589 assert(coal && coal->type == COALITION_TYPE_JETSAM);
590 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
591
592 coal_dbg("removing PID:%d from coalition id:%lld",
593 task_pid(task), coal->id);
594
595 if (task == coal->j.leader) {
596 coal->j.leader = NULL;
597 coal_dbg(" PID:%d was the leader!", task_pid(task));
598 } else {
599 assert(!queue_empty(&task->task_coalition[COALITION_TYPE_JETSAM]));
600 }
601
602 /* remove the task from the specific coalition role queue */
603 remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
604 queue_chain_init(task->task_coalition[COALITION_TYPE_RESOURCE]);
605
606 return KERN_SUCCESS;
607}
608
609static kern_return_t
610i_coal_jetsam_set_taskrole(coalition_t coal, task_t task, int role)
611{
612 struct i_jetsam_coalition *cj;
613 queue_t q = NULL;
614 assert(coal && coal->type == COALITION_TYPE_JETSAM);
615 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
616
617 cj = &coal->j;
618
619 switch (role) {
620 case COALITION_TASKROLE_LEADER:
621 coal_dbg("setting PID:%d as LEADER of %lld",
622 task_pid(task), coal->id);
623 if (cj->leader != TASK_NULL) {
624 /* re-queue the exiting leader onto the "other" list */
625 coal_dbg(" re-queue existing leader (%d) as OTHER",
626 task_pid(cj->leader));
627 re_queue_tail(&cj->other, &cj->leader->task_coalition[COALITION_TYPE_JETSAM]);
628 }
629 /*
630 * remove the task from the "other" list
631 * (where it was put by default)
632 */
633 remqueue(&task->task_coalition[COALITION_TYPE_JETSAM]);
634 queue_chain_init(task->task_coalition[COALITION_TYPE_JETSAM]);
635
636 /* set the coalition leader */
637 cj->leader = task;
638 break;
639 case COALITION_TASKROLE_UNDEF:
640 coal_dbg("setting PID:%d as UNDEF in %lld",
641 task_pid(task), coal->id);
642 q = (queue_t)&cj->other;
643 break;
644 case COALITION_TASKROLE_XPC:
645 coal_dbg("setting PID:%d as XPC in %lld",
646 task_pid(task), coal->id);
647 q = (queue_t)&cj->services;
648 break;
649 case COALITION_TASKROLE_EXT:
650 coal_dbg("setting PID:%d as EXT in %lld",
651 task_pid(task), coal->id);
652 q = (queue_t)&cj->extensions;
653 break;
654 default:
655 panic("%s: invalid role(%d) for task", __func__, role);
656 return KERN_INVALID_ARGUMENT;
657 }
658
659 if (q != NULL)
660 re_queue_tail(q, &task->task_coalition[COALITION_TYPE_JETSAM]);
661
662 return KERN_SUCCESS;
663}
664
665static int
666i_coal_jetsam_get_taskrole(coalition_t coal, task_t task)
667{
668 struct i_jetsam_coalition *cj;
669 task_t t;
670
671 assert(coal && coal->type == COALITION_TYPE_JETSAM);
672 assert(task->coalition[COALITION_TYPE_JETSAM] == coal);
673
674 cj = &coal->j;
675
676 if (task == cj->leader)
677 return COALITION_TASKROLE_LEADER;
678
679 qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM]) {
680 if (t == task)
681 return COALITION_TASKROLE_XPC;
682 }
683
684 qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM]) {
685 if (t == task)
686 return COALITION_TASKROLE_EXT;
687 }
688
689 qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM]) {
690 if (t == task)
691 return COALITION_TASKROLE_UNDEF;
692 }
693
694 /* task not in the coalition?! */
695 return -1;
696}
697
698static void
699i_coal_jetsam_iterate_tasks(coalition_t coal, void *ctx, void (*callback)(coalition_t, void *, task_t))
700{
701 struct i_jetsam_coalition *cj;
702 task_t t;
703
704 assert(coal && coal->type == COALITION_TYPE_JETSAM);
705
706 cj = &coal->j;
707
708 if (cj->leader)
709 callback(coal, ctx, cj->leader);
710
711 qe_foreach_element(t, &cj->services, task_coalition[COALITION_TYPE_JETSAM])
712 callback(coal, ctx, t);
713
714 qe_foreach_element(t, &cj->extensions, task_coalition[COALITION_TYPE_JETSAM])
715 callback(coal, ctx, t);
716
717 qe_foreach_element(t, &cj->other, task_coalition[COALITION_TYPE_JETSAM])
718 callback(coal, ctx, t);
719}
720
721
722/*
723 *
724 * Main Coalition implementation
725 *
726 */
727
fe8ab488
A
728/*
729 * coalition_create_internal
730 * Returns: New coalition object, referenced for the caller and unlocked.
731 * Condition: coalitions_list_lock must be UNLOCKED.
732 */
733kern_return_t
3e170ce0 734coalition_create_internal(int type, boolean_t privileged, coalition_t *out)
fe8ab488 735{
3e170ce0
A
736 kern_return_t kr;
737 struct coalition *new_coal;
738
739 if (type < 0 || type > COALITION_TYPE_MAX)
740 return KERN_INVALID_ARGUMENT;
741
742 new_coal = (struct coalition *)zalloc(coalition_zone);
743 if (new_coal == COALITION_NULL)
fe8ab488 744 return KERN_RESOURCE_SHORTAGE;
fe8ab488
A
745 bzero(new_coal, sizeof(*new_coal));
746
3e170ce0
A
747 new_coal->type = type;
748
749 /* initialize type-specific resources */
750 kr = coal_call(new_coal, init, privileged);
751 if (kr != KERN_SUCCESS) {
fe8ab488 752 zfree(coalition_zone, new_coal);
3e170ce0 753 return kr;
fe8ab488
A
754 }
755
756 /* One for caller, one for coalitions list */
757 new_coal->ref_count = 2;
758
759 new_coal->privileged = privileged ? TRUE : FALSE;
3e170ce0
A
760#if defined(DEVELOPMENT) || defined(DEBUG)
761 new_coal->should_notify = 1;
762#endif
fe8ab488
A
763
764 lck_mtx_init(&new_coal->lock, &coalitions_lck_grp, &coalitions_lck_attr);
fe8ab488
A
765
766 lck_mtx_lock(&coalitions_list_lock);
767 new_coal->id = coalition_next_id++;
768 coalition_count++;
3e170ce0 769 enqueue_tail(&coalitions_q, &new_coal->coalitions);
fe8ab488
A
770 lck_mtx_unlock(&coalitions_list_lock);
771
3e170ce0 772 coal_dbg("id:%llu, type:%s", new_coal->id, coal_type_str(new_coal->type));
fe8ab488
A
773
774 *out = new_coal;
775 return KERN_SUCCESS;
776}
777
778/*
779 * coalition_release
780 * Condition: coalition must be UNLOCKED.
781 * */
782void
783coalition_release(coalition_t coal)
784{
fe8ab488
A
785 /* TODO: This can be done with atomics. */
786 coalition_lock(coal);
787 coal->ref_count--;
3e170ce0 788
fe8ab488
A
789#if COALITION_DEBUG
790 uint32_t rc = coal->ref_count;
3e170ce0 791 uint32_t ac = coal->active_count;
fe8ab488
A
792#endif /* COALITION_DEBUG */
793
3e170ce0
A
794 coal_dbg("id:%llu type:%s ref_count:%u active_count:%u%s",
795 coal->id, coal_type_str(coal->type), rc, ac,
796 rc <= 0 ? ", will deallocate now" : "");
797
798 if (coal->ref_count > 0) {
799 coalition_unlock(coal);
800 return;
801 }
802
803 assert(coal->termrequested);
804 assert(coal->terminated);
805 assert(coal->active_count == 0);
806 assert(coal->reaped);
807 assert(coal->focal_task_count == 0);
808 assert(coal->nonfocal_task_count == 0);
809
810 coal_call(coal, dealloc);
811
fe8ab488
A
812 coalition_unlock(coal);
813
3e170ce0 814 lck_mtx_destroy(&coal->lock, &coalitions_lck_grp);
fe8ab488 815
3e170ce0
A
816 zfree(coalition_zone, coal);
817}
fe8ab488 818
3e170ce0
A
819/*
820 * coalition_find_by_id_internal
821 * Returns: Coalition object with specified id, NOT referenced.
822 * If not found, returns COALITION_NULL.
823 * Condition: coalitions_list_lock must be LOCKED.
824 */
825static coalition_t
826coalition_find_by_id_internal(uint64_t coal_id)
827{
828 if (coal_id == 0) {
829 return COALITION_NULL;
fe8ab488 830 }
3e170ce0
A
831
832 lck_mtx_assert(&coalitions_list_lock, LCK_MTX_ASSERT_OWNED);
833 coalition_t coal;
834 qe_foreach_element(coal, &coalitions_q, coalitions) {
835 if (coal->id == coal_id) {
836 return coal;
837 }
838 }
839 return COALITION_NULL;
fe8ab488
A
840}
841
842/*
843 * coalition_find_by_id
844 * Returns: Coalition object with specified id, referenced.
845 * Condition: coalitions_list_lock must be UNLOCKED.
846 */
847coalition_t
848coalition_find_by_id(uint64_t cid)
849{
850 if (cid == 0) {
851 return COALITION_NULL;
852 }
853
854 lck_mtx_lock(&coalitions_list_lock);
855
856 coalition_t coal = coalition_find_by_id_internal(cid);
857 if (coal == COALITION_NULL) {
858 lck_mtx_unlock(&coalitions_list_lock);
859 return COALITION_NULL;
860 }
861
862 coalition_lock(coal);
863
864 if (coal->reaped) {
865 coalition_unlock(coal);
866 lck_mtx_unlock(&coalitions_list_lock);
867 return COALITION_NULL;
868 }
869
870 if (coal->ref_count == 0) {
3e170ce0
A
871 panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n",
872 coal, coal->id, coal_type_str(coal->type), coal->active_count);
fe8ab488
A
873 }
874 coal->ref_count++;
875#if COALITION_DEBUG
876 uint32_t rc = coal->ref_count;
877#endif
878
879 coalition_unlock(coal);
880 lck_mtx_unlock(&coalitions_list_lock);
881
3e170ce0
A
882 coal_dbg("id:%llu type:%s ref_count:%u",
883 coal->id, coal_type_str(coal->type), rc);
884
fe8ab488
A
885 return coal;
886}
887
888/*
889 * coalition_find_and_activate_by_id
890 * Returns: Coalition object with specified id, referenced, and activated.
891 * Condition: coalitions_list_lock must be UNLOCKED.
892 * This is the function to use when putting a 'new' thing into a coalition,
893 * like posix_spawn of an XPC service by launchd.
894 * See also coalition_extend_active.
895 */
896coalition_t
897coalition_find_and_activate_by_id(uint64_t cid)
898{
899 if (cid == 0) {
900 return COALITION_NULL;
901 }
902
903 lck_mtx_lock(&coalitions_list_lock);
904
905 coalition_t coal = coalition_find_by_id_internal(cid);
906 if (coal == COALITION_NULL) {
907 lck_mtx_unlock(&coalitions_list_lock);
908 return COALITION_NULL;
909 }
910
911 coalition_lock(coal);
912
913 if (coal->reaped || coal->terminated) {
914 /* Too late to put something new into this coalition, it's
915 * already on its way out the door */
916 coalition_unlock(coal);
917 lck_mtx_unlock(&coalitions_list_lock);
918 return COALITION_NULL;
919 }
920
921 if (coal->ref_count == 0) {
3e170ce0
A
922 panic("resurrecting coalition %p id:%llu type:%s, active_count:%u\n",
923 coal, coal->id, coal_type_str(coal->type), coal->active_count);
fe8ab488
A
924 }
925
926 coal->ref_count++;
927 coal->active_count++;
928
929#if COALITION_DEBUG
930 uint32_t rc = coal->ref_count;
931 uint32_t ac = coal->active_count;
932#endif
933
934 coalition_unlock(coal);
935 lck_mtx_unlock(&coalitions_list_lock);
936
3e170ce0
A
937 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u",
938 coal->id, coal_type_str(coal->type), rc, ac);
939
fe8ab488
A
940 return coal;
941}
942
943uint64_t
944coalition_id(coalition_t coal)
945{
946 return coal->id;
947}
948
3e170ce0
A
949void
950task_coalition_ids(task_t task, uint64_t ids[COALITION_NUM_TYPES])
951{
952 int i;
953 for (i = 0; i < COALITION_NUM_TYPES; i++) {
954 if (task->coalition[i])
955 ids[i] = task->coalition[i]->id;
956 else
957 ids[i] = 0;
958 }
959}
960
961void
962task_coalition_roles(task_t task, int roles[COALITION_NUM_TYPES])
fe8ab488 963{
3e170ce0
A
964 int i;
965 memset(roles, 0, COALITION_NUM_TYPES * sizeof(roles[0]));
966
967 for (i = 0; i < COALITION_NUM_TYPES; i++) {
968 if (task->coalition[i]) {
969 coalition_lock(task->coalition[i]);
970 roles[i] = coal_call(task->coalition[i],
971 get_taskrole, task);
972 coalition_unlock(task->coalition[i]);
973 } else {
974 roles[i] = -1;
975 }
976 }
977}
978
979
980int
981coalition_type(coalition_t coal)
982{
983 return coal->type;
fe8ab488
A
984}
985
986boolean_t
987coalition_is_privileged(coalition_t coal)
988{
989 return coal->privileged || unrestrict_coalition_syscalls;
990}
991
992boolean_t
3e170ce0 993task_is_in_privileged_coalition(task_t task, int type)
fe8ab488 994{
3e170ce0
A
995 if (type < 0 || type > COALITION_TYPE_MAX)
996 return FALSE;
997 if (unrestrict_coalition_syscalls)
998 return TRUE;
999 if (!task->coalition[type])
1000 return FALSE;
1001 return task->coalition[type]->privileged;
fe8ab488
A
1002}
1003
3e170ce0 1004void task_coalition_update_gpu_stats(task_t task, uint64_t gpu_ns_delta)
fe8ab488 1005{
3e170ce0
A
1006 coalition_t coal;
1007
1008 assert(task != TASK_NULL);
1009 if (gpu_ns_delta == 0)
1010 return;
1011
1012 coal = task->coalition[COALITION_TYPE_RESOURCE];
1013 assert(coal != COALITION_NULL);
1014
1015 coalition_lock(coal);
1016 coal->r.gpu_time += gpu_ns_delta;
1017 coalition_unlock(coal);
fe8ab488
A
1018}
1019
3e170ce0 1020uint32_t task_coalition_adjust_focal_count(task_t task, int count)
fe8ab488 1021{
3e170ce0
A
1022 coalition_t coal;
1023 uint32_t ret;
fe8ab488 1024
3e170ce0
A
1025 /*
1026 * For now: only use the resource coalition. Perhaps in the
1027 * future we may combine all coalition types, or even make
1028 * a special coalition type just for this.
1029 */
1030 coal = task->coalition[COALITION_TYPE_RESOURCE];
1031 assert(coal != COALITION_NULL);
fe8ab488 1032
3e170ce0 1033 ret = hw_atomic_add(&coal->focal_task_count, count);
fe8ab488 1034
3e170ce0
A
1035 /* catch underflow */
1036 assert(ret != UINT32_MAX);
1037 return ret;
1038}
1039
1040uint32_t task_coalition_focal_count(task_t task)
1041{
1042 coalition_t coal;
1043 coal = task->coalition[COALITION_TYPE_RESOURCE];
1044 assert(coal != COALITION_NULL);
1045
1046 return coal->focal_task_count;
1047}
1048
1049uint32_t task_coalition_adjust_nonfocal_count(task_t task, int count)
1050{
1051 coalition_t coal;
1052 uint32_t ret;
1053
1054 /*
1055 * For now: only use the resource coalition. Perhaps in the
1056 * future we may combine all coalition types, or even make
1057 * a special coalition type just for this.
1058 */
1059 coal = task->coalition[COALITION_TYPE_RESOURCE];
1060 assert(coal != COALITION_NULL);
1061
1062 ret = hw_atomic_add(&coal->nonfocal_task_count, count);
1063
1064 /* catch underflow */
1065 assert(ret != UINT32_MAX);
1066 return ret;
1067}
1068
1069uint32_t task_coalition_nonfocal_count(task_t task)
1070{
1071 coalition_t coal;
1072 coal = task->coalition[COALITION_TYPE_RESOURCE];
1073 assert(coal != COALITION_NULL);
1074
1075 return coal->nonfocal_task_count;
1076}
1077
1078void coalition_for_each_task(coalition_t coal, void *ctx,
1079 void (*callback)(coalition_t, void *, task_t))
1080{
1081 assert(coal != COALITION_NULL);
1082
1083 coal_dbg("iterating tasks in coalition %p id:%llu type:%s, active_count:%u",
1084 coal, coal->id, coal_type_str(coal->type), coal->active_count);
1085
1086 coalition_lock(coal);
1087
1088 coal_call(coal, iterate_tasks, ctx, callback);
fe8ab488
A
1089
1090 coalition_unlock(coal);
fe8ab488
A
1091}
1092
3e170ce0 1093
fe8ab488
A
1094void
1095coalition_remove_active(coalition_t coal)
1096{
1097 coalition_lock(coal);
1098
1099 assert(!coal->reaped);
1100 assert(coal->active_count > 0);
1101
1102 coal->active_count--;
1103
1104 boolean_t do_notify = FALSE;
1105 uint64_t notify_id = 0;
1106 uint32_t notify_flags = 0;
1107 if (coal->termrequested && coal->active_count == 0) {
1108 /* We only notify once, when active_count reaches zero.
1109 * We just decremented, so if it reached zero, we mustn't have
1110 * notified already.
1111 */
1112 assert(!coal->terminated);
1113 coal->terminated = TRUE;
1114
1115 assert(!coal->notified);
1116
1117 coal->notified = TRUE;
3e170ce0
A
1118#if defined(DEVELOPMENT) || defined(DEBUG)
1119 do_notify = coal->should_notify;
1120#else
fe8ab488 1121 do_notify = TRUE;
3e170ce0 1122#endif
fe8ab488
A
1123 notify_id = coal->id;
1124 notify_flags = 0;
1125 }
1126
3e170ce0
A
1127#if COALITION_DEBUG
1128 uint64_t cid = coal->id;
1129 uint32_t rc = coal->ref_count;
1130 int ac = coal->active_count;
1131 int ct = coal->type;
1132#endif
fe8ab488
A
1133 coalition_unlock(coal);
1134
3e170ce0
A
1135 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u,%s",
1136 cid, coal_type_str(ct), rc, ac, do_notify ? " NOTIFY" : " ");
1137
fe8ab488
A
1138 if (do_notify) {
1139 coalition_notify_user(notify_id, notify_flags);
1140 }
1141}
1142
1143/* Used for kernel_task, launchd, launchd's early boot tasks... */
1144kern_return_t
3e170ce0 1145coalitions_adopt_init_task(task_t task)
fe8ab488
A
1146{
1147 kern_return_t kr;
3e170ce0 1148 kr = coalitions_adopt_task(init_coalition, task);
fe8ab488
A
1149 if (kr != KERN_SUCCESS) {
1150 panic("failed to adopt task %p into default coalition: %d", task, kr);
1151 }
1152 return kr;
1153}
1154
1155/*
3e170ce0 1156 * coalition_adopt_task_internal
fe8ab488
A
1157 * Condition: Coalition must be referenced and unlocked. Will fail if coalition
1158 * is already terminated.
1159 */
3e170ce0
A
1160static kern_return_t
1161coalition_adopt_task_internal(coalition_t coal, task_t task)
fe8ab488 1162{
3e170ce0
A
1163 kern_return_t kr;
1164
1165 if (task->coalition[coal->type]) {
fe8ab488
A
1166 return KERN_ALREADY_IN_SET;
1167 }
1168
1169 coalition_lock(coal);
1170
1171 if (coal->reaped || coal->terminated) {
1172 coalition_unlock(coal);
1173 return KERN_TERMINATED;
1174 }
1175
3e170ce0
A
1176 kr = coal_call(coal, adopt_task, task);
1177 if (kr != KERN_SUCCESS)
1178 goto out_unlock;
1179
fe8ab488
A
1180 coal->active_count++;
1181
1182 coal->ref_count++;
fe8ab488 1183
3e170ce0 1184 task->coalition[coal->type] = coal;
fe8ab488 1185
3e170ce0 1186out_unlock:
fe8ab488 1187#if COALITION_DEBUG
3e170ce0
A
1188 (void)coal; /* need expression after label */
1189 uint64_t cid = coal->id;
fe8ab488 1190 uint32_t rc = coal->ref_count;
3e170ce0 1191 uint32_t ct = coal->type;
fe8ab488 1192#endif
fe8ab488
A
1193 coalition_unlock(coal);
1194
3e170ce0
A
1195 coal_dbg("task:%d, id:%llu type:%s ref_count:%u, kr=%d",
1196 task_pid(task), cid, coal_type_str(ct), rc, kr);
1197 return kr;
1198}
1199
1200static kern_return_t
1201coalition_remove_task_internal(task_t task, int type)
1202{
1203 kern_return_t kr;
1204
1205 coalition_t coal = task->coalition[type];
1206
1207 if (!coal)
1208 return KERN_SUCCESS;
1209
1210 assert(coal->type == (uint32_t)type);
1211
1212 coalition_lock(coal);
1213
1214 kr = coal_call(coal, remove_task, task);
1215
fe8ab488 1216#if COALITION_DEBUG
3e170ce0
A
1217 uint64_t cid = coal->id;
1218 uint32_t rc = coal->ref_count;
1219 int ac = coal->active_count;
1220 int ct = coal->type;
fe8ab488 1221#endif
3e170ce0
A
1222 coalition_unlock(coal);
1223
1224 coal_dbg("id:%llu type:%s ref_count:%u, active_count:%u, kr=%d",
1225 cid, coal_type_str(ct), rc, ac, kr);
1226
1227 coalition_remove_active(coal);
1228
1229 return kr;
fe8ab488
A
1230}
1231
1232/*
3e170ce0
A
1233 * coalitions_adopt_task
1234 * Condition: All coalitions must be referenced and unlocked.
1235 * Will fail if any coalition is already terminated.
fe8ab488
A
1236 */
1237kern_return_t
3e170ce0 1238coalitions_adopt_task(coalition_t *coals, task_t task)
fe8ab488 1239{
3e170ce0
A
1240 int i;
1241 kern_return_t kr;
fe8ab488 1242
3e170ce0
A
1243 if (!coals || coals[COALITION_TYPE_RESOURCE] == COALITION_NULL)
1244 return KERN_INVALID_ARGUMENT;
1245
1246 /* verify that the incoming coalitions are what they say they are */
1247 for (i = 0; i < COALITION_NUM_TYPES; i++)
1248 if (coals[i] && coals[i]->type != (uint32_t)i)
1249 return KERN_INVALID_ARGUMENT;
1250
1251 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1252 kr = KERN_SUCCESS;
1253 if (coals[i])
1254 kr = coalition_adopt_task_internal(coals[i], task);
1255 if (kr != KERN_SUCCESS) {
1256 /* dis-associate any coalitions that just adopted this task */
1257 while (--i >= 0) {
1258 if (task->coalition[i])
1259 coalition_remove_task_internal(task, i);
1260 }
1261 break;
1262 }
1263 }
1264 return kr;
1265}
fe8ab488 1266
3e170ce0
A
1267/*
1268 * coalitions_remove_task
1269 * Condition: task must be referenced and UNLOCKED; all task's coalitions must be UNLOCKED
1270 */
1271kern_return_t
1272coalitions_remove_task(task_t task)
1273{
1274 kern_return_t kr;
1275 int i;
fe8ab488 1276
3e170ce0
A
1277 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1278 kr = coalition_remove_task_internal(task, i);
1279 assert(kr == KERN_SUCCESS);
fe8ab488
A
1280 }
1281
3e170ce0
A
1282 return kr;
1283}
1284
1285/*
1286 * task_release_coalitions
1287 * helper function to release references to all coalitions in which
1288 * 'task' is a member.
1289 */
1290void
1291task_release_coalitions(task_t task)
1292{
1293 int i;
1294 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1295 if (task->coalition[i])
1296 coalition_release(task->coalition[i]);
fe8ab488 1297 }
3e170ce0 1298}
fe8ab488 1299
3e170ce0
A
1300/*
1301 * coalitions_set_roles
1302 * for each type of coalition, if the task is a member of a coalition of
1303 * that type (given in the coalitions parameter) then set the role of
1304 * the task within that that coalition.
1305 */
1306kern_return_t coalitions_set_roles(coalition_t coalitions[COALITION_NUM_TYPES],
1307 task_t task, int roles[COALITION_NUM_TYPES])
1308{
1309 kern_return_t kr = KERN_SUCCESS;
1310 int i;
fe8ab488 1311
3e170ce0
A
1312 for (i = 0; i < COALITION_NUM_TYPES; i++) {
1313 if (!coalitions[i])
1314 continue;
1315 coalition_lock(coalitions[i]);
1316 kr = coal_call(coalitions[i], set_taskrole, task, roles[i]);
1317 coalition_unlock(coalitions[i]);
1318 assert(kr == KERN_SUCCESS);
1319 }
fe8ab488 1320
3e170ce0 1321 return kr;
fe8ab488
A
1322}
1323
1324/*
1325 * coalition_terminate_internal
1326 * Condition: Coalition must be referenced and UNLOCKED.
1327 */
1328kern_return_t
1329coalition_request_terminate_internal(coalition_t coal)
1330{
3e170ce0
A
1331 assert(coal->type >= 0 && coal->type <= COALITION_TYPE_MAX);
1332
1333 if (coal == init_coalition[coal->type]) {
fe8ab488
A
1334 return KERN_DEFAULT_SET;
1335 }
1336
1337 coalition_lock(coal);
1338
1339 if (coal->reaped) {
1340 coalition_unlock(coal);
1341 return KERN_INVALID_NAME;
1342 }
1343
1344 if (coal->terminated || coal->termrequested) {
1345 coalition_unlock(coal);
1346 return KERN_TERMINATED;
1347 }
1348
1349 coal->termrequested = TRUE;
1350
1351 boolean_t do_notify = FALSE;
1352 uint64_t note_id = 0;
1353 uint32_t note_flags = 0;
1354
1355 if (coal->active_count == 0) {
1356 /*
1357 * We only notify once, when active_count reaches zero.
3e170ce0
A
1358 * We just set termrequested to zero. If the active count
1359 * was already at zero (tasks died before we could request
1360 * a termination notification), we should notify.
fe8ab488
A
1361 */
1362 assert(!coal->terminated);
1363 coal->terminated = TRUE;
1364
1365 assert(!coal->notified);
1366
1367 coal->notified = TRUE;
3e170ce0
A
1368#if defined(DEVELOPMENT) || defined(DEBUG)
1369 do_notify = coal->should_notify;
1370#else
fe8ab488 1371 do_notify = TRUE;
3e170ce0 1372#endif
fe8ab488
A
1373 note_id = coal->id;
1374 note_flags = 0;
1375 }
1376
1377 coalition_unlock(coal);
1378
1379 if (do_notify) {
1380 coalition_notify_user(note_id, note_flags);
1381 }
1382
1383 return KERN_SUCCESS;
1384}
1385
1386/*
1387 * coalition_reap_internal
1388 * Condition: Coalition must be referenced and UNLOCKED.
1389 */
1390kern_return_t
1391coalition_reap_internal(coalition_t coal)
1392{
3e170ce0
A
1393 assert(coal->type <= COALITION_TYPE_MAX);
1394
1395 if (coal == init_coalition[coal->type]) {
fe8ab488
A
1396 return KERN_DEFAULT_SET;
1397 }
1398
1399 coalition_lock(coal);
1400 if (coal->reaped) {
1401 coalition_unlock(coal);
1402 return KERN_TERMINATED;
1403 }
1404 if (!coal->terminated) {
1405 coalition_unlock(coal);
1406 return KERN_FAILURE;
1407 }
1408 assert(coal->termrequested);
1409 if (coal->active_count > 0) {
1410 coalition_unlock(coal);
1411 return KERN_FAILURE;
1412 }
1413
1414 coal->reaped = TRUE;
1415
1416 /* Caller, launchd, and coalitions list should each have a reference */
1417 assert(coal->ref_count > 2);
1418
1419 coalition_unlock(coal);
1420
1421 lck_mtx_lock(&coalitions_list_lock);
1422 coalition_count--;
3e170ce0 1423 remqueue(&coal->coalitions);
fe8ab488
A
1424 lck_mtx_unlock(&coalitions_list_lock);
1425
1426 /* Release the list's reference and launchd's reference. */
1427 coalition_release(coal);
1428 coalition_release(coal);
1429
1430 return KERN_SUCCESS;
1431}
1432
3e170ce0
A
1433#if defined(DEVELOPMENT) || defined(DEBUG)
1434int coalition_should_notify(coalition_t coal)
1435{
1436 int should;
1437 if (!coal)
1438 return -1;
1439 coalition_lock(coal);
1440 should = coal->should_notify;
1441 coalition_unlock(coal);
1442
1443 return should;
1444}
1445
1446void coalition_set_notify(coalition_t coal, int notify)
1447{
1448 if (!coal)
1449 return;
1450 coalition_lock(coal);
1451 coal->should_notify = !!notify;
1452 coalition_unlock(coal);
1453}
1454#endif
1455
fe8ab488 1456void
3e170ce0 1457coalitions_init(void)
fe8ab488 1458{
3e170ce0
A
1459 kern_return_t kr;
1460 int i;
1461 const struct coalition_type *ctype;
1462
fe8ab488
A
1463 coalition_zone = zinit(
1464 sizeof(struct coalition),
1465 CONFIG_COALITION_MAX * sizeof(struct coalition),
1466 COALITION_CHUNK * sizeof(struct coalition),
1467 "coalitions");
1468 zone_change(coalition_zone, Z_NOENCRYPT, TRUE);
3e170ce0 1469 queue_head_init(coalitions_q);
fe8ab488
A
1470
1471 if (!PE_parse_boot_argn("unrestrict_coalition_syscalls", &unrestrict_coalition_syscalls,
1472 sizeof (unrestrict_coalition_syscalls))) {
1473 unrestrict_coalition_syscalls = 0;
1474 }
1475
1476 lck_grp_attr_setdefault(&coalitions_lck_grp_attr);
1477 lck_grp_init(&coalitions_lck_grp, "coalition", &coalitions_lck_grp_attr);
1478 lck_attr_setdefault(&coalitions_lck_attr);
1479 lck_mtx_init(&coalitions_list_lock, &coalitions_lck_grp, &coalitions_lck_attr);
1480
1481 init_task_ledgers();
1482
3e170ce0
A
1483 for (i = 0, ctype = &s_coalition_types[0]; i < COALITION_NUM_TYPES; ctype++, i++) {
1484 /* verify the entry in the global coalition types array */
1485 if (ctype->type != i ||
1486 !ctype->init ||
1487 !ctype->dealloc ||
1488 !ctype->adopt_task ||
1489 !ctype->remove_task) {
1490 panic("%s: Malformed coalition type %s(%d) in slot for type:%s(%d)",
1491 __func__, coal_type_str(ctype->type), ctype->type, coal_type_str(i), i);
1492 }
1493 if (!ctype->has_default)
1494 continue;
1495 kr = coalition_create_internal(ctype->type, TRUE, &init_coalition[ctype->type]);
1496 if (kr != KERN_SUCCESS)
1497 panic("%s: could not create init %s coalition: kr:%d",
1498 __func__, coal_type_str(i), kr);
fe8ab488 1499 }
3e170ce0 1500
fe8ab488
A
1501 /* "Leak" our reference to the global object */
1502}
1503
3e170ce0
A
1504/*
1505 * BSD Kernel interface functions
1506 *
1507 */
1508static void coalition_fill_procinfo(struct coalition *coal,
1509 struct procinfo_coalinfo *coalinfo)
a1c7dba1 1510{
3e170ce0
A
1511 coalinfo->coalition_id = coal->id;
1512 coalinfo->coalition_type = coal->type;
1513 coalinfo->coalition_tasks = coalition_get_task_count(coal);
a1c7dba1
A
1514}
1515
3e170ce0
A
1516
1517int coalitions_get_list(int type, struct procinfo_coalinfo *coal_list, int list_sz)
a1c7dba1 1518{
3e170ce0
A
1519 int ncoals = 0;
1520 struct coalition *coal;
1521
1522 lck_mtx_lock(&coalitions_list_lock);
1523 qe_foreach_element(coal, &coalitions_q, coalitions) {
1524 if (!coal->reaped && (type < 0 || type == (int)coal->type)) {
1525 if (coal_list && ncoals < list_sz)
1526 coalition_fill_procinfo(coal, &coal_list[ncoals]);
1527 ++ncoals;
1528 }
1529 }
1530 lck_mtx_unlock(&coalitions_list_lock);
1531
1532 return ncoals;
a1c7dba1
A
1533}
1534
3e170ce0
A
1535/*
1536 * Jetsam coalition interface
1537 *
1538 */
1539boolean_t coalition_is_leader(task_t task, int coal_type, coalition_t *coal)
a1c7dba1 1540{
3e170ce0
A
1541 coalition_t c;
1542 boolean_t ret;
1543
1544 if (coal) /* handle the error cases gracefully */
1545 *coal = COALITION_NULL;
1546
1547 if (!task)
1548 return FALSE;
1549
1550 if (coal_type > COALITION_TYPE_MAX)
1551 return FALSE;
1552
1553 c = task->coalition[coal_type];
1554 if (!c)
1555 return FALSE;
1556
1557 assert((int)c->type == coal_type);
1558
1559 coalition_lock(c);
1560
1561 if (coal)
1562 *coal = c;
1563
1564 ret = FALSE;
1565 if (c->type == COALITION_TYPE_JETSAM && c->j.leader == task)
1566 ret = TRUE;
1567
1568 coalition_unlock(c);
1569
1570 return ret;
a1c7dba1
A
1571}
1572
3e170ce0
A
1573
1574int coalition_get_task_count(coalition_t coal)
a1c7dba1 1575{
3e170ce0
A
1576 int ntasks = 0;
1577 struct queue_entry *qe;
1578 if (!coal)
1579 return 0;
1580
1581 coalition_lock(coal);
1582 switch (coal->type) {
1583 case COALITION_TYPE_RESOURCE:
1584 qe_foreach(qe, &coal->r.tasks)
1585 ntasks++;
1586 break;
1587 case COALITION_TYPE_JETSAM:
1588 if (coal->j.leader)
1589 ntasks++;
1590 qe_foreach(qe, &coal->j.other)
1591 ntasks++;
1592 qe_foreach(qe, &coal->j.extensions)
1593 ntasks++;
1594 qe_foreach(qe, &coal->j.services)
1595 ntasks++;
1596 break;
1597 default:
1598 break;
1599 }
1600 coalition_unlock(coal);
1601
1602 return ntasks;
a1c7dba1
A
1603}
1604
3e170ce0
A
1605
1606static uint64_t i_get_list_footprint(queue_t list, int type, int *ntasks)
1607{
a1c7dba1 1608 task_t task;
3e170ce0 1609 uint64_t bytes = 0;
a1c7dba1 1610
3e170ce0
A
1611 qe_foreach_element(task, list, task_coalition[type]) {
1612 bytes += get_task_phys_footprint(task);
1613 coal_dbg(" [%d] task_pid:%d, type:%d, footprint:%lld",
1614 *ntasks, task_pid(task), type, bytes);
1615 *ntasks += 1;
1616 }
a1c7dba1 1617
3e170ce0
A
1618 return bytes;
1619}
a1c7dba1 1620
3e170ce0
A
1621uint64_t coalition_get_page_count(coalition_t coal, int *ntasks)
1622{
1623 uint64_t bytes = 0;
1624 int num_tasks = 0;
1625
1626 if (ntasks)
1627 *ntasks = 0;
1628 if (!coal)
1629 return bytes;
a1c7dba1 1630
3e170ce0 1631 coalition_lock(coal);
a1c7dba1 1632
3e170ce0
A
1633 switch (coal->type) {
1634 case COALITION_TYPE_RESOURCE:
1635 bytes += i_get_list_footprint(&coal->r.tasks, COALITION_TYPE_RESOURCE, &num_tasks);
1636 break;
1637 case COALITION_TYPE_JETSAM:
1638 if (coal->j.leader) {
1639 bytes += get_task_phys_footprint(coal->j.leader);
1640 num_tasks = 1;
a1c7dba1 1641 }
3e170ce0
A
1642 bytes += i_get_list_footprint(&coal->j.extensions, COALITION_TYPE_JETSAM, &num_tasks);
1643 bytes += i_get_list_footprint(&coal->j.services, COALITION_TYPE_JETSAM, &num_tasks);
1644 bytes += i_get_list_footprint(&coal->j.other, COALITION_TYPE_JETSAM, &num_tasks);
1645 break;
1646 default:
1647 break;
a1c7dba1 1648 }
3e170ce0 1649
a1c7dba1 1650 coalition_unlock(coal);
3e170ce0
A
1651
1652 if (ntasks)
1653 *ntasks = num_tasks;
1654
1655 return bytes / PAGE_SIZE_64;
a1c7dba1
A
1656}
1657
3e170ce0
A
1658struct coal_sort_s {
1659 int pid;
1660 int usr_order;
1661 uint64_t bytes;
1662};
1663
1664/*
1665 * return < 0 for a < b
1666 * 0 for a == b
1667 * > 0 for a > b
1668 */
1669typedef int (*cmpfunc_t)(const void *a, const void *b);
1670
1671extern void
1672qsort(void *a, size_t n, size_t es, cmpfunc_t cmp);
1673
1674static int dflt_cmp(const void *a, const void *b)
1675{
1676 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
1677 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
1678
1679 /*
1680 * if both A and B are equal, use a memory descending sort
1681 */
1682 if (csA->usr_order == csB->usr_order)
1683 return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
1684
1685 /* otherwise, return the relationship between user specified orders */
1686 return (csA->usr_order - csB->usr_order);
1687}
1688
1689static int mem_asc_cmp(const void *a, const void *b)
1690{
1691 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
1692 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
1693
1694 return (int)((int64_t)csA->bytes - (int64_t)csB->bytes);
1695}
1696
1697static int mem_dec_cmp(const void *a, const void *b)
1698{
1699 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
1700 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
1701
1702 return (int)((int64_t)csB->bytes - (int64_t)csA->bytes);
1703}
1704
1705static int usr_asc_cmp(const void *a, const void *b)
1706{
1707 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
1708 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
1709
1710 return (csA->usr_order - csB->usr_order);
1711}
1712
1713static int usr_dec_cmp(const void *a, const void *b)
1714{
1715 const struct coal_sort_s *csA = (const struct coal_sort_s *)a;
1716 const struct coal_sort_s *csB = (const struct coal_sort_s *)b;
1717
1718 return (csB->usr_order - csA->usr_order);
1719}
1720
1721/* avoid dynamic allocation in this path */
1722#define MAX_SORTED_PIDS 80
1723
1724static int coalition_get_sort_list(coalition_t coal, int sort_order, queue_t list,
1725 struct coal_sort_s *sort_array, int array_sz)
1726{
1727 int ntasks = 0;
1728 task_t task;
1729
1730 assert(sort_array != NULL);
1731
1732 if (array_sz <= 0)
1733 return 0;
1734
1735 if (!list) {
1736 /*
1737 * this function will only be called with a NULL
1738 * list for JETSAM-type coalitions, and is intended
1739 * to investigate the leader process
1740 */
1741 if (coal->type != COALITION_TYPE_JETSAM ||
1742 coal->j.leader == TASK_NULL)
1743 return 0;
1744 sort_array[0].pid = task_pid(coal->j.leader);
1745 switch (sort_order) {
1746 case COALITION_SORT_DEFAULT:
1747 sort_array[0].usr_order = 0;
1748 /* fall-through */
1749 case COALITION_SORT_MEM_ASC:
1750 case COALITION_SORT_MEM_DEC:
1751 sort_array[0].bytes = get_task_phys_footprint(coal->j.leader);
1752 break;
1753 case COALITION_SORT_USER_ASC:
1754 case COALITION_SORT_USER_DEC:
1755 sort_array[0].usr_order = 0;
1756 break;
1757 default:
1758 break;
1759 }
1760 return 1;
1761 }
1762
1763 qe_foreach_element(task, list, task_coalition[coal->type]) {
1764 if (ntasks >= array_sz) {
1765 printf("WARNING: more than %d pids in coalition %llu\n",
1766 MAX_SORTED_PIDS, coal->id);
1767 break;
1768 }
1769
1770 sort_array[ntasks].pid = task_pid(task);
1771
1772 switch (sort_order) {
1773 case COALITION_SORT_DEFAULT:
1774 sort_array[ntasks].usr_order = 0;
1775 /* fall-through */
1776 case COALITION_SORT_MEM_ASC:
1777 case COALITION_SORT_MEM_DEC:
1778 sort_array[ntasks].bytes = get_task_phys_footprint(task);
1779 break;
1780 case COALITION_SORT_USER_ASC:
1781 case COALITION_SORT_USER_DEC:
1782 sort_array[ntasks].usr_order = 0;
1783 break;
1784 default:
1785 break;
1786 }
1787
1788 ntasks++;
1789 }
1790
1791 return ntasks;
1792}
1793
1794int coalition_get_pid_list(coalition_t coal, uint32_t rolemask, int sort_order,
1795 int *pid_list, int list_sz)
1796{
1797 struct i_jetsam_coalition *cj;
1798 int ntasks = 0;
1799 cmpfunc_t cmp_func = NULL;
1800 struct coal_sort_s sort_array[MAX_SORTED_PIDS] = { {0,0,0} }; /* keep to < 2k */
1801
1802 if (!coal ||
1803 !(rolemask & COALITION_ROLEMASK_ALLROLES) ||
1804 !pid_list || list_sz < 1) {
1805 coal_dbg("Invalid parameters: coal:%p, type:%d, rolemask:0x%x, "
1806 "pid_list:%p, list_sz:%d", coal, coal ? coal->type : -1,
1807 rolemask, pid_list, list_sz);
1808 return -EINVAL;
1809 }
1810
1811 switch (sort_order) {
1812 case COALITION_SORT_NOSORT:
1813 cmp_func = NULL;
1814 break;
1815 case COALITION_SORT_DEFAULT:
1816 cmp_func = dflt_cmp;
1817 break;
1818 case COALITION_SORT_MEM_ASC:
1819 cmp_func = mem_asc_cmp;
1820 break;
1821 case COALITION_SORT_MEM_DEC:
1822 cmp_func = mem_dec_cmp;
1823 break;
1824 case COALITION_SORT_USER_ASC:
1825 cmp_func = usr_asc_cmp;
1826 break;
1827 case COALITION_SORT_USER_DEC:
1828 cmp_func = usr_dec_cmp;
1829 break;
1830 default:
1831 return -ENOTSUP;
1832 }
1833
1834 coalition_lock(coal);
1835
1836 if (coal->type == COALITION_TYPE_RESOURCE) {
1837 ntasks += coalition_get_sort_list(coal, sort_order, &coal->r.tasks,
1838 sort_array, MAX_SORTED_PIDS);
1839 goto unlock_coal;
1840 }
1841
1842 cj = &coal->j;
1843
1844 if (rolemask & COALITION_ROLEMASK_UNDEF)
1845 ntasks += coalition_get_sort_list(coal, sort_order, &cj->other,
1846 sort_array + ntasks,
1847 MAX_SORTED_PIDS - ntasks);
1848
1849 if (rolemask & COALITION_ROLEMASK_XPC)
1850 ntasks += coalition_get_sort_list(coal, sort_order, &cj->services,
1851 sort_array + ntasks,
1852 MAX_SORTED_PIDS - ntasks);
1853
1854 if (rolemask & COALITION_ROLEMASK_EXT)
1855 ntasks += coalition_get_sort_list(coal, sort_order, &cj->extensions,
1856 sort_array + ntasks,
1857 MAX_SORTED_PIDS - ntasks);
1858
1859 if (rolemask & COALITION_ROLEMASK_LEADER)
1860 ntasks += coalition_get_sort_list(coal, sort_order, NULL,
1861 sort_array + ntasks,
1862 MAX_SORTED_PIDS - ntasks);
1863
1864unlock_coal:
1865 coalition_unlock(coal);
1866
1867 /* sort based on the chosen criterion (no sense sorting 1 item) */
1868 if (cmp_func && ntasks > 1)
1869 qsort(sort_array, ntasks, sizeof(struct coal_sort_s), cmp_func);
1870
1871 for (int i = 0; i < ntasks; i++) {
1872 if (i >= list_sz)
1873 break;
1874 coal_dbg(" [%d] PID:%d, footprint:%lld, usr_order:%d",
1875 i, sort_array[i].pid, sort_array[i].bytes,
1876 sort_array[i].usr_order);
1877 pid_list[i] = sort_array[i].pid;
1878 }
1879
1880 return ntasks;
1881}