]> git.saurik.com Git - apple/hfs.git/blob - tests/cases/test-throttled-io.c
hfs-522.0.9.tar.gz
[apple/hfs.git] / tests / cases / test-throttled-io.c
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