]>
Commit | Line | Data |
---|---|---|
3e170ce0 A |
1 | /* |
2 | * Copyright (c) 2014 Apple Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | ||
29 | #if HFS_EXTENTS_TEST | |
30 | ||
31 | #include "hfs_extents_test.h" | |
32 | #include "hfs_extents.h" | |
33 | ||
34 | #else | |
35 | ||
36 | #include "hfs_extents.h" | |
37 | ||
38 | // In this file, group refers to a set of 8 extents | |
39 | ||
40 | static uint32_t hfs_total_blocks(const HFSPlusExtentDescriptor *ext, int count); | |
41 | static errno_t hfs_ext_iter_next_group(struct hfs_ext_iter *iter); | |
42 | static errno_t hfs_ext_iter_update(struct hfs_ext_iter *iter, | |
43 | HFSPlusExtentDescriptor *extents, | |
44 | int count, | |
45 | HFSPlusExtentRecord cat_extents); | |
46 | static errno_t hfs_ext_iter_check_group(hfs_ext_iter_t *iter); | |
47 | ||
48 | #endif | |
49 | ||
50 | #define CHECK(x, var, goto_label) \ | |
51 | do { \ | |
52 | var = (x); \ | |
53 | if (var) { \ | |
54 | printf("%s:%u error: %d\n", __func__, __LINE__, var); \ | |
55 | goto goto_label; \ | |
56 | } \ | |
57 | } while (0) | |
58 | ||
59 | #define min(a,b) \ | |
60 | ({ typeof (a) _a = (a); typeof (b) _b = (b); _a < _b ? _a : _b; }) | |
61 | ||
62 | static __attribute__((pure)) | |
63 | const HFSPlusExtentKey *hfs_ext_iter_key(const hfs_ext_iter_t *iter) | |
64 | { | |
65 | return (const HFSPlusExtentKey *)&iter->bt_iter.key; | |
66 | } | |
67 | ||
68 | static __attribute__((pure)) | |
69 | HFSPlusExtentKey *hfs_ext_iter_key_mut(hfs_ext_iter_t *iter) | |
70 | { | |
71 | return (HFSPlusExtentKey *)&iter->bt_iter.key; | |
72 | } | |
73 | ||
74 | // Returns the total number of blocks for the @count extents provided | |
75 | uint32_t hfs_total_blocks(const HFSPlusExtentDescriptor *extents, int count) | |
76 | { | |
77 | uint32_t block_count = 0; | |
78 | for (int i = 0; i < count; ++i) | |
79 | block_count += extents[i].blockCount; | |
80 | return block_count; | |
81 | } | |
82 | ||
83 | /* | |
84 | * Checks a group of extents: makes sure that if it's the last group | |
85 | * for a fork, that all the remaining extents are properly zeroed and | |
86 | * if it's not then checks that all extents are set. This also sets | |
87 | * @group_block_count and @last_in_fork. Returns ESTALE if | |
88 | * inconsistent. | |
89 | */ | |
90 | errno_t hfs_ext_iter_check_group(hfs_ext_iter_t *iter) | |
91 | { | |
92 | filefork_t *ff = VTOF(iter->vp); | |
93 | const HFSPlusExtentKey *key = hfs_ext_iter_key(iter); | |
94 | uint32_t count = 0; | |
95 | int i; | |
96 | ||
97 | for (i = 0; i < kHFSPlusExtentDensity; ++i) { | |
98 | if (!iter->group[i].blockCount) | |
99 | break; | |
100 | count += iter->group[i].blockCount; | |
101 | } | |
102 | ||
103 | if (i < kHFSPlusExtentDensity) { | |
104 | iter->last_in_fork = true; | |
105 | if (key->startBlock + count != ff_allocblocks(ff)) | |
106 | goto bad; | |
107 | ||
108 | // Check remainder of extents | |
109 | for (++i; i < kHFSPlusExtentDensity; ++i) { | |
110 | if (iter->group[i].blockCount) | |
111 | goto bad; | |
112 | } | |
113 | } else { | |
114 | if (key->startBlock + count > ff_allocblocks(ff)) | |
115 | goto bad; | |
116 | ||
117 | iter->last_in_fork = (key->startBlock + count == ff_allocblocks(ff)); | |
118 | } | |
119 | ||
120 | iter->group_block_count = count; | |
121 | ||
122 | return 0; | |
123 | ||
124 | bad: | |
125 | ||
126 | #if DEBUG | |
127 | printf("hfs_ext_iter_check_group: bad group; start: %u, total blocks: %u\n", | |
128 | key->startBlock, ff_allocblocks(ff)); | |
129 | ||
130 | for (int j = 0; j < kHFSPlusExtentDensity; ++j) { | |
131 | printf("%s<%u, %u>", j ? ", " : "", | |
132 | iter->group[j].startBlock, iter->group[j].blockCount); | |
133 | } | |
134 | ||
135 | printf("\n"); | |
136 | #endif | |
137 | ||
138 | return ESTALE; | |
139 | } | |
140 | ||
141 | // NOTE: doesn't copy group data | |
142 | static void hfs_ext_iter_copy(const hfs_ext_iter_t *src, hfs_ext_iter_t *dst) | |
143 | { | |
144 | dst->vp = src->vp; | |
145 | memcpy(&dst->bt_iter.key, &src->bt_iter.key, sizeof(HFSPlusExtentKey)); | |
146 | ||
147 | dst->file_block = src->file_block; | |
148 | dst->ndx = src->ndx; | |
149 | ||
150 | dst->bt_iter.hint = src->bt_iter.hint; | |
151 | dst->bt_iter.version = 0; | |
152 | dst->bt_iter.reserved = 0; | |
153 | dst->bt_iter.hitCount = 0; | |
154 | dst->bt_iter.maxLeafRecs = 0; | |
155 | } | |
156 | ||
157 | bool hfs_ext_iter_is_catalog_extents(hfs_ext_iter_t *iter) | |
158 | { | |
159 | return hfs_ext_iter_key(iter)->startBlock == 0; | |
160 | } | |
161 | ||
162 | #if !HFS_EXTENTS_TEST | |
163 | ||
164 | /* | |
165 | * Finds the extent for offset. It might be in the catalog or the extents | |
166 | * file. | |
167 | */ | |
168 | errno_t hfs_ext_find(vnode_t vp, off_t offset, hfs_ext_iter_t *iter) | |
169 | { | |
170 | errno_t ret; | |
171 | hfsmount_t *hfsmp = VTOHFS(vp); | |
172 | ||
173 | iter->vp = vp; | |
174 | ||
175 | uint32_t end_block, index; | |
176 | HFSPlusExtentKey *key = hfs_ext_iter_key_mut(iter); | |
177 | ||
178 | filefork_t *ff = VTOF(vp); | |
179 | ||
180 | CHECK(SearchExtentFile(hfsmp, ff, offset, | |
181 | key, iter->group, &index, | |
182 | &iter->bt_iter.hint.nodeNum, &end_block), ret, exit); | |
183 | ||
184 | iter->ndx = index; | |
185 | iter->file_block = end_block - iter->group[index].blockCount; | |
186 | ||
187 | if (!key->keyLength) { | |
188 | // We're pointing at the catalog record extents so fix up the key | |
189 | key->keyLength = kHFSPlusExtentKeyMaximumLength; | |
190 | key->forkType = (VNODE_IS_RSRC(iter->vp) | |
191 | ? kHFSResourceForkType : kHFSDataForkType); | |
192 | key->pad = 0; | |
193 | key->fileID = VTOC(iter->vp)->c_fileid; | |
194 | key->startBlock = 0; | |
195 | } | |
196 | ||
197 | CHECK(hfs_ext_iter_check_group(iter), ret, exit); | |
198 | ||
199 | ret = 0; | |
200 | ||
201 | exit: | |
202 | ||
203 | return MacToVFSError(ret); | |
204 | } | |
205 | ||
206 | static uint32_t hfs_ext_iter_next_group_block(const hfs_ext_iter_t *iter) | |
207 | { | |
208 | const HFSPlusExtentKey *key = hfs_ext_iter_key(iter); | |
209 | ||
210 | return key->startBlock + iter->group_block_count; | |
211 | } | |
212 | ||
213 | /* | |
214 | * Move the iterator to the next group. Don't call if there's a chance | |
215 | * there is no entry; the caller should check last_in_fork instead. | |
216 | */ | |
217 | static errno_t hfs_ext_iter_next_group(hfs_ext_iter_t *iter) | |
218 | { | |
219 | errno_t ret; | |
220 | hfsmount_t *hfsmp = VTOHFS(iter->vp); | |
221 | filefork_t * const tree = hfsmp->hfs_extents_cp->c_datafork; | |
222 | HFSPlusExtentKey *key = hfs_ext_iter_key_mut(iter); | |
223 | const bool catalog_extents = hfs_ext_iter_is_catalog_extents(iter); | |
224 | const uint32_t next_block = hfs_ext_iter_next_group_block(iter); | |
225 | ||
226 | FSBufferDescriptor fbd = { | |
227 | .bufferAddress = &iter->group, | |
228 | .itemCount = 1, | |
229 | .itemSize = sizeof(iter->group) | |
230 | }; | |
231 | ||
232 | if (catalog_extents) { | |
233 | key->startBlock = next_block; | |
234 | ||
235 | CHECK(BTSearchRecord(tree, &iter->bt_iter, &fbd, NULL, | |
236 | &iter->bt_iter), ret, exit); | |
237 | } else { | |
238 | const uint32_t file_id = key->fileID; | |
239 | const uint8_t fork_type = key->forkType; | |
240 | ||
241 | CHECK(BTIterateRecord(tree, kBTreeNextRecord, &iter->bt_iter, | |
242 | &fbd, NULL), ret, exit); | |
243 | ||
244 | if (key->fileID != file_id | |
245 | || key->forkType != fork_type | |
246 | || key->startBlock != next_block) { | |
247 | // This indicates an inconsistency | |
248 | ret = ESTALE; | |
249 | goto exit; | |
250 | } | |
251 | } | |
252 | ||
253 | iter->file_block = key->startBlock; | |
254 | iter->ndx = 0; | |
255 | ||
256 | CHECK(hfs_ext_iter_check_group(iter), ret, exit); | |
257 | ||
258 | ret = 0; | |
259 | ||
260 | exit: | |
261 | ||
262 | return MacToVFSError(ret); | |
263 | } | |
264 | ||
265 | /* | |
266 | * Updates with the extents provided and sets the key up for the next group. | |
267 | * It is assumed that any previous record that might collide has been deleted. | |
268 | * NOTE: @extents must point to a buffer that can be zero padded to multiple | |
269 | * of 8 extents. | |
270 | */ | |
271 | errno_t hfs_ext_iter_update(hfs_ext_iter_t *iter, | |
272 | HFSPlusExtentDescriptor *extents, | |
273 | int count, | |
274 | HFSPlusExtentRecord cat_extents) | |
275 | { | |
276 | errno_t ret; | |
277 | hfsmount_t *hfsmp = VTOHFS(iter->vp); | |
278 | cnode_t *cp = VTOC(iter->vp); | |
279 | HFSPlusExtentKey *key = hfs_ext_iter_key_mut(iter); | |
280 | int ndx = 0; | |
281 | ||
282 | if (!extents) | |
283 | extents = iter->group; | |
284 | ||
285 | if (count % kHFSPlusExtentDensity) { | |
286 | // Zero out last group | |
287 | bzero(&extents[count], (kHFSPlusExtentDensity | |
288 | - (count % 8)) * sizeof(*extents)); | |
289 | } | |
290 | ||
291 | if (hfs_ext_iter_is_catalog_extents(iter)) { | |
292 | // Caller is responsible for in-memory updates | |
293 | ||
294 | if (cat_extents) | |
295 | hfs_ext_copy_rec(extents, cat_extents); | |
296 | ||
297 | struct cat_fork fork; | |
298 | ||
299 | hfs_fork_copy(&fork, &VTOF(iter->vp)->ff_data, extents); | |
300 | hfs_prepare_fork_for_update(VTOF(iter->vp), &fork, &fork, hfsmp->blockSize); | |
301 | ||
302 | bool is_rsrc = VNODE_IS_RSRC(iter->vp); | |
303 | CHECK(cat_update(hfsmp, &cp->c_desc, &cp->c_attr, | |
304 | is_rsrc ? NULL : &fork, | |
305 | is_rsrc ? &fork : NULL), ret, exit); | |
306 | ||
307 | // Set the key to the next group | |
308 | key->startBlock = hfs_total_blocks(extents, kHFSPlusExtentDensity); | |
309 | ||
310 | ndx += 8; | |
311 | } | |
312 | ||
313 | // Deal with the remainder which must be overflow extents | |
314 | for (; ndx < count; ndx += 8) { | |
315 | filefork_t * const tree = hfsmp->hfs_extents_cp->c_datafork; | |
316 | ||
317 | FSBufferDescriptor fbd = { | |
318 | .bufferAddress = &extents[ndx], | |
319 | .itemCount = 1, | |
320 | .itemSize = sizeof(HFSPlusExtentRecord) | |
321 | }; | |
322 | ||
323 | CHECK(BTInsertRecord(tree, &iter->bt_iter, &fbd, | |
324 | sizeof(HFSPlusExtentRecord)), ret, exit); | |
325 | ||
326 | // Set the key to the next group | |
327 | key->startBlock += hfs_total_blocks(&extents[ndx], kHFSPlusExtentDensity); | |
328 | } | |
329 | ||
330 | ret = 0; | |
331 | ||
332 | exit: | |
333 | ||
334 | return ret; | |
335 | } | |
336 | ||
337 | #endif // !HFS_EXTENTS_TEST | |
338 | ||
339 | static void push_ext(HFSPlusExtentDescriptor *extents, int *count, | |
340 | const HFSPlusExtentDescriptor *ext) | |
341 | { | |
342 | if (!ext->blockCount) | |
343 | return; | |
344 | ||
345 | if (*count && hfs_ext_end(&extents[*count - 1]) == ext->startBlock) | |
346 | extents[*count - 1].blockCount += ext->blockCount; | |
347 | else | |
348 | extents[(*count)++] = *ext; | |
349 | } | |
350 | ||
351 | /* | |
352 | * NOTE: Here we rely on the replacement extents not being too big as | |
353 | * otherwise the number of BTree records that we have to delete could be | |
354 | * too large. | |
355 | */ | |
356 | errno_t hfs_ext_replace(hfsmount_t *hfsmp, vnode_t vp, | |
357 | uint32_t file_block, | |
358 | const HFSPlusExtentDescriptor *repl, | |
359 | int repl_count, | |
360 | HFSPlusExtentRecord catalog_extents) | |
361 | { | |
362 | errno_t ret; | |
363 | filefork_t * const tree = hfsmp->hfs_extents_cp->c_datafork; | |
364 | hfs_ext_iter_t *iter_in = NULL, *iter_out; | |
365 | HFSPlusExtentDescriptor *extents = NULL; | |
366 | HFSPlusExtentDescriptor *roll_back_extents = NULL; | |
367 | int roll_back_count = 0; | |
368 | const uint32_t end_file_block = file_block + hfs_total_blocks(repl, repl_count); | |
369 | filefork_t *ff = VTOF(vp); | |
370 | ||
371 | // Indicate we haven't touched catalog extents | |
372 | catalog_extents[0].blockCount = 0; | |
373 | ||
374 | if (end_file_block > ff_allocblocks(ff)) { | |
375 | ret = EINVAL; | |
376 | goto exit; | |
377 | } | |
378 | ||
379 | MALLOC(iter_in, hfs_ext_iter_t *, sizeof(*iter_in) * 2, M_TEMP, M_WAITOK); | |
380 | iter_out = iter_in + 1; | |
381 | HFSPlusExtentKey *key_in = hfs_ext_iter_key_mut(iter_in); | |
382 | ||
383 | // Get to where we want to start | |
384 | off_t offset = hfs_blk_to_bytes(file_block, hfsmp->blockSize); | |
385 | ||
386 | /* | |
387 | * If the replacement is at the start of a group, we want to pull in the | |
388 | * group before so that we tidy up any padding that we might have done | |
389 | * in a prior hfs_ext_replace call. | |
390 | */ | |
391 | if (offset > 0) | |
392 | --offset; | |
393 | ||
394 | CHECK(hfs_ext_find(vp, offset, iter_in), ret, exit); | |
395 | ||
396 | const uint32_t start_group_block = key_in->startBlock; | |
397 | ||
398 | const int max_roll_back_extents = 128 * 1024 / sizeof(HFSPlusExtentDescriptor); | |
399 | MALLOC(roll_back_extents, HFSPlusExtentDescriptor *, 128 * 1024, M_TEMP, M_WAITOK); | |
400 | ||
401 | // Move to the first extent in this group | |
402 | iter_in->ndx = 0; | |
403 | ||
404 | hfs_ext_iter_copy(iter_in, iter_out); | |
405 | ||
406 | // Create a buffer for our extents | |
407 | const int buffered_extents = roundup(3 * kHFSPlusExtentDensity + repl_count, | |
408 | kHFSPlusExtentDensity); | |
409 | MALLOC(extents, HFSPlusExtentDescriptor *, | |
410 | sizeof(*extents) * buffered_extents, M_TEMP, M_WAITOK); | |
411 | int count = 0; | |
412 | ||
413 | /* | |
414 | * Iterate through the extents that are affected by this replace operation. | |
415 | * We cannot push more than 16 + repl_count extents here; 8 for the group | |
416 | * containing the replacement start, repl_count for the replacements and 8 | |
417 | * for the group containing the end. If we went back a group due to | |
418 | * decrementing the offset above, it's still the same because we know in | |
419 | * that case the replacement starts at the beginning of the next group. | |
420 | */ | |
421 | uint32_t block = start_group_block; | |
422 | for (;;) { | |
423 | if (!iter_in->ndx) { | |
424 | hfs_ext_copy_rec(iter_in->group, &roll_back_extents[roll_back_count]); | |
425 | roll_back_count += kHFSPlusExtentDensity; | |
426 | ||
427 | if (!hfs_ext_iter_is_catalog_extents(iter_in)) { | |
428 | // Delete this extent group; we're going to replace it | |
429 | CHECK(BTDeleteRecord(tree, &iter_in->bt_iter), ret, exit); | |
430 | } | |
431 | } | |
432 | ||
433 | HFSPlusExtentDescriptor *ext = &iter_in->group[iter_in->ndx]; | |
434 | if (!ext->blockCount) { | |
435 | /* | |
436 | * We ran out of existing extents so we just write the | |
437 | * extents and we're done. | |
438 | */ | |
439 | goto finish; | |
440 | } | |
441 | ||
442 | // If the current extent does not overlap replacement... | |
443 | if (block + ext->blockCount <= file_block || block >= end_file_block) { | |
444 | // Keep the current extent exactly as it is | |
445 | push_ext(extents, &count, ext); | |
446 | } else { | |
447 | HFSPlusExtentDescriptor dealloc_ext = *ext; | |
448 | ||
449 | if (block <= file_block) { | |
450 | /* | |
451 | * The middle or tail of the current extent overlaps | |
452 | * the replacement extents. Keep the non-overlapping | |
453 | * head of the current extent. | |
454 | */ | |
455 | uint32_t trimmed_len = file_block - block; | |
456 | ||
457 | if (trimmed_len) { | |
458 | // Push (keep) non-overlapping head of current extent | |
459 | push_ext(extents, &count, | |
460 | &(HFSPlusExtentDescriptor){ ext->startBlock, | |
461 | trimmed_len }); | |
462 | ||
463 | /* | |
464 | * Deallocate the part of the current extent that | |
465 | * overlaps the replacement extents. That starts | |
466 | * at @file_block. For now, assume it goes | |
467 | * through the end of the current extent. (If the | |
468 | * current extent extends beyond the end of the | |
469 | * replacement extents, we'll update the | |
470 | * blockCount below.) | |
471 | */ | |
472 | dealloc_ext.startBlock += trimmed_len; | |
473 | dealloc_ext.blockCount -= trimmed_len; | |
474 | } | |
475 | ||
476 | // Insert the replacements | |
477 | for (int i = 0; i < repl_count; ++i) | |
478 | push_ext(extents, &count, &repl[i]); | |
479 | } | |
480 | ||
481 | if (block + ext->blockCount > end_file_block) { | |
482 | /* | |
483 | * The head or middle of the current extent overlaps | |
484 | * the replacement extents. Keep the non-overlapping | |
485 | * tail of the current extent. | |
486 | */ | |
487 | uint32_t overlap = end_file_block - block; | |
488 | ||
489 | // Push (keep) non-overlapping tail of current extent | |
490 | push_ext(extents, &count, | |
491 | &(HFSPlusExtentDescriptor){ ext->startBlock + overlap, | |
492 | ext->blockCount - overlap }); | |
493 | ||
494 | /* | |
495 | * Deallocate the part of current extent that overlaps | |
496 | * the replacements. | |
497 | */ | |
498 | dealloc_ext.blockCount = (ext->startBlock + overlap | |
499 | - dealloc_ext.startBlock); | |
500 | } | |
501 | ||
502 | CHECK(BlockDeallocate(hfsmp, dealloc_ext.startBlock, | |
503 | dealloc_ext.blockCount, 0), ret, exit); | |
504 | } | |
505 | ||
506 | // Move to next (existing) extent from iterator | |
507 | block += ext->blockCount; | |
508 | ||
509 | if (++iter_in->ndx >= kHFSPlusExtentDensity) { | |
510 | if (block >= end_file_block) { | |
511 | if (iter_in->last_in_fork || !(count % kHFSPlusExtentDensity)) { | |
512 | /* | |
513 | * This is the easy case. We've hit the end or we have a | |
514 | * multiple of 8, so we can just write out the extents we | |
515 | * have and it should all fit within a transaction. | |
516 | */ | |
517 | ||
518 | goto finish; | |
519 | } | |
520 | ||
521 | if (count + kHFSPlusExtentDensity > buffered_extents | |
522 | || (roll_back_count | |
523 | + kHFSPlusExtentDensity > max_roll_back_extents)) { | |
524 | /* | |
525 | * We've run out of room for the next group, so drop out | |
526 | * and take a different strategy. | |
527 | */ | |
528 | break; | |
529 | } | |
530 | } | |
531 | ||
532 | CHECK(hfs_ext_iter_next_group(iter_in), ret, exit); | |
533 | } | |
534 | } // for (;;) | |
535 | ||
536 | /* | |
537 | * We're not at the end so we need to try and pad to a multiple of 8 | |
538 | * so that we don't have to touch all the subsequent records. We pad | |
539 | * by stealing single blocks. | |
540 | */ | |
541 | ||
542 | int stop_at = 0; | |
543 | ||
544 | for (;;) { | |
545 | // @in points to the record we're stealing from | |
546 | int in = count - 1; | |
547 | ||
548 | count = roundup(count, kHFSPlusExtentDensity); | |
549 | ||
550 | // @out is where we put the stolen single blocks | |
551 | int out = count - 1; | |
552 | ||
553 | do { | |
554 | if (out <= in) { | |
555 | // We suceeded in padding; we're done | |
556 | goto finish; | |
557 | } | |
558 | ||
559 | /* | |
560 | * "Steal" a block, or move a one-block extent within the | |
561 | * @extents array. | |
562 | * | |
563 | * If the extent we're "stealing" from (@in) is only one | |
564 | * block long, we'll end up copying it to @out, setting | |
565 | * @in's blockCount to zero, and decrementing @in. So, we | |
566 | * either split a multi-block extent; or move it within | |
567 | * the @extents array. | |
568 | */ | |
569 | extents[out].blockCount = 1; | |
570 | extents[out].startBlock = (extents[in].startBlock | |
571 | + extents[in].blockCount - 1); | |
572 | --out; | |
573 | } while (--extents[in].blockCount || --in >= stop_at); | |
574 | ||
575 | // We ran out of extents | |
576 | if (roll_back_count + kHFSPlusExtentDensity > max_roll_back_extents) { | |
577 | ret = ENOSPC; | |
578 | goto exit; | |
579 | } | |
580 | ||
581 | // Need to shift extents starting at out + 1 | |
582 | ++out; | |
583 | memmove(&extents[stop_at], &extents[out], | |
584 | (count - out) * sizeof(*extents)); | |
585 | count -= out - stop_at; | |
586 | ||
587 | // Pull in the next group | |
588 | CHECK(hfs_ext_iter_next_group(iter_in), ret, exit); | |
589 | ||
590 | // Take a copy of these extents for roll back purposes | |
591 | hfs_ext_copy_rec(iter_in->group, &roll_back_extents[roll_back_count]); | |
592 | roll_back_count += kHFSPlusExtentDensity; | |
593 | ||
594 | // Delete this group; we're going to replace it | |
595 | CHECK(BTDeleteRecord(tree, &iter_in->bt_iter), ret, exit); | |
596 | ||
597 | if (iter_in->last_in_fork) { | |
598 | // Great! We've hit the end. Coalesce and write out. | |
599 | int old_count = count; | |
600 | count = 0; | |
601 | ||
602 | /* | |
603 | * First coalesce the extents we already have. Takes | |
604 | * advantage of push_ext coalescing the input extent with | |
605 | * the last extent in @extents. If the extents are not | |
606 | * contiguous, then this just copies the extents over | |
607 | * themselves and sets @count back to @old_count. | |
608 | */ | |
609 | for (int i = 0; i < old_count; ++i) | |
610 | push_ext(extents, &count, &extents[i]); | |
611 | ||
612 | // Make room if necessary | |
613 | const int flush_count = buffered_extents - kHFSPlusExtentDensity; | |
614 | if (count > flush_count) { | |
615 | CHECK(hfs_ext_iter_update(iter_out, extents, | |
616 | flush_count, catalog_extents), ret, exit); | |
617 | ||
618 | memmove(&extents[0], &extents[flush_count], | |
619 | (count - flush_count) * sizeof(*extents)); | |
620 | ||
621 | count -= flush_count; | |
622 | } | |
623 | ||
624 | // Add in the extents we just read in | |
625 | for (int i = 0; i < kHFSPlusExtentDensity; ++i) { | |
626 | HFSPlusExtentDescriptor *ext = &iter_in->group[i]; | |
627 | if (!ext->blockCount) | |
628 | break; | |
629 | push_ext(extents, &count, ext); | |
630 | } | |
631 | ||
632 | goto finish; | |
633 | } // if (iter_in->last_in_fork) | |
634 | ||
635 | /* | |
636 | * Otherwise, we're not at the end, so we add these extents and then | |
637 | * try and pad out again to a multiple of 8. We start by making room. | |
638 | */ | |
639 | if (count > buffered_extents - kHFSPlusExtentDensity) { | |
640 | // Only write out one group here | |
641 | CHECK(hfs_ext_iter_update(iter_out, extents, | |
642 | kHFSPlusExtentDensity, | |
643 | catalog_extents), ret, exit); | |
644 | ||
645 | memmove(&extents[0], &extents[kHFSPlusExtentDensity], | |
646 | (count - kHFSPlusExtentDensity) * sizeof(*extents)); | |
647 | ||
648 | count -= kHFSPlusExtentDensity; | |
649 | } | |
650 | ||
651 | // Record where to stop when padding above | |
652 | stop_at = count; | |
653 | ||
654 | // Copy in the new extents | |
655 | hfs_ext_copy_rec(iter_in->group, &extents[count]); | |
656 | count += kHFSPlusExtentDensity; | |
657 | } // for (;;) | |
658 | ||
659 | finish: | |
660 | ||
661 | // Write the remaining extents | |
662 | CHECK(hfs_ext_iter_update(iter_out, extents, count, | |
663 | catalog_extents), ret, exit); | |
664 | ||
665 | CHECK(BTFlushPath(hfsmp->hfs_catalog_cp->c_datafork), ret, exit); | |
666 | CHECK(BTFlushPath(hfsmp->hfs_extents_cp->c_datafork), ret, exit); | |
667 | ||
668 | exit: | |
669 | ||
670 | if (ret && roll_back_count) { | |
671 | ||
672 | #define RB_FAILED \ | |
673 | do { \ | |
674 | printf("hfs_ext_replace:%u: roll back failed\n", __LINE__); \ | |
675 | hfs_mark_inconsistent(hfsmp, HFS_ROLLBACK_FAILED); \ | |
676 | goto roll_back_failed; \ | |
677 | } while (0) | |
678 | ||
679 | // First delete any groups we inserted | |
680 | HFSPlusExtentKey *key_out = hfs_ext_iter_key_mut(iter_out); | |
681 | ||
682 | key_in->startBlock = start_group_block; | |
683 | if (!key_in->startBlock && key_out->startBlock > key_in->startBlock) { | |
684 | key_in->startBlock += hfs_total_blocks(catalog_extents, | |
685 | kHFSPlusExtentDensity); | |
686 | } | |
687 | ||
688 | if (key_out->startBlock > key_in->startBlock) { | |
689 | FSBufferDescriptor fbd = { | |
690 | .bufferAddress = &iter_in->group, | |
691 | .itemCount = 1, | |
692 | .itemSize = sizeof(iter_in->group) | |
693 | }; | |
694 | ||
695 | if (BTSearchRecord(tree, &iter_in->bt_iter, &fbd, NULL, | |
696 | &iter_in->bt_iter)) { | |
697 | RB_FAILED; | |
698 | } | |
699 | ||
700 | for (;;) { | |
701 | if (BTDeleteRecord(tree, &iter_in->bt_iter)) | |
702 | RB_FAILED; | |
703 | ||
704 | key_in->startBlock += hfs_total_blocks(iter_in->group, | |
705 | kHFSPlusExtentDensity); | |
706 | ||
707 | if (key_in->startBlock >= key_out->startBlock) | |
708 | break; | |
709 | ||
710 | if (BTSearchRecord(tree, &iter_in->bt_iter, &fbd, NULL, | |
711 | &iter_in->bt_iter)) { | |
712 | RB_FAILED; | |
713 | } | |
714 | } | |
715 | } | |
716 | ||
717 | // Position iter_out | |
718 | key_out->startBlock = start_group_block; | |
719 | ||
720 | // Roll back all the extents | |
721 | if (hfs_ext_iter_update(iter_out, roll_back_extents, roll_back_count, | |
722 | catalog_extents)) { | |
723 | RB_FAILED; | |
724 | } | |
725 | ||
726 | // And we need to reallocate the blocks we deallocated | |
727 | const uint32_t end_block = min(block, end_file_block); | |
728 | block = start_group_block; | |
729 | for (int i = 0; i < roll_back_count && block < end_block; ++i) { | |
730 | HFSPlusExtentDescriptor *ext = &roll_back_extents[i]; | |
731 | ||
732 | if (block + ext->blockCount <= file_block) | |
733 | continue; | |
734 | ||
735 | HFSPlusExtentDescriptor alloc_ext = *ext; | |
736 | ||
737 | if (block <= file_block) { | |
738 | uint32_t trimmed_len = file_block - block; | |
739 | ||
740 | alloc_ext.startBlock += trimmed_len; | |
741 | alloc_ext.blockCount -= trimmed_len; | |
742 | } | |
743 | ||
744 | if (block + ext->blockCount > end_file_block) { | |
745 | uint32_t overlap = end_file_block - block; | |
746 | ||
747 | alloc_ext.blockCount = (ext->startBlock + overlap | |
748 | - alloc_ext.startBlock); | |
749 | } | |
750 | ||
751 | if (hfs_block_alloc(hfsmp, &alloc_ext, HFS_ALLOC_ROLL_BACK, NULL)) | |
752 | RB_FAILED; | |
753 | ||
754 | block += ext->blockCount; | |
755 | } | |
756 | ||
757 | if (BTFlushPath(hfsmp->hfs_catalog_cp->c_datafork) | |
758 | || BTFlushPath(hfsmp->hfs_extents_cp->c_datafork)) { | |
759 | RB_FAILED; | |
760 | } | |
761 | } // if (ret && roll_back_count) | |
762 | ||
763 | roll_back_failed: | |
764 | ||
765 | FREE(iter_in, M_TEMP); | |
766 | FREE(extents, M_TEMP); | |
767 | FREE(roll_back_extents, M_TEMP); | |
768 | ||
769 | return MacToVFSError(ret); | |
770 | } |