]>
Commit | Line | Data |
---|---|---|
558d2836 A |
1 | #include <sys/mman.h> |
2 | #include <unistd.h> | |
3 | #include <fcntl.h> | |
4 | #include <sys/param.h> | |
5 | #include <spawn.h> | |
6 | #include <signal.h> | |
7 | #include <sys/stat.h> | |
8 | #include <TargetConditionals.h> | |
9 | ||
10 | #import <Foundation/Foundation.h> | |
11 | ||
12 | #include "hfs-tests.h" | |
13 | #include "test-utils.h" | |
14 | #include "disk-image.h" | |
15 | #include "systemx.h" | |
16 | ||
17 | TEST(invalid_ranges) | |
18 | ||
19 | static disk_image_t *di; | |
20 | ||
21 | #define DISK_IMAGE "/tmp/invalid-ranges.sparseimage" | |
22 | ||
23 | int run_invalid_ranges(__unused test_ctx_t *ctx) | |
24 | { | |
25 | di = disk_image_get(); | |
26 | ||
27 | char *file; | |
28 | asprintf(&file, "%s/invalid-ranges.data", di->mount_point); | |
29 | ||
30 | unlink(file); | |
31 | ||
32 | int fd = open(file, O_CREAT | O_RDWR, 0666); | |
33 | ||
34 | assert_with_errno(fd >= 0); | |
35 | ||
36 | off_t size = 1000000; | |
37 | ||
38 | // Make a big file and punch holes in it | |
39 | ftruncate(fd, size); | |
40 | ||
41 | void *buf = valloc(65536); | |
42 | ||
43 | memset(buf, 0xaf, 65536); | |
44 | ||
45 | assert_no_err(fcntl(fd, F_NOCACHE, 1)); | |
46 | ||
47 | off_t o; | |
48 | for (o = 0; o < size; o += 131072) | |
49 | check_io(pwrite(fd, buf, 65536, o + 65536), 65536); | |
50 | ||
51 | // This should cause everything to be flushed | |
52 | assert_no_err(close(fd)); | |
53 | ||
54 | assert_with_errno((fd = open(file, O_RDWR)) >= 0); | |
55 | ||
56 | uint8_t *p; | |
57 | assert_with_errno((p = mmap(NULL, o, PROT_READ | PROT_WRITE, MAP_SHARED, | |
58 | fd, 0)) != MAP_FAILED); | |
59 | ||
60 | assert_no_err(msync(p, o, MS_INVALIDATE)); | |
61 | ||
62 | void *zero = malloc(65536); | |
63 | bzero(zero, 65536); | |
64 | ||
65 | off_t n; | |
66 | uint8_t *q; | |
67 | ||
68 | for (n = 0, q = p; n < o; n += 131072, q += 131072) { | |
69 | assert(!memcmp(q, zero, 65536)); | |
70 | assert(!memcmp(q + 65536, buf, 65536)); | |
71 | } | |
72 | ||
73 | assert(p[size] == 0xaf); | |
74 | ||
75 | assert_no_err(ftruncate(fd, size)); | |
76 | ||
77 | // Check the tail portion of the page is zero | |
78 | assert(p[size] == 0); | |
79 | ||
80 | p[size] = 0xbe; | |
81 | ||
82 | msync(p + size - 1, 1, MS_SYNC); | |
83 | ||
84 | int ps = getpagesize(); | |
85 | int ps_mask = ps - 1; | |
86 | ||
87 | // Force the page out | |
88 | assert_no_err(msync((void *)((uintptr_t)(p + size - 1) & ~ps_mask), | |
89 | ps, MS_INVALIDATE)); | |
90 | ||
91 | // Page back in and check it's zeroed | |
92 | assert(p[size] == 0); | |
93 | ||
94 | p[size] = 0x75; | |
95 | ||
96 | // Extend the file to include the change we made above | |
97 | assert_no_err(ftruncate(fd, size + 1)); | |
98 | ||
99 | // That change should have been zeroed out | |
100 | assert(p[size] == 0); | |
101 | ||
102 | assert_no_err(munmap(p, o)); | |
103 | ||
104 | // Extend the file | |
105 | assert_no_err(ftruncate(fd, o + 2 * ps)); | |
106 | ||
107 | // Write something into the middle of the page following o | |
108 | off_t hello_offset = roundup(o, ps) + 100; | |
109 | ||
110 | check_io(pwrite(fd, "hello", 5, hello_offset), 5); | |
111 | ||
112 | // Close and re-read | |
113 | assert_no_err(close(fd)); | |
114 | ||
115 | assert_with_errno((fd = open(file, O_RDWR)) >= 0); | |
116 | ||
117 | assert_with_errno((p = mmap(NULL, o + 2 * ps, PROT_READ | PROT_WRITE, MAP_SHARED, | |
118 | fd, 0)) != MAP_FAILED); | |
119 | ||
120 | assert_no_err(msync(p, o + 2 * ps, MS_INVALIDATE)); | |
121 | ||
122 | assert(!memcmp(p + hello_offset, "hello", 5)); | |
123 | assert(!memcmp(p + size, zero, hello_offset - size)); | |
124 | assert(!memcmp(p + hello_offset + 5, zero, o + ps * 2 - hello_offset - 5)); | |
125 | ||
126 | assert_no_err(close(fd)); | |
127 | assert_no_err(unlink(file)); | |
128 | ||
129 | // Make a large number of invalid ranges | |
130 | assert_with_errno((fd = open(file, | |
131 | O_RDWR | O_CREAT, 0666)) >= 0); | |
132 | for (int i = 0; i < 1024; ++i) { | |
133 | pwrite(fd, "hello", 5, i * ps * 2); | |
134 | } | |
135 | ||
136 | // OK, that should have created 1024 invalid ranges. Sync the data. | |
137 | p = mmap(NULL, 1024 * ps * 2, PROT_READ | PROT_WRITE, MAP_SHARED, | |
138 | fd, 0); | |
139 | assert(p != MAP_FAILED); | |
140 | ||
141 | assert_no_err(msync(p, 1024 * ps * 2, MS_SYNC)); | |
142 | ||
143 | // Now sync the invalid ranges | |
144 | assert_no_err(fcntl(fd, F_FULLFSYNC)); | |
145 | ||
146 | assert_no_err(close(fd)); | |
147 | ||
148 | assert_no_err(unlink(file)); | |
149 | ||
150 | #if !TARGET_OS_EMBEDDED | |
151 | disk_image_t *di2 = disk_image_create(DISK_IMAGE, &(disk_image_opts_t){ | |
152 | .size = 100 * 1024 * 1024 | |
153 | }); | |
154 | ||
155 | // Find the diskimages_helper process | |
156 | char *dev_device = strdup(di2->disk + 5); | |
157 | ||
158 | // Strip off the s bit for the partition | |
159 | char *spos = strrchr(dev_device, 's'); | |
160 | assert(spos); | |
161 | *spos = 0; | |
162 | ||
163 | io_service_t obj = IOServiceGetMatchingService(kIOMasterPortDefault, | |
164 | IOBSDNameMatching(kIOMasterPortDefault, 0, dev_device)); | |
165 | ||
166 | assert(obj); | |
167 | ||
168 | io_service_t parent; | |
169 | ||
170 | // obj should be the IOMedia object. Go up three to the IOHDIXHDDrive object. | |
171 | assert(!IORegistryEntryGetParentEntry(obj, kIOServicePlane, &parent)); | |
172 | ||
173 | assert(parent); | |
174 | ||
175 | IOObjectRelease(obj); | |
176 | ||
177 | assert(!IORegistryEntryGetParentEntry(parent, kIOServicePlane, &obj)); | |
178 | ||
179 | IOObjectRelease(parent); | |
180 | ||
181 | assert(!IORegistryEntryGetParentEntry(obj, kIOServicePlane, &parent)); | |
182 | ||
183 | assert(parent); | |
184 | ||
185 | IOObjectRelease(obj); | |
186 | ||
187 | NSString *creator = (id)CFBridgingRelease(IORegistryEntrySearchCFProperty(parent, kIOServicePlane, | |
188 | CFSTR("IOUserClientCreator"), | |
189 | kCFAllocatorDefault, | |
190 | kIORegistryIterateRecursively)); | |
191 | ||
192 | IOObjectRelease(parent); | |
193 | ||
194 | assert(creator); | |
195 | ||
196 | // Extract the pid of disk_images_helper | |
197 | pid_t disk_images_helper_pid; | |
198 | assert(sscanf([creator UTF8String], "pid %u", &disk_images_helper_pid) == 1); | |
199 | ||
200 | // Create a file | |
201 | ||
202 | char *path; | |
203 | asprintf(&path, "%s/test-file", di2->mount_point); | |
204 | ||
205 | fd = open(path, O_CREAT | O_RDWR, 0666); | |
206 | ||
207 | /* | |
208 | * Workaround for <rdar://20688964>: force the journal to | |
209 | * have at least one transaction in it. | |
210 | */ | |
211 | assert_no_err(fcntl(fd, F_FULLFSYNC)); | |
212 | ||
213 | assert(fd >= 0); | |
214 | ||
215 | assert_no_err(fcntl(fd, F_NOCACHE, 1)); | |
216 | ||
217 | int block_size = 65536; | |
218 | ||
219 | // Preallocate | |
220 | struct fstore fst = { | |
221 | .fst_posmode = F_PEOFPOSMODE, | |
222 | .fst_length = 2 * block_size, | |
223 | }; | |
224 | ||
225 | assert_no_err(fcntl(fd, F_PREALLOCATE, &fst)); | |
226 | ||
227 | assert(fst.fst_bytesalloc >= 2 * block_size); | |
228 | ||
229 | // Figure out where that is on the device | |
230 | struct log2phys l2p = { .l2p_contigbytes = block_size, .l2p_devoffset = block_size }; | |
231 | assert_no_err(fcntl(fd, F_LOG2PHYS_EXT, &l2p)); | |
232 | ||
233 | assert(l2p.l2p_contigbytes > 0); | |
234 | ||
235 | // Now open the raw device and write some garbage to that location | |
236 | ||
237 | assert(!strncmp(di2->disk, "/dev/", 5)); | |
238 | ||
239 | char *raw_path; | |
240 | asprintf(&raw_path, "/dev/r%s", di2->disk + 5); | |
241 | ||
242 | int raw_dev = open(raw_path, O_RDWR); | |
243 | ||
244 | assert_with_errno(raw_dev >= 0); | |
245 | ||
246 | memset(buf, 0x57, block_size); | |
247 | ||
248 | check_io(pwrite(raw_dev, buf, l2p.l2p_contigbytes, l2p.l2p_devoffset), | |
249 | l2p.l2p_contigbytes); | |
250 | ||
251 | assert_no_err(close(raw_dev)); | |
252 | ||
253 | // OK, so now we have some garbage where we want it. | |
254 | ||
255 | // Check fcntl F_LOG2PHYS_EXT is doing what we expect | |
256 | off_t file_offset = block_size; | |
257 | do { | |
258 | assert(l2p.l2p_contigbytes > 0); | |
259 | assert(l2p.l2p_contigbytes < 1024 * 1024); | |
260 | file_offset += l2p.l2p_contigbytes; | |
261 | assert(file_offset < 1024 * 1024); | |
262 | l2p.l2p_devoffset = file_offset; | |
263 | l2p.l2p_contigbytes = INT64_MAX; | |
264 | } while (!fcntl(fd, F_LOG2PHYS_EXT, &l2p)); | |
265 | ||
266 | assert_with_errno(errno == ERANGE); | |
267 | ||
268 | // Do some writing to the file normally | |
269 | ||
270 | memset(buf, 0xaa, block_size); | |
271 | ||
272 | check_io(pwrite(fd, buf, block_size, 0), block_size); | |
273 | ||
274 | check_io(pwrite(fd, buf, block_size, block_size * 2), block_size); | |
275 | ||
276 | // So now we have a hole that should be zeroed at <block_size, block_size> | |
277 | ||
278 | // Touch every page in that hole | |
279 | for (int i = 0; i < block_size / 4096; ++i) | |
280 | check_io(pwrite(fd, "hello", 5, block_size + i * 4096 + 1000), 5); | |
281 | ||
282 | // Check what we have in the cache | |
283 | check_io(pread(fd, buf, ps, block_size), ps); | |
284 | ||
285 | assert(!memcmp(buf, zero, 1000)); | |
286 | assert(!memcmp(buf + 1000, "hello", 5)); | |
287 | assert(!memcmp(buf + 1005, zero, ps - 1005)); | |
288 | ||
289 | /* Write something into the block beyond the hole. This should | |
290 | cause a metadata update. */ | |
291 | check_io(pwrite(fd, "hello", 5, block_size * 3), 5); | |
292 | ||
293 | /* Create another file so we can do a full fsync to | |
294 | force a journal flush. */ | |
295 | char *fsync_path; | |
296 | asprintf(&fsync_path, "%s/fsync", di->mount_point); | |
297 | ||
298 | fd = open(fsync_path, O_CREAT | O_RDWR | O_TRUNC, 0666); | |
299 | ||
300 | assert_with_errno(fd >= 0); | |
301 | ||
302 | assert_no_err(fcntl(fd, F_FULLFSYNC)); | |
303 | ||
304 | // Kill disk_images_helper to simulate a crash | |
305 | assert_no_err(kill(disk_images_helper_pid, SIGKILL)); | |
306 | ||
307 | // Wait until it gets unmounted | |
308 | struct stat sb; | |
309 | while (!stat(raw_path, &sb)) | |
310 | sleep(1); | |
311 | ||
312 | // Wait another second for things to settle down | |
313 | sleep(1); | |
314 | ||
315 | // Attach to the disk image again | |
316 | assert(!systemx("/usr/bin/hdiutil", SYSTEMX_QUIET, "attach", DISK_IMAGE, NULL)); | |
317 | ||
318 | assert_with_errno((fd = open(path, O_RDWR)) >= 0); | |
319 | ||
320 | // Either the file should be short, or there should be zeroes | |
321 | ssize_t amount = pread(fd, buf, block_size, block_size); | |
322 | assert_with_errno(amount >= 0); | |
323 | ||
324 | assert(!memcmp(buf, zero, amount)); | |
325 | ||
326 | assert_no_err(close(fd)); | |
327 | ||
328 | #endif | |
329 | ||
330 | // Test for <rdar://20994239> | |
331 | fd = open(file, O_CREAT | O_RDWR, 0666); | |
332 | assert_with_errno(fd >= 0); | |
333 | assert_no_err(fcntl(fd, F_NOCACHE, 1)); | |
334 | ||
335 | void *buf2 = malloc(0x100000); | |
336 | memset(buf2, 0x16, 0x100000); | |
337 | check_io(pwrite(fd, buf2, 0x200, 0), 0x200); | |
338 | check_io(pwrite(fd, buf2, 0x100000, 0x00100200), 0x100000); | |
339 | check_io(pwrite(fd, buf2, 0x100000, 0x00300200), 0x100000); | |
340 | check_io(pwrite(fd, buf2, 0x100000, 0x00500200), 0x100000); | |
341 | check_io(pwrite(fd, buf2, 0x200, 0), 0x200); | |
342 | check_io(pwrite(fd, buf2, 0x100000, 0x00700200), 0x100000); | |
343 | check_io(pwrite(fd, buf2, 0x100000, 0x200), 0x100000); | |
344 | ||
345 | void *buf3 = malloc(0x100000); | |
346 | check_io(pread(fd, buf3, 0x100000, 0x100000), 0x100000); | |
347 | assert(!memcmp(buf2, buf3, 0x100000)); | |
348 | ||
349 | assert_no_err(close(fd)); | |
350 | assert_no_err(unlink(file)); | |
351 | ||
352 | return 0; | |
353 | } |