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