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