]>
Commit | Line | Data |
---|---|---|
39236c6e A |
1 | #include <asl.h> |
2 | #include <assert.h> | |
3 | #include <fcntl.h> | |
4 | #include <pthread.h> | |
5 | #include <signal.h> | |
6 | #include <stdio.h> | |
7 | #include <stdlib.h> | |
8 | #include <string.h> | |
9 | #include <time.h> | |
10 | #include <unistd.h> | |
11 | ||
12 | #include <libproc.h> | |
13 | ||
14 | #include <mach/mach.h> | |
15 | #include <mach/mach_types.h> | |
16 | #include <mach/mach_vm.h> | |
17 | #include <mach/shared_region.h> | |
18 | #include <mach/task_info.h> | |
19 | #include <mach/vm_map.h> | |
fe8ab488 | 20 | #include <mach/vm_page_size.h> /* Needed for vm_region info */ |
39236c6e A |
21 | |
22 | #include <sys/event.h> | |
23 | #include <sys/ipc.h> | |
24 | #include <sys/kern_memorystatus.h> | |
25 | #include <sys/mman.h> | |
26 | #include <sys/shm.h> | |
27 | #include <sys/stat.h> | |
28 | #include <sys/sysctl.h> | |
29 | #include <sys/wait.h> | |
30 | ||
31 | #include <xpc/xpc.h> | |
32 | #include <xpc/private.h> | |
33 | ||
34 | #include <CoreFoundation/CoreFoundation.h> | |
35 | ||
36 | #include <Security/Security.h> | |
37 | #include <ServiceManagement/ServiceManagement.h> | |
38 | #include <ServiceManagement/SMErrors.h> | |
39 | ||
40 | #include <Kernel/kern/ledger.h> | |
41 | ||
42 | #include <sys/spawn_internal.h> | |
43 | #include <spawn_private.h> | |
44 | ||
45 | #define CR_JOB "com.apple.ReportCrash.Jetsam" | |
46 | #define CR_JOB_PLIST_PATH "/System/Library/LaunchDaemons/com.apple.ReportCrash.Jetsam.plist" | |
47 | ||
48 | #define ERR_BUF_LEN 1024 | |
49 | ||
50 | #ifndef VM_PAGE_SIZE | |
51 | #define VM_PAGE_SIZE 4096 | |
52 | #endif | |
53 | ||
fe8ab488 A |
54 | #define TASK_LIMIT_MB 75 |
55 | #define HWM_LIMIT_MB 8 | |
56 | ||
57 | /* | |
58 | * Blob of data that is not easily compressed. | |
59 | * Guaranteed during setup to be at least | |
60 | * RANDOM_DATA_SIZE in length. | |
61 | */ | |
62 | ||
63 | #define RANDOM_DATA_SIZE 4096 | |
64 | char random_data[] = "ffd8ffe000104a46494600010101002400240000ffe100744578696600004d4d002a000000080004011a0005000000010000003e011b0005000000010000004601280003000000010002000087690004000000010000004e00000000000000240000000100000024000000010002a002000400000001000003c0a003000400000001000001ff00000000ffdb00430002020202020102020202020202030306040303030307050504060807080808070808090a0d0b09090c0a08080b0f0b0c0d0e0e0e0e090b10110f0e110d0e0e0effdb004301020202030303060404060e0908090e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0effc000110801ff03c003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00f9e74fbd37baa2db99e6506391f28371f9519ba67fd9fcabd46cbc1315de8d6776752d7419e049084b152a37283c1dfc8e6bc02db4af18d9df79c9e1bd59a40ae9b65b1761f32953c63ae09c7a1c57656fe24f8896da7c16c9e0bb3748a358d5a4d04b31006324f73c75a00935f7fec9f165ee98b7372e2ddc05795763f2a0f20138ebeb590bac3e70d2b6e1fed1ac6d4ecbc65aa6b973a85c7867528a6998168edec1a38c1c01c2f61c550fec1f16ff00d0bdade4f5ff00447ff0a00eaffb5dbfe7abfe668fed76ff009eaff99ae57fb07c5bff0042f6b7ff00808ffe147f60f8b7fe85ed6fff00011ffc2803aafed76ff9eaff0099a3fb5dbfe7abfe66b95fec1f16ff00d0bdadff00e023ff00851fd83e2dff00a17b5bff00c047ff000a00eabfb5dbfe7abfe668fed76ff9eaff0099ae57fb07c5bff42f6b7ff808ff00e147f60f8b7fe85ed6ff00f011ff00c2803aafed76ff009eaff99a3fb5dbfe7abfe66b95fec1f16ffd0bdadffe023ff851fd83e2dffa17b5bffc047ff0a00eabfb5dbfe7abfe668fed76ff009eaff99ae57fb07c5bff0042f6b7ff00808ffe147f60f8b7fe85ed6fff00011ffc2803aafed76ff9eaff0099a3fb5dbfe7abfe66b95fec1f16ff00d0bdadff00e023ff00851fd83e2dff00a17b5bff00c047ff000a00eabfb5dbfe7abfe668fed76ff9eaff0099ae57fb07c5bff42f6b7ff808ff00e147f60f8b7fe85ed6ff00f011ff00c2803aafed76ff009eaff99a3fb5dbfe7abfe66b95fec1f16ffd0bdadffe023ff851fd83e2dffa17b5bffc047ff0a00eabfb5dbfe7abfe668fed76ff009eaff99ae57fb07c5bff0042f6b7ff00808ffe147f60f8b7fe85ed6fff00011ffc2803aafed76ff9eaff0099a3fb5dbfe7abfe66b95fec1f16ff00d0bdadff00e023ff00851fd83e2dff00a17b5bff00c047ff000a00eabfb5dbfe7abfe668fed76ff9eaff0099ae57fb07c5bff42f6b7ff808ff00e147f60f8b7fe85ed6ff00f011ff00c2803aafed76ff009eaff99a3fb5dbfe7abfe66b95fec1f16ffd0bdadffe023ff851fd83e2dffa17b5bffc047ff0a00eabfb5dbfe7abfe668fed76ff009eaff99ae57fb07c5bff0042f6b7ff00808ffe147f60f8b7fe85ed6fff00011ffc2803aafed76ff9eaff0099a3fb5dbfe7abfe66b95fec1f16ff00d0bdadff00e023ff00851fd83e2dff00a17b5bff00c047ff000a00eabfb5dbfe7abfe668fed76ff9eaff0099ae57fb07c5bff42f6b7ff808ff00e147f60f8b7fe85ed6ff00f011ff00c2803aafed76ff009eaff99a3fb5dbfe7abfe66b95fec1f16ffd0bdadffe023ff851fd83e2dffa17b5bffc047ff0a00eabfb5dbfe7abfe668fed76ff009eaff99ae57fb07c5bff0042f6b7ff00808ffe147f60f8b7fe85ed6fff00011ffc2803aafed76ff9eaff0099a3fb5dbfe7abfe66b95fec1f16ff00d0bdadff00e023ff00851fd83e2dff00a17b5bff00c047ff000a00eabfb5dbfe7abfe668fed76ff9eaff0099ae57fb07c5bff42f6b7ff808ff00e147f60f8b7fe85ed6ff00f011ff00c2803aafed76ff009eaff99a3fb5dbfe7abfe66b95fec1f16ffd0bdadffe023ff851fd83e2dffa17b5bffc047ff0a00eabfb5dbfe7abfe668fed76ff009eaff99ae57fb07c5bff0042f6b7ff00808ffe147f60f8b7fe85ed6fff00011ffc2803aafed76ff9eaff0099a3fb5dbfe7abfe66b95fec1f16ff00d0bdadff00e023ff00851fd83e2dff00a17b5bff00c047ff000a00eabfb5dbfe7abfe668fed76ff9eaff0099ae57fb07c5bff42f6b7ff808ff00e147f60f8b7fe85ed6ff00f011ff00c2803aafed76ff009eaff99a3fb5dbfe7abfe66b95fec1f"; | |
65 | ||
39236c6e A |
66 | /* |
67 | * TODO: import header (currently vm_pageout.h) without pulling in extraneous definitions; | |
68 | * see <rdar://problem/13374916>. | |
69 | */ | |
70 | #ifndef VM_PAGER_FREEZER_DEFAULT | |
71 | #define VM_PAGER_FREEZER_DEFAULT 0x8 /* Freezer backed by default pager.*/ | |
72 | #endif | |
73 | ||
74 | /* | |
75 | * Special note to ourselves: the jetsam cause to look out for is *either* | |
76 | * a high watermark kill, *or* a per-process kill. | |
77 | */ | |
78 | #define CAUSE_HIWAT_OR_PERPROC -1 | |
79 | ||
80 | typedef enum jetsam_test { | |
81 | kSimpleJetsamTest = 1, | |
fe8ab488 | 82 | kCustomTaskLimitTest, |
39236c6e A |
83 | kPressureJetsamTestFG, |
84 | kPressureJetsamTestBG, | |
85 | kHighwaterJetsamTest, | |
86 | kVnodeJetsamTest, | |
87 | kBackgroundJetsamTest | |
88 | } jetsam_test_t; | |
89 | ||
90 | typedef enum idle_exit_test { | |
91 | kDeferTimeoutCleanTest = 1, | |
92 | kDeferTimeoutDirtyTest, | |
93 | kCancelTimeoutCleanTest, | |
94 | kCancelTimeoutDirtyTest | |
95 | } idle_exit_test_t; | |
96 | ||
97 | typedef struct shared_mem_t { | |
98 | pthread_mutex_t mutex; | |
99 | pthread_cond_t cv; | |
100 | boolean_t completed; | |
101 | boolean_t pressure_event_fired; | |
fe8ab488 | 102 | boolean_t child_failed; |
39236c6e A |
103 | } shared_mem_t; |
104 | ||
105 | shared_mem_t *g_shared = NULL; | |
106 | unsigned long g_physmem = 0; | |
fe8ab488 | 107 | int g_compressor_mode=0; |
39236c6e A |
108 | int g_ledger_count = -1, g_footprint_index = -1; |
109 | int64_t g_per_process_limit = -1; | |
110 | ||
fe8ab488 A |
111 | /* |
112 | * g_exit_status: | |
113 | * Holds the PASS/FAIL status of the memorystatus | |
114 | * test run as a whole. | |
115 | * e.g: If one subtest reports failure, the entire | |
116 | * test run reports failure. | |
117 | * | |
118 | * PASS: returns 0 (default) | |
119 | * FAIL: returns -1 | |
120 | * | |
121 | * The only time the g_exit_status changes state | |
122 | * is when printTestResult() reports a FAIL status. | |
123 | */ | |
124 | int g_exit_status = 0; | |
125 | ||
39236c6e A |
126 | |
127 | extern int ledger(int cmd, caddr_t arg1, caddr_t arg2, caddr_t arg3); | |
128 | static boolean_t check_properties(pid_t pid, int32_t requested_priority, int32_t requested_limit_mb, uint64_t requested_user_data, const char *test); | |
129 | ||
130 | /* Utilities. */ | |
131 | ||
132 | static void | |
133 | printTestHeader(pid_t testPid, const char *testName, ...) | |
134 | { | |
135 | va_list va; | |
136 | printf("========================================\n"); | |
137 | printf("[TEST] "); | |
138 | va_start(va, testName); | |
139 | vprintf(testName, va); | |
140 | va_end(va); | |
141 | printf("\n"); | |
142 | printf("[PID] %d\n", testPid); | |
143 | printf("========================================\n"); | |
144 | printf("[BEGIN]\n"); | |
fe8ab488 | 145 | fflush(stdout); |
39236c6e A |
146 | } |
147 | ||
148 | static void | |
149 | printTestResult(const char *testName, boolean_t didPass, const char *msg, ...) | |
150 | { | |
151 | if (msg != NULL) { | |
152 | va_list va; | |
153 | printf("\t\t"); | |
154 | va_start(va, msg); | |
155 | vprintf(msg, va); | |
156 | va_end(va); | |
157 | printf("\n"); | |
158 | } | |
159 | if (didPass) { | |
160 | printf("[PASS]\t%s\n\n", testName); | |
161 | } else { | |
162 | printf("[FAIL]\t%s\n\n", testName); | |
fe8ab488 A |
163 | |
164 | /* Any single failure, fails full test run */ | |
165 | g_exit_status = -1; | |
39236c6e | 166 | } |
fe8ab488 A |
167 | fflush(stdout); |
168 | } | |
169 | ||
170 | static int | |
171 | _get_munch_interval(int given_interval) | |
172 | { | |
173 | int res; | |
174 | int new_interval=0; | |
175 | char *slow_device; | |
176 | char model_name_buf[1025]; | |
177 | size_t mnb_size = 1024; | |
178 | res = sysctlbyname("hw.model", model_name_buf, &mnb_size, NULL, 0); | |
179 | ||
180 | if (res) { | |
181 | perror("\t\tsysctlbyname(hw.model...)"); | |
182 | } | |
183 | else { | |
184 | /* see if we're a slow device (N90, K66, J33) */ | |
185 | slow_device = strstr(model_name_buf, "N90"); | |
186 | if (slow_device == NULL) { | |
187 | slow_device = strstr(model_name_buf, "K66"); | |
188 | } | |
189 | if (slow_device == NULL) { | |
190 | slow_device = strstr(model_name_buf, "J33"); | |
191 | } | |
192 | ||
193 | if (slow_device != NULL) { | |
194 | printf("\t\tRunning on a slow device...\n"); | |
195 | } | |
196 | ||
197 | if (given_interval == 0) { | |
198 | if (slow_device != NULL) { | |
199 | new_interval = 500 * 1000; /* want sleep time in microseconds */ | |
200 | } | |
201 | else { | |
202 | new_interval = 100 * 1000;/* want sleep time in microseconds */ | |
203 | } | |
204 | } | |
205 | else { | |
206 | new_interval = given_interval * USEC_PER_SEC; | |
207 | } | |
208 | } | |
209 | ||
210 | return new_interval; | |
39236c6e A |
211 | } |
212 | ||
213 | static CFDictionaryRef create_dictionary_from_plist(const char *path) { | |
214 | void *bytes = NULL; | |
215 | CFDataRef data = NULL; | |
216 | CFDictionaryRef options = NULL; | |
217 | size_t bufLen; | |
218 | int fd = open(path, O_RDONLY, 0); | |
219 | if (fd == -1) { | |
220 | goto exit; | |
221 | } | |
222 | struct stat sb; | |
223 | if (fstat(fd, &sb) == -1) { | |
224 | goto exit; | |
225 | } | |
226 | ||
227 | bufLen = (size_t)sb.st_size; | |
228 | bytes = malloc(bufLen); | |
229 | if (bytes == NULL) { | |
230 | goto exit; | |
231 | } | |
232 | ||
233 | if (read(fd, bytes, bufLen) != bufLen) { | |
234 | goto exit; | |
235 | } | |
236 | ||
237 | data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *) bytes, bufLen, kCFAllocatorNull); | |
238 | if (data == NULL) { | |
239 | goto exit; | |
240 | } | |
241 | ||
fe8ab488 | 242 | options = (CFDictionaryRef) CFPropertyListCreateWithData(kCFAllocatorDefault, data, kCFPropertyListImmutable, NULL, NULL); |
39236c6e | 243 | if (options == NULL) { |
fe8ab488 | 244 | goto exit; |
39236c6e A |
245 | } |
246 | ||
247 | exit: | |
248 | if (data != NULL) { | |
249 | CFRelease(data); | |
250 | } | |
251 | if (bytes != NULL) { | |
252 | free(bytes); | |
253 | } | |
254 | if (fd != -1) { | |
255 | close(fd); | |
256 | } | |
257 | ||
258 | return options; | |
259 | } | |
260 | ||
39236c6e | 261 | |
fe8ab488 A |
262 | /* |
263 | * cleanup_and_exit(): | |
264 | * The parent process can call this routine to exit or abort | |
265 | * the test run at any time. | |
266 | * | |
267 | * The child process on the other hand should not call this routine. | |
268 | * Be mindful about how re-enabling the crashreporter can affect tests | |
269 | * further down the line. | |
270 | */ | |
39236c6e | 271 | static void cleanup_and_exit(int status) { |
39236c6e A |
272 | |
273 | /* Exit. Pretty literal. */ | |
274 | exit(status); | |
275 | } | |
276 | ||
fe8ab488 A |
277 | /* |
278 | * child_ready(): | |
279 | * After a child process takes care of its inital setup, it | |
280 | * synchronizes back to the parent using this call. | |
281 | * | |
282 | * If the child process experiences a failure during its | |
283 | * intial setup, it should abort using a standard exit | |
284 | * routine, leaving crashreporter cleanup to the parent. | |
285 | * | |
286 | * The child should never call cleanup_and_exit(). | |
287 | * That's for the parent only. | |
288 | */ | |
39236c6e A |
289 | static void child_ready() { |
290 | pthread_mutex_lock(&g_shared->mutex); | |
291 | pthread_cond_signal(&g_shared->cv); | |
292 | pthread_mutex_unlock(&g_shared->mutex); | |
293 | } | |
294 | ||
295 | static pid_t init_and_fork() { | |
296 | int pid; | |
fe8ab488 | 297 | |
39236c6e A |
298 | g_shared->completed = 0; |
299 | g_shared->pressure_event_fired = 0; | |
300 | ||
301 | pthread_mutex_lock(&g_shared->mutex); | |
302 | ||
303 | pid = fork(); | |
304 | if (pid == 0) { | |
305 | return 0; | |
306 | } else if (pid == -1) { | |
fe8ab488 | 307 | printTestResult(__func__, false, "Fork error!"); |
39236c6e A |
308 | cleanup_and_exit(-1); |
309 | } | |
310 | ||
311 | /* Wait for child's signal */ | |
312 | pthread_cond_wait(&g_shared->cv, &g_shared->mutex); | |
313 | pthread_mutex_unlock(&g_shared->mutex); | |
314 | return (pid_t)pid; | |
315 | } | |
316 | ||
317 | static memorystatus_priority_entry_t *get_priority_list(int *size) { | |
318 | memorystatus_priority_entry_t *list = NULL; | |
319 | ||
320 | assert(size); | |
321 | ||
322 | *size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, NULL, 0); | |
323 | if (*size <= 0) { | |
324 | printf("\t\tCan't get list size: %d!\n", *size); | |
325 | goto exit; | |
326 | } | |
327 | ||
328 | list = (memorystatus_priority_entry_t*)malloc(*size); | |
329 | if (!list) { | |
330 | printf("\t\tCan't allocate list!\n"); | |
331 | goto exit; | |
332 | } | |
333 | ||
334 | *size = memorystatus_control(MEMORYSTATUS_CMD_GET_PRIORITY_LIST, 0, 0, list, *size); | |
335 | if (*size <= 0) { | |
336 | printf("\t\tCan't retrieve list!\n"); | |
337 | goto exit; | |
338 | } | |
339 | ||
340 | exit: | |
341 | return list; | |
342 | } | |
343 | ||
344 | /* Tests */ | |
345 | ||
39236c6e A |
346 | |
347 | static boolean_t get_ledger_info(pid_t pid, int64_t *balance_mb, int64_t *limit_mb) { | |
348 | struct ledger_entry_info *lei; | |
349 | uint64_t count; | |
350 | boolean_t res = false; | |
351 | ||
352 | lei = (struct ledger_entry_info *)malloc((size_t)(g_ledger_count * sizeof (*lei))); | |
353 | if (lei) { | |
354 | void *arg; | |
355 | ||
356 | arg = (void *)(long)pid; | |
357 | count = g_ledger_count; | |
358 | ||
359 | if ((ledger(LEDGER_ENTRY_INFO, arg, (caddr_t)lei, (caddr_t)&count) >= 0) && (g_footprint_index < count)) { | |
360 | if (balance_mb) { | |
361 | *balance_mb = lei[g_footprint_index].lei_balance; | |
362 | } | |
363 | if (limit_mb) { | |
364 | *limit_mb = lei[g_footprint_index].lei_limit; | |
365 | } | |
366 | res = true; | |
367 | } | |
368 | ||
369 | free(lei); | |
370 | } | |
371 | ||
372 | return res; | |
373 | } | |
374 | ||
375 | static boolean_t get_priority_props(pid_t pid, int32_t *priority, int32_t *limit_mb, uint64_t *user_data) { | |
376 | int size; | |
377 | memorystatus_priority_entry_t *entries = NULL; | |
378 | int i; | |
379 | boolean_t res = false; | |
380 | ||
381 | entries = get_priority_list(&size); | |
382 | if (!entries) { | |
383 | goto exit; | |
384 | } | |
385 | ||
386 | /* Locate */ | |
387 | for (i = 0; i < size/sizeof(memorystatus_priority_entry_t); i++ ){ | |
388 | if (entries[i].pid == pid) { | |
389 | int64_t limit; | |
390 | ||
391 | *priority = entries[i].priority; | |
392 | *user_data = entries[i].user_data; | |
393 | #if 1 | |
394 | *limit_mb = entries[i].limit; | |
395 | res = true; | |
396 | #else | |
397 | res = get_ledger_info(entries[i].pid, NULL, &limit); | |
398 | if (false == res) { | |
399 | printf("Failed to get highwater!\n"); | |
400 | } | |
401 | /* The limit is retrieved in bytes, but set in MB, so rescale */ | |
402 | *limit_mb = (int32_t)(limit/(1024 * 1024)); | |
403 | #endif | |
404 | goto exit; | |
405 | } | |
406 | } | |
407 | ||
408 | printf("\t\tCan't find pid: %d!\n", pid); | |
409 | ||
410 | exit: | |
fe8ab488 A |
411 | if (entries) |
412 | free(entries); | |
39236c6e A |
413 | |
414 | return res; | |
415 | } | |
416 | ||
417 | static boolean_t check_properties(pid_t pid, int32_t requested_priority, int32_t requested_limit_mb, uint64_t requested_user_data, const char *test) { | |
418 | const char *PROP_GET_ERROR_STRING = "failed to get properties"; | |
419 | const char *PROP_CHECK_ERROR_STRING = "property mismatch"; | |
420 | ||
421 | int32_t actual_priority, actual_hiwat; | |
422 | uint64_t actual_user_data; | |
423 | ||
424 | if (!get_priority_props(pid, &actual_priority, &actual_hiwat, &actual_user_data)) { | |
425 | printf("\t\t%s test failed: %s\n", test, PROP_GET_ERROR_STRING); | |
426 | return false; | |
427 | } | |
428 | ||
429 | /* -1 really means the default per-process limit, which varies per device */ | |
430 | if (requested_limit_mb <= 0) { | |
fe8ab488 | 431 | requested_limit_mb = (int32_t)g_per_process_limit; |
39236c6e A |
432 | } |
433 | ||
434 | if (actual_priority != requested_priority || actual_hiwat != requested_limit_mb || actual_user_data != requested_user_data) { | |
435 | printf("\t\t%s test failed: %s\n", test, PROP_CHECK_ERROR_STRING); | |
436 | printf("priority is %d, should be %d\n", actual_priority, requested_priority); | |
437 | printf("hiwat is %d, should be %d\n", actual_hiwat, requested_limit_mb); | |
438 | printf("user data is 0x%llx, should be 0x%llx\n", actual_user_data, requested_user_data); | |
439 | return false; | |
440 | } | |
441 | ||
442 | printf("\t\t%s test ok...\n", test); | |
443 | ||
444 | return true; | |
445 | } | |
446 | ||
39236c6e A |
447 | |
448 | static void start_list_validation_test() { | |
449 | int size; | |
450 | memorystatus_priority_entry_t *entries = NULL; | |
451 | int i; | |
452 | boolean_t valid = false; | |
453 | ||
454 | printTestHeader(getpid(), "List validation test"); | |
455 | ||
456 | entries = get_priority_list(&size); | |
457 | if (!entries) { | |
458 | printf("Can't get entries!\n"); | |
459 | goto exit; | |
460 | } | |
461 | ||
462 | /* Validate */ | |
463 | for (i = 0; i < size/sizeof(memorystatus_priority_entry_t); i++ ) { | |
464 | int dirty_ret; | |
465 | uint32_t dirty_flags; | |
466 | ||
467 | /* Make sure launchd isn't in the list - <rdar://problem/13168754> */ | |
468 | if (entries[i].pid <= 1) { | |
469 | printf("\t\tBad process (%d) in list!\n", entries[i].pid); | |
470 | goto exit; | |
471 | } | |
472 | ||
473 | /* Sanity check idle exit state */ | |
474 | dirty_ret = proc_get_dirty(entries[i].pid, &dirty_flags); | |
475 | if (dirty_ret != 0) { | |
476 | dirty_flags = 0; | |
477 | } | |
478 | ||
479 | if (dirty_flags & PROC_DIRTY_ALLOWS_IDLE_EXIT) { | |
480 | /* Check that the process isn't at idle priority when dirty */ | |
481 | if ((entries[i].priority == JETSAM_PRIORITY_IDLE) && (dirty_flags & PROC_DIRTY_IS_DIRTY)) { | |
482 | printf("\t\tProcess %d at idle priority when dirty (priority %d, flags 0x%x)!\n", entries[i].pid, entries[i].priority, dirty_flags); | |
483 | goto exit; | |
484 | } | |
485 | /* Check that the process is at idle (or deferred) priority when clean. */ | |
486 | if ((entries[i].priority > JETSAM_PRIORITY_IDLE_DEFERRED) && !(dirty_flags & PROC_DIRTY_IS_DIRTY)) { | |
487 | printf("\t\tProcess %d not at non-idle priority when clean(priority %d, flags 0x%x)\n", entries[i].pid, entries[i].priority, dirty_flags); | |
488 | goto exit; | |
489 | } | |
490 | } | |
491 | } | |
492 | ||
493 | valid = true; | |
494 | ||
495 | exit: | |
fe8ab488 A |
496 | if (entries) |
497 | free(entries); | |
39236c6e A |
498 | |
499 | printTestResult("List validation test", valid, NULL); | |
500 | } | |
501 | ||
502 | /* Random individual tests */ | |
503 | static void start_general_sanity_test() { | |
504 | int ret, size; | |
39236c6e A |
505 | int i; |
506 | boolean_t valid = false; | |
fe8ab488 A |
507 | |
508 | /* | |
509 | * The sanity test checks for permission failures | |
510 | * against P_MEMSTAT_INTERNAL processes. | |
511 | * Currently only launchd (pid==1) qualifies. | |
512 | */ | |
39236c6e A |
513 | |
514 | printTestHeader(getpid(), "Sanity test"); | |
515 | ||
fe8ab488 A |
516 | |
517 | /* Ensure that launchd's transaction state is fixed */ | |
39236c6e A |
518 | ret = proc_track_dirty(1, PROC_DIRTY_TRACK | PROC_DIRTY_ALLOW_IDLE_EXIT | PROC_DIRTY_DEFER); |
519 | if (ret != EPERM) { | |
520 | printf("\t\tNo EPERM tracking launchd (%d/%d)!\n", ret, errno); | |
521 | goto exit; | |
522 | } else { | |
523 | printf("\t\tlaunchd track test OK!\n"); | |
524 | } | |
525 | ||
526 | ret = proc_set_dirty(1, true); | |
527 | if (ret != EPERM) { | |
528 | printf("\t\tNo EPERM setting launchd dirty state (%d/%d)!\n", ret, errno); | |
529 | goto exit; | |
530 | } else { | |
531 | printf("\t\tlaunchd dirty test OK!\n"); | |
532 | } | |
533 | ||
fe8ab488 | 534 | |
39236c6e A |
535 | valid = true; |
536 | ||
537 | exit: | |
fe8ab488 | 538 | printTestResult("Sanity test", valid, NULL); |
39236c6e A |
539 | } |
540 | ||
541 | static void idle_exit_deferral_test(idle_exit_test_t test) { | |
542 | int secs = DEFERRED_IDLE_EXIT_TIME_SECS; | |
543 | ||
544 | child_ready(); | |
545 | ||
546 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#1 - pre xpc_track_activity()")) { | |
547 | goto exit; | |
548 | } | |
549 | ||
550 | proc_track_dirty(getpid(), PROC_DIRTY_TRACK | PROC_DIRTY_ALLOW_IDLE_EXIT | PROC_DIRTY_DEFER); | |
551 | ||
552 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#2 - post xpc_track_activity()")) { | |
553 | goto exit; | |
554 | } | |
555 | ||
556 | /* Toggle */ | |
557 | proc_set_dirty(getpid(), true); | |
558 | proc_set_dirty(getpid(), false); | |
559 | proc_set_dirty(getpid(), true); | |
560 | proc_set_dirty(getpid(), false); | |
561 | ||
562 | switch (test) { | |
563 | case kDeferTimeoutCleanTest: | |
564 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#3 - post toggle")) { | |
565 | goto exit; | |
566 | } | |
567 | ||
568 | /* Approximate transition check */ | |
569 | sleep(secs - 1); | |
570 | ||
571 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#4 - pre timeout")) { | |
572 | goto exit; | |
573 | } | |
574 | ||
575 | sleep(2); | |
576 | ||
577 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#5 - post timeout")) { | |
578 | goto exit; | |
579 | } | |
580 | ||
581 | proc_set_dirty(getpid(), true); | |
582 | ||
583 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#6 - post dirty")) { | |
584 | goto exit; | |
585 | } | |
586 | ||
587 | proc_set_dirty(getpid(), false); | |
588 | ||
589 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#7 - post clean")) { | |
590 | goto exit; | |
591 | } | |
592 | ||
593 | break; | |
594 | case kDeferTimeoutDirtyTest: | |
595 | proc_set_dirty(getpid(), true); | |
596 | ||
597 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#3 - post dirty")) { | |
598 | goto exit; | |
599 | } | |
600 | ||
601 | /* Approximate transition check */ | |
602 | sleep(secs - 1); | |
603 | ||
604 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#4 - pre timeout")) { | |
605 | goto exit; | |
606 | } | |
607 | ||
608 | sleep(2); | |
609 | ||
610 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#5 - post timeout")) { | |
611 | goto exit; | |
612 | } | |
613 | ||
614 | proc_set_dirty(getpid(), false); | |
615 | ||
616 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#6 - post clean")) { | |
617 | goto exit; | |
618 | } | |
619 | ||
620 | break; | |
621 | case kCancelTimeoutDirtyTest: | |
622 | proc_set_dirty(getpid(), true); | |
623 | ||
624 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#3 - post toggle")) { | |
625 | goto exit; | |
626 | } | |
627 | ||
fe8ab488 | 628 | proc_clear_dirty(getpid(), PROC_DIRTY_DEFER); |
39236c6e A |
629 | |
630 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#4 - post deferral cancellation")) { | |
631 | goto exit; | |
632 | } | |
633 | ||
634 | proc_set_dirty(getpid(), false); | |
635 | ||
636 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#5 - post toggle")) { | |
637 | goto exit; | |
638 | } | |
639 | ||
640 | break; | |
641 | case kCancelTimeoutCleanTest: | |
642 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE_DEFERRED, -1, 0x0, "#3 - post toggle")) { | |
643 | goto exit; | |
644 | } | |
645 | ||
fe8ab488 | 646 | proc_clear_dirty(getpid(), PROC_DIRTY_DEFER); |
39236c6e A |
647 | |
648 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#4 - post deferral cancellation")) { | |
649 | goto exit; | |
650 | } | |
651 | ||
652 | proc_set_dirty(getpid(), true); | |
653 | ||
654 | if (!check_properties(getpid(), JETSAM_PRIORITY_DEFAULT, -1, 0x0, "#5 - post dirty")) { | |
655 | goto exit; | |
656 | } | |
657 | ||
658 | proc_set_dirty(getpid(), false); | |
659 | ||
660 | if (!check_properties(getpid(), JETSAM_PRIORITY_IDLE, -1, 0x0, "#6 - post clean")) { | |
661 | goto exit; | |
662 | } | |
663 | ||
664 | break; | |
665 | } | |
666 | ||
667 | g_shared->completed = 1; | |
fe8ab488 | 668 | exit(0); |
39236c6e A |
669 | |
670 | exit: | |
671 | printTestResult(__func__, false, "Something bad happened..."); | |
fe8ab488 | 672 | exit(-1); |
39236c6e A |
673 | } |
674 | ||
675 | static void start_idle_exit_defer_test(idle_exit_test_t test) { | |
676 | pid_t pid; | |
677 | int status; | |
678 | ||
679 | /* Reset */ | |
680 | memset(g_shared, 0, sizeof(shared_mem_t)); | |
681 | ||
682 | pid = init_and_fork(); | |
683 | if (pid == 0) { | |
684 | idle_exit_deferral_test(test); | |
685 | } | |
686 | else { | |
fe8ab488 | 687 | printTestHeader(pid, "Idle exit deferral test: %d", test); |
39236c6e A |
688 | } |
689 | ||
690 | /* Wait for exit */ | |
691 | waitpid(pid, &status, 0); | |
692 | /* Idle exit not reported on embedded */ | |
693 | // wait_for_exit_event(pid, kMemorystatusKilledIdleExit); | |
694 | ||
695 | printTestResult("Idle exit deferral test", g_shared->completed, NULL); | |
696 | } | |
697 | ||
698 | static void ledger_init(void) { | |
699 | const char *physFootprintName = "phys_footprint"; | |
700 | struct ledger_info li; | |
701 | int64_t template_cnt; | |
702 | struct ledger_template_info *templateInfo; | |
703 | void *arg; | |
704 | int i; | |
705 | ||
706 | /* Grab ledger entries */ | |
707 | arg = (void *)(long)getpid(); | |
708 | if (ledger(LEDGER_INFO, arg, (caddr_t)&li, NULL) < 0) { | |
709 | exit(-1); | |
710 | } | |
711 | ||
712 | g_ledger_count = template_cnt = li.li_entries; | |
713 | ||
714 | templateInfo = malloc(template_cnt * sizeof (struct ledger_template_info)); | |
715 | if (templateInfo == NULL) { | |
716 | exit (-1); | |
717 | } | |
718 | ||
719 | if (!(ledger(LEDGER_TEMPLATE_INFO, (caddr_t)templateInfo, (caddr_t)&template_cnt, NULL) < 0)) { | |
720 | for (i = 0; i < template_cnt; i++) { | |
721 | if (!strncmp(templateInfo[i].lti_name, physFootprintName, strlen(physFootprintName))) { | |
722 | g_footprint_index = i; | |
723 | break; | |
724 | } | |
725 | } | |
726 | } | |
727 | ||
728 | free(templateInfo); | |
729 | } | |
730 | ||
731 | static void run_tests(const char *path) { | |
732 | /* Embedded-only */ | |
39236c6e | 733 | #pragma unused(path) |
39236c6e A |
734 | |
735 | /* Generic */ | |
736 | start_general_sanity_test(); | |
737 | start_list_validation_test(); | |
738 | start_idle_exit_defer_test(kDeferTimeoutCleanTest); | |
739 | start_idle_exit_defer_test(kDeferTimeoutDirtyTest); | |
740 | start_idle_exit_defer_test(kCancelTimeoutCleanTest); | |
741 | start_idle_exit_defer_test(kCancelTimeoutDirtyTest); | |
742 | } | |
743 | ||
39236c6e A |
744 | |
745 | int main(int argc, char **argv) | |
746 | { | |
747 | pthread_mutexattr_t attr; | |
748 | pthread_condattr_t cattr; | |
749 | size_t size; | |
39236c6e A |
750 | |
751 | /* Must be run as root for priority retrieval */ | |
752 | if (getuid() != 0) { | |
753 | fprintf(stderr, "%s must be run as root.\n", getprogname()); | |
754 | exit(EXIT_FAILURE); | |
755 | } | |
756 | ||
39236c6e A |
757 | |
758 | /* Memory */ | |
759 | size = sizeof(g_physmem); | |
760 | if (sysctlbyname("hw.physmem", &g_physmem, &size, NULL, 0) != 0 || !g_physmem) { | |
761 | printTestResult(__func__, false, "Failed to retrieve system memory"); | |
762 | cleanup_and_exit(-1); | |
763 | } | |
764 | ||
fe8ab488 A |
765 | /* VM Compressor Mode */ |
766 | size = sizeof(g_compressor_mode); | |
767 | if (sysctlbyname("vm.compressor_mode", &g_compressor_mode, &size, NULL, 0) != 0) { | |
768 | printTestResult(__func__, false, "Failed to retrieve compressor config"); | |
769 | cleanup_and_exit(-1); | |
770 | } | |
771 | ||
39236c6e A |
772 | /* Ledger; default limit applies to this process, so grab it here */ |
773 | ledger_init(); | |
774 | if ((-1 == g_ledger_count) || (-1 == g_footprint_index) || (false == get_ledger_info(getpid(), NULL, &g_per_process_limit))) { | |
775 | printTestResult("setup", false, "Unable to init ledger!\n"); | |
776 | cleanup_and_exit(-1); | |
777 | } | |
778 | ||
fe8ab488 A |
779 | if (g_per_process_limit == LEDGER_LIMIT_INFINITY) { |
780 | g_per_process_limit = 0; | |
781 | } else { | |
782 | /* Rescale to MB */ | |
783 | g_per_process_limit /= (1024 * 1024); | |
784 | } | |
39236c6e A |
785 | |
786 | /* Shared memory */ | |
787 | g_shared = mmap(NULL, sizeof(shared_mem_t), PROT_WRITE|PROT_READ, MAP_ANON|MAP_SHARED, 0, 0); | |
788 | if (!g_shared) { | |
789 | printTestResult(__func__, false, "Failed mmap"); | |
790 | cleanup_and_exit(-1); | |
791 | } | |
792 | ||
fe8ab488 A |
793 | /* Guarantee size of random_data buffer */ |
794 | if (sizeof(random_data) < RANDOM_DATA_SIZE) { | |
795 | printTestResult(__func__, false, "Failed to guarantee random_data buffer size [expected %d, actual %d]", | |
796 | RANDOM_DATA_SIZE, sizeof(random_data)); | |
797 | cleanup_and_exit(-1); | |
798 | } | |
799 | ||
39236c6e A |
800 | pthread_mutexattr_init(&attr); |
801 | pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED ); | |
802 | ||
803 | pthread_condattr_init(&cattr); | |
804 | pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); | |
805 | ||
806 | if (pthread_mutex_init(&g_shared->mutex, &attr) || pthread_cond_init(&g_shared->cv, &cattr)) { | |
fe8ab488 | 807 | printTestResult("setup", false, "Unable to init condition variable!"); |
39236c6e A |
808 | cleanup_and_exit(-1); |
809 | } | |
810 | ||
811 | run_tests(argv[0]); | |
812 | ||
813 | /* Teardown */ | |
814 | pthread_mutex_destroy(&g_shared->mutex); | |
815 | pthread_cond_destroy(&g_shared->cv); | |
816 | ||
817 | pthread_mutexattr_destroy(&attr); | |
818 | pthread_condattr_destroy(&cattr); | |
819 | ||
39236c6e | 820 | |
fe8ab488 | 821 | return (g_exit_status); /* exit status 0 on success, -1 on failure */ |
39236c6e | 822 | } |