]>
Commit | Line | Data |
---|---|---|
1 | #include <TargetConditionals.h> | |
2 | ||
3 | #if !TARGET_OS_BRIDGE | |
4 | ||
5 | #include <sys/fcntl.h> | |
6 | #include <unistd.h> | |
7 | #include <stdlib.h> | |
8 | #include <sys/resource.h> | |
9 | #include <CommonCrypto/CommonDigest.h> | |
10 | #include <stdbool.h> | |
11 | #include <stdio.h> | |
12 | #include <spawn.h> | |
13 | #include <string.h> | |
14 | #include <signal.h> | |
15 | #include <stdarg.h> | |
16 | #include <sys/errno.h> | |
17 | #include <libkern/OSAtomic.h> | |
18 | #include <zlib.h> | |
19 | #include <pthread.h> | |
20 | #include <sys/mount.h> | |
21 | ||
22 | #include "hfs-tests.h" | |
23 | #include "test-utils.h" | |
24 | #include "disk-image.h" | |
25 | ||
26 | #define TEST_PATH "/tmp/throttled-io.sparseimage" | |
27 | #define MOUNT_POINT "/tmp/throttled_io" | |
28 | ||
29 | TEST(throttled_io) | |
30 | ||
31 | static disk_image_t *gDI; | |
32 | static char *gFile1, *gFile2, *gFile3; | |
33 | ||
34 | static void *gBuf; | |
35 | static const size_t gBuf_size = 64 * 1024 * 1024; | |
36 | ||
37 | static pid_t bg_io_pid = 0; | |
38 | static size_t BG_IOSIZE = (4U << 10); // BG-IO buffer size 4KiB | |
39 | static off_t BG_MAX_FILESIZE = (1ULL << 30); // Max BG-IO File-size 1GiB | |
40 | ||
41 | // | |
42 | // A worker function called from the background-io child process. First it | |
43 | // attempts to open file at path `gFile1` ( a new file is created if one does | |
44 | // does not exist). If the file is opened successfully, the file is written | |
45 | // continiously, wrapping around when write offset is greater or equal to | |
46 | // `BG_MAX_FILESIZE`. | |
47 | // | |
48 | errno_t background_io_worker(void) | |
49 | { | |
50 | int fd; | |
51 | off_t offset; | |
52 | char *buffer; | |
53 | ||
54 | // | |
55 | // Open the file at path `gFile1`, create a new file if one does not | |
56 | // exists. | |
57 | // | |
58 | fd = open(gFile1, O_RDWR|O_TRUNC); | |
59 | if (fd == -1 && errno == ENOENT) { | |
60 | ||
61 | fd = creat(gFile1, 0666); | |
62 | if (fd == -1) { | |
63 | fprintf(stderr, "Failed to create file: %s\n", | |
64 | gFile1); | |
65 | return errno; | |
66 | } | |
67 | close(fd); | |
68 | fd = open(gFile1, O_RDWR); | |
69 | } | |
70 | ||
71 | // | |
72 | // Return errno if we could not open the file. | |
73 | // | |
74 | if (fd == -1) { | |
75 | fprintf(stderr, "Failed to open file: %s\n", gFile1); | |
76 | return errno; | |
77 | } | |
78 | ||
79 | // | |
80 | // Allocate the write buffer on-stack such that we don't have to free | |
81 | // it explicitly. | |
82 | // | |
83 | buffer = alloca(BG_IOSIZE); | |
84 | if (!buffer) | |
85 | return ENOMEM; | |
86 | (void)memset(buffer, -1, BG_IOSIZE); | |
87 | ||
88 | offset = 0; | |
89 | while (true) { | |
90 | ssize_t written; | |
91 | ||
92 | written = pwrite(fd, buffer, BG_IOSIZE, offset); | |
93 | if (written == -1) { | |
94 | return errno; | |
95 | } | |
96 | ||
97 | offset += written; | |
98 | if (offset >= BG_MAX_FILESIZE) { | |
99 | offset = 0; | |
100 | } | |
101 | ||
102 | // | |
103 | // Voluntarily relinquish cpu to allow the throttled process to | |
104 | // schedule after every 128 MiB of write, else the test can | |
105 | // take very long time and timeout. Sleep half a second after | |
106 | // we have written 128 MiB. | |
107 | // | |
108 | if (!(offset % (off_t)(BG_IOSIZE * 1024 * 32))) { | |
109 | usleep(500000); | |
110 | } | |
111 | ||
112 | // | |
113 | // Just in case the test times-out for some reason and parent | |
114 | // terminates without killing this background-io process, let | |
115 | // us poll this child process's parent. If the parent process | |
116 | // has died then to ensure cleanup we return from this child | |
117 | // process. | |
118 | // | |
119 | if (getppid() == 1) { | |
120 | return ETIMEDOUT; | |
121 | } | |
122 | } | |
123 | ||
124 | // | |
125 | // Should not come here. | |
126 | // | |
127 | return 0; | |
128 | } | |
129 | ||
130 | // | |
131 | // Start a continious background-io process, if successful the pid of the | |
132 | // background-io is cached in `bg_io_pid'. | |
133 | // | |
134 | static void start_background_io_process(void) | |
135 | { | |
136 | pid_t child_pid; | |
137 | ||
138 | child_pid = fork(); | |
139 | switch(child_pid) { | |
140 | ||
141 | case -1: | |
142 | assert_fail("Failed to spawn background-io " | |
143 | "process, error %d, %s.\n", errno, | |
144 | strerror(errno)); | |
145 | case 0: { | |
146 | int child_ret; | |
147 | ||
148 | child_ret = background_io_worker(); | |
149 | _exit(child_ret); | |
150 | } | |
151 | default: | |
152 | bg_io_pid = child_pid; | |
153 | } | |
154 | } | |
155 | ||
156 | // | |
157 | // Kill the background-io process if it was started. The background-io process | |
158 | // should perform IO continuously and should not exit normally. If the | |
159 | // background-io exited normally, the error is reported. | |
160 | // | |
161 | static void kill_background_io_process(void) | |
162 | { | |
163 | int child_status; | |
164 | pid_t child_pid; | |
165 | ||
166 | if (!bg_io_pid) | |
167 | return; | |
168 | ||
169 | kill(bg_io_pid, SIGKILL); | |
170 | do { | |
171 | child_pid = waitpid(bg_io_pid, &child_status, WUNTRACED); | |
172 | } while (child_pid == -1 && errno == EINTR); | |
173 | ||
174 | if (child_pid == -1 && errno != ECHILD) { | |
175 | assert_fail("Failed to wait for child pid: %ld, error %d, " | |
176 | "%s.\n", (long)bg_io_pid, errno, | |
177 | strerror(errno)); | |
178 | } | |
179 | ||
180 | if (WIFEXITED(child_status)) { | |
181 | int error; | |
182 | ||
183 | error = WEXITSTATUS(child_status); | |
184 | if (error) { | |
185 | assert_fail("background-io exited with error %d, " | |
186 | "%s.\n", error, strerror(error)); | |
187 | } | |
188 | } | |
189 | ||
190 | bg_io_pid = 0; | |
191 | } | |
192 | ||
193 | static int run_test1(void) | |
194 | { | |
195 | int fd, fd2; | |
196 | int orig_io_policy; | |
197 | ||
198 | // | |
199 | // Kick off another process to ensure we get throttled. | |
200 | // | |
201 | start_background_io_process(); | |
202 | ||
203 | // | |
204 | // Cache the set IO policy of this process. | |
205 | // | |
206 | orig_io_policy = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS); | |
207 | assert_with_errno(orig_io_policy != -1); | |
208 | ||
209 | // | |
210 | // Set new IO policy for this process. | |
211 | // | |
212 | assert_no_err(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, | |
213 | IOPOL_THROTTLE)); | |
214 | ||
215 | assert_with_errno((fd = open("/dev/random", O_RDONLY)) >= 0); | |
216 | assert_with_errno((fd2 = open(gFile2, O_RDWR | O_CREAT | O_TRUNC, | |
217 | 0666)) >= 0); | |
218 | ||
219 | assert_no_err(fcntl(fd2, F_SINGLE_WRITER, 1)); | |
220 | assert_no_err(fcntl(fd2, F_NOCACHE, 1)); | |
221 | ||
222 | gBuf = valloc(gBuf_size); | |
223 | CC_SHA1_CTX ctx; | |
224 | CC_SHA1_Init(&ctx); | |
225 | ||
226 | ssize_t res = check_io(read(fd, gBuf, gBuf_size), gBuf_size); | |
227 | ||
228 | CC_SHA1_Update(&ctx, gBuf, (CC_LONG)res); | |
229 | ||
230 | res = check_io(write(fd2, gBuf, res), res); | |
231 | ||
232 | bzero(gBuf, gBuf_size); | |
233 | ||
234 | CC_SHA1_CTX ctx2; | |
235 | CC_SHA1_Init(&ctx2); | |
236 | ||
237 | lseek(fd2, 0, SEEK_SET); | |
238 | ||
239 | res = check_io(read(fd2, gBuf, gBuf_size), gBuf_size); | |
240 | ||
241 | CC_SHA1_Update(&ctx2, gBuf, (CC_LONG)res); | |
242 | ||
243 | uint8_t digest1[CC_SHA1_DIGEST_LENGTH], digest2[CC_SHA1_DIGEST_LENGTH]; | |
244 | CC_SHA1_Final(digest1, &ctx); | |
245 | CC_SHA1_Final(digest2, &ctx2); | |
246 | ||
247 | assert(!memcmp(digest1, digest2, CC_SHA1_DIGEST_LENGTH)); | |
248 | ||
249 | assert_no_err (close(fd)); | |
250 | assert_no_err (close(fd2)); | |
251 | ||
252 | // | |
253 | // Kill the background IO process. | |
254 | // | |
255 | kill_background_io_process(); | |
256 | ||
257 | // | |
258 | // Restore the orig. IO policy. | |
259 | // | |
260 | assert_no_err(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, | |
261 | orig_io_policy)); | |
262 | return 0; | |
263 | } | |
264 | ||
265 | static volatile uint64_t written; | |
266 | static volatile bool done; | |
267 | ||
268 | static void test2_thread(void) | |
269 | { | |
270 | int fd = open(gFile3, O_RDONLY); | |
271 | assert(fd >= 0); | |
272 | ||
273 | void *b = gBuf + gBuf_size / 2; | |
274 | uLong seq = crc32(0, Z_NULL, 0); | |
275 | uint32_t offset = 0; | |
276 | ||
277 | do { | |
278 | ssize_t res; | |
279 | ||
280 | do { | |
281 | res = check_io(pread(fd, b, gBuf_size / 2, offset), -1); | |
282 | } while (res == 0 && !done); | |
283 | ||
284 | assert (res % 4 == 0); | |
285 | ||
286 | offset += res; | |
287 | ||
288 | for (uLong *p = b; res; ++p, res -= sizeof(uLong)) { | |
289 | seq = crc32(Z_NULL, (void *)&seq, 4); | |
290 | assert(*p == seq); | |
291 | } | |
292 | ||
293 | if (offset < written) | |
294 | continue; | |
295 | OSMemoryBarrier(); | |
296 | } while (!done); | |
297 | ||
298 | assert_no_err (close(fd)); | |
299 | } | |
300 | ||
301 | static int run_test2(void) | |
302 | { | |
303 | int fd; | |
304 | int orig_io_policy; | |
305 | ||
306 | // | |
307 | // Kick off another process to ensure we get throttled. | |
308 | // | |
309 | start_background_io_process(); | |
310 | ||
311 | // | |
312 | // Cache the set IO policy of this process. | |
313 | // | |
314 | orig_io_policy = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS); | |
315 | assert_with_errno(orig_io_policy != -1); | |
316 | ||
317 | // | |
318 | // Set new IO policy for this process. | |
319 | // | |
320 | assert_no_err(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, | |
321 | IOPOL_THROTTLE)); | |
322 | ||
323 | fd = open(gFile3, O_RDWR | O_CREAT | O_TRUNC, 0666); | |
324 | assert(fd >= 0); | |
325 | ||
326 | assert_no_err(fcntl(fd, F_SINGLE_WRITER, 1)); | |
327 | assert_no_err(fcntl(fd, F_NOCACHE, 1)); | |
328 | ||
329 | pthread_t thread; | |
330 | pthread_create(&thread, NULL, (void *(*)(void *))test2_thread, NULL); | |
331 | uLong seq = crc32(0, Z_NULL, 0); | |
332 | ||
333 | for (int i = 0; i < 4; ++i) { | |
334 | uLong *p = gBuf; | |
335 | for (unsigned i = 0; i < gBuf_size / 2 / sizeof(uLong); ++i) { | |
336 | seq = crc32(Z_NULL, (void *)&seq, 4); | |
337 | p[i] = seq; | |
338 | } | |
339 | ||
340 | ssize_t res = check_io(write(fd, gBuf, gBuf_size / 2), gBuf_size / 2); | |
341 | ||
342 | written += res; | |
343 | } | |
344 | ||
345 | OSMemoryBarrier(); | |
346 | ||
347 | done = true; | |
348 | ||
349 | pthread_join(thread, NULL); | |
350 | ||
351 | assert_no_err (close(fd)); | |
352 | ||
353 | // | |
354 | // Kill the background IO process. | |
355 | // | |
356 | kill_background_io_process(); | |
357 | ||
358 | // | |
359 | // Restore the orig. IO policy. | |
360 | // | |
361 | assert_no_err(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, | |
362 | orig_io_policy)); | |
363 | ||
364 | return 0; | |
365 | } | |
366 | ||
367 | static bool clean_up(void) | |
368 | { | |
369 | kill_background_io_process(); | |
370 | ||
371 | unlink(gFile1); | |
372 | unlink(gFile2); | |
373 | unlink(gFile3); | |
374 | ||
375 | free(gFile1); | |
376 | free(gFile2); | |
377 | free(gFile3); | |
378 | ||
379 | return true; | |
380 | } | |
381 | ||
382 | int run_throttled_io(__unused test_ctx_t *ctx) | |
383 | { | |
384 | ||
385 | gDI = disk_image_create(TEST_PATH, &(disk_image_opts_t){ | |
386 | .size = 8 GB, | |
387 | .mount_point = MOUNT_POINT | |
388 | }); | |
389 | ||
390 | asprintf(&gFile1, "%s/throttled_io.1", gDI->mount_point); | |
391 | asprintf(&gFile2, "%s/throttled_io.2", gDI->mount_point); | |
392 | asprintf(&gFile3, "%s/throttled_io.3", gDI->mount_point); | |
393 | ||
394 | test_cleanup(^ bool { | |
395 | return clean_up(); | |
396 | }); | |
397 | ||
398 | int res = run_test1(); | |
399 | if (res) | |
400 | return res; | |
401 | ||
402 | res = run_test2(); | |
403 | if (res) | |
404 | return res; | |
405 | ||
406 | return res; | |
407 | } | |
408 | ||
409 | #endif // !TARGET_OS_BRIDGE |