1 #include <stdio.h>
2 #include <signal.h>
3 #include <sys/proc.h>
4 #include <sys/sysctl.h>
5 #include <sys/kern_memorystatus.h>
6 #include <sys/kern_memorystatus_freeze.h>
7 #include <time.h>
8 #include <mach-o/dyld.h>
9 #include <mach/mach_vm.h>
10 #include <mach/vm_page_size.h>
11 #include <mach/shared_region.h>
12 #include <mach/mach.h>
13 #include <os/reason_private.h>
14 #include <TargetConditionals.h>
15 #include <sys/coalition.h>
16 #include <spawn_private.h>
17
18 #ifdef T_NAMESPACE
19 #undef T_NAMESPACE
20 #endif
21 #include <darwintest.h>
22 #include <darwintest_utils.h>
23
24 #include "memorystatus_assertion_helpers.h"
25 #include "test_utils.h"
26
27 T_GLOBAL_META(
28 T_META_NAMESPACE("xnu.memorystatus"),
29 T_META_RADAR_COMPONENT_NAME("xnu"),
30 T_META_RADAR_COMPONENT_VERSION("VM - memory pressure"),
31 T_META_CHECK_LEAKS(false),
32 T_META_OWNER("jarrad"),
33 T_META_RUN_CONCURRENTLY(false)
34 );
35
36 #define MEM_SIZE_MB 10
37 #define NUM_ITERATIONS 5
38 #define FREEZE_PAGES_MAX 256
39
40 #define HAS_FREEZER ((TARGET_OS_IOS && !TARGET_OS_XR) || TARGET_OS_WATCH)
41
42 #define CREATE_LIST(X) \
43 X(SUCCESS) \
44 X(TOO_FEW_ARGUMENTS) \
45 X(SYSCTL_VM_PAGESIZE_FAILED) \
46 X(VM_PAGESIZE_IS_ZERO) \
47 X(DISPATCH_SOURCE_CREATE_FAILED) \
48 X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
49 X(SIGNAL_TO_PARENT_FAILED) \
50 X(MEMORYSTATUS_CONTROL_FAILED) \
51 X(IS_FREEZABLE_NOT_AS_EXPECTED) \
52 X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
53 X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
54 X(FROZEN_BIT_SET) \
55 X(FROZEN_BIT_NOT_SET) \
56 X(MEMORYSTATUS_CONTROL_ERROR) \
57 X(UNABLE_TO_ALLOCATE) \
58 X(EXIT_CODE_MAX) \
59
60 #define EXIT_CODES_ENUM(VAR) VAR,
61 enum exit_codes_num {
62 CREATE_LIST(EXIT_CODES_ENUM)
63 };
64
65 #define EXIT_CODES_STRING(VAR) #VAR,
66 static const char *exit_codes_str[] = {
67 CREATE_LIST(EXIT_CODES_STRING)
68 };
69
70 static int
get_vmpage_size(void)71 get_vmpage_size(void)
72 {
73 int vmpage_size;
74 size_t size = sizeof(vmpage_size);
75 int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
76 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
77 T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
78 return vmpage_size;
79 }
80
81 static pid_t child_pid = -1;
82 static int freeze_count = 0;
83
84 void move_to_idle_band(pid_t);
85 void run_freezer_test(int);
86 void freeze_helper_process(void);
87 /* Gets and optionally sets the freeze pages max threshold */
88 int sysctl_freeze_pages_max(int* new_value);
89
90 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
91 * We're moving away from those unit tests, so they're copied here.
92 */
93
94 /* Cribbed from 'top'... */
95 static int
in_shared_region(mach_vm_address_t addr,cpu_type_t type)96 in_shared_region(mach_vm_address_t addr, cpu_type_t type)
97 {
98 mach_vm_address_t base = 0, size = 0;
99
100 switch (type) {
101 case CPU_TYPE_ARM:
102 base = SHARED_REGION_BASE_ARM;
103 size = SHARED_REGION_SIZE_ARM;
104 break;
105
106 case CPU_TYPE_ARM64:
107 base = SHARED_REGION_BASE_ARM64;
108 size = SHARED_REGION_SIZE_ARM64;
109 break;
110
111
112 case CPU_TYPE_X86_64:
113 base = SHARED_REGION_BASE_X86_64;
114 size = SHARED_REGION_SIZE_X86_64;
115 break;
116
117 case CPU_TYPE_I386:
118 base = SHARED_REGION_BASE_I386;
119 size = SHARED_REGION_SIZE_I386;
120 break;
121
122 case CPU_TYPE_POWERPC:
123 base = SHARED_REGION_BASE_PPC;
124 size = SHARED_REGION_SIZE_PPC;
125 break;
126
127 case CPU_TYPE_POWERPC64:
128 base = SHARED_REGION_BASE_PPC64;
129 size = SHARED_REGION_SIZE_PPC64;
130 break;
131
132 default: {
133 int t = type;
134
135 fprintf(stderr, "unknown CPU type: 0x%x\n", t);
136 abort();
137 }
138 }
139
140 return addr >= base && addr < (base + size);
141 }
142
143 static int orig_freezer;
144
145 static void
restore_freezer()146 restore_freezer() {
147 int ret;
148 T_LOG("Restoring original vm.freeze_enabled value (%d)...", orig_freezer);
149 ret = sysctlbyname("vm.freeze_enabled", NULL, NULL, &orig_freezer, sizeof(orig_freezer));
150 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Enabling freezer via sysctl");
151 }
152
153 static void
check_for_and_enable_freezer()154 check_for_and_enable_freezer() {
155 int ret, freeze_enabled;
156 size_t size = sizeof(freeze_enabled);
157
158 ret = sysctlbyname("vm.freeze_enabled", &freeze_enabled, &size, NULL, 0);
159 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "Could not find vm.freeze_enabled");
160
161 if (!freeze_enabled) {
162 T_LOG("Freezer not enabled, enabling...");
163 orig_freezer = freeze_enabled;
164 freeze_enabled = 1;
165 ret = sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, sizeof(freeze_enabled));
166 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Enabling freezer via sysctl");
167 T_ATEND(restore_freezer);
168 }
169 }
170
171 /* Get the resident private memory of the given pid */
172 static unsigned long long
get_rprvt(pid_t pid)173 get_rprvt(pid_t pid)
174 {
175 mach_port_name_t task;
176 kern_return_t kr;
177
178 mach_vm_size_t rprvt = 0;
179 mach_vm_size_t empty = 0;
180 mach_vm_size_t fw_private = 0;
181 mach_vm_size_t pagesize = vm_kernel_page_size; // The vm_region page info is reported
182 // in terms of vm_kernel_page_size.
183 mach_vm_size_t regs = 0;
184
185 mach_vm_address_t addr;
186 mach_vm_size_t size;
187
188 int split = 0;
189
190 kr = task_for_pid(mach_task_self(), pid, &task);
191 T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
192
193 for (addr = 0;; addr += size) {
194 vm_region_top_info_data_t info;
195 mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
196 mach_port_t object_name;
197
198 kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
199 if (kr != KERN_SUCCESS) {
200 break;
201 }
202
203 #if defined (__arm64__)
204 if (in_shared_region(addr, CPU_TYPE_ARM64)) {
205 #else
206 if (in_shared_region(addr, CPU_TYPE_ARM)) {
207 #endif
208 // Private Shared
209 fw_private += info.private_pages_resident * pagesize;
210
211 /*
212 * Check if this process has the globally shared
213 * text and data regions mapped in. If so, set
214 * split to TRUE and avoid checking
215 * again.
216 */
217 if (split == FALSE && info.share_mode == SM_EMPTY) {
218 vm_region_basic_info_data_64_t b_info;
219 mach_vm_address_t b_addr = addr;
220 mach_vm_size_t b_size = size;
221 count = VM_REGION_BASIC_INFO_COUNT_64;
222
223 kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
224 if (kr != KERN_SUCCESS) {
225 break;
226 }
227
228 if (b_info.reserved) {
229 split = TRUE;
230 }
231 }
232
233 /*
234 * Short circuit the loop if this isn't a shared
235 * private region, since that's the only region
236 * type we care about within the current address
237 * range.
238 */
239 if (info.share_mode != SM_PRIVATE) {
240 continue;
241 }
242 }
243
244 regs++;
245
246 /*
247 * Update counters according to the region type.
248 */
249
250 if (info.share_mode == SM_COW && info.ref_count == 1) {
251 // Treat single reference SM_COW as SM_PRIVATE
252 info.share_mode = SM_PRIVATE;
253 }
254
255 switch (info.share_mode) {
256 case SM_LARGE_PAGE:
257 // Treat SM_LARGE_PAGE the same as SM_PRIVATE
258 // since they are not shareable and are wired.
259 case SM_PRIVATE:
260 rprvt += info.private_pages_resident * pagesize;
261 rprvt += info.shared_pages_resident * pagesize;
262 break;
263
264 case SM_EMPTY:
265 empty += size;
266 break;
267
268 case SM_COW:
269 case SM_SHARED:
270 if (pid == 0) {
271 // Treat kernel_task specially
272 if (info.share_mode == SM_COW) {
273 rprvt += info.private_pages_resident * pagesize;
274 }
275 break;
276 }
277
278 if (info.share_mode == SM_COW) {
279 rprvt += info.private_pages_resident * pagesize;
280 }
281 break;
282
283 default:
284 assert(0);
285 break;
286 }
287 }
288
289 return rprvt;
290 }
291
292 void
293 move_to_idle_band(pid_t pid)
294 {
295 memorystatus_priority_properties_t props;
296 /*
297 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
298 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
299 */
300 props.priority = JETSAM_PRIORITY_IDLE;
301 props.user_data = 0;
302
303 /*
304 * This requires us to run as root (in the absence of entitlement).
305 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
306 */
307 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) {
308 exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
309 }
310 }
311
312 void
313 freeze_helper_process(void)
314 {
315 size_t length;
316 int ret, freeze_enabled, errno_freeze_sysctl;
317 uint64_t resident_memory_before, resident_memory_after, vmpage_size;
318 vmpage_size = (uint64_t) get_vmpage_size();
319 resident_memory_before = get_rprvt(child_pid) / vmpage_size;
320
321 T_LOG("Freezing child pid %d", child_pid);
322 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
323 errno_freeze_sysctl = errno;
324 sleep(1);
325
326 /*
327 * The child process toggles its freezable state on each iteration.
328 * So a failure for every alternate freeze is expected.
329 */
330 if (freeze_count % 2) {
331 length = sizeof(freeze_enabled);
332 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
333 "failed to query vm.freeze_enabled");
334 if (freeze_enabled) {
335 errno = errno_freeze_sysctl;
336 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
337 } else {
338 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
339 T_LOG("Freeze has been disabled. Terminating early.");
340 T_END;
341 }
342 resident_memory_after = get_rprvt(child_pid) / vmpage_size;
343 uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
344 T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
345 if (resident_memory_before > freeze_pages_max) {
346 T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
347 }
348 ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
349 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
350 } else {
351 T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
352 T_LOG("Freeze failed as expected");
353 }
354
355 freeze_count++;
356
357 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
358 }
359
360 void
361 run_freezer_test(int num_pages)
362 {
363 int ret;
364 char sz_str[50];
365 char **launch_tool_args;
366 char testpath[PATH_MAX];
367 uint32_t testpath_buf_size;
368 dispatch_source_t ds_freeze, ds_proc;
369
370 signal(SIGUSR1, SIG_IGN);
371 ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
372 T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
373
374 dispatch_source_set_event_handler(ds_freeze, ^{
375 if (freeze_count < NUM_ITERATIONS) {
376 freeze_helper_process();
377 } else {
378 kill(child_pid, SIGKILL);
379 dispatch_source_cancel(ds_freeze);
380 }
381 });
382 dispatch_activate(ds_freeze);
383
384 testpath_buf_size = sizeof(testpath);
385 ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
386 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
387 T_LOG("Executable path: %s", testpath);
388
389 sprintf(sz_str, "%d", num_pages);
390 launch_tool_args = (char *[]){
391 testpath,
392 "-n",
393 "allocate_pages",
394 "--",
395 sz_str,
396 NULL
397 };
398
399 /* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
400 ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
401 if (ret != 0) {
402 T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
403 }
404 T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
405
406 ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
407 T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
408
409 dispatch_source_set_event_handler(ds_proc, ^{
410 int status = 0, code = 0;
411 pid_t rc = waitpid(child_pid, &status, 0);
412 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
413 code = WEXITSTATUS(status);
414
415 if (code == 0) {
416 T_END;
417 } else if (code > 0 && code < EXIT_CODE_MAX) {
418 T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
419 } else {
420 T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
421 }
422 });
423 dispatch_activate(ds_proc);
424
425 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
426 dispatch_main();
427 }
428
429 static void
430 allocate_pages(int num_pages)
431 {
432 int i, j, vmpgsize;
433 char val;
434 __block int num_iter = 0;
435 __block char **buf;
436 dispatch_source_t ds_signal;
437 vmpgsize = get_vmpage_size();
438 if (num_pages < 1) {
439 printf("Invalid number of pages to allocate: %d\n", num_pages);
440 exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
441 }
442
443 buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
444
445 /* Gives us the compression ratio we see in the typical case (~2.7) */
446 for (j = 0; j < num_pages; j++) {
447 buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
448 val = 0;
449 for (i = 0; i < vmpgsize; i += 16) {
450 memset(&buf[j][i], val, 16);
451 if (i < 3400 * (vmpgsize / 4096)) {
452 val++;
453 }
454 }
455 }
456
457 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
458 /* Signal to the parent that we're done allocating and it's ok to freeze us */
459 printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
460 if (kill(getppid(), SIGUSR1) != 0) {
461 exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
462 }
463 });
464
465 signal(SIGUSR1, SIG_IGN);
466 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
467 if (ds_signal == NULL) {
468 exit(DISPATCH_SOURCE_CREATE_FAILED);
469 }
470
471 dispatch_source_set_event_handler(ds_signal, ^{
472 int current_state, new_state;
473 volatile int tmp;
474
475 /* Make sure all the pages are accessed before trying to freeze again */
476 for (int x = 0; x < num_pages; x++) {
477 tmp = buf[x][0];
478 }
479
480 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
481 /* Sysprocs start off as unfreezable. Verify that first. */
482 if (num_iter == 0 && current_state != 0) {
483 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
484 }
485
486 /* Toggle freezable state */
487 new_state = (current_state) ? 0: 1;
488 printf("[%d] Changing state from %s to %s\n", getpid(),
489 (current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
490 if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
491 exit(MEMORYSTATUS_CONTROL_FAILED);
492 }
493
494 /* Verify that the state has been set correctly */
495 current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
496 if (new_state != current_state) {
497 exit(IS_FREEZABLE_NOT_AS_EXPECTED);
498 }
499 num_iter++;
500
501 if (kill(getppid(), SIGUSR1) != 0) {
502 exit(SIGNAL_TO_PARENT_FAILED);
503 }
504 });
505 dispatch_activate(ds_signal);
506 move_to_idle_band(getpid());
507
508 dispatch_main();
509 }
510
511 T_HELPER_DECL(allocate_pages,
512 "allocates pages to freeze",
513 T_META_ASROOT(true)) {
514 if (argc < 1) {
515 exit(TOO_FEW_ARGUMENTS);
516 }
517
518 int num_pages = atoi(argv[0]);
519 allocate_pages(num_pages);
520 }
521
522 T_DECL(memorystatus_freeze_default_state, "Test that the freezer is enabled or disabled as expected by default.", T_META_ASROOT(true), T_META_TAG_VM_NOT_PREFERRED) {
523
524 #if TARGET_OS_IOS || TARGET_OS_WATCH
525 bool expected_freeze_enabled = true;
526 #else
527 bool expected_freeze_enabled = false;
528 #endif
529
530
531 int freeze_enabled;
532 size_t length = sizeof(freeze_enabled);
533 int ret = sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0);
534
535 if (ret != 0) {
536 if (expected_freeze_enabled) {
537 T_ASSERT_FAIL("Expected freezer enabled but did not find sysctl vm.freeze_enabled");
538 } else {
539 T_PASS("Did not expect freezer to be enabled and did not find sysctl vm.freeze_enabled");
540 }
541 } else {
542 T_ASSERT_EQ(freeze_enabled, expected_freeze_enabled, "Expected freezer sysctl status %d, got %d", expected_freeze_enabled, freeze_enabled);
543 }
544 }
545
546 T_DECL(freeze, "VM freezer test",
547 T_META_ENABLED(HAS_FREEZER),
548 T_META_ASROOT(true),
549 T_META_TAG_VM_NOT_PREFERRED) {
550 check_for_and_enable_freezer();
551 run_freezer_test(
552 (MEM_SIZE_MB << 20) / get_vmpage_size());
553 }
554
555 static int old_freeze_pages_max = 0;
556 static void
557 reset_freeze_pages_max(void)
558 {
559 if (old_freeze_pages_max != 0) {
560 sysctl_freeze_pages_max(&old_freeze_pages_max);
561 }
562 }
563
564 int
565 sysctl_freeze_pages_max(int* new_value)
566 {
567 static int set_end_handler = false;
568 int freeze_pages_max, ret;
569 size_t size = sizeof(freeze_pages_max);
570 ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
571 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
572 if (!set_end_handler) {
573 // Save the original value and instruct darwintest to restore it after the test completes
574 old_freeze_pages_max = freeze_pages_max;
575 T_ATEND(reset_freeze_pages_max);
576 set_end_handler = true;
577 }
578 return old_freeze_pages_max;
579 }
580
581 T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced",
582 T_META_ENABLED(HAS_FREEZER),
583 T_META_ASROOT(true),
584 T_META_TAG_VM_NOT_PREFERRED) {
585 int freeze_pages_max = FREEZE_PAGES_MAX;
586 check_for_and_enable_freezer();
587 sysctl_freeze_pages_max(&freeze_pages_max);
588 run_freezer_test(FREEZE_PAGES_MAX * 2);
589 }
590
591 T_HELPER_DECL(frozen_background, "Frozen background process",
592 T_META_ENABLED(HAS_FREEZER),
593 T_META_ASROOT(true)) {
594 kern_return_t kern_ret;
595 check_for_and_enable_freezer();
596 /* Set the process to freezable */
597 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
598 T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
599 /* Signal to our parent that we can be frozen */
600 if (kill(getppid(), SIGUSR1) != 0) {
601 T_LOG("Unable to signal to parent process!");
602 exit(1);
603 }
604 while (1) {
605 ;
606 }
607 }
608
609 static void
610 freeze_process(pid_t pid)
611 {
612 int ret, freeze_enabled, errno_freeze_sysctl;
613 size_t length;
614 T_LOG("Freezing pid %d", pid);
615
616 ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
617 errno_freeze_sysctl = errno;
618 length = sizeof(freeze_enabled);
619 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
620 "failed to query vm.freeze_enabled");
621 if (freeze_enabled) {
622 errno = errno_freeze_sysctl;
623 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
624 } else {
625 /* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
626 T_LOG("Freeze has been disabled. Terminating early.");
627 T_END;
628 }
629 }
630
631 static uint32_t max_frozen_demotions_daily_default;
632
633 static void
634 reset_max_frozen_demotions_daily(void)
635 {
636 int sysctl_ret = sysctlbyname("kern.memorystatus_max_freeze_demotions_daily", NULL, NULL, &max_frozen_demotions_daily_default, sizeof(max_frozen_demotions_daily_default));
637 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "set kern.memorystatus_max_freeze_demotions_daily to default");
638 }
639
640 static void
641 allow_unlimited_demotions(void)
642 {
643 size_t size = sizeof(max_frozen_demotions_daily_default);
644 uint32_t new_value = UINT32_MAX;
645 int sysctl_ret = sysctlbyname("kern.memorystatus_max_freeze_demotions_daily", &max_frozen_demotions_daily_default, &size, &new_value, sizeof(new_value));
646 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "kern.memorystatus_max_freeze_demotions_daily = UINT32_MAX");
647 T_ATEND(reset_max_frozen_demotions_daily);
648 }
649
650 static void
651 memorystatus_assertion_test_demote_frozen(void)
652 {
653 /*
654 * Test that if we assert a priority on a process, freeze it, and then demote all frozen processes, it does not get demoted below the asserted priority.
655 * Then remove thee assertion, and ensure it gets demoted properly.
656 */
657 /* these values will remain fixed during testing */
658 int active_limit_mb = 15; /* arbitrary */
659 int inactive_limit_mb = 7; /* arbitrary */
660 __block int demote_value = 1;
661 /* Launch the child process, and elevate its priority */
662 int requestedpriority;
663 dispatch_source_t ds_signal, ds_exit;
664 requestedpriority = JETSAM_PRIORITY_FREEZER;
665 allow_unlimited_demotions();
666
667 /* Wait for the child process to tell us that it's ready, and then freeze it */
668 signal(SIGUSR1, SIG_IGN);
669 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
670 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
671 dispatch_source_set_event_handler(ds_signal, ^{
672 int sysctl_ret;
673 /* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
674 freeze_process(child_pid);
675 /* Agressive demotion */
676 sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value));
677 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
678 /* Check */
679 (void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
680 T_LOG("Relinquishing our assertion.");
681 /* Relinquish our assertion, and check that it gets demoted. */
682 relinquish_assertion_priority(child_pid, 0x0);
683 (void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
684 /* Kill the child */
685 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
686 T_END;
687 });
688
689 /* Launch the child process and set the initial properties on it. */
690 child_pid = launch_background_helper("frozen_background", false, true);
691 set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
692 set_assertion_priority(child_pid, requestedpriority, 0x0);
693 (void)check_properties(child_pid, requestedpriority, active_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
694 /* Listen for exit. */
695 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
696 dispatch_source_set_event_handler(ds_exit, ^{
697 int status = 0, code = 0;
698 pid_t rc = waitpid(child_pid, &status, 0);
699 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
700 code = WEXITSTATUS(status);
701 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
702 T_END;
703 });
704
705 dispatch_activate(ds_exit);
706 dispatch_activate(ds_signal);
707 dispatch_main();
708 }
709
710 T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.",
711 /* T_META_ENABLED(HAS_FREEZER), */
712 T_META_ASROOT(true),
713 T_META_TAG_VM_NOT_PREFERRED,
714 T_META_ENABLED(false) /* rdar://133461319 */)
715 {
716 check_for_and_enable_freezer();
717 memorystatus_assertion_test_demote_frozen();
718 }
719
720 static unsigned int
721 get_freeze_daily_pages_max(void)
722 {
723 unsigned int memorystatus_freeze_daily_mb_max;
724 size_t length = sizeof(memorystatus_freeze_daily_mb_max);
725 int ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0);
726 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
727 return memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / vm_kernel_page_size;
728 }
729
730 static uint64_t
731 get_budget_multiplier(void)
732 {
733 uint64_t budget_multiplier = 0;
734 size_t size = sizeof(budget_multiplier);
735 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_multiplier", &budget_multiplier, &size, NULL, 0),
736 "get kern.memorystatus_freeze_budget_multiplier");
737 return budget_multiplier;
738 }
739 static void
740 set_budget_multiplier(uint64_t multiplier)
741 {
742 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_multiplier", NULL, NULL, &multiplier, sizeof(multiplier)),
743 "set kern.memorystatus_freeze_budget_multiplier");
744 }
745
746 static uint64_t original_budget_multiplier;
747 static void
748 reset_budget_multiplier(void)
749 {
750 set_budget_multiplier(original_budget_multiplier);
751 }
752
753 static unsigned int
754 get_memorystatus_swap_all_apps(void)
755 {
756 unsigned int memorystatus_swap_all_apps;
757 size_t length = sizeof(memorystatus_swap_all_apps);
758 int ret = sysctlbyname("kern.memorystatus_swap_all_apps", &memorystatus_swap_all_apps, &length, NULL, 0);
759 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_swap_all_apps");
760 return memorystatus_swap_all_apps;
761 }
762
763 T_DECL(budget_replenishment, "budget replenishes properly",
764 T_META_ENABLED(HAS_FREEZER),
765 T_META_REQUIRES_SYSCTL_NE("kern.memorystatus_freeze_daily_mb_max", UINT32_MAX),
766 T_META_TAG_VM_NOT_PREFERRED) {
767 size_t length;
768 int ret;
769 static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours
770 unsigned int memorystatus_freeze_daily_pages_max;
771 static unsigned int kFixedPointFactor = 100;
772 static unsigned int kNumSecondsInDay = 60 * 60 * 24;
773 unsigned int new_budget, expected_new_budget_pages;
774 size_t new_budget_ln;
775 vm_size_t page_size = vm_kernel_page_size;
776
777 check_for_and_enable_freezer();
778
779 original_budget_multiplier = get_budget_multiplier();
780 T_ATEND(reset_budget_multiplier);
781 set_budget_multiplier(100);
782 /*
783 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
784 * ago and we used up its entire budget.
785 */
786 length = sizeof(kTestIntervalSecs);
787 new_budget_ln = sizeof(new_budget);
788 ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length);
789 T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
790
791 memorystatus_freeze_daily_pages_max = get_freeze_daily_pages_max();
792 T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max);
793 T_LOG("page_size %lu", page_size);
794
795 /*
796 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
797 * seconds of budget.
798 */
799 expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
800 T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages);
801 T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change");
802 expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
803 * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
804 if (get_memorystatus_swap_all_apps()) {
805 /*
806 * memorystatus_swap_all_apps is enabled; the budget is unlimited
807 */
808 expected_new_budget_pages = UINT32_MAX;
809 }
810 T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages);
811 T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max);
812
813 T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
814 }
815
816 static void
817 get_frozen_list(global_frozen_procs_t *frozen_procs)
818 {
819 int bytes_written;
820 bytes_written = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_GET_PROCS, frozen_procs, sizeof(global_frozen_procs_t));
821 T_QUIET; T_ASSERT_LE((size_t) bytes_written, sizeof(global_frozen_procs_t), "Didn't overflow buffer");
822 T_QUIET; T_ASSERT_GT(bytes_written, 0, "Wrote someting");
823 }
824
825 static bool
826 is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len)
827 {
828 bool found = false;
829 global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t));
830 T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc");
831
832 get_frozen_list(frozen_procs);
833
834 for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) {
835 if (frozen_procs->gfp_procs[i].fp_pid == pid) {
836 found = true;
837 strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len);
838 }
839 }
840 return found;
841 }
842
843 static void
844 unset_testing_pid(void)
845 {
846 int ret;
847 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_UNSET_TESTING_PID, NULL, 0);
848 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot");
849 }
850
851 static void
852 set_testing_pid(void)
853 {
854 int ret;
855 ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_SET_TESTING_PID, NULL, 0);
856 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot");
857 T_ATEND(unset_testing_pid);
858 }
859
860 /*
861 * Retrieve a jetsam snapshot.
862 *
863 * return:
864 * pointer to snapshot.
865 *
866 * Caller is responsible for freeing snapshot.
867 */
868 static
869 memorystatus_jetsam_snapshot_t *
870 get_jetsam_snapshot(uint32_t flags, bool empty_allowed)
871 {
872 memorystatus_jetsam_snapshot_t * snapshot = NULL;
873 int ret;
874 uint32_t size;
875
876 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, NULL, 0);
877 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Get jetsam snapshot size");
878 size = (uint32_t) ret;
879 if (size == 0 && empty_allowed) {
880 return snapshot;
881 }
882
883 snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
884 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size);
885
886 ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size);
887 T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot");
888
889 if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) {
890 T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size,
891 sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t),
892 sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t)));
893 if (snapshot) {
894 free(snapshot);
895 }
896 }
897
898 return snapshot;
899 }
900
901 /*
902 * Look for the given pid in the snapshot.
903 *
904 * return:
905 * pointer to pid's entry or NULL if pid is not found.
906 *
907 * Caller has ownership of snapshot before and after call.
908 */
909 static
910 memorystatus_jetsam_snapshot_entry_t *
911 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid)
912 {
913 T_QUIET; T_ASSERT_NOTNULL(snapshot, "Got snapshot");
914 for (size_t i = 0; i < snapshot->entry_count; i++) {
915 memorystatus_jetsam_snapshot_entry_t *curr = &(snapshot->entries[i]);
916 if (curr->pid == pid) {
917 return curr;
918 }
919 }
920
921 return NULL;
922 }
923
924 static void
925 resume_and_kill_proc(pid_t pid)
926 {
927 int ret = pid_resume(pid);
928 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "proc resumed after freeze");
929 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "Killed process");
930 }
931
932 static void
933 resume_and_kill_child(void)
934 {
935 /* Used for test cleanup. proc might not be suspended so pid_resume might fail. */
936 pid_resume(child_pid);
937 kill(child_pid, SIGKILL);
938 }
939
940 static dispatch_source_t
941 run_block_after_signal(int sig, dispatch_block_t block)
942 {
943 dispatch_source_t ds_signal;
944 signal(sig, SIG_IGN);
945 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) sig, 0, dispatch_get_main_queue());
946 T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
947 dispatch_source_set_event_handler(ds_signal, block);
948 return ds_signal;
949 }
950
951 /*
952 * Launches the child & runs the given block after the child signals.
953 * If exit_with_child is true, the test will exit when the child exits.
954 */
955 static void
956 test_after_background_helper_launches(bool exit_with_child, const char* variant, dispatch_block_t test_block)
957 {
958 dispatch_source_t ds_signal, ds_exit;
959
960 ds_signal = run_block_after_signal(SIGUSR1, test_block);
961 /* Launch the child process. */
962 child_pid = launch_background_helper(variant, false, true);
963 T_ATEND(resume_and_kill_child);
964 /* Listen for exit. */
965 if (exit_with_child) {
966 ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
967 dispatch_source_set_event_handler(ds_exit, ^{
968 int status = 0, code = 0;
969 pid_t rc = waitpid(child_pid, &status, 0);
970 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
971 code = WEXITSTATUS(status);
972 if (code != 0) {
973 T_LOG("Child exited with error: %s", exit_codes_str[code]);
974 }
975 T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
976 T_END;
977 });
978
979 dispatch_activate(ds_exit);
980 }
981 dispatch_activate(ds_signal);
982 }
983
984 T_DECL(get_frozen_procs, "List processes in the freezer",
985 T_META_ENABLED(HAS_FREEZER),
986 T_META_TAG_VM_NOT_PREFERRED) {
987
988 check_for_and_enable_freezer();
989 test_after_background_helper_launches(true, "frozen_background", ^{
990 proc_name_t name;
991 /* Place the child in the idle band so that it gets elevated like a typical app. */
992 move_to_idle_band(child_pid);
993 /* Freeze the process, and check that it's in the list of frozen processes. */
994 freeze_process(child_pid);
995 /* Check */
996 T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
997 T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
998 /* Kill the child */
999 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
1000 T_END;
1001 });
1002 dispatch_main();
1003 }
1004
1005 T_DECL(frozen_to_swap_accounting, "jetsam snapshot has frozen_to_swap accounting",
1006 T_META_ENABLED(HAS_FREEZER),
1007 T_META_TAG_VM_NOT_PREFERRED) {
1008 static const size_t kSnapshotSleepDelay = 5;
1009 static const size_t kFreezeToDiskMaxDelay = 60;
1010
1011
1012 check_for_and_enable_freezer();
1013 test_after_background_helper_launches(true, "frozen_background", ^{
1014 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1015 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1016 /* Place the child in the idle band so that it gets elevated like a typical app. */
1017 move_to_idle_band(child_pid);
1018 freeze_process(child_pid);
1019 /*
1020 * Wait until the child's pages get paged out to disk.
1021 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
1022 * something is either wrong with the compactor or the accounting.
1023 */
1024 for (size_t i = 0; i < kFreezeToDiskMaxDelay / kSnapshotSleepDelay; i++) {
1025 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1026 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1027 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
1028 if (child_entry->jse_frozen_to_swap_pages > 0) {
1029 break;
1030 }
1031 free(snapshot);
1032 sleep(kSnapshotSleepDelay);
1033 }
1034 T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap");
1035 free(snapshot);
1036 /* Kill the child */
1037 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
1038 T_END;
1039 });
1040 dispatch_main();
1041 }
1042
1043 T_DECL(freezer_snapshot, "App kills are recorded in the freezer snapshot",
1044 T_META_ENABLED(HAS_FREEZER),
1045 T_META_TAG_VM_NOT_PREFERRED) {
1046 check_for_and_enable_freezer();
1047
1048 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1049 set_testing_pid();
1050
1051 test_after_background_helper_launches(false, "frozen_background", ^{
1052 int ret;
1053 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1054 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1055
1056 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1057 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1058
1059 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1060 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1061 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1062 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1063 T_QUIET; T_ASSERT_EQ(child_entry->killed, (unsigned long long) JETSAM_REASON_GENERIC, "Child entry was killed");
1064
1065 free(snapshot);
1066 T_END;
1067 });
1068 dispatch_main();
1069 }
1070
1071 T_DECL(freezer_snapshot_consume, "Freezer snapshot is consumed on read",
1072 T_META_ENABLED(HAS_FREEZER),
1073 T_META_TAG_VM_NOT_PREFERRED) {
1074 check_for_and_enable_freezer();
1075
1076 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1077 set_testing_pid();
1078
1079 test_after_background_helper_launches(false, "frozen_background", ^{
1080 int ret;
1081 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1082 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1083
1084 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1085 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1086
1087 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1088 T_ASSERT_NOTNULL(snapshot, "Got first freezer snapshot");
1089 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1090 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in first freezer snapshot");
1091
1092 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, true);
1093 if (snapshot != NULL) {
1094 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1095 T_QUIET; T_ASSERT_NULL(child_entry, "Child is not in second freezer snapshot");
1096 }
1097
1098 free(snapshot);
1099 T_END;
1100 });
1101 dispatch_main();
1102 }
1103
1104 T_DECL(freezer_snapshot_frozen_state, "Frozen state is recorded in freezer snapshot",
1105 T_META_ENABLED(HAS_FREEZER),
1106 T_META_TAG_VM_NOT_PREFERRED) {
1107 check_for_and_enable_freezer();
1108
1109 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1110 set_testing_pid();
1111
1112 test_after_background_helper_launches(false, "frozen_background", ^{
1113 int ret;
1114 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1115 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1116
1117 move_to_idle_band(child_pid);
1118 freeze_process(child_pid);
1119
1120 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1121 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1122
1123 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1124 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1125 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1126 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1127 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is set");
1128
1129 free(snapshot);
1130 T_END;
1131 });
1132 dispatch_main();
1133 }
1134
1135 T_DECL(freezer_snapshot_thaw_state, "Thaw count is recorded in freezer snapshot",
1136 T_META_ENABLED(HAS_FREEZER),
1137 T_META_TAG_VM_NOT_PREFERRED) {
1138 check_for_and_enable_freezer();
1139
1140 /* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1141 set_testing_pid();
1142
1143 test_after_background_helper_launches(false, "frozen_background", ^{
1144 int ret;
1145 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1146 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1147
1148 move_to_idle_band(child_pid);
1149 ret = pid_suspend(child_pid);
1150 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1151 freeze_process(child_pid);
1152 ret = pid_resume(child_pid);
1153 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1154
1155 ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1156 T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1157
1158 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1159 T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1160 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1161 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1162 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is still set after thaw");
1163 T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusWasThawed, "Child entry was thawed");
1164 T_QUIET; T_ASSERT_EQ(child_entry->jse_thaw_count, 1ULL, "Child entry's thaw count was incremented");
1165
1166 free(snapshot);
1167 T_END;
1168 });
1169 dispatch_main();
1170 }
1171
1172 T_HELPER_DECL(check_frozen, "Check frozen state", T_META_ASROOT(true)) {
1173 int kern_ret;
1174 dispatch_source_t ds_signal;
1175 __block int is_frozen;
1176 /* Set the process to freezable */
1177 kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
1178 T_QUIET; T_ASSERT_POSIX_SUCCESS(kern_ret, "set process is freezable");
1179
1180 /* We should not be frozen yet. */
1181 is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1182 if (is_frozen == -1) {
1183 T_LOG("memorystatus_control error: %s", strerror(errno));
1184 exit(MEMORYSTATUS_CONTROL_ERROR);
1185 }
1186 if (is_frozen) {
1187 exit(FROZEN_BIT_SET);
1188 }
1189
1190 ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1191 if (ds_signal == NULL) {
1192 exit(DISPATCH_SOURCE_CREATE_FAILED);
1193 }
1194
1195 dispatch_source_set_event_handler(ds_signal, ^{
1196 /* We should now be frozen. */
1197 is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1198 if (is_frozen == -1) {
1199 T_LOG("memorystatus_control error: %s", strerror(errno));
1200 exit(MEMORYSTATUS_CONTROL_ERROR);
1201 }
1202 if (!is_frozen) {
1203 exit(FROZEN_BIT_NOT_SET);
1204 }
1205 exit(SUCCESS);
1206 });
1207 dispatch_activate(ds_signal);
1208
1209 sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1210 T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1211
1212 /* Signal to our parent that we can be frozen */
1213 if (kill(getppid(), SIGUSR1) != 0) {
1214 T_LOG("Unable to signal to parent process!");
1215 exit(SIGNAL_TO_PARENT_FAILED);
1216 }
1217
1218 dispatch_main();
1219 }
1220
1221 T_DECL(memorystatus_get_process_is_frozen, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state",
1222 T_META_ENABLED(HAS_FREEZER),
1223 T_META_TAG_VM_NOT_PREFERRED) {
1224
1225 check_for_and_enable_freezer();
1226 test_after_background_helper_launches(true, "check_frozen", ^{
1227 int ret;
1228 /* Freeze the child, resume it, and signal it to check its state */
1229 move_to_idle_band(child_pid);
1230 ret = pid_suspend(child_pid);
1231 T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1232 freeze_process(child_pid);
1233 ret = pid_resume(child_pid);
1234 T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1235
1236 kill(child_pid, SIGUSR1);
1237 /* The child will checks its own frozen state & exit. */
1238 });
1239 dispatch_main();
1240 }
1241
1242 static unsigned int freeze_pages_min_old;
1243 static int throttle_enabled_old;
1244 static void
1245 cleanup_memorystatus_freeze_top_process(void)
1246 {
1247 sysctlbyname("kern.memorystatus_freeze_pages_min", NULL, NULL, &freeze_pages_min_old, sizeof(freeze_pages_min_old));
1248 sysctlbyname("kern.memorystatus_freeze_throttle_enabled", NULL, NULL, &throttle_enabled_old, sizeof(throttle_enabled_old));
1249 }
1250
1251 /*
1252 * Disables heuristics that could prevent us from freezing the child via memorystatus_freeze_top_process.
1253 */
1254 static void
1255 memorystatus_freeze_top_process_setup(void)
1256 {
1257 size_t freeze_pages_min_size = sizeof(freeze_pages_min_old);
1258 unsigned int freeze_pages_min_new = 0;
1259 size_t throttle_enabled_old_size = sizeof(throttle_enabled_old);
1260 int throttle_enabled_new = 1, ret;
1261
1262 ret = sysctlbyname("kern.memorystatus_freeze_pages_min", &freeze_pages_min_old, &freeze_pages_min_size, &freeze_pages_min_new, sizeof(freeze_pages_min_new));
1263 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_pages_min");
1264 ret = sysctlbyname("kern.memorystatus_freeze_throttle_enabled", &throttle_enabled_old, &throttle_enabled_old_size, &throttle_enabled_new, sizeof(throttle_enabled_new));
1265 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_throttle_enabled");
1266 T_ATEND(cleanup_memorystatus_freeze_top_process);
1267 /* Take ownership of the freezer probabilities for the duration of the test so that we don't race with dasd. */
1268 set_testing_pid();
1269 }
1270
1271 /*
1272 * Moves the proc to the idle band and suspends it.
1273 */
1274 static void
1275 prepare_proc_for_freezing(pid_t pid)
1276 {
1277 move_to_idle_band(pid);
1278 int ret = pid_suspend(pid);
1279 T_ASSERT_POSIX_SUCCESS(ret, "proc suspended");
1280 }
1281
1282 #define P_MEMSTAT_FROZEN 0x00000002
1283 static void
1284 verify_proc_frozen_state(pid_t pid, bool expected)
1285 {
1286 memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1287 memorystatus_jetsam_snapshot_entry_t *entry = get_jetsam_snapshot_entry(snapshot, pid);
1288 T_ASSERT_NOTNULL(entry, "%d is in snapshot", pid);
1289 bool is_frozen = (entry->state & P_MEMSTAT_FROZEN) != 0;
1290 if (is_frozen != expected) {
1291 T_LOG("%s frozen state is wrong. Expected %d, got %d. Skip reason: %d. Jetsam band: %d", entry->name, expected, is_frozen, entry->jse_freeze_skip_reason, entry->priority);
1292 }
1293 T_ASSERT_EQ(is_frozen, expected, "%s frozen state", entry->name);
1294 free(snapshot);
1295 }
1296
1297 static void
1298 verify_proc_is_frozen(pid_t pid)
1299 {
1300 verify_proc_frozen_state(pid, true);
1301 }
1302
1303 static void
1304 verify_proc_not_frozen(pid_t pid)
1305 {
1306 verify_proc_frozen_state(pid, false);
1307 }
1308
1309 T_DECL(memorystatus_freeze_top_process, "memorystatus_freeze_top_process chooses the correct process",
1310 T_META_ENABLED(HAS_FREEZER),
1311 T_META_ASROOT(true),
1312 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1313 T_META_TAG_VM_NOT_PREFERRED) {
1314 T_SKIP("Skipping flaky test"); // rdar://76986376
1315 int32_t memorystatus_freeze_band = 0;
1316 size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band);
1317 __block errno_t ret;
1318 __block int maxproc;
1319 size_t maxproc_size = sizeof(maxproc);
1320
1321 check_for_and_enable_freezer();
1322 ret = sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, NULL, 0);
1323 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.maxproc");
1324 ret = sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0);
1325 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_jetsam_band");
1326
1327 memorystatus_freeze_top_process_setup();
1328 test_after_background_helper_launches(true, "frozen_background", ^{
1329 int32_t child_band = JETSAM_PRIORITY_DEFAULT;
1330 prepare_proc_for_freezing(child_pid);
1331
1332 size_t buffer_len = sizeof(memorystatus_properties_entry_v1_t) * (size_t) maxproc;
1333 memorystatus_properties_entry_v1_t *properties_list = malloc(buffer_len);
1334 T_QUIET; T_ASSERT_NOTNULL(properties_list, "malloc properties array");
1335 size_t properties_list_len = 0;
1336 /* The child needs to age down into the idle band before it's eligible to be frozen. */
1337 T_LOG("Waiting for child to age into the idle band.");
1338 while (child_band != JETSAM_PRIORITY_IDLE) {
1339 memset(properties_list, 0, buffer_len);
1340 properties_list_len = 0;
1341 memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1342
1343 bool found = false;
1344 for (size_t i = 0; i < snapshot->entry_count; i++) {
1345 memorystatus_jetsam_snapshot_entry_t *snapshot_entry = &snapshot->entries[i];
1346 if (snapshot_entry->priority <= memorystatus_freeze_band && !snapshot_entry->killed) {
1347 pid_t pid = snapshot_entry->pid;
1348 memorystatus_properties_entry_v1_t *property_entry = &properties_list[properties_list_len++];
1349 property_entry->version = 1;
1350 property_entry->pid = pid;
1351 if (pid == child_pid) {
1352 found = true;
1353 property_entry->use_probability = 1;
1354 child_band = snapshot_entry->priority;
1355 } else {
1356 property_entry->use_probability = 0;
1357 }
1358 strncpy(property_entry->proc_name, snapshot_entry->name, MAXCOMLEN);
1359 property_entry->proc_name[MAXCOMLEN] = '\0';
1360 }
1361 }
1362 T_QUIET; T_ASSERT_TRUE(found, "Child is in on demand snapshot");
1363 free(snapshot);
1364 }
1365 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY, properties_list, sizeof(memorystatus_properties_entry_v1_t) * properties_list_len);
1366 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY");
1367 free(properties_list);
1368 int val = 1;
1369 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1370 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1371
1372 verify_proc_is_frozen(child_pid);
1373 resume_and_kill_proc(child_pid);
1374 T_END;
1375 });
1376 dispatch_main();
1377 }
1378 static unsigned int use_ordered_list_original;
1379 static unsigned int use_demotion_list_original;
1380 static void
1381 reset_ordered_freeze_mode(void)
1382 {
1383 sysctlbyname("kern.memorystatus_freezer_use_ordered_list", NULL, NULL, &use_ordered_list_original, sizeof(use_ordered_list_original));
1384 }
1385
1386 static void
1387 reset_ordered_demote_mode(void)
1388 {
1389 sysctlbyname("kern.memorystatus_freezer_use_demotion_list", NULL, NULL, &use_demotion_list_original, sizeof(use_demotion_list_original));
1390 }
1391
1392 static void
1393 enable_ordered_freeze_mode(void)
1394 {
1395 int ret;
1396 int val = 1;
1397 size_t size = sizeof(use_ordered_list_original);
1398 ret = sysctlbyname("kern.memorystatus_freezer_use_ordered_list", &use_ordered_list_original, &size, &val, sizeof(val));
1399 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freezer_use_ordered_list");
1400 T_ATEND(reset_ordered_freeze_mode);
1401 }
1402
1403 static void
1404 enable_ordered_demote_mode(void)
1405 {
1406 int ret;
1407 int val = 1;
1408 size_t size = sizeof(use_demotion_list_original);
1409 ret = sysctlbyname("kern.memorystatus_freezer_use_demotion_list", &use_demotion_list_original, &size, &val, sizeof(val));
1410 T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freezer_use_demotion_list");
1411 T_ATEND(reset_ordered_demote_mode);
1412 }
1413
1414 static void
1415 construct_child_freeze_entry(memorystatus_properties_freeze_entry_v1 *entry)
1416 {
1417 memset(entry, 0, sizeof(memorystatus_properties_freeze_entry_v1));
1418 entry->version = 1;
1419 entry->pid = child_pid;
1420 entry->priority = 1;
1421
1422 /* Get the child's name. */
1423 memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1424 memorystatus_jetsam_snapshot_entry_t *snapshot_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1425 strncpy(entry->proc_name, snapshot_entry->name, sizeof(entry->proc_name));
1426 free(snapshot);
1427 }
1428
1429 T_DECL(memorystatus_freeze_top_process_ordered, "memorystatus_freeze_top_process chooses the correct process when using an ordered list",
1430 T_META_ENABLED(HAS_FREEZER),
1431 T_META_ASROOT(true),
1432 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1433 T_META_TAG_VM_NOT_PREFERRED) {
1434 check_for_and_enable_freezer();
1435 memorystatus_freeze_top_process_setup();
1436 enable_ordered_freeze_mode();
1437 test_after_background_helper_launches(true, "frozen_background", ^{
1438 int ret, val = 1;
1439 memorystatus_properties_freeze_entry_v1 entries[1];
1440
1441 construct_child_freeze_entry(&entries[0]);
1442 prepare_proc_for_freezing(child_pid);
1443
1444 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1445 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1446 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1447
1448 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1449 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1450
1451 verify_proc_is_frozen(child_pid);
1452 resume_and_kill_proc(child_pid);
1453
1454 T_END;
1455 });
1456 dispatch_main();
1457 }
1458
1459 static void
1460 memorystatus_freeze_top_process_ordered_wrong_pid(pid_t (^pid_for_entry)(pid_t))
1461 {
1462 memorystatus_freeze_top_process_setup();
1463 enable_ordered_freeze_mode();
1464 test_after_background_helper_launches(true, "frozen_background", ^{
1465 int ret, val = 1;
1466 memorystatus_properties_freeze_entry_v1 entries[1];
1467
1468 construct_child_freeze_entry(&entries[0]);
1469 entries[0].pid = pid_for_entry(child_pid);
1470 prepare_proc_for_freezing(child_pid);
1471
1472 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1473 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1474 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1475
1476 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1477 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1478
1479 verify_proc_is_frozen(child_pid);
1480 resume_and_kill_proc(child_pid);
1481
1482 T_END;
1483 });
1484 dispatch_main();
1485 }
1486
1487 /*
1488 * Try both with a pid that's used by another process
1489 * and a pid that is likely unused.
1490 * In both cases the child should still get frozen.
1491 */
1492 T_DECL(memorystatus_freeze_top_process_ordered_reused_pid, "memorystatus_freeze_top_process is resilient to pid changes",
1493 T_META_ENABLED(HAS_FREEZER),
1494 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1495 T_META_ASROOT(true),
1496 T_META_TAG_VM_NOT_PREFERRED) {
1497 check_for_and_enable_freezer();
1498 memorystatus_freeze_top_process_ordered_wrong_pid(^(__unused pid_t child) {
1499 return 1;
1500 });
1501 }
1502
1503 T_DECL(memorystatus_freeze_top_process_ordered_wrong_pid, "memorystatus_freeze_top_process is resilient to pid changes",
1504 T_META_ENABLED(HAS_FREEZER),
1505 T_META_ASROOT(true),
1506 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1507 T_META_TAG_VM_NOT_PREFERRED) {
1508 check_for_and_enable_freezer();
1509 memorystatus_freeze_top_process_ordered_wrong_pid(^(__unused pid_t child) {
1510 return child + 1000;
1511 });
1512 }
1513
1514 T_DECL(memorystatus_freeze_demote_ordered, "memorystatus_demote_frozen_processes_using_demote_list chooses the correct process",
1515 T_META_ENABLED(HAS_FREEZER),
1516 T_META_ASROOT(true),
1517 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1518 T_META_TAG_VM_NOT_PREFERRED) {
1519 check_for_and_enable_freezer();
1520 memorystatus_freeze_top_process_setup();
1521 enable_ordered_freeze_mode();
1522 enable_ordered_demote_mode();
1523 test_after_background_helper_launches(true, "frozen_background", ^{
1524 int ret, val = 1;
1525 int32_t memorystatus_freeze_band = 0;
1526 size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band);
1527 memorystatus_jetsam_snapshot_t *snapshot = NULL;
1528 memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1529 memorystatus_properties_freeze_entry_v1 entries[1];
1530
1531 ret = sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0);
1532 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_jetsam_band");
1533
1534 construct_child_freeze_entry(&entries[0]);
1535 prepare_proc_for_freezing(child_pid);
1536
1537 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1538 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1539 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1540
1541 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1542 T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1543
1544 verify_proc_is_frozen(child_pid);
1545
1546 /*
1547 * Place the child at the head of the demotion list.
1548 */
1549 T_LOG("Telling kernel to demote %s", entries[0].proc_name);
1550 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_DEMOTE_PRIORITY, entries, sizeof(entries));
1551 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_DEMOTE_PRIORITY");
1552
1553 /* Resume the child */
1554 ret = pid_resume(child_pid);
1555 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1556
1557 /* Trigger a demotion check */
1558 val = 1;
1559 ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &val, sizeof(val));
1560 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
1561
1562 /* Verify that the child was demoted */
1563 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1564 child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1565 T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
1566 T_QUIET; T_ASSERT_LT(child_entry->priority, memorystatus_freeze_band, "child was demoted");
1567 free(snapshot);
1568
1569 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed process");
1570
1571 T_END;
1572 });
1573 dispatch_main();
1574 }
1575
1576 static int
1577 memorystatus_freezer_thaw_percentage(void)
1578 {
1579 int val;
1580 size_t size = sizeof(val);
1581 int ret = sysctlbyname("kern.memorystatus_freezer_thaw_percentage", &val, &size, NULL, 0);
1582 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freezer_thaw_percentage");
1583 return val;
1584 }
1585
1586 static void
1587 reset_interval(void)
1588 {
1589 uint32_t freeze_daily_budget_mb = 0;
1590 size_t size = sizeof(freeze_daily_budget_mb);
1591 int ret;
1592 uint64_t new_budget;
1593 ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &freeze_daily_budget_mb, &size, NULL, 0);
1594 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freeze_daily_mb_max");
1595 new_budget = (freeze_daily_budget_mb * (1UL << 20) / vm_page_size);
1596 ret = sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &new_budget, sizeof(new_budget));
1597 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to set kern.memorystatus_freeze_budget_pages_remaining");
1598 }
1599
1600 static pid_t second_child;
1601 static void
1602 cleanup_memorystatus_freezer_thaw_percentage(void)
1603 {
1604 kill(second_child, SIGKILL);
1605 }
1606
1607 T_DECL(memorystatus_freezer_thaw_percentage, "memorystatus_freezer_thaw_percentage updates correctly",
1608 T_META_ENABLED(HAS_FREEZER),
1609 T_META_ASROOT(true),
1610 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1611 T_META_TAG_VM_NOT_PREFERRED) {
1612 __block dispatch_source_t first_signal_block;
1613 check_for_and_enable_freezer();
1614 /* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1615 set_testing_pid();
1616 reset_interval();
1617
1618 /* Spawn one child that will remain frozen throughout the whole test & another that will be thawed. */
1619 first_signal_block = run_block_after_signal(SIGUSR1, ^{
1620 move_to_idle_band(second_child);
1621 __block int ret = pid_suspend(second_child);
1622 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1623 freeze_process(second_child);
1624 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage is still 0 after freeze");
1625 dispatch_source_cancel(first_signal_block);
1626 test_after_background_helper_launches(true, "frozen_background", ^{
1627 reset_interval();
1628 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a thaw percentage of 0");
1629 move_to_idle_band(child_pid);
1630 ret = pid_suspend(child_pid);
1631 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1632 freeze_process(child_pid);
1633 ret = pid_resume(child_pid);
1634 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1635 int percentage_after_thaw = memorystatus_freezer_thaw_percentage();
1636 T_QUIET; T_ASSERT_GT(percentage_after_thaw, 0, "thaw percentage is higher after thaw");
1637
1638 ret = pid_suspend(child_pid);
1639 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1640 freeze_process(child_pid);
1641 ret = pid_resume(child_pid);
1642 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1643 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), percentage_after_thaw, "thaw percentage is unchanged after second thaw");
1644
1645 ret = pid_suspend(child_pid);
1646 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1647 freeze_process(child_pid);
1648 reset_interval();
1649 T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a 0 thaw percentage");
1650 ret = pid_resume(child_pid);
1651 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1652 T_QUIET; T_ASSERT_GT(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage goes back up in new interval");
1653
1654 T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "failed to kill child");
1655 T_END;
1656 });
1657 });
1658
1659 second_child = launch_background_helper("frozen_background", false, true);
1660 T_ATEND(cleanup_memorystatus_freezer_thaw_percentage);
1661 dispatch_activate(first_signal_block);
1662 dispatch_main();
1663 }
1664
1665 static uint64_t
1666 get_budget_pages_remaining(void)
1667 {
1668 uint64_t pages_remaining = 0;
1669 size_t size = sizeof(pages_remaining);
1670 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", &pages_remaining, &size, NULL, 0),
1671 "get kern.memorystatus_freeze_budget_pages_remaining");
1672 return pages_remaining;
1673 }
1674
1675 static void
1676 set_budget_pages_remaining(uint64_t pages_remaining)
1677 {
1678 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &pages_remaining, sizeof(pages_remaining)),
1679 "get kern.memorystatus_freeze_budget_pages_remaining");
1680 }
1681
1682 static void
1683 set_freeze_enabled(int freeze_enabled)
1684 {
1685 size_t length = sizeof(freeze_enabled);
1686 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, length),
1687 "enable vm.freeze_enabled");
1688 }
1689
1690 static void
1691 enable_freeze(void)
1692 {
1693 set_freeze_enabled(1);
1694 }
1695
1696 T_DECL(memorystatus_freeze_budget_multiplier, "memorystatus_budget_multiplier multiplies budget",
1697 T_META_ASROOT(true),
1698 T_META_REQUIRES_SYSCTL_NE("kern.memorystatus_freeze_daily_mb_max", UINT32_MAX),
1699 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1700 T_META_ENABLED(HAS_FREEZER),
1701 T_META_ENABLED(false && HAS_FREEZER) /* rdar://87165483 */,
1702 T_META_TAG_VM_NOT_PREFERRED) {
1703 /* Disable freezer so that the budget doesn't change out from underneath us. */
1704 int freeze_enabled = 0;
1705 size_t length = sizeof(freeze_enabled);
1706 uint64_t freeze_daily_pages_max;
1707 check_for_and_enable_freezer();
1708 T_ATEND(enable_freeze);
1709 T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, length),
1710 "disable vm.freeze_enabled");
1711 freeze_daily_pages_max = get_freeze_daily_pages_max();
1712 original_budget_multiplier = get_budget_multiplier();
1713 T_ATEND(reset_budget_multiplier);
1714 set_budget_multiplier(100);
1715 T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max, "multiplier=100%%");
1716 set_budget_multiplier(50);
1717 T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max / 2, "multiplier=50%%");
1718 set_budget_multiplier(200);
1719 T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max * 2, "multiplier=200%%");
1720 }
1721
1722 T_DECL(memorystatus_freeze_set_dasd_trial_identifiers, "set dasd trial identifiers",
1723 T_META_ENABLED(HAS_FREEZER),
1724 T_META_ASROOT(true),
1725 T_META_TAG_VM_NOT_PREFERRED) {
1726 #define TEST_STR "freezer-das-trial"
1727 memorystatus_freezer_trial_identifiers_v1 identifiers = {0};
1728 identifiers.version = 1;
1729 check_for_and_enable_freezer();
1730 strncpy(identifiers.treatment_id, TEST_STR, sizeof(identifiers.treatment_id));
1731 strncpy(identifiers.experiment_id, TEST_STR, sizeof(identifiers.treatment_id));
1732 identifiers.deployment_id = 2;
1733 int ret = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_SET_DASD_TRIAL_IDENTIFIERS, &identifiers, sizeof(identifiers));
1734 T_WITH_ERRNO; T_ASSERT_EQ(ret, 0, "FREEZER_CONTROL_SET_DASD_TRIAL_IDENTIFIERS");
1735 }
1736
1737 T_DECL(memorystatus_reset_freezer_state, "FREEZER_CONTROL_RESET_STATE kills frozen proccesses",
1738 T_META_ENABLED(HAS_FREEZER),
1739 T_META_ASROOT(true),
1740 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1741 T_META_TAG_VM_NOT_PREFERRED) {
1742 check_for_and_enable_freezer();
1743 /* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1744 set_testing_pid();
1745 reset_interval();
1746
1747 test_after_background_helper_launches(false, "frozen_background", ^{
1748 proc_name_t name;
1749 int ret;
1750
1751 /* Freeze the child and verify they're frozen. */
1752 move_to_idle_band(child_pid);
1753 ret = pid_suspend(child_pid);
1754 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1755 freeze_process(child_pid);
1756 T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
1757 T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
1758 /* Set the budget to 0. */
1759 set_budget_pages_remaining(0);
1760
1761 /* FREEZER_CONTROL_RESET_STATE */
1762 ret = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_RESET_STATE, NULL, 0);
1763 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "FREEZER_CONRTOL_RESET_STATE");
1764
1765 /* Verify budget resets to a non-zero value. Some devices may have a configured
1766 * budget of 0. Skip this assertion if so. */
1767 uint64_t budget_after_reset = get_budget_pages_remaining();
1768 #if TARGET_OS_XR
1769 T_QUIET; T_ASSERT_EQ(budget_after_reset, 0ULL, "freeze budget after reset == 0");
1770 #else
1771 T_QUIET; T_ASSERT_GT(budget_after_reset, 0ULL, "freeze budget after reset > 0");
1772 #endif
1773 /*
1774 * Verify child has been killed
1775 * Note that the task termination isn't synchronous with the RESET_STATE call so we may
1776 * block in waitpid temporarily.
1777 */
1778 int stat;
1779 while (true) {
1780 pid_t wait_p = waitpid(child_pid, &stat, 0);
1781 if (wait_p == child_pid) {
1782 break;
1783 }
1784 }
1785 T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(stat), "child was signaled");
1786 T_QUIET; T_ASSERT_EQ(WTERMSIG(stat), SIGKILL, "Child received SIGKILL");
1787
1788 T_END;
1789 });
1790 dispatch_main();
1791 }
1792
1793 static void
1794 dock_proc(pid_t pid)
1795 {
1796 int ret;
1797 ret = memorystatus_control(MEMORYSTATUS_CMD_ELEVATED_INACTIVEJETSAMPRIORITY_ENABLE, pid, 0, NULL, 0);
1798 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dock_proc");
1799 }
1800
1801 T_DECL(memorystatus_freeze_skip_docked, "memorystatus_freeze_top_process does not freeze docked processes",
1802 T_META_ENABLED(HAS_FREEZER && !TARGET_OS_WATCH),
1803 T_META_ASROOT(true),
1804 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1805 T_META_TAG_VM_NOT_PREFERRED) {
1806 check_for_and_enable_freezer();
1807 memorystatus_freeze_top_process_setup();
1808 enable_ordered_freeze_mode();
1809 test_after_background_helper_launches(true, "frozen_background", ^{
1810 int ret, val = 1;
1811 memorystatus_properties_freeze_entry_v1 entries[1];
1812
1813 dock_proc(child_pid);
1814 construct_child_freeze_entry(&entries[0]);
1815 prepare_proc_for_freezing(child_pid);
1816
1817 T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1818 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1819 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1820
1821 ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1822 T_ASSERT_EQ(errno, ESRCH, "freeze_top_process errno");
1823 T_ASSERT_EQ(ret, -1, "freeze_top_process");
1824
1825 verify_proc_not_frozen(child_pid);
1826 resume_and_kill_proc(child_pid);
1827
1828 T_END;
1829 });
1830 dispatch_main();
1831 }
1832
1833 T_HELPER_DECL(corpse_generation, "Generate a large corpse", T_META_ASROOT(false)) {
1834 /*
1835 * Allocate and fault in a bunch of memory so that it takes a while
1836 * to generate our corpse.
1837 */
1838 size_t bytes_to_allocate = 304 * (1UL << 20);
1839 size_t block_size = 8 * (1UL << 20);
1840 for (size_t i = 0; i < bytes_to_allocate / block_size; i++) {
1841 unsigned char *ptr = malloc(block_size);
1842 if (ptr == NULL) {
1843 T_LOG("Unable to allocate memory in child process!");
1844 exit(UNABLE_TO_ALLOCATE);
1845 }
1846 for (size_t j = 0; j < block_size / vm_page_size; j++) {
1847 *ptr = (unsigned char) j;
1848 ptr += vm_page_size;
1849 }
1850 }
1851 dispatch_source_t ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1852 if (ds_signal == NULL) {
1853 T_LOG("Unable to create dispatch source");
1854 exit(DISPATCH_SOURCE_CREATE_FAILED);
1855 }
1856 dispatch_source_set_event_handler(ds_signal, ^{
1857 uint64_t val = 1;
1858 /*
1859 * We should now be frozen.
1860 * Simulate a crash so that our P_MEMSTAT_SKIP bit gets set temporarily.
1861 * The parent process will try to kill us due to disk space shortage in parallel.
1862 */
1863 os_fault_with_payload(OS_REASON_LIBSYSTEM, OS_REASON_LIBSYSTEM_CODE_FAULT,
1864 &val, sizeof(val), "freeze_test", 0);
1865 });
1866 dispatch_activate(ds_signal);
1867
1868 sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1869 T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1870
1871 /* Signal to our parent that we can be frozen */
1872 if (kill(getppid(), SIGUSR1) != 0) {
1873 T_LOG("Unable to signal to parent process!");
1874 exit(SIGNAL_TO_PARENT_FAILED);
1875 }
1876 dispatch_main();
1877 }
1878
1879 T_DECL(memorystatus_disable_freeze_corpse, "memorystatus_disable_freeze with parallel corpse creation",
1880 T_META_ENABLED(HAS_FREEZER),
1881 T_META_ASROOT(true),
1882 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1883 T_META_TAG_VM_NOT_PREFERRED) {
1884 /*
1885 * In the past, we've had race conditions w.r.t. killing on disk space shortage
1886 * and corpse generation of frozen processes.
1887 * This test spwans a frozen helper
1888 * which generates a large corpse (which should take in the 10s to 100s of m.s. to complete)
1889 * while the test process triggers disk space kills.
1890 * We should see that the test process is jetsammed successfully.
1891 */
1892 check_for_and_enable_freezer();
1893 test_after_background_helper_launches(false, "corpse_generation", ^{
1894 int ret, val, stat;
1895 /* Place the child in the idle band so that it gets elevated like a typical app. */
1896 move_to_idle_band(child_pid);
1897 ret = pid_suspend(child_pid);
1898 T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1899 freeze_process(child_pid);
1900
1901 ret = pid_resume(child_pid);
1902 T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1903
1904 kill(child_pid, SIGUSR1);
1905
1906 T_ATEND(enable_freeze);
1907 val = 0;
1908 ret = sysctlbyname("vm.freeze_enabled", NULL, NULL, &val, sizeof(val));
1909 T_ASSERT_POSIX_SUCCESS(ret, "freeze disabled");
1910 /*
1911 * Verify child has been killed
1912 * Note that the task termination isn't synchronous with the freeze_enabled call so we may
1913 * block in waitpid temporarily.
1914 */
1915 while (true) {
1916 pid_t wait_p = waitpid(child_pid, &stat, 0);
1917 if (wait_p == child_pid) {
1918 break;
1919 }
1920 }
1921 T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(stat), "child was signaled");
1922 T_QUIET; T_ASSERT_EQ(WTERMSIG(stat), SIGKILL, "Child received SIGKILL");
1923
1924 T_END;
1925 });
1926 dispatch_main();
1927 }
1928
1929 static int original_unrestrict_coalitions_val;
1930
1931 static void
1932 unrestrict_coalitions()
1933 {
1934 int ret, val = 1;
1935 size_t val_size = sizeof(val);
1936 size_t original_unrestrict_coalitions_size = sizeof(original_unrestrict_coalitions_val);
1937 ret = sysctlbyname("kern.unrestrict_coalitions", &original_unrestrict_coalitions_val, &original_unrestrict_coalitions_size, &val, val_size);
1938 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "unrestrict_coalitions");
1939 }
1940
1941 static void
1942 reset_unrestrict_coalitions()
1943 {
1944 size_t size = sizeof(original_unrestrict_coalitions_val);
1945 int ret = sysctlbyname("kern.unrestrict_coalitions", NULL, NULL, &original_unrestrict_coalitions_val, size);
1946 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "unrestrict_coalitions");
1947 }
1948
1949 static uint64_t
1950 create_coalition(int type)
1951 {
1952 uint64_t id = 0;
1953 uint32_t flags = 0;
1954 uint64_t param[2];
1955 int ret;
1956
1957 COALITION_CREATE_FLAGS_SET_TYPE(flags, type);
1958 ret = coalition_create(&id, flags);
1959 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "coalition_create");
1960 T_QUIET; T_ASSERT_GE(id, 0ULL, "coalition_create returned a valid id");
1961
1962 /* disable notifications for this coalition so launchd doesn't freak out */
1963 param[0] = id;
1964 param[1] = 0;
1965 ret = sysctlbyname("kern.coalition_notify", NULL, NULL, param, sizeof(param));
1966 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.coalition_notify");
1967
1968 return id;
1969 }
1970
1971 static uint64_t jetsam_coal, resource_coal;
1972
1973 #define COAL_MAX_MEMBERS 50
1974 #define MAX_XPC_SERVICE_FREEZE 10 /* xnu will only freeze 10 XPC services per coalition. */
1975 int n_coalition_members;
1976 pid_t coalition_members[COAL_MAX_MEMBERS];
1977
1978 /*
1979 * Spawns the given command as the leader of the given coalitions.
1980 * Process will start in a stopped state (waiting for SIGCONT)
1981 */
1982 static pid_t
1983 spawn_coalition_member(const char *path, char *const *argv, int role, short spawn_flags)
1984 {
1985 int ret;
1986 posix_spawnattr_t attr;
1987 extern char **environ;
1988 pid_t new_pid = 0;
1989 kern_return_t kr;
1990
1991 ret = posix_spawnattr_init(&attr);
1992 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init failed with %s", strerror(ret));
1993
1994 ret = posix_spawnattr_setcoalition_np(&attr, jetsam_coal,
1995 COALITION_TYPE_JETSAM, role);
1996 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setcoalition_np failed with %s", strerror(ret));
1997 ret = posix_spawnattr_setcoalition_np(&attr, resource_coal,
1998 COALITION_TYPE_RESOURCE, role);
1999 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setcoalition_np failed with %s", strerror(ret));
2000
2001 ret = posix_spawnattr_setflags(&attr, spawn_flags);
2002 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setflags failed with %s", strerror(ret));
2003
2004 ret = posix_spawn(&new_pid, path, NULL, &attr, argv, environ);
2005 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn failed with %s", strerror(ret));
2006
2007 ret = posix_spawnattr_destroy(&attr);
2008 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_destroy failed with %s\n", strerror(ret));
2009
2010 kr = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, new_pid, 1, NULL, 0);
2011 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
2012
2013 return new_pid;
2014 }
2015
2016 static pid_t
2017 get_coalition_leader(pid_t p)
2018 {
2019 static const size_t kMaxPids = 500;
2020 int ret;
2021 int pid_list[kMaxPids];
2022 size_t pid_list_size = sizeof(pid_list);
2023
2024 int iparam[3];
2025 #define p_type iparam[0]
2026 #define p_order iparam[1]
2027 #define p_pid iparam[2]
2028 p_type = COALITION_TYPE_JETSAM;
2029 p_order = COALITION_SORT_DEFAULT;
2030 p_pid = p;
2031
2032 ret = sysctlbyname("kern.coalition_pid_list", pid_list, &pid_list_size, iparam, sizeof(iparam));
2033 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.coalition_pid_list");
2034 T_QUIET; T_ASSERT_LE(pid_list_size, kMaxPids * sizeof(int), "coalition is small enough");
2035
2036 for (size_t i = 0; i < pid_list_size / sizeof(int); i++) {
2037 int curr_pid = pid_list[i];
2038 int roles[COALITION_NUM_TYPES] = {};
2039 size_t roles_size = sizeof(roles);
2040
2041 ret = sysctlbyname("kern.coalition_roles", roles, &roles_size, &curr_pid, sizeof(curr_pid));
2042 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.coalition_roles");
2043 if (roles[COALITION_TYPE_JETSAM] == COALITION_TASKROLE_LEADER) {
2044 return curr_pid;
2045 }
2046 }
2047
2048 T_FAIL("No leader in coalition!");
2049 return 0;
2050 }
2051
2052 void
2053 setup_coalitions() {
2054 }
2055
2056 void
2057 coal_foreach(void (^callback)(pid_t)) {
2058 for (int i = 0; i < n_coalition_members; i++) {
2059 callback(coalition_members[i]);
2060 }
2061 }
2062
2063 void
2064 kill_all_coalition_members() {
2065 int __block ret;
2066 T_LOG("Killing coalition members...");
2067 coal_foreach(^(pid_t pid){
2068 ret = kill(pid, SIGKILL);
2069 T_QUIET; T_EXPECT_POSIX_SUCCESS(ret, "kill");
2070 });
2071 }
2072
2073 void
2074 spawn_coalition_and_run(dispatch_block_t after_spawn) {
2075 int ret, __block i = 0;
2076 static uint32_t path_size;
2077 static char pathbuf[PATH_MAX];
2078 dispatch_source_t sig_source;
2079 pid_t leader_pid;
2080
2081 /* Setup coalitions */
2082 jetsam_coal = create_coalition(COALITION_TYPE_JETSAM);
2083 resource_coal = create_coalition(COALITION_TYPE_RESOURCE);
2084
2085 path_size = sizeof(pathbuf);
2086 ret = _NSGetExecutablePath(pathbuf, &path_size);
2087 T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
2088 static char *const args[] = {
2089 pathbuf,
2090 "-n",
2091 "frozen_background",
2092 NULL
2093 };
2094
2095 T_ATEND(kill_all_coalition_members);
2096
2097 sig_source = run_block_after_signal(SIGUSR1, ^{
2098 i++;
2099 if (i < n_coalition_members) {
2100 /* Spawn next child */
2101 coalition_members[i] = spawn_coalition_member(pathbuf, args, COALITION_TASKROLE_XPC, 0);
2102 } else {
2103 after_spawn();
2104 }
2105 });
2106 dispatch_activate(sig_source);
2107
2108 /* Spawn leader */
2109 child_pid = spawn_coalition_member(pathbuf, args, COALITION_TASKROLE_LEADER, 0);
2110 coalition_members[0] = child_pid;
2111
2112 /* Double-check the coalition was set up correctly */
2113 leader_pid = get_coalition_leader(child_pid);
2114 T_ASSERT_EQ(child_pid, leader_pid, "Child is leader of coalition");
2115 }
2116
2117 static void
2118 kill_all_frozen()
2119 {
2120 /* Disabling and re-enabling the freezer will evict all frozen processes. */
2121 set_freeze_enabled(0);
2122 set_freeze_enabled(1);
2123 }
2124
2125 static unsigned int
2126 get_used_freezer_slots ()
2127 {
2128 int ret;
2129 unsigned int frozen_procs;
2130 size_t frozen_procs_size = sizeof(frozen_procs);
2131
2132 ret = sysctlbyname("kern.memorystatus_freeze_count", &frozen_procs, &frozen_procs_size, NULL, 0);
2133 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "get kern.memorystatus_freeze_count");
2134
2135 return frozen_procs;
2136 }
2137
2138 static void
2139 setup_coalition_freezing()
2140 {
2141
2142 /* Setup freezer. Set ordered freeze mode and kill all frozen procs. */
2143 memorystatus_freeze_top_process_setup();
2144 enable_ordered_freeze_mode();
2145 }
2146
2147 static uint32_t
2148 get_max_freezer_slots()
2149 {
2150 int ret;
2151 uint32_t max_freeze_processes = 0;
2152 size_t max_freeze_processes_len = sizeof(max_freeze_processes);
2153 ret = sysctlbyname("kern.memorystatus_freeze_processes_max", &max_freeze_processes, &max_freeze_processes_len, NULL, 0);
2154 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_processes_max");
2155 return max_freeze_processes;
2156 }
2157
2158 static int orig_freezer_slots = -1;
2159 static void restore_freezer_slots(void);
2160
2161 static void
2162 set_max_freezer_slots(uint32_t max_freeze_processes)
2163 {
2164 int ret;
2165 if (orig_freezer_slots == -1) {
2166 orig_freezer_slots = get_max_freezer_slots();
2167 T_ATEND(restore_freezer_slots);
2168 }
2169 ret = sysctlbyname("kern.memorystatus_freeze_processes_max", NULL, NULL, &max_freeze_processes, sizeof(max_freeze_processes));
2170 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_processes_max");
2171 }
2172
2173 static void
2174 restore_freezer_slots(void)
2175 {
2176 set_max_freezer_slots(orig_freezer_slots);
2177 }
2178
2179 static void
2180 prepare_coalition_for_freezing(void)
2181 {
2182 coal_foreach(^(pid_t pid){
2183 prepare_proc_for_freezing(pid);
2184 });
2185 }
2186
2187 static int
2188 freeze_coalition_leader(void)
2189 {
2190 int ret, val = 1;
2191 memorystatus_properties_freeze_entry_v1 entry;
2192 construct_child_freeze_entry(&entry);
2193 ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, &entry, sizeof(entry));
2194 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
2195 return sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
2196
2197 }
2198
2199 #define TEST_MAX_FREEZER_SLOTS 8
2200 #define N_UNFROZEN_MEMBERS 3
2201
2202 T_DECL(memorystatus_coalition_freeze, "Freezing a coalition leader should freeze all of its XPC service members",
2203 T_META_ENABLED(HAS_FREEZER),
2204 T_META_ASROOT(true),
2205 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
2206 T_META_TAG_VM_NOT_PREFERRED)
2207 {
2208 check_for_and_enable_freezer();
2209 unrestrict_coalitions();
2210 T_ATEND(reset_unrestrict_coalitions);
2211 setup_coalition_freezing();
2212 set_max_freezer_slots(TEST_MAX_FREEZER_SLOTS);
2213 kill_all_frozen();
2214 T_QUIET; T_ASSERT_EQ(get_used_freezer_slots(), 0, "No freezer slots used");
2215
2216 /* Spawn max freezer slots coalition members */
2217 n_coalition_members = TEST_MAX_FREEZER_SLOTS;
2218 static_assert(TEST_MAX_FREEZER_SLOTS <= COAL_MAX_MEMBERS);
2219 static_assert(TEST_MAX_FREEZER_SLOTS < MAX_XPC_SERVICE_FREEZE);
2220
2221 /* Create our coalitions and spawn the leader / "XPC service" members */
2222 spawn_coalition_and_run(^{
2223 int i, ret, n_coal_frozen = 0;
2224 memorystatus_jetsam_snapshot_t *snapshot;
2225 memorystatus_jetsam_snapshot_entry_t *entry;
2226
2227 /* Freeze coalition */
2228 prepare_coalition_for_freezing();
2229 ret = freeze_coalition_leader();
2230 T_EXPECT_POSIX_SUCCESS(ret, "freeze_coalition_leader");
2231
2232 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
2233 for (i = 0; i < n_coalition_members; i++) {
2234 entry = get_jetsam_snapshot_entry(snapshot, coalition_members[i]);
2235 if (entry->state & P_MEMSTAT_FROZEN) {
2236 n_coal_frozen++;
2237 }
2238 }
2239 T_ASSERT_EQ(n_coal_frozen, n_coalition_members, "All coalition members frozen");
2240
2241 T_END;
2242 });
2243 dispatch_main();
2244 }
2245
2246 T_DECL(memorystatus_coalition_freezer_slot_limit, "Exhausting freezer slots and freezing a large coalition should not exceed slots",
2247 T_META_ENABLED(HAS_FREEZER),
2248 T_META_ASROOT(true),
2249 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
2250 T_META_TAG_VM_NOT_PREFERRED)
2251 {
2252 check_for_and_enable_freezer();
2253 unrestrict_coalitions();
2254 T_ATEND(reset_unrestrict_coalitions);
2255 setup_coalition_freezing();
2256 set_max_freezer_slots(TEST_MAX_FREEZER_SLOTS);
2257 kill_all_frozen();
2258 T_QUIET; T_ASSERT_EQ(get_used_freezer_slots(), 0, "No freezer slots used");
2259
2260 /* Spawn max freezer slots + 1 coalition members */
2261 n_coalition_members = TEST_MAX_FREEZER_SLOTS + N_UNFROZEN_MEMBERS;
2262 static_assert(TEST_MAX_FREEZER_SLOTS + N_UNFROZEN_MEMBERS <= COAL_MAX_MEMBERS);
2263 static_assert(TEST_MAX_FREEZER_SLOTS < MAX_XPC_SERVICE_FREEZE);
2264
2265 /* Create our coalitions and spawn the leader / "XPC service" members */
2266 spawn_coalition_and_run(^{
2267 int i, j, ret, n_coal_frozen = 0;
2268 memorystatus_jetsam_snapshot_t *snapshot;
2269 memorystatus_jetsam_snapshot_entry_t *entry;
2270
2271 /* Freeze coalition */
2272 prepare_coalition_for_freezing();
2273 ret = freeze_coalition_leader();
2274 T_EXPECT_POSIX_SUCCESS(ret, "freeze_coalition_leader");
2275
2276 /* Ensure coalition leader is frozen */
2277 verify_proc_is_frozen(coalition_members[0]);
2278
2279 /* Make sure freezer did not exceed its slots */
2280 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
2281 for (i = 0; i < n_coalition_members; i++) {
2282 entry = get_jetsam_snapshot_entry(snapshot, coalition_members[i]);
2283 if (entry->state & P_MEMSTAT_FROZEN) {
2284 n_coal_frozen++;
2285 }
2286 }
2287 T_ASSERT_EQ(n_coal_frozen, n_coalition_members - N_UNFROZEN_MEMBERS, "N_UNFROZEN_MEMBERS coalition members remain unfrozen");
2288
2289 T_END;
2290 });
2291 dispatch_main();
2292 }
2293
2294 pid_t helper_pid;
2295 static void
2296 kill_helper()
2297 {
2298 int ret = kill(helper_pid, SIGUSR2);
2299 T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kill(helper_pid, SIGUSR2)");
2300 }
2301
2302 T_DECL(memorystatus_two_coalition_freeze, "Exhausting freezer slots with one coalition and freezing another should fail",
2303 T_META_ENABLED(HAS_FREEZER),
2304 T_META_ASROOT(true),
2305 T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
2306 T_META_TAG_VM_NOT_PREFERRED)
2307 {
2308 dispatch_source_t sig_disp, exit_disp;
2309
2310 check_for_and_enable_freezer();
2311 unrestrict_coalitions();
2312 T_ATEND(reset_unrestrict_coalitions);
2313 setup_coalition_freezing();
2314 set_max_freezer_slots(TEST_MAX_FREEZER_SLOTS);
2315 kill_all_frozen();
2316 T_QUIET; T_ASSERT_EQ(get_used_freezer_slots(), 0, "No freezer slots used");
2317
2318 /* Spawn max freezer slots coalition members */
2319 n_coalition_members = TEST_MAX_FREEZER_SLOTS;
2320 static_assert(TEST_MAX_FREEZER_SLOTS <= COAL_MAX_MEMBERS);
2321 static_assert(TEST_MAX_FREEZER_SLOTS < MAX_XPC_SERVICE_FREEZE);
2322
2323 sig_disp = run_block_after_signal(SIGUSR2, ^{
2324 /* After our child signals, we can try spawning and freezing our coalition */
2325 spawn_coalition_and_run(^{
2326 int i, j, ret, n_coal_frozen = 0;
2327 memorystatus_jetsam_snapshot_t *snapshot;
2328 memorystatus_jetsam_snapshot_entry_t *entry;
2329
2330 /* Freeze coalition */
2331 prepare_coalition_for_freezing();
2332 ret = freeze_coalition_leader();
2333 T_EXPECT_POSIX_FAILURE(ret, ESRCH, "freeze_coalition_leader");
2334
2335 /* Make sure freezer did not freeze anyone */
2336 snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
2337 for (i = 0; i < n_coalition_members; i++) {
2338 entry = get_jetsam_snapshot_entry(snapshot, coalition_members[i]);
2339 if (entry->state & P_MEMSTAT_FROZEN) {
2340 n_coal_frozen++;
2341 }
2342 }
2343
2344 T_ASSERT_EQ(n_coal_frozen, 0, "all coalition members remain unfrozen");
2345
2346 T_END;
2347 });
2348 });
2349 dispatch_activate(sig_disp);
2350
2351
2352 /* Spawn helper and wait for it to spawn its coalition and freeze it */
2353 helper_pid = launch_background_helper("coalition_freezer", false, false);
2354 T_ATEND(kill_helper);
2355
2356 exit_disp = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)helper_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
2357 dispatch_source_set_event_handler(exit_disp, ^{
2358 int status = 0, code = 0;
2359 pid_t rc = waitpid(child_pid, &status, 0);
2360 T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
2361 code = WEXITSTATUS(status);
2362 if (code != 0) {
2363 T_LOG("Helper exited with error: %s", exit_codes_str[code]);
2364 }
2365 T_QUIET; T_ASSERT_EQ(code, 0, "Helper exited cleanly");
2366 });
2367 dispatch_activate(exit_disp);
2368
2369 dispatch_main();
2370 }
2371
2372 T_HELPER_DECL(coalition_freezer, "Spawns a coalition and freezes it",
2373 T_META_ASROOT(true))
2374 {
2375 dispatch_source_t sig_disp;
2376
2377 /* The parent will have already set up the freezer for us. */
2378 n_coalition_members = TEST_MAX_FREEZER_SLOTS;
2379 spawn_coalition_and_run(^{
2380 prepare_coalition_for_freezing();
2381 freeze_coalition_leader();
2382 kill(getppid(), SIGUSR2);
2383 });
2384
2385 sig_disp = run_block_after_signal(SIGUSR2, ^{
2386 kill_all_coalition_members();
2387 exit(0);
2388 });
2389 dispatch_activate(sig_disp);
2390
2391 dispatch_main();
2392 }
2393