xref: /xnu-8019.80.24/tests/memorystatus_freeze_test.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
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 
15 #ifdef T_NAMESPACE
16 #undef T_NAMESPACE
17 #endif
18 #include <darwintest.h>
19 #include <darwintest_utils.h>
20 
21 #include "memorystatus_assertion_helpers.h"
22 
23 T_GLOBAL_META(
24 	T_META_NAMESPACE("xnu.vm"),
25 	T_META_RADAR_COMPONENT_NAME("xnu"),
26 	T_META_RADAR_COMPONENT_VERSION("VM"),
27 	T_META_CHECK_LEAKS(false)
28 	);
29 
30 #define MEM_SIZE_MB                     10
31 #define NUM_ITERATIONS          5
32 #define FREEZE_PAGES_MAX 256
33 
34 #define CREATE_LIST(X) \
35 	X(SUCCESS) \
36 	X(TOO_FEW_ARGUMENTS) \
37 	X(SYSCTL_VM_PAGESIZE_FAILED) \
38 	X(VM_PAGESIZE_IS_ZERO) \
39 	X(DISPATCH_SOURCE_CREATE_FAILED) \
40 	X(INITIAL_SIGNAL_TO_PARENT_FAILED) \
41 	X(SIGNAL_TO_PARENT_FAILED) \
42 	X(MEMORYSTATUS_CONTROL_FAILED) \
43 	X(IS_FREEZABLE_NOT_AS_EXPECTED) \
44 	X(MEMSTAT_PRIORITY_CHANGE_FAILED) \
45 	X(INVALID_ALLOCATE_PAGES_ARGUMENTS) \
46 	X(FROZEN_BIT_SET) \
47 	X(FROZEN_BIT_NOT_SET) \
48 	X(MEMORYSTATUS_CONTROL_ERROR) \
49 	X(UNABLE_TO_ALLOCATE) \
50 	X(EXIT_CODE_MAX) \
51 
52 #define EXIT_CODES_ENUM(VAR) VAR,
53 enum exit_codes_num {
54 	CREATE_LIST(EXIT_CODES_ENUM)
55 };
56 
57 #define EXIT_CODES_STRING(VAR) #VAR,
58 static const char *exit_codes_str[] = {
59 	CREATE_LIST(EXIT_CODES_STRING)
60 };
61 
62 static int
get_vmpage_size()63 get_vmpage_size()
64 {
65 	int vmpage_size;
66 	size_t size = sizeof(vmpage_size);
67 	int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0);
68 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize");
69 	T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0");
70 	return vmpage_size;
71 }
72 
73 static pid_t child_pid = -1;
74 static int freeze_count = 0;
75 
76 void move_to_idle_band(pid_t);
77 void run_freezer_test(int);
78 void freeze_helper_process(void);
79 /* Gets and optionally sets the freeze pages max threshold */
80 int sysctl_freeze_pages_max(int* new_value);
81 
82 /* NB: in_shared_region and get_rprvt are pulled from the memorystatus unit test.
83  * We're moving away from those unit tests, so they're copied here.
84  */
85 
86 /* Cribbed from 'top'... */
87 static int
in_shared_region(mach_vm_address_t addr,cpu_type_t type)88 in_shared_region(mach_vm_address_t addr, cpu_type_t type)
89 {
90 	mach_vm_address_t base = 0, size = 0;
91 
92 	switch (type) {
93 	case CPU_TYPE_ARM:
94 		base = SHARED_REGION_BASE_ARM;
95 		size = SHARED_REGION_SIZE_ARM;
96 		break;
97 
98 	case CPU_TYPE_ARM64:
99 		base = SHARED_REGION_BASE_ARM64;
100 		size = SHARED_REGION_SIZE_ARM64;
101 		break;
102 
103 
104 	case CPU_TYPE_X86_64:
105 		base = SHARED_REGION_BASE_X86_64;
106 		size = SHARED_REGION_SIZE_X86_64;
107 		break;
108 
109 	case CPU_TYPE_I386:
110 		base = SHARED_REGION_BASE_I386;
111 		size = SHARED_REGION_SIZE_I386;
112 		break;
113 
114 	case CPU_TYPE_POWERPC:
115 		base = SHARED_REGION_BASE_PPC;
116 		size = SHARED_REGION_SIZE_PPC;
117 		break;
118 
119 	case CPU_TYPE_POWERPC64:
120 		base = SHARED_REGION_BASE_PPC64;
121 		size = SHARED_REGION_SIZE_PPC64;
122 		break;
123 
124 	default: {
125 		int t = type;
126 
127 		fprintf(stderr, "unknown CPU type: 0x%x\n", t);
128 		abort();
129 	}
130 	}
131 
132 	return addr >= base && addr < (base + size);
133 }
134 
135 /* Get the resident private memory of the given pid */
136 static unsigned long long
get_rprvt(pid_t pid)137 get_rprvt(pid_t pid)
138 {
139 	mach_port_name_t task;
140 	kern_return_t kr;
141 
142 	mach_vm_size_t rprvt = 0;
143 	mach_vm_size_t empty = 0;
144 	mach_vm_size_t fw_private = 0;
145 	mach_vm_size_t pagesize = vm_kernel_page_size;  // The vm_region page info is reported
146 	                                                // in terms of vm_kernel_page_size.
147 	mach_vm_size_t regs = 0;
148 
149 	mach_vm_address_t addr;
150 	mach_vm_size_t size;
151 
152 	int split = 0;
153 
154 	kr = task_for_pid(mach_task_self(), pid, &task);
155 	T_QUIET; T_ASSERT_TRUE(kr == KERN_SUCCESS, "Unable to get task_for_pid of child");
156 
157 	for (addr = 0;; addr += size) {
158 		vm_region_top_info_data_t info;
159 		mach_msg_type_number_t count = VM_REGION_TOP_INFO_COUNT;
160 		mach_port_t object_name;
161 
162 		kr = mach_vm_region(task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, &count, &object_name);
163 		if (kr != KERN_SUCCESS) {
164 			break;
165 		}
166 
167 #if   defined (__arm64__)
168 		if (in_shared_region(addr, CPU_TYPE_ARM64)) {
169 #else
170 		if (in_shared_region(addr, CPU_TYPE_ARM)) {
171 #endif
172 			// Private Shared
173 			fw_private += info.private_pages_resident * pagesize;
174 
175 			/*
176 			 * Check if this process has the globally shared
177 			 * text and data regions mapped in.  If so, set
178 			 * split to TRUE and avoid checking
179 			 * again.
180 			 */
181 			if (split == FALSE && info.share_mode == SM_EMPTY) {
182 				vm_region_basic_info_data_64_t  b_info;
183 				mach_vm_address_t b_addr = addr;
184 				mach_vm_size_t b_size = size;
185 				count = VM_REGION_BASIC_INFO_COUNT_64;
186 
187 				kr = mach_vm_region(task, &b_addr, &b_size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&b_info, &count, &object_name);
188 				if (kr != KERN_SUCCESS) {
189 					break;
190 				}
191 
192 				if (b_info.reserved) {
193 					split = TRUE;
194 				}
195 			}
196 
197 			/*
198 			 * Short circuit the loop if this isn't a shared
199 			 * private region, since that's the only region
200 			 * type we care about within the current address
201 			 * range.
202 			 */
203 			if (info.share_mode != SM_PRIVATE) {
204 				continue;
205 			}
206 		}
207 
208 		regs++;
209 
210 		/*
211 		 * Update counters according to the region type.
212 		 */
213 
214 		if (info.share_mode == SM_COW && info.ref_count == 1) {
215 			// Treat single reference SM_COW as SM_PRIVATE
216 			info.share_mode = SM_PRIVATE;
217 		}
218 
219 		switch (info.share_mode) {
220 		case SM_LARGE_PAGE:
221 		// Treat SM_LARGE_PAGE the same as SM_PRIVATE
222 		// since they are not shareable and are wired.
223 		case SM_PRIVATE:
224 			rprvt += info.private_pages_resident * pagesize;
225 			rprvt += info.shared_pages_resident * pagesize;
226 			break;
227 
228 		case SM_EMPTY:
229 			empty += size;
230 			break;
231 
232 		case SM_COW:
233 		case SM_SHARED:
234 			if (pid == 0) {
235 				// Treat kernel_task specially
236 				if (info.share_mode == SM_COW) {
237 					rprvt += info.private_pages_resident * pagesize;
238 				}
239 				break;
240 			}
241 
242 			if (info.share_mode == SM_COW) {
243 				rprvt += info.private_pages_resident * pagesize;
244 			}
245 			break;
246 
247 		default:
248 			assert(0);
249 			break;
250 		}
251 	}
252 
253 	return rprvt;
254 }
255 
256 void
257 move_to_idle_band(pid_t pid)
258 {
259 	memorystatus_priority_properties_t props;
260 	/*
261 	 * Freezing a process also moves it to an elevated jetsam band in order to protect it from idle exits.
262 	 * So we move the child process to the idle band to mirror the typical 'idle app being frozen' scenario.
263 	 */
264 	props.priority = JETSAM_PRIORITY_IDLE;
265 	props.user_data = 0;
266 
267 	/*
268 	 * This requires us to run as root (in the absence of entitlement).
269 	 * Hence the T_META_ASROOT(true) in the T_HELPER_DECL.
270 	 */
271 	if (memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props))) {
272 		exit(MEMSTAT_PRIORITY_CHANGE_FAILED);
273 	}
274 }
275 
276 void
277 freeze_helper_process(void)
278 {
279 	size_t length;
280 	int ret, freeze_enabled, errno_freeze_sysctl;
281 	uint64_t resident_memory_before, resident_memory_after, vmpage_size;
282 	vmpage_size = (uint64_t) get_vmpage_size();
283 	resident_memory_before = get_rprvt(child_pid) / vmpage_size;
284 
285 	T_LOG("Freezing child pid %d", child_pid);
286 	ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &child_pid, sizeof(child_pid));
287 	errno_freeze_sysctl = errno;
288 	sleep(1);
289 
290 	/*
291 	 * The child process toggles its freezable state on each iteration.
292 	 * So a failure for every alternate freeze is expected.
293 	 */
294 	if (freeze_count % 2) {
295 		length = sizeof(freeze_enabled);
296 		T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
297 		    "failed to query vm.freeze_enabled");
298 		if (freeze_enabled) {
299 			errno = errno_freeze_sysctl;
300 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
301 		} else {
302 			/* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
303 			T_LOG("Freeze has been disabled. Terminating early.");
304 			T_END;
305 		}
306 		resident_memory_after = get_rprvt(child_pid) / vmpage_size;
307 		uint64_t freeze_pages_max = (uint64_t) sysctl_freeze_pages_max(NULL);
308 		T_QUIET; T_ASSERT_LT(resident_memory_after, resident_memory_before, "Freeze didn't reduce resident memory set");
309 		if (resident_memory_before > freeze_pages_max) {
310 			T_QUIET; T_ASSERT_LE(resident_memory_before - resident_memory_after, freeze_pages_max, "Freeze pages froze more than the threshold.");
311 		}
312 		ret = sysctlbyname("kern.memorystatus_thaw", NULL, NULL, &child_pid, sizeof(child_pid));
313 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_thaw failed");
314 	} else {
315 		T_QUIET; T_ASSERT_TRUE(ret != KERN_SUCCESS, "Freeze should have failed");
316 		T_LOG("Freeze failed as expected");
317 	}
318 
319 	freeze_count++;
320 
321 	T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGUSR1), "failed to send SIGUSR1 to child process");
322 }
323 
324 static void
325 skip_if_freezer_is_disabled()
326 {
327 	int freeze_enabled;
328 	size_t length = sizeof(freeze_enabled);
329 
330 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
331 	    "failed to query vm.freeze_enabled");
332 	if (!freeze_enabled) {
333 		/* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
334 		T_SKIP("Freeze has been disabled. Skipping test.");
335 	}
336 }
337 
338 void
339 run_freezer_test(int num_pages)
340 {
341 	int ret;
342 	char sz_str[50];
343 	char **launch_tool_args;
344 	char testpath[PATH_MAX];
345 	uint32_t testpath_buf_size;
346 	dispatch_source_t ds_freeze, ds_proc;
347 
348 	skip_if_freezer_is_disabled();
349 
350 	signal(SIGUSR1, SIG_IGN);
351 	ds_freeze = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
352 	T_QUIET; T_ASSERT_NOTNULL(ds_freeze, "dispatch_source_create (ds_freeze)");
353 
354 	dispatch_source_set_event_handler(ds_freeze, ^{
355 		if (freeze_count < NUM_ITERATIONS) {
356 		        freeze_helper_process();
357 		} else {
358 		        kill(child_pid, SIGKILL);
359 		        dispatch_source_cancel(ds_freeze);
360 		}
361 	});
362 	dispatch_activate(ds_freeze);
363 
364 	testpath_buf_size = sizeof(testpath);
365 	ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
366 	T_QUIET; T_ASSERT_POSIX_ZERO(ret, "_NSGetExecutablePath");
367 	T_LOG("Executable path: %s", testpath);
368 
369 	sprintf(sz_str, "%d", num_pages);
370 	launch_tool_args = (char *[]){
371 		testpath,
372 		"-n",
373 		"allocate_pages",
374 		"--",
375 		sz_str,
376 		NULL
377 	};
378 
379 	/* Spawn the child process. Suspend after launch until the exit proc handler has been set up. */
380 	ret = dt_launch_tool(&child_pid, launch_tool_args, true, NULL, NULL);
381 	if (ret != 0) {
382 		T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
383 	}
384 	T_QUIET; T_ASSERT_POSIX_SUCCESS(child_pid, "dt_launch_tool");
385 
386 	ds_proc = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
387 	T_QUIET; T_ASSERT_NOTNULL(ds_proc, "dispatch_source_create (ds_proc)");
388 
389 	dispatch_source_set_event_handler(ds_proc, ^{
390 		int status = 0, code = 0;
391 		pid_t rc = waitpid(child_pid, &status, 0);
392 		T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
393 		code = WEXITSTATUS(status);
394 
395 		if (code == 0) {
396 		        T_END;
397 		} else if (code > 0 && code < EXIT_CODE_MAX) {
398 		        T_ASSERT_FAIL("Child exited with %s", exit_codes_str[code]);
399 		} else {
400 		        T_ASSERT_FAIL("Child exited with unknown exit code %d", code);
401 		}
402 	});
403 	dispatch_activate(ds_proc);
404 
405 	T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGCONT), "failed to send SIGCONT to child process");
406 	dispatch_main();
407 }
408 
409 static void
410 allocate_pages(int num_pages)
411 {
412 	int i, j, vmpgsize;
413 	char val;
414 	__block int num_iter = 0;
415 	__block char **buf;
416 	dispatch_source_t ds_signal;
417 	vmpgsize = get_vmpage_size();
418 	if (num_pages < 1) {
419 		printf("Invalid number of pages to allocate: %d\n", num_pages);
420 		exit(INVALID_ALLOCATE_PAGES_ARGUMENTS);
421 	}
422 
423 	buf = (char**)malloc(sizeof(char*) * (size_t)num_pages);
424 
425 	/* Gives us the compression ratio we see in the typical case (~2.7) */
426 	for (j = 0; j < num_pages; j++) {
427 		buf[j] = (char*)malloc((size_t)vmpgsize * sizeof(char));
428 		val = 0;
429 		for (i = 0; i < vmpgsize; i += 16) {
430 			memset(&buf[j][i], val, 16);
431 			if (i < 3400 * (vmpgsize / 4096)) {
432 				val++;
433 			}
434 		}
435 	}
436 
437 	dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC), dispatch_get_main_queue(), ^{
438 		/* Signal to the parent that we're done allocating and it's ok to freeze us */
439 		printf("[%d] Sending initial signal to parent to begin freezing\n", getpid());
440 		if (kill(getppid(), SIGUSR1) != 0) {
441 		        exit(INITIAL_SIGNAL_TO_PARENT_FAILED);
442 		}
443 	});
444 
445 	signal(SIGUSR1, SIG_IGN);
446 	ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
447 	if (ds_signal == NULL) {
448 		exit(DISPATCH_SOURCE_CREATE_FAILED);
449 	}
450 
451 	dispatch_source_set_event_handler(ds_signal, ^{
452 		int current_state, new_state;
453 		volatile int tmp;
454 
455 		/* Make sure all the pages are accessed before trying to freeze again */
456 		for (int x = 0; x < num_pages; x++) {
457 		        tmp = buf[x][0];
458 		}
459 
460 		current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
461 		/* Sysprocs start off as unfreezable. Verify that first. */
462 		if (num_iter == 0 && current_state != 0) {
463 		        exit(IS_FREEZABLE_NOT_AS_EXPECTED);
464 		}
465 
466 		/* Toggle freezable state */
467 		new_state = (current_state) ? 0: 1;
468 		printf("[%d] Changing state from %s to %s\n", getpid(),
469 		(current_state) ? "freezable": "unfreezable", (new_state) ? "freezable": "unfreezable");
470 		if (memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), (uint32_t)new_state, NULL, 0) != KERN_SUCCESS) {
471 		        exit(MEMORYSTATUS_CONTROL_FAILED);
472 		}
473 
474 		/* Verify that the state has been set correctly */
475 		current_state = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FREEZABLE, getpid(), 0, NULL, 0);
476 		if (new_state != current_state) {
477 		        exit(IS_FREEZABLE_NOT_AS_EXPECTED);
478 		}
479 		num_iter++;
480 
481 		if (kill(getppid(), SIGUSR1) != 0) {
482 		        exit(SIGNAL_TO_PARENT_FAILED);
483 		}
484 	});
485 	dispatch_activate(ds_signal);
486 	move_to_idle_band(getpid());
487 
488 	dispatch_main();
489 }
490 
491 T_HELPER_DECL(allocate_pages,
492     "allocates pages to freeze",
493     T_META_ASROOT(true)) {
494 	if (argc < 1) {
495 		exit(TOO_FEW_ARGUMENTS);
496 	}
497 
498 	int num_pages = atoi(argv[0]);
499 	allocate_pages(num_pages);
500 }
501 
502 T_DECL(freeze, "VM freezer test", T_META_ASROOT(true)) {
503 	run_freezer_test(
504 		(MEM_SIZE_MB << 20) / get_vmpage_size());
505 }
506 
507 static int old_freeze_pages_max = 0;
508 static void
509 reset_freeze_pages_max()
510 {
511 	if (old_freeze_pages_max != 0) {
512 		sysctl_freeze_pages_max(&old_freeze_pages_max);
513 	}
514 }
515 
516 int
517 sysctl_freeze_pages_max(int* new_value)
518 {
519 	static int set_end_handler = false;
520 	int freeze_pages_max, ret;
521 	size_t size = sizeof(freeze_pages_max);
522 	ret = sysctlbyname("kern.memorystatus_freeze_pages_max", &freeze_pages_max, &size, new_value, size);
523 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Unable to query kern.memorystatus_freeze_pages_max");
524 	if (!set_end_handler) {
525 		// Save the original value and instruct darwintest to restore it after the test completes
526 		old_freeze_pages_max = freeze_pages_max;
527 		T_ATEND(reset_freeze_pages_max);
528 		set_end_handler = true;
529 	}
530 	return old_freeze_pages_max;
531 }
532 
533 T_DECL(freeze_over_max_threshold, "Max Freeze Threshold is Enforced", T_META_ASROOT(true)) {
534 	int freeze_pages_max = FREEZE_PAGES_MAX;
535 	sysctl_freeze_pages_max(&freeze_pages_max);
536 	run_freezer_test(FREEZE_PAGES_MAX * 2);
537 }
538 
539 T_HELPER_DECL(frozen_background, "Frozen background process", T_META_ASROOT(true)) {
540 	kern_return_t kern_ret;
541 	/* Set the process to freezable */
542 	kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
543 	T_QUIET; T_ASSERT_EQ(kern_ret, KERN_SUCCESS, "set process is freezable");
544 	/* Signal to our parent that we can be frozen */
545 	if (kill(getppid(), SIGUSR1) != 0) {
546 		T_LOG("Unable to signal to parent process!");
547 		exit(1);
548 	}
549 	while (1) {
550 		;
551 	}
552 }
553 
554 /* Launches the frozen_background helper as a managed process. */
555 static pid_t
556 launch_background_helper(const char* variant)
557 {
558 	pid_t pid;
559 	char **launch_tool_args;
560 	char testpath[PATH_MAX];
561 	char *variant_cpy = strdup(variant);
562 	uint32_t testpath_buf_size;
563 	int ret;
564 
565 	testpath_buf_size = sizeof(testpath);
566 	ret = _NSGetExecutablePath(testpath, &testpath_buf_size);
567 	printf("Launching %s\n", testpath);
568 	launch_tool_args = (char *[]){
569 		testpath,
570 		"-n",
571 		variant_cpy,
572 		NULL
573 	};
574 	ret = dt_launch_tool(&pid, launch_tool_args, false, NULL, NULL);
575 	if (ret != 0) {
576 		T_LOG("dt_launch tool returned %d with error code %d", ret, errno);
577 	}
578 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dt_launch_tool");
579 	/* Set the process's managed bit, so that the kernel treats this process like an app instead of a sysproc. */
580 	ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_MANAGED, pid, 1, NULL, 0);
581 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "memorystatus_control");
582 	free(variant_cpy);
583 	return pid;
584 }
585 
586 static void
587 freeze_process(pid_t pid)
588 {
589 	int ret, freeze_enabled, errno_freeze_sysctl;
590 	size_t length;
591 	T_LOG("Freezing pid %d", pid);
592 
593 	ret = sysctlbyname("kern.memorystatus_freeze", NULL, NULL, &pid, sizeof(pid));
594 	errno_freeze_sysctl = errno;
595 	length = sizeof(freeze_enabled);
596 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", &freeze_enabled, &length, NULL, 0),
597 	    "failed to query vm.freeze_enabled");
598 	if (freeze_enabled) {
599 		errno = errno_freeze_sysctl;
600 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_freeze failed");
601 	} else {
602 		/* If freezer is disabled, skip the test. This can happen due to disk space shortage. */
603 		T_LOG("Freeze has been disabled. Terminating early.");
604 		T_END;
605 	}
606 }
607 
608 static uint32_t max_frozen_demotions_daily_default;
609 
610 static void
611 reset_max_frozen_demotions_daily()
612 {
613 	int sysctl_ret = sysctlbyname("kern.memorystatus_max_freeze_demotions_daily", NULL, NULL, &max_frozen_demotions_daily_default, sizeof(max_frozen_demotions_daily_default));
614 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "set kern.memorystatus_max_freeze_demotions_daily to default");
615 }
616 
617 static void
618 allow_unlimited_demotions()
619 {
620     size_t size = sizeof(max_frozen_demotions_daily_default);
621     uint32_t new_value = UINT32_MAX;
622     int sysctl_ret = sysctlbyname("kern.memorystatus_max_freeze_demotions_daily", &max_frozen_demotions_daily_default, &size, &new_value, sizeof(new_value));
623     T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "kern.memorystatus_max_freeze_demotions_daily = UINT32_MAX");
624     T_ATEND(reset_max_frozen_demotions_daily);
625 }
626 
627 static void
628 memorystatus_assertion_test_demote_frozen()
629 {
630 	/*
631 	 * 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.
632 	 * Then remove thee assertion, and ensure it gets demoted properly.
633 	 */
634 	/* these values will remain fixed during testing */
635 	int             active_limit_mb = 15;   /* arbitrary */
636 	int             inactive_limit_mb = 7;  /* arbitrary */
637 	__block int             demote_value = 1;
638 	/* Launch the child process, and elevate its priority */
639 	int requestedpriority;
640 	dispatch_source_t ds_signal, ds_exit;
641 	requestedpriority = JETSAM_PRIORITY_UI_SUPPORT;
642 	allow_unlimited_demotions();
643 
644 	/* Wait for the child process to tell us that it's ready, and then freeze it */
645 	signal(SIGUSR1, SIG_IGN);
646 	ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
647 	T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
648 	dispatch_source_set_event_handler(ds_signal, ^{
649 		int sysctl_ret;
650 		/* Freeze the process, trigger agressive demotion, and check that it hasn't been demoted. */
651 		freeze_process(child_pid);
652 		/* Agressive demotion */
653 		sysctl_ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &demote_value, sizeof(demote_value));
654 		T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctl_ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
655 		/* Check */
656 		(void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
657 		T_LOG("Relinquishing our assertion.");
658 		/* Relinquish our assertion, and check that it gets demoted. */
659 		relinquish_assertion_priority(child_pid, 0x0);
660 		(void)check_properties(child_pid, JETSAM_PRIORITY_AGING_BAND2, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_RELINQUISHED, "Assertion was reqlinquished.");
661 		/* Kill the child */
662 		T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
663 		T_END;
664 	});
665 
666 	/* Launch the child process and set the initial properties on it. */
667 	child_pid = launch_background_helper("frozen_background");
668 	set_memlimits(child_pid, active_limit_mb, inactive_limit_mb, false, false);
669 	set_assertion_priority(child_pid, requestedpriority, 0x0);
670 	(void)check_properties(child_pid, requestedpriority, inactive_limit_mb, 0x0, ASSERTION_STATE_IS_SET, "Priority was set");
671 	/* Listen for exit. */
672 	ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
673 	dispatch_source_set_event_handler(ds_exit, ^{
674 		int status = 0, code = 0;
675 		pid_t rc = waitpid(child_pid, &status, 0);
676 		T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
677 		code = WEXITSTATUS(status);
678 		T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
679 		T_END;
680 	});
681 
682 	dispatch_activate(ds_exit);
683 	dispatch_activate(ds_signal);
684 	dispatch_main();
685 }
686 
687 T_DECL(assertion_test_demote_frozen, "demoted frozen process goes to asserted priority.", T_META_ASROOT(true)) {
688 	memorystatus_assertion_test_demote_frozen();
689 }
690 
691 static unsigned int
692 get_freeze_daily_pages_max(void)
693 {
694 	unsigned int memorystatus_freeze_daily_mb_max;
695 	size_t length = sizeof(memorystatus_freeze_daily_mb_max);
696 	int ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &memorystatus_freeze_daily_mb_max, &length, NULL, 0);
697 	T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_daily_mb_max");
698 	return memorystatus_freeze_daily_mb_max * 1024UL * 1024UL / vm_kernel_page_size;
699 }
700 
701 static uint64_t
702 get_budget_multiplier(void)
703 {
704 	uint64_t budget_multiplier = 0;
705 	size_t size = sizeof(budget_multiplier);
706 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_multiplier", &budget_multiplier, &size, NULL, 0),
707 	    "get kern.memorystatus_freeze_budget_multiplier");
708 	return budget_multiplier;
709 }
710 static void
711 set_budget_multiplier(uint64_t multiplier)
712 {
713 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_multiplier", NULL, NULL, &multiplier, sizeof(multiplier)),
714 	    "set kern.memorystatus_freeze_budget_multiplier");
715 }
716 
717 static uint64_t original_budget_multiplier;
718 static void
719 reset_budget_multiplier(void)
720 {
721 	set_budget_multiplier(original_budget_multiplier);
722 }
723 
724 T_DECL(budget_replenishment, "budget replenishes properly") {
725 	size_t length;
726 	int ret;
727 	static unsigned int kTestIntervalSecs = 60 * 60 * 32; // 32 Hours
728 	unsigned int memorystatus_freeze_daily_pages_max;
729 	static unsigned int kFixedPointFactor = 100;
730 	static unsigned int kNumSecondsInDay = 60 * 60 * 24;
731 	unsigned int new_budget, expected_new_budget_pages;
732 	size_t new_budget_ln;
733 	vm_size_t page_size = vm_kernel_page_size;
734 	original_budget_multiplier = get_budget_multiplier();
735 	T_ATEND(reset_budget_multiplier);
736 	set_budget_multiplier(100);
737 	/*
738 	 * Calculate a new budget as if the previous interval expired kTestIntervalSecs
739 	 * ago and we used up its entire budget.
740 	 */
741 	length = sizeof(kTestIntervalSecs);
742 	new_budget_ln = sizeof(new_budget);
743 	ret = sysctlbyname("vm.memorystatus_freeze_calculate_new_budget", &new_budget, &new_budget_ln, &kTestIntervalSecs, length);
744 	T_ASSERT_POSIX_SUCCESS(ret, "vm.memorystatus_freeze_calculate_new_budget");
745 
746 	memorystatus_freeze_daily_pages_max = get_freeze_daily_pages_max();
747 	T_LOG("memorystatus_freeze_daily_pages_max %u", memorystatus_freeze_daily_pages_max);
748 	T_LOG("page_size %lu", page_size);
749 
750 	/*
751 	 * We're kTestIntervalSecs past a new interval. Which means we are owed kNumSecondsInDay
752 	 * seconds of budget.
753 	 */
754 	expected_new_budget_pages = memorystatus_freeze_daily_pages_max;
755 	T_LOG("expected_new_budget_pages before %u", expected_new_budget_pages);
756 	T_ASSERT_EQ(kTestIntervalSecs, 60 * 60 * 32, "kTestIntervalSecs did not change");
757 	expected_new_budget_pages += ((kTestIntervalSecs * kFixedPointFactor) / (kNumSecondsInDay)
758 	    * memorystatus_freeze_daily_pages_max) / kFixedPointFactor;
759 	T_LOG("expected_new_budget_pages after %u", expected_new_budget_pages);
760 	T_LOG("memorystatus_freeze_daily_pages_max after %u", memorystatus_freeze_daily_pages_max);
761 
762 	T_QUIET; T_ASSERT_EQ(new_budget, expected_new_budget_pages, "Calculate new budget behaves correctly.");
763 }
764 
765 
766 static bool
767 is_proc_in_frozen_list(pid_t pid, char* name, size_t name_len)
768 {
769 	int bytes_written;
770 	bool found = false;
771 	global_frozen_procs_t *frozen_procs = malloc(sizeof(global_frozen_procs_t));
772 	T_QUIET; T_ASSERT_NOTNULL(frozen_procs, "malloc");
773 
774 	bytes_written = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_GET_PROCS, frozen_procs, sizeof(global_frozen_procs_t));
775 	T_QUIET; T_ASSERT_LE((size_t) bytes_written, sizeof(global_frozen_procs_t), "Didn't overflow buffer");
776 	T_QUIET; T_ASSERT_GT(bytes_written, 0, "Wrote someting");
777 
778 	for (size_t i = 0; i < frozen_procs->gfp_num_frozen; i++) {
779 		if (frozen_procs->gfp_procs[i].fp_pid == pid) {
780 			found = true;
781 			strlcpy(name, frozen_procs->gfp_procs[i].fp_name, name_len);
782 		}
783 	}
784 	return found;
785 }
786 
787 static void
788 unset_testing_pid(void)
789 {
790 	int ret;
791 	ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_UNSET_TESTING_PID, NULL, 0);
792 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Drop ownership of jetsam snapshot");
793 }
794 
795 static void
796 set_testing_pid(void)
797 {
798 	int ret;
799 	ret = memorystatus_control(MEMORYSTATUS_CMD_SET_TESTING_PID, 0, MEMORYSTATUS_FLAGS_SET_TESTING_PID, NULL, 0);
800 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "Take ownership of jetsam snapshot");
801 	T_ATEND(unset_testing_pid);
802 }
803 
804 /*
805  * Retrieve a jetsam snapshot.
806  *
807  * return:
808  *      pointer to snapshot.
809  *
810  *	Caller is responsible for freeing snapshot.
811  */
812 static
813 memorystatus_jetsam_snapshot_t *
814 get_jetsam_snapshot(uint32_t flags, bool empty_allowed)
815 {
816 	memorystatus_jetsam_snapshot_t * snapshot = NULL;
817 	int ret;
818 	uint32_t size;
819 
820 	ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, NULL, 0);
821 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, 0, "Get jetsam snapshot size");
822 	size = (uint32_t) ret;
823 	if (size == 0 && empty_allowed) {
824 		return snapshot;
825 	}
826 
827 	snapshot = (memorystatus_jetsam_snapshot_t*)malloc(size);
828 	T_QUIET; T_ASSERT_NOTNULL(snapshot, "Allocate snapshot of size %d", size);
829 
830 	ret = memorystatus_control(MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT, 0, flags, snapshot, size);
831 	T_QUIET; T_ASSERT_GT(size, 0, "Get jetsam snapshot");
832 
833 	if (((size - sizeof(memorystatus_jetsam_snapshot_t)) / sizeof(memorystatus_jetsam_snapshot_entry_t)) != snapshot->entry_count) {
834 		T_FAIL("Malformed snapshot: %d! Expected %ld + %zd x %ld = %ld\n", size,
835 		    sizeof(memorystatus_jetsam_snapshot_t), snapshot->entry_count, sizeof(memorystatus_jetsam_snapshot_entry_t),
836 		    sizeof(memorystatus_jetsam_snapshot_t) + (snapshot->entry_count * sizeof(memorystatus_jetsam_snapshot_entry_t)));
837 		if (snapshot) {
838 			free(snapshot);
839 		}
840 	}
841 
842 	return snapshot;
843 }
844 
845 /*
846  * Look for the given pid in the snapshot.
847  *
848  * return:
849  *     pointer to pid's entry or NULL if pid is not found.
850  *
851  * Caller has ownership of snapshot before and after call.
852  */
853 static
854 memorystatus_jetsam_snapshot_entry_t *
855 get_jetsam_snapshot_entry(memorystatus_jetsam_snapshot_t *snapshot, pid_t pid)
856 {
857 	T_QUIET; T_ASSERT_NOTNULL(snapshot, "Got snapshot");
858 	for (size_t i = 0; i < snapshot->entry_count; i++) {
859 		memorystatus_jetsam_snapshot_entry_t *curr = &(snapshot->entries[i]);
860 		if (curr->pid == pid) {
861 			return curr;
862 		}
863 	}
864 
865 	return NULL;
866 }
867 
868 static void
869 resume_and_kill_proc(pid_t pid)
870 {
871 	int ret = pid_resume(pid);
872 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "proc resumed after freeze");
873 	T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(pid, SIGKILL), "Killed process");
874 }
875 
876 static void
877 resume_and_kill_child()
878 {
879 	/* Used for test cleanup. proc might not be suspended so pid_resume might fail. */
880 	pid_resume(child_pid);
881 	kill(child_pid, SIGKILL);
882 }
883 
884 static dispatch_source_t
885 run_block_after_signal(int sig, dispatch_block_t block)
886 {
887 	dispatch_source_t ds_signal;
888 	signal(sig, SIG_IGN);
889 	ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) sig, 0, dispatch_get_main_queue());
890 	T_QUIET; T_ASSERT_NOTNULL(ds_signal, "dispatch_source_create");
891 	dispatch_source_set_event_handler(ds_signal, block);
892 	return ds_signal;
893 }
894 
895 /*
896  * Launches the child & runs the given block after the child signals.
897  * If exit_with_child is true, the test will exit when the child exits.
898  */
899 static void
900 test_after_background_helper_launches(bool exit_with_child, const char* variant, dispatch_block_t test_block)
901 {
902 	dispatch_source_t ds_signal, ds_exit;
903 
904 	ds_signal = run_block_after_signal(SIGUSR1, test_block);
905 	/* Launch the child process. */
906 	child_pid = launch_background_helper(variant);
907 	T_ATEND(resume_and_kill_child);
908 	/* Listen for exit. */
909 	if (exit_with_child) {
910 		ds_exit = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, (uintptr_t)child_pid, DISPATCH_PROC_EXIT, dispatch_get_main_queue());
911 		dispatch_source_set_event_handler(ds_exit, ^{
912 			int status = 0, code = 0;
913 			pid_t rc = waitpid(child_pid, &status, 0);
914 			T_QUIET; T_ASSERT_EQ(rc, child_pid, "waitpid");
915 			code = WEXITSTATUS(status);
916 			if (code != 0) {
917 			        T_LOG("Child exited with error: %s", exit_codes_str[code]);
918 			}
919 			T_QUIET; T_ASSERT_EQ(code, 0, "Child exited cleanly");
920 			T_END;
921 		});
922 
923 		dispatch_activate(ds_exit);
924 	}
925 	dispatch_activate(ds_signal);
926 }
927 
928 T_DECL(get_frozen_procs, "List processes in the freezer") {
929 	skip_if_freezer_is_disabled();
930 
931 	test_after_background_helper_launches(true, "frozen_background", ^{
932 		proc_name_t name;
933 		/* Place the child in the idle band so that it gets elevated like a typical app. */
934 		move_to_idle_band(child_pid);
935 		/* Freeze the process, and check that it's in the list of frozen processes. */
936 		freeze_process(child_pid);
937 		/* Check */
938 		T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
939 		T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
940 		/* Kill the child */
941 		T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
942 		T_END;
943 	});
944 	dispatch_main();
945 }
946 
947 T_DECL(frozen_to_swap_accounting, "jetsam snapshot has frozen_to_swap accounting") {
948 	static const size_t kSnapshotSleepDelay = 5;
949 	static const size_t kFreezeToDiskMaxDelay = 60;
950 
951 	skip_if_freezer_is_disabled();
952 
953 	test_after_background_helper_launches(true, "frozen_background", ^{
954 		memorystatus_jetsam_snapshot_t *snapshot = NULL;
955 		memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
956 		/* Place the child in the idle band so that it gets elevated like a typical app. */
957 		move_to_idle_band(child_pid);
958 		freeze_process(child_pid);
959 		/*
960 		 * Wait until the child's pages get paged out to disk.
961 		 * If we don't see any pages get sent to disk before kFreezeToDiskMaxDelay seconds,
962 		 * something is either wrong with the compactor or the accounting.
963 		 */
964 		for (size_t i = 0; i < kFreezeToDiskMaxDelay / kSnapshotSleepDelay; i++) {
965 		        snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
966 		        child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
967 		        T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
968 		        if (child_entry->jse_frozen_to_swap_pages > 0) {
969 		                break;
970 			}
971 		        free(snapshot);
972 		        sleep(kSnapshotSleepDelay);
973 		}
974 		T_QUIET; T_ASSERT_GT(child_entry->jse_frozen_to_swap_pages, 0ULL, "child has some pages in swap");
975 		free(snapshot);
976 		/* Kill the child */
977 		T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed child process");
978 		T_END;
979 	});
980 	dispatch_main();
981 }
982 
983 T_DECL(freezer_snapshot, "App kills are recorded in the freezer snapshot") {
984 	/* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
985 	set_testing_pid();
986 
987 	test_after_background_helper_launches(false, "frozen_background", ^{
988 		int ret;
989 		memorystatus_jetsam_snapshot_t *snapshot = NULL;
990 		memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
991 
992 		ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
993 		T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
994 
995 		snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
996 		T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
997 		child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
998 		T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
999 		T_QUIET; T_ASSERT_EQ(child_entry->killed, (unsigned long long) JETSAM_REASON_GENERIC, "Child entry was killed");
1000 
1001 		free(snapshot);
1002 		T_END;
1003 	});
1004 	dispatch_main();
1005 }
1006 
1007 T_DECL(freezer_snapshot_consume, "Freezer snapshot is consumed on read") {
1008 	/* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1009 	set_testing_pid();
1010 
1011 	test_after_background_helper_launches(false, "frozen_background", ^{
1012 		int ret;
1013 		memorystatus_jetsam_snapshot_t *snapshot = NULL;
1014 		memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1015 
1016 		ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1017 		T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1018 
1019 		snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1020 		T_ASSERT_NOTNULL(snapshot, "Got first freezer snapshot");
1021 		child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1022 		T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in first freezer snapshot");
1023 
1024 		snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, true);
1025 		if (snapshot != NULL) {
1026 		        child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1027 		        T_QUIET; T_ASSERT_NULL(child_entry, "Child is not in second freezer snapshot");
1028 		}
1029 
1030 		free(snapshot);
1031 		T_END;
1032 	});
1033 	dispatch_main();
1034 }
1035 
1036 T_DECL(freezer_snapshot_frozen_state, "Frozen state is recorded in freezer snapshot") {
1037 	skip_if_freezer_is_disabled();
1038 	/* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1039 	set_testing_pid();
1040 
1041 	test_after_background_helper_launches(false, "frozen_background", ^{
1042 		int ret;
1043 		memorystatus_jetsam_snapshot_t *snapshot = NULL;
1044 		memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1045 
1046 		move_to_idle_band(child_pid);
1047 		freeze_process(child_pid);
1048 
1049 		ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1050 		T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1051 
1052 		snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1053 		T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1054 		child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1055 		T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1056 		T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is set");
1057 
1058 		free(snapshot);
1059 		T_END;
1060 	});
1061 	dispatch_main();
1062 }
1063 
1064 T_DECL(freezer_snapshot_thaw_state, "Thaw count is recorded in freezer snapshot") {
1065 	skip_if_freezer_is_disabled();
1066 	/* Take ownership of the snapshot to ensure we don't race with another process trying to consume them. */
1067 	set_testing_pid();
1068 
1069 	test_after_background_helper_launches(false, "frozen_background", ^{
1070 		int ret;
1071 		memorystatus_jetsam_snapshot_t *snapshot = NULL;
1072 		memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1073 
1074 		move_to_idle_band(child_pid);
1075 		ret = pid_suspend(child_pid);
1076 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1077 		freeze_process(child_pid);
1078 		ret = pid_resume(child_pid);
1079 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1080 
1081 		ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM, child_pid, 0, 0, 0);
1082 		T_ASSERT_POSIX_SUCCESS(ret, "jetsam'd the child");
1083 
1084 		snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_FREEZER, false);
1085 		T_ASSERT_NOTNULL(snapshot, "Got freezer snapshot");
1086 		child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1087 		T_QUIET; T_ASSERT_NOTNULL(child_entry, "Child is in freezer snapshot");
1088 		T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusFrozen, "Child entry's frozen bit is still set after thaw");
1089 		T_QUIET; T_ASSERT_TRUE(child_entry->state & kMemorystatusWasThawed, "Child entry was thawed");
1090 		T_QUIET; T_ASSERT_EQ(child_entry->jse_thaw_count, 1ULL, "Child entry's thaw count was incremented");
1091 
1092 		free(snapshot);
1093 		T_END;
1094 	});
1095 	dispatch_main();
1096 }
1097 
1098 T_HELPER_DECL(check_frozen, "Check frozen state", T_META_ASROOT(true)) {
1099 	int kern_ret;
1100 	dispatch_source_t ds_signal;
1101 	__block int is_frozen;
1102 	/* Set the process to freezable */
1103 	kern_ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PROCESS_IS_FREEZABLE, getpid(), 1, NULL, 0);
1104 	T_QUIET; T_ASSERT_POSIX_SUCCESS(kern_ret, "set process is freezable");
1105 
1106 	/* We should not be frozen yet. */
1107 	is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1108 	if (is_frozen == -1) {
1109 		T_LOG("memorystatus_control error: %s", strerror(errno));
1110 		exit(MEMORYSTATUS_CONTROL_ERROR);
1111 	}
1112 	if (is_frozen) {
1113 		exit(FROZEN_BIT_SET);
1114 	}
1115 
1116 	ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1117 	if (ds_signal == NULL) {
1118 		exit(DISPATCH_SOURCE_CREATE_FAILED);
1119 	}
1120 
1121 	dispatch_source_set_event_handler(ds_signal, ^{
1122 		/* We should now be frozen. */
1123 		is_frozen = memorystatus_control(MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN, getpid(), 0, NULL, 0);
1124 		if (is_frozen == -1) {
1125 		        T_LOG("memorystatus_control error: %s", strerror(errno));
1126 		        exit(MEMORYSTATUS_CONTROL_ERROR);
1127 		}
1128 		if (!is_frozen) {
1129 		        exit(FROZEN_BIT_NOT_SET);
1130 		}
1131 		exit(SUCCESS);
1132 	});
1133 	dispatch_activate(ds_signal);
1134 
1135 	sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1136 	T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1137 
1138 	/* Signal to our parent that we can be frozen */
1139 	if (kill(getppid(), SIGUSR1) != 0) {
1140 		T_LOG("Unable to signal to parent process!");
1141 		exit(SIGNAL_TO_PARENT_FAILED);
1142 	}
1143 
1144 	dispatch_main();
1145 }
1146 
1147 T_DECL(memorystatus_get_process_is_frozen, "MEMORYSTATUS_CMD_GET_PROCESS_IS_FROZEN returns correct state") {
1148 	skip_if_freezer_is_disabled();
1149 
1150 	test_after_background_helper_launches(true, "check_frozen", ^{
1151 		int ret;
1152 		/* Freeze the child, resume it, and signal it to check its state */
1153 		move_to_idle_band(child_pid);
1154 		ret = pid_suspend(child_pid);
1155 		T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1156 		freeze_process(child_pid);
1157 		ret = pid_resume(child_pid);
1158 		T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1159 
1160 		kill(child_pid, SIGUSR1);
1161 		/* The child will checks its own frozen state & exit. */
1162 	});
1163 	dispatch_main();
1164 }
1165 
1166 static unsigned int freeze_pages_min_old;
1167 static int throttle_enabled_old;
1168 static void
1169 cleanup_memorystatus_freeze_top_process()
1170 {
1171 	sysctlbyname("kern.memorystatus_freeze_pages_min", NULL, NULL, &freeze_pages_min_old, sizeof(freeze_pages_min_old));
1172 	sysctlbyname("kern.memorystatus_freeze_throttle_enabled", NULL, NULL, &throttle_enabled_old, sizeof(throttle_enabled_old));
1173 }
1174 
1175 /*
1176  * Disables heuristics that could prevent us from freezing the child via memorystatus_freeze_top_process.
1177  */
1178 static void
1179 memorystatus_freeze_top_process_setup()
1180 {
1181 	size_t freeze_pages_min_size = sizeof(freeze_pages_min_old);
1182 	unsigned int freeze_pages_min_new = 0;
1183 	size_t throttle_enabled_old_size = sizeof(throttle_enabled_old);
1184 	int throttle_enabled_new = 1, ret;
1185 
1186 	ret = sysctlbyname("kern.memorystatus_freeze_pages_min", &freeze_pages_min_old, &freeze_pages_min_size, &freeze_pages_min_new, sizeof(freeze_pages_min_new));
1187 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_pages_min");
1188 	ret = sysctlbyname("kern.memorystatus_freeze_throttle_enabled", &throttle_enabled_old, &throttle_enabled_old_size, &throttle_enabled_new, sizeof(throttle_enabled_new));
1189 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "set kern.memorystatus_freeze_throttle_enabled");
1190 	T_ATEND(cleanup_memorystatus_freeze_top_process);
1191 	/* Take ownership of the freezer probabilities for the duration of the test so that we don't race with dasd. */
1192 	set_testing_pid();
1193 }
1194 
1195 /*
1196  * Moves the proc to the idle band and suspends it.
1197  */
1198 static void
1199 prepare_proc_for_freezing(pid_t pid)
1200 {
1201 	move_to_idle_band(pid);
1202 	int ret = pid_suspend(pid);
1203 	T_ASSERT_POSIX_SUCCESS(ret, "proc suspended");
1204 }
1205 
1206 #define P_MEMSTAT_FROZEN 0x00000002
1207 static void
1208 verify_proc_frozen_state(pid_t pid, bool expected)
1209 {
1210 	memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1211 	memorystatus_jetsam_snapshot_entry_t *entry = get_jetsam_snapshot_entry(snapshot, pid);
1212 	T_ASSERT_NOTNULL(entry, "%d is in snapshot", pid);
1213 	bool is_frozen = (entry->state & P_MEMSTAT_FROZEN) != 0;
1214 	if (is_frozen != expected) {
1215 		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);
1216 	}
1217 	T_ASSERT_EQ(is_frozen, expected, "%s frozen state", entry->name);
1218 	free(snapshot);
1219 }
1220 
1221 static void
1222 verify_proc_is_frozen(pid_t pid)
1223 {
1224 	verify_proc_frozen_state(pid, true);
1225 }
1226 
1227 static void
1228 verify_proc_not_frozen(pid_t pid)
1229 {
1230 	verify_proc_frozen_state(pid, false);
1231 }
1232 
1233 T_DECL(memorystatus_freeze_top_process, "memorystatus_freeze_top_process chooses the correct process",
1234     T_META_ASROOT(true),
1235     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1236     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1237 	int32_t memorystatus_freeze_band = 0;
1238 	size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band);
1239 	__block errno_t ret;
1240 	__block int maxproc;
1241 	size_t maxproc_size = sizeof(maxproc);
1242 
1243 	ret = sysctlbyname("kern.maxproc", &maxproc, &maxproc_size, NULL, 0);
1244 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.maxproc");
1245 	ret = sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0);
1246 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_jetsam_band");
1247 
1248 	memorystatus_freeze_top_process_setup();
1249 	test_after_background_helper_launches(true, "frozen_background", ^{
1250 		int32_t child_band = JETSAM_PRIORITY_DEFAULT;
1251 		prepare_proc_for_freezing(child_pid);
1252 
1253 		size_t buffer_len = sizeof(memorystatus_properties_entry_v1_t) * (size_t) maxproc;
1254 		memorystatus_properties_entry_v1_t *properties_list = malloc(buffer_len);
1255 		T_QUIET; T_ASSERT_NOTNULL(properties_list, "malloc properties array");
1256 		size_t properties_list_len = 0;
1257 		/* The child needs to age down into the idle band before it's eligible to be frozen. */
1258 		T_LOG("Waiting for child to age into the idle band.");
1259 		while (child_band != JETSAM_PRIORITY_IDLE) {
1260 		        memset(properties_list, 0, buffer_len);
1261 		        properties_list_len = 0;
1262 		        memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1263 
1264 		        bool found = false;
1265 		        for (size_t i = 0; i < snapshot->entry_count; i++) {
1266 		                memorystatus_jetsam_snapshot_entry_t *snapshot_entry = &snapshot->entries[i];
1267 		                if (snapshot_entry->priority <= memorystatus_freeze_band && !snapshot_entry->killed) {
1268 		                        pid_t pid = snapshot_entry->pid;
1269 		                        memorystatus_properties_entry_v1_t *property_entry = &properties_list[properties_list_len++];
1270 		                        property_entry->version = 1;
1271 		                        property_entry->pid = pid;
1272 		                        if (pid == child_pid) {
1273 		                                found = true;
1274 		                                property_entry->use_probability = 1;
1275 		                                child_band = snapshot_entry->priority;
1276 					} else {
1277 		                                property_entry->use_probability = 0;
1278 					}
1279 		                        strncpy(property_entry->proc_name, snapshot_entry->name, MAXCOMLEN);
1280 		                        property_entry->proc_name[MAXCOMLEN] = '\0';
1281 				}
1282 			}
1283 		        T_QUIET; T_ASSERT_TRUE(found, "Child is in on demand snapshot");
1284 		        free(snapshot);
1285 		}
1286 		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);
1287 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_PROBABILITY");
1288 		free(properties_list);
1289 		int val = 1;
1290 		ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1291 		T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1292 
1293 		verify_proc_is_frozen(child_pid);
1294 		resume_and_kill_proc(child_pid);
1295 		T_END;
1296 	});
1297 	dispatch_main();
1298 }
1299 static unsigned int use_ordered_list_original;
1300 static unsigned int use_demotion_list_original;
1301 static void
1302 reset_ordered_freeze_mode()
1303 {
1304 	sysctlbyname("kern.memorystatus_freezer_use_ordered_list", NULL, NULL, &use_ordered_list_original, sizeof(use_ordered_list_original));
1305 }
1306 
1307 static void
1308 reset_ordered_demote_mode()
1309 {
1310 	sysctlbyname("kern.memorystatus_freezer_use_demotion_list", NULL, NULL, &use_demotion_list_original, sizeof(use_demotion_list_original));
1311 }
1312 
1313 static void
1314 enable_ordered_freeze_mode()
1315 {
1316 	int ret;
1317 	int val = 1;
1318 	size_t size = sizeof(use_ordered_list_original);
1319 	ret = sysctlbyname("kern.memorystatus_freezer_use_ordered_list", &use_ordered_list_original, &size, &val, sizeof(val));
1320 	T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freezer_use_ordered_list");
1321 	T_ATEND(reset_ordered_freeze_mode);
1322 }
1323 
1324 static void
1325 enable_ordered_demote_mode()
1326 {
1327 	int ret;
1328 	int val = 1;
1329 	size_t size = sizeof(use_demotion_list_original);
1330 	ret = sysctlbyname("kern.memorystatus_freezer_use_demotion_list", &use_demotion_list_original, &size, &val, sizeof(val));
1331 	T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freezer_use_demotion_list");
1332 	T_ATEND(reset_ordered_demote_mode);
1333 }
1334 
1335 static void
1336 construct_child_freeze_entry(memorystatus_properties_freeze_entry_v1 *entry)
1337 {
1338 	memset(entry, 0, sizeof(memorystatus_properties_freeze_entry_v1));
1339 	entry->version = 1;
1340 	entry->pid = child_pid;
1341 	entry->priority = 1;
1342 
1343 	/* Get the child's name. */
1344 	memorystatus_jetsam_snapshot_t *snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1345 	memorystatus_jetsam_snapshot_entry_t *snapshot_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1346 	strncpy(entry->proc_name, snapshot_entry->name, sizeof(entry->proc_name));
1347 	free(snapshot);
1348 }
1349 
1350 T_DECL(memorystatus_freeze_top_process_ordered, "memorystatus_freeze_top_process chooses the correct process when using an ordered list",
1351     T_META_ASROOT(true),
1352     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1353     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1354 	memorystatus_freeze_top_process_setup();
1355 	enable_ordered_freeze_mode();
1356 	test_after_background_helper_launches(true, "frozen_background", ^{
1357 		int ret, val = 1;
1358 		memorystatus_properties_freeze_entry_v1 entries[1];
1359 
1360 		construct_child_freeze_entry(&entries[0]);
1361 		prepare_proc_for_freezing(child_pid);
1362 
1363 		T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1364 		ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1365 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1366 
1367 		ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1368 		T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1369 
1370 		verify_proc_is_frozen(child_pid);
1371 		resume_and_kill_proc(child_pid);
1372 
1373 		T_END;
1374 	});
1375 	dispatch_main();
1376 }
1377 
1378 static void
1379 memorystatus_freeze_top_process_ordered_wrong_pid(pid_t (^pid_for_entry)(pid_t))
1380 {
1381 	memorystatus_freeze_top_process_setup();
1382 	enable_ordered_freeze_mode();
1383 	test_after_background_helper_launches(true, "frozen_background", ^{
1384 		int ret, val = 1;
1385 		memorystatus_properties_freeze_entry_v1 entries[1];
1386 
1387 		construct_child_freeze_entry(&entries[0]);
1388 		entries[0].pid = pid_for_entry(child_pid);
1389 		prepare_proc_for_freezing(child_pid);
1390 
1391 		T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1392 		ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1393 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1394 
1395 		ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1396 		T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1397 
1398 		verify_proc_is_frozen(child_pid);
1399 		resume_and_kill_proc(child_pid);
1400 
1401 		T_END;
1402 	});
1403 	dispatch_main();
1404 }
1405 
1406 /*
1407  * Try both with a pid that's used by another process
1408  * and a pid that is likely unused.
1409  * In both cases the child should still get frozen.
1410  */
1411 T_DECL(memorystatus_freeze_top_process_ordered_reused_pid, "memorystatus_freeze_top_process is resilient to pid changes",
1412     T_META_ASROOT(true),
1413     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1414     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1415 	memorystatus_freeze_top_process_ordered_wrong_pid(^(__unused pid_t child) {
1416 		return 1;
1417 	});
1418 }
1419 
1420 T_DECL(memorystatus_freeze_top_process_ordered_wrong_pid, "memorystatus_freeze_top_process is resilient to pid changes",
1421     T_META_ASROOT(true),
1422     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1423     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1424 	memorystatus_freeze_top_process_ordered_wrong_pid(^(__unused pid_t child) {
1425 		return child + 1000;
1426 	});
1427 }
1428 
1429 T_DECL(memorystatus_freeze_demote_ordered, "memorystatus_demote_frozen_processes_using_demote_list chooses the correct process",
1430     T_META_ASROOT(true),
1431     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1432     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1433 	memorystatus_freeze_top_process_setup();
1434 	enable_ordered_freeze_mode();
1435 	enable_ordered_demote_mode();
1436 	test_after_background_helper_launches(true, "frozen_background", ^{
1437 		int ret, val = 1;
1438 		int32_t memorystatus_freeze_band = 0;
1439 		size_t memorystatus_freeze_band_size = sizeof(memorystatus_freeze_band);
1440 		memorystatus_jetsam_snapshot_t *snapshot = NULL;
1441 		memorystatus_jetsam_snapshot_entry_t *child_entry = NULL;
1442 		memorystatus_properties_freeze_entry_v1 entries[1];
1443 
1444 		ret = sysctlbyname("kern.memorystatus_freeze_jetsam_band", &memorystatus_freeze_band, &memorystatus_freeze_band_size, NULL, 0);
1445 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.memorystatus_freeze_jetsam_band");
1446 
1447 		construct_child_freeze_entry(&entries[0]);
1448 		prepare_proc_for_freezing(child_pid);
1449 
1450 		T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1451 		ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1452 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1453 
1454 		ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1455 		T_ASSERT_POSIX_SUCCESS(ret, "freeze_top_process");
1456 
1457 		verify_proc_is_frozen(child_pid);
1458 
1459 		/*
1460 		 * Place the child at the head of the demotion list.
1461 		 */
1462 		T_LOG("Telling kernel to demote %s", entries[0].proc_name);
1463 		ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_DEMOTE_PRIORITY, entries, sizeof(entries));
1464 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_DEMOTE_PRIORITY");
1465 
1466 		/* Resume the child */
1467 		ret = pid_resume(child_pid);
1468 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1469 
1470 		/* Trigger a demotion check */
1471 		val = 1;
1472 		ret = sysctlbyname("kern.memorystatus_demote_frozen_processes", NULL, NULL, &val, sizeof(val));
1473 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "sysctl kern.memorystatus_demote_frozen_processes succeeded");
1474 
1475 		/* Verify that the child was demoted */
1476 		snapshot = get_jetsam_snapshot(MEMORYSTATUS_FLAGS_SNAPSHOT_ON_DEMAND, false);
1477 		child_entry = get_jetsam_snapshot_entry(snapshot, child_pid);
1478 		T_QUIET; T_ASSERT_NOTNULL(child_entry, "Found child in snapshot");
1479 		T_QUIET; T_ASSERT_LT(child_entry->priority, memorystatus_freeze_band, "child was demoted");
1480 		free(snapshot);
1481 
1482 		T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "Killed process");
1483 
1484 		T_END;
1485 	});
1486 	dispatch_main();
1487 }
1488 
1489 static int
1490 memorystatus_freezer_thaw_percentage(void)
1491 {
1492 	int val;
1493 	size_t size = sizeof(val);
1494 	int ret = sysctlbyname("kern.memorystatus_freezer_thaw_percentage", &val, &size, NULL, 0);
1495 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freezer_thaw_percentage");
1496 	return val;
1497 }
1498 
1499 static void
1500 reset_interval(void)
1501 {
1502 	uint32_t freeze_daily_budget_mb = 0;
1503 	size_t size = sizeof(freeze_daily_budget_mb);
1504 	int ret;
1505 	uint64_t new_budget;
1506 	ret = sysctlbyname("kern.memorystatus_freeze_daily_mb_max", &freeze_daily_budget_mb, &size, NULL, 0);
1507 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query kern.memorystatus_freeze_daily_mb_max");
1508 	new_budget = (freeze_daily_budget_mb * (1UL << 20) / vm_page_size);
1509 	ret = sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &new_budget, sizeof(new_budget));
1510 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to set kern.memorystatus_freeze_budget_pages_remaining");
1511 }
1512 
1513 static pid_t second_child;
1514 static void
1515 cleanup_memorystatus_freezer_thaw_percentage(void)
1516 {
1517 	kill(second_child, SIGKILL);
1518 }
1519 
1520 T_DECL(memorystatus_freezer_thaw_percentage, "memorystatus_freezer_thaw_percentage updates correctly",
1521     T_META_ASROOT(true),
1522     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1523     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1524 	__block dispatch_source_t first_signal_block;
1525 	/* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1526 	set_testing_pid();
1527 	reset_interval();
1528 
1529 	/* Spawn one child that will remain frozen throughout the whole test & another that will be thawed. */
1530 	first_signal_block = run_block_after_signal(SIGUSR1, ^{
1531 		move_to_idle_band(second_child);
1532 		__block int ret = pid_suspend(second_child);
1533 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1534 		freeze_process(second_child);
1535 		T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage is still 0 after freeze");
1536 		dispatch_source_cancel(first_signal_block);
1537 		test_after_background_helper_launches(true, "frozen_background", ^{
1538 			reset_interval();
1539 			T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a thaw percentage of 0");
1540 			move_to_idle_band(child_pid);
1541 			ret = pid_suspend(child_pid);
1542 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1543 			freeze_process(child_pid);
1544 			ret = pid_resume(child_pid);
1545 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1546 			int percentage_after_thaw = memorystatus_freezer_thaw_percentage();
1547 			T_QUIET; T_ASSERT_GT(percentage_after_thaw, 0, "thaw percentage is higher after thaw");
1548 
1549 			ret = pid_suspend(child_pid);
1550 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1551 			freeze_process(child_pid);
1552 			ret = pid_resume(child_pid);
1553 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1554 			T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), percentage_after_thaw, "thaw percentage is unchanged after second thaw");
1555 
1556 			ret = pid_suspend(child_pid);
1557 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1558 			freeze_process(child_pid);
1559 			reset_interval();
1560 			T_QUIET; T_ASSERT_EQ(memorystatus_freezer_thaw_percentage(), 0, "new interval starts with a 0 thaw percentage");
1561 			ret = pid_resume(child_pid);
1562 			T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1563 			T_QUIET; T_ASSERT_GT(memorystatus_freezer_thaw_percentage(), 0, "thaw percentage goes back up in new interval");
1564 
1565 			T_QUIET; T_ASSERT_POSIX_SUCCESS(kill(child_pid, SIGKILL), "failed to kill child");
1566 			T_END;
1567 		});
1568 	});
1569 
1570 	second_child = launch_background_helper("frozen_background");
1571 	T_ATEND(cleanup_memorystatus_freezer_thaw_percentage);
1572 	dispatch_activate(first_signal_block);
1573 	dispatch_main();
1574 }
1575 
1576 static uint64_t
1577 get_budget_pages_remaining(void)
1578 {
1579 	uint64_t pages_remaining = 0;
1580 	size_t size = sizeof(pages_remaining);
1581 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", &pages_remaining, &size, NULL, 0),
1582 	    "get kern.memorystatus_freeze_budget_pages_remaining");
1583 	return pages_remaining;
1584 }
1585 
1586 static void
1587 set_budget_pages_remaining(uint64_t pages_remaining)
1588 {
1589 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("kern.memorystatus_freeze_budget_pages_remaining", NULL, NULL, &pages_remaining, sizeof(pages_remaining)),
1590 	    "get kern.memorystatus_freeze_budget_pages_remaining");
1591 }
1592 
1593 static void
1594 enable_freeze(void)
1595 {
1596 	int freeze_enabled = 1;
1597 	size_t length = sizeof(freeze_enabled);
1598 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, length),
1599 	    "enable vm.freeze_enabled");
1600 }
1601 
1602 T_DECL(memorystatus_freeze_budget_multiplier, "memorystatus_budget_multiplier multiplies budget",
1603     T_META_ASROOT(true),
1604     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1605     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1606 	/* Disable freeze so that the budget doesn't change out from underneath us. */
1607 	int freeze_enabled = 0;
1608 	size_t length = sizeof(freeze_enabled);
1609 	uint64_t freeze_daily_pages_max;
1610 	T_ATEND(enable_freeze);
1611 	T_QUIET; T_ASSERT_POSIX_SUCCESS(sysctlbyname("vm.freeze_enabled", NULL, NULL, &freeze_enabled, length),
1612 	    "disable vm.freeze_enabled");
1613 	freeze_daily_pages_max = get_freeze_daily_pages_max();
1614 	original_budget_multiplier = get_budget_multiplier();
1615 	T_ATEND(reset_budget_multiplier);
1616 	set_budget_multiplier(100);
1617 	T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max, "multiplier=100%%");
1618 	set_budget_multiplier(50);
1619 	T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max / 2, "multiplier=50%%");
1620 	set_budget_multiplier(200);
1621 	T_QUIET; T_ASSERT_EQ(get_budget_pages_remaining(), freeze_daily_pages_max * 2, "multiplier=200%%");
1622 }
1623 
1624 T_DECL(memorystatus_freeze_set_dasd_trial_identifiers, "set dasd trial identifiers",
1625     T_META_ASROOT(true)) {
1626 #define TEST_STR "freezer-das-trial"
1627 	memorystatus_freezer_trial_identifiers_v1 identifiers = {0};
1628 	identifiers.version = 1;
1629 	strncpy(identifiers.treatment_id, TEST_STR, sizeof(identifiers.treatment_id));
1630 	strncpy(identifiers.experiment_id, TEST_STR, sizeof(identifiers.treatment_id));
1631 	identifiers.deployment_id = 2;
1632 	int ret = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_SET_DASD_TRIAL_IDENTIFIERS, &identifiers, sizeof(identifiers));
1633 	T_WITH_ERRNO; T_ASSERT_EQ(ret, 0, "FREEZER_CONTROL_SET_DASD_TRIAL_IDENTIFIERS");
1634 }
1635 
1636 T_DECL(memorystatus_reset_freezer_state, "FREEZER_CONTROL_RESET_STATE kills frozen proccesses",
1637 	T_META_ASROOT(true),
1638 	T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1639 	T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1640 	/* Take ownership of the freezer probabilities for the duration of the test so that nothing new gets frozen by dasd. */
1641 	set_testing_pid();
1642 	reset_interval();
1643 
1644 	test_after_background_helper_launches(false, "frozen_background", ^{
1645 		proc_name_t name;
1646 		int ret;
1647 		/* Freeze the child and verify they're frozen. */
1648 		move_to_idle_band(child_pid);
1649 		ret = pid_suspend(child_pid);
1650 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1651 		freeze_process(child_pid);
1652 		T_QUIET; T_ASSERT_TRUE(is_proc_in_frozen_list(child_pid, name, sizeof(name)), "Found proc in frozen list");
1653 		T_QUIET; T_EXPECT_EQ_STR(name, "memorystatus_freeze_test", "Proc has correct name");
1654 		/* Set the budget to 0. */
1655 		set_budget_pages_remaining(0);
1656 
1657 		/* FREEZER_CONTROL_RESET_STATE */
1658 		ret = memorystatus_control(MEMORYSTATUS_CMD_FREEZER_CONTROL, 0, FREEZER_CONTROL_RESET_STATE, NULL, 0);
1659 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "FREEZER_CONRTOL_RESET_STATE");
1660 
1661 		/* Verify budget > 0 */
1662 		uint64_t budget_after_reset = get_budget_pages_remaining();
1663 		T_QUIET; T_ASSERT_GT(budget_after_reset, 0ULL, "freeze budget after reset > 0");
1664 		/*
1665 		 * Verify child has been killed
1666 		 * Note that the task termination isn't synchronous with the RESET_STATE call so we may
1667 		 * block in waitpid temporarily.
1668 		 */
1669 		int stat;
1670 		while (true) {
1671 			pid_t wait_p = waitpid(child_pid, &stat, 0);
1672 			if (wait_p == child_pid) {
1673 				break;
1674 			}
1675 		}
1676 		T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(stat), "child was signaled");
1677 		T_QUIET; T_ASSERT_EQ(WTERMSIG(stat), SIGKILL, "Child received SIGKILL");
1678 
1679 		T_END;
1680 	});
1681 	dispatch_main();
1682 }
1683 
1684 static void
1685 dock_proc(pid_t pid)
1686 {
1687 	int ret;
1688 	ret = memorystatus_control(MEMORYSTATUS_CMD_ELEVATED_INACTIVEJETSAMPRIORITY_ENABLE, pid, 0, NULL, 0);
1689 	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "dock_proc");
1690 }
1691 
1692 T_DECL(memorystatus_freeze_skip_docked, "memorystatus_freeze_top_process does not freeze docked processes",
1693     T_META_ASROOT(true),
1694     T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1695     T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1696 	memorystatus_freeze_top_process_setup();
1697 	enable_ordered_freeze_mode();
1698 	test_after_background_helper_launches(true, "frozen_background", ^{
1699 		int ret, val = 1;
1700 		memorystatus_properties_freeze_entry_v1 entries[1];
1701 
1702 		dock_proc(child_pid);
1703 		construct_child_freeze_entry(&entries[0]);
1704 		prepare_proc_for_freezing(child_pid);
1705 
1706 		T_LOG("Telling kernel to freeze %s", entries[0].proc_name);
1707 		ret = memorystatus_control(MEMORYSTATUS_CMD_GRP_SET_PROPERTIES, 0, MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY, entries, sizeof(entries));
1708 		T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "MEMORYSTATUS_FLAGS_GRP_SET_FREEZE_PRIORITY");
1709 
1710 		ret = sysctlbyname("vm.memorystatus_freeze_top_process", NULL, NULL, &val, sizeof(val));
1711 		T_ASSERT_EQ(errno, ESRCH, "freeze_top_process errno");
1712 		T_ASSERT_EQ(ret, -1, "freeze_top_process");
1713 
1714 		verify_proc_not_frozen(child_pid);
1715 		resume_and_kill_proc(child_pid);
1716 
1717 		T_END;
1718 	});
1719 	dispatch_main();
1720 }
1721 
1722 T_HELPER_DECL(corpse_generation, "Generate a large corpse", T_META_ASROOT(false)) {
1723 	/*
1724 	 * Allocate and fault in a bunch of memory so that it takes a while
1725 	 * to generate our corpse.
1726 	 */
1727 	size_t bytes_to_allocate = 304 * (1UL << 20);
1728 	size_t block_size = 8 * (1UL << 20);
1729 	for (size_t i = 0; i < bytes_to_allocate / block_size; i++) {
1730 		unsigned char *ptr = malloc(block_size);
1731 		if (ptr == NULL) {
1732 			T_LOG("Unable to allocate memory in child process!");
1733 			exit(UNABLE_TO_ALLOCATE);
1734 		}
1735 		for (size_t j = 0; j < block_size / vm_page_size; j++) {
1736 			*ptr = (unsigned char) j;
1737 			ptr += vm_page_size;
1738 		}
1739 	}
1740 	dispatch_source_t ds_signal = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue());
1741 	if (ds_signal == NULL) {
1742 		T_LOG("Unable to create dispatch source");
1743 		exit(DISPATCH_SOURCE_CREATE_FAILED);
1744 	}
1745 	dispatch_source_set_event_handler(ds_signal, ^{
1746 		uint64_t val = 1;
1747 		/*
1748 		 * We should now be frozen.
1749 		 * Simulate a crash so that our P_MEMSTAT_SKIP bit gets set temporarily.
1750 		 * The parent process will try to kill us due to disk space shortage in parallel.
1751 		 */
1752 		os_fault_with_payload(OS_REASON_LIBSYSTEM, OS_REASON_LIBSYSTEM_CODE_FAULT,
1753 		    &val, sizeof(val), "freeze_test", 0);
1754 	});
1755 	dispatch_activate(ds_signal);
1756 
1757 	sig_t sig_ret = signal(SIGUSR1, SIG_IGN);
1758 	T_QUIET; T_WITH_ERRNO; T_ASSERT_NE(sig_ret, SIG_ERR, "signal(SIGUSR1, SIG_IGN)");
1759 
1760 	/* Signal to our parent that we can be frozen */
1761 	if (kill(getppid(), SIGUSR1) != 0) {
1762 		T_LOG("Unable to signal to parent process!");
1763 		exit(SIGNAL_TO_PARENT_FAILED);
1764 	}
1765 	dispatch_main();
1766 }
1767 
1768 T_DECL(memorystatus_disable_freeze_corpse, "memorystatus_disable_freeze with parallel corpse creation",
1769 	T_META_ASROOT(true),
1770 	T_META_REQUIRES_SYSCTL_EQ("kern.development", 1),
1771 	T_META_REQUIRES_SYSCTL_EQ("vm.freeze_enabled", 1)) {
1772 	/*
1773 	 * In the past, we've had race conditions w.r.t. killing on disk space shortage
1774 	 * and corpse generation of frozen processes.
1775 	 * This test spwans a frozen helper
1776 	 * which generates a large corpse (which should take in the 10s to 100s of m.s. to complete)
1777 	 * while the test process triggers disk space kills.
1778 	 * We should see that the test process is jetsammed successfully.
1779 	 */
1780 	test_after_background_helper_launches(false, "corpse_generation", ^{
1781 		int ret, val, stat;
1782 		/* Place the child in the idle band so that it gets elevated like a typical app. */
1783 		move_to_idle_band(child_pid);
1784 		ret = pid_suspend(child_pid);
1785 		T_ASSERT_POSIX_SUCCESS(ret, "child suspended");
1786 		freeze_process(child_pid);
1787 
1788 		ret = pid_resume(child_pid);
1789 		T_ASSERT_POSIX_SUCCESS(ret, "child resumed after freeze");
1790 
1791 		kill(child_pid, SIGUSR1);
1792 
1793 		T_ATEND(enable_freeze);
1794 		val = 0;
1795 		ret = sysctlbyname("vm.freeze_enabled", NULL, NULL, &val, sizeof(val));
1796 		T_ASSERT_POSIX_SUCCESS(ret, "freeze disabled");
1797 		/*
1798 		 * Verify child has been killed
1799 		 * Note that the task termination isn't synchronous with the freeze_enabled call so we may
1800 		 * block in waitpid temporarily.
1801 		 */
1802 		while (true) {
1803 			pid_t wait_p = waitpid(child_pid, &stat, 0);
1804 			if (wait_p == child_pid) {
1805 				break;
1806 			}
1807 		}
1808 		T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(stat), "child was signaled");
1809 		T_QUIET; T_ASSERT_EQ(WTERMSIG(stat), SIGKILL, "Child received SIGKILL");
1810 
1811 		T_END;
1812 	});
1813 	dispatch_main();
1814 }
1815