]>
Commit | Line | Data |
---|---|---|
f427ee49 A |
1 | #include <signal.h> |
2 | #include <spawn.h> | |
3 | #include <stdlib.h> | |
4 | #include <sys/sysctl.h> | |
5 | ||
6 | #include <darwintest.h> | |
7 | #include <dispatch/dispatch.h> | |
8 | #include <mach-o/dyld.h> | |
9 | ||
10 | /* internal */ | |
11 | #include <spawn_private.h> | |
12 | #include <sys/coalition.h> | |
13 | #include <sys/kern_memorystatus.h> | |
14 | ||
15 | #define JETSAM_PRIORITY_IDLE 0 | |
16 | #define JETSAM_PRIORITY_FOREGROUND 10 | |
17 | ||
18 | #define kNumProcsInCoalition 4 | |
19 | typedef struct { | |
20 | pid_t pids[kNumProcsInCoalition]; // An array of pids in this coalition. Owned by this struct. | |
21 | pid_t expected_order[kNumProcsInCoalition]; // An array of pids in this coalition in proper sorted order. | |
22 | uint64_t ids[COALITION_NUM_TYPES]; | |
23 | } coalition_info_t; | |
24 | ||
25 | /* | |
26 | * Children pids spawned by this test that need to be cleaned up. | |
27 | * Has to be a global because the T_ATEND API doesn't take any arguments. | |
28 | */ | |
29 | #define kMaxChildrenProcs 16 | |
30 | static pid_t children_pids[kMaxChildrenProcs]; | |
31 | static size_t num_children = 0; | |
32 | ||
33 | /* | |
34 | * Sets up a new coalition. | |
35 | */ | |
36 | static void init_coalition(coalition_info_t*); | |
37 | ||
38 | /* | |
39 | * Places all procs in the coalition in the given band. | |
40 | */ | |
41 | static void place_coalition_in_band(const coalition_info_t *, int band); | |
42 | ||
43 | /* | |
44 | * Place the given proc in the given band. | |
45 | */ | |
46 | static void place_proc_in_band(pid_t pid, int band); | |
47 | ||
48 | /* | |
49 | * Cleans up any children processes. | |
50 | */ | |
51 | static void cleanup_children(void); | |
52 | ||
53 | /* | |
54 | * Check if we're on a kernel where we can test coalitions. | |
55 | */ | |
56 | static bool has_unrestrict_coalitions(void); | |
57 | ||
58 | /* | |
59 | * Unrestrict coalition syscalls. | |
60 | */ | |
61 | static void unrestrict_coalitions(void); | |
62 | ||
63 | /* | |
64 | * Restrict coalition syscalls | |
65 | */ | |
66 | static void restrict_coalitions(void); | |
67 | ||
68 | /* | |
69 | * Allocate the requested number of pages and fault them in. | |
70 | * Used to achieve a desired footprint. | |
71 | */ | |
72 | static void *allocate_pages(int); | |
73 | ||
74 | /* | |
75 | * Get the vm page size. | |
76 | */ | |
77 | static int get_vmpage_size(void); | |
78 | ||
79 | /* | |
80 | * Launch a proc with a role in a coalition. | |
81 | * If coalition_ids is NULL, skip adding the proc to the coalition. | |
82 | */ | |
83 | static pid_t | |
84 | launch_proc_in_coalition(uint64_t *coalition_ids, int role, int num_pages); | |
85 | ||
86 | /* | |
87 | * Background process that will munch some memory, signal its parent, and | |
88 | * then sit in a loop. | |
89 | */ | |
90 | T_HELPER_DECL(coalition_member, "Mock coalition member") { | |
91 | int num_pages = 0; | |
92 | if (argc == 1) { | |
93 | num_pages = atoi(argv[0]); | |
94 | } | |
95 | allocate_pages(num_pages); | |
96 | // Signal to the parent that we've touched all of our pages. | |
97 | if (kill(getppid(), SIGUSR1) != 0) { | |
98 | T_LOG("Unable to signal to parent process!"); | |
99 | exit(1); | |
100 | } | |
101 | while (true) { | |
102 | ; | |
103 | } | |
104 | } | |
105 | ||
106 | /* | |
107 | * Test that sorting the fg bucket in coalition order works properly. | |
108 | * Spawns children in the same coalition in the fg band. Each child | |
109 | * has a different coalition role. Verifies that the coalition | |
110 | * is sorted properly by role. | |
111 | */ | |
112 | T_DECL(memorystatus_sort_coalition, "Coalition sort order", | |
113 | T_META_ASROOT(true)) { | |
114 | int ret; | |
115 | sig_t res; | |
116 | coalition_info_t coalition; | |
117 | if (!has_unrestrict_coalitions()) { | |
118 | T_SKIP("Unable to test coalitions on this kernel."); | |
119 | } | |
120 | res = signal(SIGUSR1, SIG_IGN); | |
121 | T_WITH_ERRNO; T_ASSERT_NE(res, SIG_ERR, "SIG_IGN SIGUSR1"); | |
122 | unrestrict_coalitions(); | |
123 | ||
124 | // Set up a new coalition with various members. | |
125 | init_coalition(&coalition); | |
126 | T_ATEND(cleanup_children); | |
127 | T_ATEND(restrict_coalitions); | |
128 | // Place all procs in the coalition in the foreground band | |
129 | place_coalition_in_band(&coalition, JETSAM_PRIORITY_FOREGROUND); | |
130 | // Have the kernel sort the foreground bucket and verify that it's | |
131 | // sorted correctly. | |
132 | ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM_SORT, JETSAM_PRIORITY_FOREGROUND, 0, | |
133 | coalition.expected_order, kNumProcsInCoalition * sizeof(pid_t)); | |
134 | T_QUIET; T_ASSERT_EQ(ret, 0, "Error while sorting or validating sorted order.\n" | |
135 | "Check os log output for details.\n" | |
136 | "Look for memorystatus_verify_sort_order."); | |
137 | } | |
138 | ||
139 | /* | |
140 | * Test that sorting the idle bucket in footprint order works properly. | |
141 | * | |
142 | * Spawns some children with very different footprints in the idle band, | |
143 | * and then ensures that they get sorted properly. | |
144 | */ | |
145 | T_DECL(memorystatus_sort_footprint, "Footprint sort order", | |
146 | T_META_ASROOT(true)) { | |
147 | #define kNumChildren 3 | |
148 | static const int kChildrenFootprints[kNumChildren] = {500, 0, 2500}; | |
149 | /* | |
150 | * The expected sort order of the children in the order that they were launched. | |
151 | * Used to construct the expected_order pid array. | |
152 | * Note that procs should be sorted in descending footprint order. | |
153 | */ | |
154 | static const int kExpectedOrder[kNumChildren] = {2, 0, 1}; | |
155 | static const int kJetsamBand = JETSAM_PRIORITY_IDLE; | |
156 | __block pid_t pid; | |
157 | sig_t res; | |
158 | dispatch_source_t ds_allocated; | |
159 | T_ATEND(cleanup_children); | |
160 | ||
161 | // After we spawn the children, they'll signal that they've touched their pages. | |
162 | res = signal(SIGUSR1, SIG_IGN); | |
163 | T_WITH_ERRNO; T_ASSERT_NE(res, SIG_ERR, "SIG_IGN SIGUSR1"); | |
164 | ds_allocated = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGUSR1, 0, dispatch_get_main_queue()); | |
165 | T_QUIET; T_ASSERT_NOTNULL(ds_allocated, "dispatch_source_create (ds_allocated)"); | |
166 | ||
167 | dispatch_source_set_event_handler(ds_allocated, ^{ | |
168 | if (num_children < kNumChildren) { | |
169 | pid = launch_proc_in_coalition(NULL, 0, kChildrenFootprints[num_children]); | |
170 | place_proc_in_band(pid, kJetsamBand); | |
171 | } else { | |
172 | pid_t expected_order[kNumChildren] = {0}; | |
173 | int ret; | |
174 | for (int i = 0; i < kNumChildren; i++) { | |
175 | expected_order[i] = children_pids[kExpectedOrder[i]]; | |
176 | } | |
177 | // Verify the sort order | |
178 | ret = memorystatus_control(MEMORYSTATUS_CMD_TEST_JETSAM_SORT, kJetsamBand, 0, | |
179 | expected_order, sizeof(expected_order)); | |
180 | T_QUIET; T_ASSERT_EQ(ret, 0, "Error while sorting or validating sorted order.\n" | |
181 | "Check os log output for details.\n" | |
182 | "Look for memorystatus_verify_sort_order."); | |
183 | T_END; | |
184 | } | |
185 | }); | |
186 | dispatch_activate(ds_allocated); | |
187 | ||
188 | pid = launch_proc_in_coalition(NULL, 0, kChildrenFootprints[num_children]); | |
189 | place_proc_in_band(pid, kJetsamBand); | |
190 | ||
191 | dispatch_main(); | |
192 | ||
193 | #undef kNumChildren | |
194 | } | |
195 | ||
196 | static pid_t | |
197 | launch_proc_in_coalition(uint64_t *coalition_ids, int role, int num_pages) | |
198 | { | |
199 | int ret; | |
200 | posix_spawnattr_t attr; | |
201 | pid_t pid; | |
202 | char testpath[PATH_MAX]; | |
203 | uint32_t testpath_buf_size = PATH_MAX; | |
204 | char num_pages_str[32] = {0}; | |
205 | char *argv[5] = {testpath, "-n", "coalition_member", num_pages_str, NULL}; | |
206 | extern char **environ; | |
207 | T_QUIET; T_ASSERT_LT(num_children + 1, (size_t) kMaxChildrenProcs, "Don't create too many children."); | |
208 | ret = posix_spawnattr_init(&attr); | |
209 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_init"); | |
210 | if (coalition_ids != NULL) { | |
211 | for (int i = 0; i < COALITION_NUM_TYPES; i++) { | |
212 | ret = posix_spawnattr_setcoalition_np(&attr, coalition_ids[i], i, role); | |
213 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_setcoalition_np"); | |
214 | } | |
215 | } | |
216 | ||
217 | ret = snprintf(num_pages_str, sizeof(num_pages_str), "%d", num_pages); | |
218 | T_QUIET; T_ASSERT_LE((size_t) ret, sizeof(num_pages_str), "Don't allocate too many pages."); | |
219 | ret = _NSGetExecutablePath(testpath, &testpath_buf_size); | |
220 | T_QUIET; T_ASSERT_EQ(ret, 0, "_NSGetExecutablePath"); | |
221 | ret = posix_spawn(&pid, argv[0], NULL, &attr, argv, environ); | |
222 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawn"); | |
223 | ret = posix_spawnattr_destroy(&attr); | |
224 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "posix_spawnattr_destroy"); | |
225 | children_pids[num_children++] = pid; | |
226 | return pid; | |
227 | } | |
228 | ||
229 | static void | |
230 | init_coalition(coalition_info_t *coalition) | |
231 | { | |
232 | int ret; | |
233 | uint32_t flags = 0; | |
234 | memset(coalition, 0, sizeof(coalition_info_t)); | |
235 | for (int i = 0; i < COALITION_NUM_TYPES; i++) { | |
236 | COALITION_CREATE_FLAGS_SET_TYPE(flags, i); | |
237 | ret = coalition_create(&coalition->ids[i], flags); | |
238 | T_QUIET; T_ASSERT_POSIX_ZERO(ret, "coalition_create"); | |
239 | } | |
240 | ||
241 | /* | |
242 | * Spawn procs for each coalition role, and construct the expected | |
243 | * sorted order. | |
244 | */ | |
245 | for (size_t i = 0; i < kNumProcsInCoalition; i++) { | |
246 | int role; | |
247 | if (i == 0) { | |
248 | role = COALITION_TASKROLE_LEADER; | |
249 | } else if (i == 1) { | |
250 | role = COALITION_TASKROLE_EXT; | |
251 | } else if (i == 2) { | |
252 | role = COALITION_TASKROLE_UNDEF; | |
253 | } else { | |
254 | role = COALITION_TASKROLE_XPC; | |
255 | } | |
256 | pid_t pid = launch_proc_in_coalition(coalition->ids, role, 0); | |
257 | coalition->pids[i] = pid; | |
258 | /* | |
259 | * Determine the expected sorted order. | |
260 | * After a bucket has been coalition sorted, coalition members should | |
261 | * be in the following kill order: | |
262 | * undefined coalition members, extensions, xpc services, leader | |
263 | */ | |
264 | if (role == COALITION_TASKROLE_LEADER) { | |
265 | coalition->expected_order[3] = pid; | |
266 | } else if (role == COALITION_TASKROLE_XPC) { | |
267 | coalition->expected_order[2] = pid; | |
268 | } else if (role == COALITION_TASKROLE_EXT) { | |
269 | coalition->expected_order[1] = pid; | |
270 | } else { | |
271 | coalition->expected_order[0] = pid; | |
272 | } | |
273 | } | |
274 | } | |
275 | ||
276 | static void | |
277 | place_proc_in_band(pid_t pid, int band) | |
278 | { | |
279 | memorystatus_priority_properties_t props = {0}; | |
280 | int ret; | |
281 | props.priority = band; | |
282 | props.user_data = 0; | |
283 | ret = memorystatus_control(MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES, pid, 0, &props, sizeof(props)); | |
284 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "move proc to band"); | |
285 | } | |
286 | ||
287 | ||
288 | static void | |
289 | place_coalition_in_band(const coalition_info_t *coalition, int band) | |
290 | { | |
291 | for (size_t i = 0; i < kNumProcsInCoalition; i++) { | |
292 | pid_t curr = coalition->pids[i]; | |
293 | place_proc_in_band(curr, band); | |
294 | } | |
295 | } | |
296 | ||
297 | static void | |
298 | cleanup_children(void) | |
299 | { | |
300 | int ret, status; | |
301 | for (size_t i = 0; i < num_children; i++) { | |
302 | pid_t exited_pid = 0; | |
303 | pid_t curr = children_pids[i]; | |
304 | ret = kill(curr, SIGKILL); | |
305 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kill"); | |
306 | while (exited_pid == 0) { | |
307 | exited_pid = waitpid(curr, &status, 0); | |
308 | } | |
309 | T_QUIET; T_ASSERT_POSIX_SUCCESS(exited_pid, "waitpid"); | |
310 | T_QUIET; T_ASSERT_TRUE(WIFSIGNALED(status), "proc was signaled."); | |
311 | T_QUIET; T_ASSERT_EQ(WTERMSIG(status), SIGKILL, "proc was killed"); | |
312 | } | |
313 | } | |
314 | ||
315 | static bool | |
316 | has_unrestrict_coalitions() | |
317 | { | |
318 | int ret, val; | |
319 | size_t val_sz; | |
320 | ||
321 | val = 0; | |
322 | val_sz = sizeof(val); | |
323 | ret = sysctlbyname("kern.unrestrict_coalitions", &val, &val_sz, NULL, 0); | |
324 | return ret >= 0; | |
325 | } | |
326 | ||
327 | static void | |
328 | unrestrict_coalitions() | |
329 | { | |
330 | int ret, val = 1; | |
331 | size_t val_sz; | |
332 | val_sz = sizeof(val); | |
333 | ret = sysctlbyname("kern.unrestrict_coalitions", NULL, 0, &val, val_sz); | |
334 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.unrestrict_coalitions <- 1"); | |
335 | } | |
336 | ||
337 | static void | |
338 | restrict_coalitions() | |
339 | { | |
340 | int ret, val = 0; | |
341 | size_t val_sz; | |
342 | val_sz = sizeof(val); | |
343 | ret = sysctlbyname("kern.unrestrict_coalitions", NULL, 0, &val, val_sz); | |
344 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "kern.unrestrict_coalitions <- 0"); | |
345 | } | |
346 | ||
347 | static void * | |
348 | allocate_pages(int num_pages) | |
349 | { | |
350 | int page_size, i; | |
351 | unsigned char *buf; | |
352 | ||
353 | page_size = get_vmpage_size(); | |
354 | buf = malloc((unsigned long)(num_pages * page_size)); | |
355 | for (i = 0; i < num_pages; i++) { | |
356 | ((volatile unsigned char *)buf)[i * page_size] = 1; | |
357 | } | |
358 | return buf; | |
359 | } | |
360 | ||
361 | static int | |
362 | get_vmpage_size() | |
363 | { | |
364 | int vmpage_size; | |
365 | size_t size = sizeof(vmpage_size); | |
366 | int ret = sysctlbyname("vm.pagesize", &vmpage_size, &size, NULL, 0); | |
367 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "failed to query vm.pagesize"); | |
368 | T_QUIET; T_ASSERT_GT(vmpage_size, 0, "vm.pagesize is not > 0"); | |
369 | return vmpage_size; | |
370 | } |