]> git.saurik.com Git - apple/hfs.git/blob - fsck_hfs/dfalib/CatalogCheck.c
hfs-366.30.3.tar.gz
[apple/hfs.git] / fsck_hfs / dfalib / CatalogCheck.c
1 /*
2 * Copyright (c) 2000-2009 Apple Inc. All rights reserved.
3 *
4 * @APPLE_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. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #include "Scavenger.h"
25 #include "DecompDataEnums.h"
26 #include "DecompData.h"
27
28 #include <sys/stat.h>
29
30 extern int RcdFCntErr( SGlobPtr GPtr, OSErr type, UInt32 correct, UInt32 incorrect, HFSCatalogNodeID);
31 extern int RcdHsFldCntErr( SGlobPtr GPtr, OSErr type, UInt32 correct, UInt32 incorrect, HFSCatalogNodeID);
32
33 /*
34 * information collected when visiting catalog records
35 */
36 struct CatalogIterationSummary {
37 UInt32 parentID;
38 UInt32 rootDirCount; /* hfs only */
39 UInt32 rootFileCount; /* hfs only */
40 UInt32 dirCount;
41 UInt32 dirThreads;
42 UInt32 fileCount;
43 UInt32 filesWithThreads; /* hfs only */
44 UInt32 fileThreads;
45 UInt32 nextCNID;
46 UInt64 encodings;
47 void * hardLinkRef;
48 };
49
50 /* Globals used during Catalog record checks */
51 struct CatalogIterationSummary gCIS;
52
53 SGlobPtr gScavGlobals;
54
55 /* Local routines for checking catalog structures */
56 static int CheckCatalogRecord(SGlobPtr GPtr, const HFSPlusCatalogKey *key,
57 const CatalogRecord *rec, UInt16 reclen);
58 static int CheckCatalogRecord_HFS(const HFSCatalogKey *key,
59 const CatalogRecord *rec, UInt16 reclen);
60
61 static int CheckDirectory(const HFSPlusCatalogKey * key, const HFSPlusCatalogFolder * dir);
62 static int CheckFile(const HFSPlusCatalogKey * key, const HFSPlusCatalogFile * file);
63 static int CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread);
64
65 static int CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir);
66 static int CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file);
67 static int CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread);
68
69 static void CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir);
70 static int CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars,
71 u_int32_t parentID, Boolean thread);
72 static int CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename,
73 u_int32_t parentID, Boolean thread);
74
75 static int CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey);
76 static OSErr UniqueDotName( SGlobPtr GPtr,
77 CatalogName * theNewNamePtr,
78 UInt32 theParID,
79 Boolean isSingleDotName,
80 Boolean isHFSPlus );
81 static Boolean FixDecomps( u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename );
82
83 /*
84 * This structure is used to keep track of the folderCount field in
85 * HFSPlusCatalogFolder records. For now, this is only done on HFSX volumes.
86 */
87 struct folderCountInfo {
88 UInt32 folderID;
89 UInt32 recordedCount;
90 UInt32 computedCount;
91 struct folderCountInfo *next;
92 };
93
94 /*
95 * Print a symbolic link name given the fileid
96 */
97 static void
98 printSymLinkName(SGlobPtr GPtr, UInt32 fid)
99 {
100 char pathname[PATH_MAX+1], filename[PATH_MAX+1];
101 unsigned int path_len = sizeof(pathname), fname_len = sizeof(filename);
102 u_int16_t status;
103
104 if (GetFileNamePathByID(GPtr, fid, pathname, &path_len, filename, &fname_len, &status) == 0) {
105 fsckPrint(GPtr->context, E_BadSymLinkName, pathname);
106 }
107 return;
108 }
109
110 /*
111 * CountFolderRecords - Counts the number of folder records contained within a
112 * given folder. That is, how many direct subdirectories it has. This is used
113 * to update the folderCount field, if necessary.
114 *
115 * CountFolderRecords is a straight-forward iteration: given a HFSPlusCatalogFolder
116 * record, it iterates through the catalog BTree until it runs out of records that
117 * belong to it. For each folder record it finds, it increments a count. When it's
118 * done, it compares the two, and if there is a mismatch, requests a repair to be
119 * done.
120 */
121 static OSErr
122 CountFolderRecords(HFSPlusCatalogKey *myKey, HFSPlusCatalogFolder *folder, SGlobPtr GPtr)
123 {
124 SFCB *fcb = GPtr->calculatedCatalogFCB;
125 OSErr err = 0;
126 BTreeIterator iterator;
127 FSBufferDescriptor btRecord;
128 union {
129 HFSPlusCatalogFolder catRecord;
130 HFSPlusCatalogFile catFile;
131 } catRecord;
132 HFSPlusCatalogKey *key;
133 UInt16 recordSize = 0;
134 UInt32 folderCount = 0;
135
136 ClearMemory(&iterator, sizeof(iterator));
137
138 key = (HFSPlusCatalogKey*)&iterator.key;
139 BuildCatalogKey(folder->folderID, NULL, true, (CatalogKey*)key);
140 btRecord.bufferAddress = &catRecord;
141 btRecord.itemCount = 1;
142 btRecord.itemSize = sizeof(catRecord);
143
144 for (err = BTSearchRecord(fcb, &iterator, kNoHint, &btRecord, &recordSize, &iterator);
145 err == 0;
146 err = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &btRecord, &recordSize)) {
147 switch (catRecord.catRecord.recordType) {
148 case kHFSPlusFolderThreadRecord:
149 case kHFSPlusFileThreadRecord:
150 continue;
151 }
152 if (key->parentID != folder->folderID)
153 break;
154 if (catRecord.catRecord.recordType == kHFSPlusFolderRecord) {
155 folderCount++;
156 } else if ((catRecord.catRecord.recordType == kHFSPlusFileRecord) &&
157 (catRecord.catFile.flags & kHFSHasLinkChainMask) &&
158 (catRecord.catFile.userInfo.fdType == kHFSAliasType) &&
159 (catRecord.catFile.userInfo.fdCreator == kHFSAliasCreator) &&
160 (key->parentID != GPtr->filelink_priv_dir_id)) {
161 /* A directory hard link is treated as normal directory
162 * for calculation of folder count.
163 */
164 folderCount++;
165 }
166 }
167 if (err == btNotFound)
168 err = 0;
169 if (err == 0) {
170 if (folderCount != folder->folderCount) {
171 err = RcdFCntErr( GPtr,
172 E_FldCount,
173 folderCount,
174 folder->folderCount,
175 folder->folderID);
176 }
177 }
178 return err;
179 }
180
181 static void
182 releaseFolderCountInfo(struct folderCountInfo *fcip, int numFolders)
183 {
184 int i;
185
186 for (i = 0; i < numFolders; i++) {
187 struct folderCountInfo *f = &fcip[i];
188
189 f = f->next;
190 while (f) {
191 struct folderCountInfo *t = f->next;
192 free(f);
193 f = t;
194 }
195 }
196 free(fcip);
197 }
198
199 static struct folderCountInfo *
200 findFolderEntry(struct folderCountInfo *fcip, int numFolders, UInt32 fid)
201 {
202 struct folderCountInfo *retval = NULL;
203 int indx;
204
205 indx = fid % numFolders; // Slot index
206
207 retval = &fcip[indx];
208 if (retval->folderID == fid) {
209 goto done;
210 }
211 while (retval->next != NULL) {
212 retval = retval->next;
213 if (retval->folderID == fid)
214 goto done;
215 }
216 retval = NULL;
217 done:
218 return retval;
219 }
220
221 static struct folderCountInfo *
222 addFolderEntry(struct folderCountInfo *fcip, int numFolders, UInt32 fid)
223 {
224 struct folderCountInfo *retval = NULL;
225 int indx;
226
227 indx = fid % numFolders;
228 retval = &fcip[indx];
229
230 if (retval->folderID == fid)
231 goto done;
232 while (retval->folderID != 0) {
233 if (retval->next == NULL) {
234 retval->next = calloc(1, sizeof(struct folderCountInfo));
235 if (retval->next == NULL) {
236 retval = NULL;
237 goto done;
238 } else
239 retval = retval->next;
240 } else if (retval->folderID == fid) {
241 goto done;
242 } else
243 retval = retval->next;
244 }
245
246 retval->folderID = fid;
247
248 done:
249 return retval;
250 }
251
252 /*
253 * folderCountAdd - Accounts for given folder record or directory hard link
254 * for folder count of the given parent directory. For directory hard links,
255 * the folder ID and count should be zero. For a folder record, the values
256 * read from the catalog record are provided which are used to add the
257 * given folderID to the cache (folderCountInfo *ficp).
258 */
259 static int
260 folderCountAdd(struct folderCountInfo *fcip, int numFolders, UInt32 parentID, UInt32 folderID, UInt32 count)
261 {
262 int retval = 0;
263 struct folderCountInfo *curp = NULL;
264
265
266 /* Only add directories represented by folder record to the cache */
267 if (folderID != 0) {
268 /*
269 * We track two things here.
270 * First, we need to find the entry matching this folderID. If we don't find it,
271 * we add it. If we do find it, or if we add it, we set the recordedCount.
272 */
273
274 curp = findFolderEntry(fcip, numFolders, folderID);
275 if (curp == NULL) {
276 curp = addFolderEntry(fcip, numFolders, folderID);
277 if (curp == NULL) {
278 retval = ENOMEM;
279 goto done;
280 }
281 }
282 curp->recordedCount = count;
283
284 }
285
286 /*
287 * After that, we try to find the parent to this entry. When we find it
288 * (or if we add it to the list), we increment the computedCount.
289 */
290 curp = findFolderEntry(fcip, numFolders, parentID);
291 if (curp == NULL) {
292 curp = addFolderEntry(fcip, numFolders, parentID);
293 if (curp == NULL) {
294 retval = ENOMEM;
295 goto done;
296 }
297 }
298 curp->computedCount++;
299
300 done:
301 return retval;
302 }
303
304 /*
305 * CheckFolderCount - Verify the folderCount fields of the HFSPlusCatalogFolder records
306 * in the catalog BTree. This is currently only done for HFSX.
307 *
308 * Conceptually, this is a fairly simple routine: simply iterate through the catalog
309 * BTree, and count the number of subfolders contained in each folder. This value
310 * is used for the stat.st_nlink field, on HFSX.
311 *
312 * However, since scanning the entire catalog can be a very costly operation, we dot
313 * it one of two ways. The first way is to simply iterate through the catalog once,
314 * and keep track of each folder ID we come across. This uses a fair bit of memory,
315 * so we limit the cache to 5MBytes, which works out to some 400k folderCountInfo
316 * entries (at the current size of three 4-byte entries per folderCountInfo entry).
317 * If the filesystem has more than that, we instead use the slower (but significantly
318 * less memory-intensive) method in CountFolderRecords: for each folder ID we
319 * come across, we call CountFolderRecords, which does its own iteration through the
320 * catalog, looking for children of the given folder.
321 */
322
323 OSErr
324 CheckFolderCount( SGlobPtr GPtr )
325 {
326 OSErr err = 0;
327 int numFolders;
328 BTreeIterator iterator;
329 FSBufferDescriptor btRecord;
330 HFSPlusCatalogKey *key;
331 union {
332 HFSPlusCatalogFolder catRecord;
333 HFSPlusCatalogFile catFile;
334 } catRecord;
335 UInt16 recordSize = 0;
336 struct folderCountInfo *fcip = NULL;
337
338 ClearMemory(&iterator, sizeof(iterator));
339 if (!VolumeObjectIsHFSX(GPtr)) {
340 goto done;
341 }
342
343 if (GPtr->calculatedVCB == NULL) {
344 err = EINVAL;
345 goto done;
346 }
347
348 #if 0
349 /*
350 * We add two so we can account for the root folder, and
351 * the root folder's parent. Neither of which is real,
352 * but they show up as parent IDs in the catalog.
353 */
354 numFolders = GPtr->calculatedVCB->vcbFolderCount + 2;
355 #else
356 /*
357 * Since we're using a slightly smarter hash method,
358 * we don't care so much about the number of folders
359 * allegedly on the volume; instead, we'll pick a nice
360 * prime number to use as the number of buckets.
361 * This bears some performance checking later.
362 */
363 numFolders = 257;
364 #endif
365
366 /*
367 * Limit the size of the folder count cache to 5Mbytes;
368 * if the requested number of folders don't fit, then
369 * we don't use the cache at all.
370 */
371 #define MAXCACHEMEM (5 * 1024 * 1024) /* 5Mbytes */
372 #define LCALLOC(c, s, l) \
373 ({ __typeof(c) _count = (c); __typeof(s) _size = (s); __typeof(l) _lim = (l); \
374 ((_count * _size) > _lim) ? NULL : calloc(_count, _size); })
375
376 fcip = LCALLOC(numFolders, sizeof(*fcip), MAXCACHEMEM);
377 #undef MAXCACHEMEM
378 #undef LCALLOC
379
380 restart:
381 /* these objects are used by the BT* functions to iterate through the catalog */
382 key = (HFSPlusCatalogKey*)&iterator.key;
383 BuildCatalogKey(kHFSRootFolderID, NULL, true, (CatalogKey*)key);
384 btRecord.bufferAddress = &catRecord;
385 btRecord.itemCount = 1;
386 btRecord.itemSize = sizeof(catRecord);
387
388 /*
389 * Iterate through the catalog BTree until the end.
390 * For each folder we either cache the value, or we call CheckFolderCount.
391 * We also check the kHFSHasFolderCountMask flag in the folder flags field;
392 * if it's not set, we set it. (When migrating a volume from an older version.
393 * this will affect every folder entry; after that, it will only affect any
394 * corrupted areas.)
395 */
396 for (err = BTIterateRecord(GPtr->calculatedCatalogFCB, kBTreeFirstRecord,
397 &iterator, &btRecord, &recordSize);
398 err == 0;
399 err = BTIterateRecord(GPtr->calculatedCatalogFCB, kBTreeNextRecord,
400 &iterator, &btRecord, &recordSize)) {
401
402 switch (catRecord.catRecord.recordType) {
403 case kHFSPlusFolderRecord:
404 if (!(catRecord.catRecord.flags & kHFSHasFolderCountMask)) {
405 /* RcdHsFldCntErr requests a repair order to fix up the flags field */
406 err = RcdHsFldCntErr( GPtr,
407 E_HsFldCount,
408 catRecord.catRecord.flags | kHFSHasFolderCountMask,
409 catRecord.catRecord.flags,
410 catRecord.catRecord.folderID );
411 if (err != 0)
412 goto done;
413 }
414 if (fcip) {
415 if (folderCountAdd(fcip, numFolders,
416 key->parentID,
417 catRecord.catRecord.folderID,
418 catRecord.catRecord.folderCount)) {
419 /*
420 * We got an error -- this only happens if folderCountAdd()
421 * cannot allocate memory for a new node. In that case, we
422 * need to bail on the whole cache, and use the slow method.
423 * This also lets us release the memory, which will hopefully
424 * let some later allocations succeed. We restart just after
425 * the cache was allocated, and start over as if we had never
426 * allocated a cache in the first place.
427 */
428 releaseFolderCountInfo(fcip, numFolders);
429 fcip = NULL;
430 goto restart;
431 }
432 } else {
433 err = CountFolderRecords(key, &catRecord.catRecord, GPtr);
434 if (err != 0)
435 goto done;
436 }
437 break;
438 case kHFSPlusFileRecord:
439 /* If this file record is a directory hard link, count
440 * it towards our folder count calculations.
441 */
442 if ((catRecord.catFile.flags & kHFSHasLinkChainMask) &&
443 (catRecord.catFile.userInfo.fdType == kHFSAliasType) &&
444 (catRecord.catFile.userInfo.fdCreator == kHFSAliasCreator) &&
445 (key->parentID != GPtr->filelink_priv_dir_id)) {
446 /* If we are using folder count cache, account
447 * for directory hard links by incrementing
448 * associated parentID in the cache. If an
449 * extensive search for catalog is being
450 * performed, account for directory hard links
451 * in CountFolderRecords()
452 */
453 if (fcip) {
454 if (folderCountAdd(fcip, numFolders,
455 key->parentID, 0, 0)) {
456 /* See above for why we release & restart */
457 releaseFolderCountInfo(fcip, numFolders);
458 fcip = NULL;
459 goto restart;
460 }
461 }
462 }
463 break;
464 }
465 }
466
467 if (err == btNotFound)
468 err = 0; // We hit the end of the file, which is okay
469 if (err == 0 && fcip != NULL) {
470 int i;
471
472 /*
473 * At this point, we are itereating through the cache, looking for
474 * mis-counts. (If we're not using the cache, then CountFolderRecords has
475 * already dealt with any miscounts.)
476 */
477 for (i = 0; i < numFolders; i++) {
478 struct folderCountInfo *curp;
479
480 for (curp = &fcip[i]; curp; curp = curp->next) {
481 if (curp->folderID == 0) {
482 // fplog(stderr, "fcip[%d] has a folderID of 0?\n", i);
483 } else if (curp->folderID == kHFSRootParentID) {
484 // Root's parent doesn't really exist
485 continue;
486 } else {
487 if (curp->recordedCount != curp->computedCount) {
488 /* RcdFCntErr requests a repair order to correct the folder count */
489 err = RcdFCntErr( GPtr,
490 E_FldCount,
491 curp->computedCount,
492 curp->recordedCount,
493 curp->folderID );
494 if (err != 0)
495 goto done;
496 }
497 }
498 }
499 }
500 }
501 done:
502 if (fcip) {
503 releaseFolderCountInfo(fcip, numFolders);
504 fcip = NULL;
505 }
506 return err;
507 }
508
509 /*
510 * CheckCatalogBTree - Verifies the catalog B-tree structure
511 *
512 * Causes CheckCatalogRecord to be called for every leaf record
513 */
514 OSErr
515 CheckCatalogBTree( SGlobPtr GPtr )
516 {
517 OSErr err;
518 int hfsplus;
519
520 gScavGlobals = GPtr;
521 hfsplus = VolumeObjectIsHFSPlus( );
522
523 ClearMemory(&gCIS, sizeof(gCIS));
524 gCIS.parentID = kHFSRootParentID;
525 gCIS.nextCNID = kHFSFirstUserCatalogNodeID;
526
527 if (hfsplus) {
528 /* Initialize check for file hard links */
529 HardLinkCheckBegin(gScavGlobals, &gCIS.hardLinkRef);
530
531 /* Initialize check for directory hard links */
532 dirhardlink_init(gScavGlobals);
533 }
534
535 GPtr->journal_file_id = GPtr->jib_file_id = 0;
536
537 if (CheckIfJournaled(GPtr, true)) {
538 CatalogName fname;
539 CatalogKey key;
540 CatalogRecord rec;
541 UInt16 recSize;
542 int i;
543
544 #define HFS_JOURNAL_FILE ".journal"
545 #define HFS_JOURNAL_INFO ".journal_info_block"
546
547 fname.ustr.length = strlen(HFS_JOURNAL_FILE);
548 for (i = 0; i < fname.ustr.length; i++)
549 fname.ustr.unicode[i] = HFS_JOURNAL_FILE[i];
550 BuildCatalogKey(kHFSRootFolderID, &fname, true, &key);
551 if (SearchBTreeRecord(GPtr->calculatedCatalogFCB, &key, kNoHint, NULL, &rec, &recSize, NULL) == noErr &&
552 rec.recordType == kHFSPlusFileRecord) {
553 GPtr->journal_file_id = rec.hfsPlusFile.fileID;
554 }
555 fname.ustr.length = strlen(HFS_JOURNAL_INFO);
556 for (i = 0; i < fname.ustr.length; i++)
557 fname.ustr.unicode[i] = HFS_JOURNAL_INFO[i];
558 BuildCatalogKey(kHFSRootFolderID, &fname, true, &key);
559 if (SearchBTreeRecord(GPtr->calculatedCatalogFCB, &key, kNoHint, NULL, &rec, &recSize, NULL) == noErr &&
560 rec.recordType == kHFSPlusFileRecord) {
561 GPtr->jib_file_id = rec.hfsPlusFile.fileID;
562 }
563 }
564
565 /* for compatibility, init these globals */
566 gScavGlobals->TarID = kHFSCatalogFileID;
567 GetVolumeObjectBlockNum( &gScavGlobals->TarBlock );
568
569 /*
570 * Check out the BTree structure
571 */
572 err = BTCheck(gScavGlobals, kCalculatedCatalogRefNum, (CheckLeafRecordProcPtr)CheckCatalogRecord);
573 if (err) goto exit;
574
575 if (gCIS.dirCount != gCIS.dirThreads) {
576 RcdError(gScavGlobals, E_IncorrectNumThdRcd);
577 gScavGlobals->CBTStat |= S_Orphan; /* a directory record is missing */
578 if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
579 plog ("\t%s: dirCount = %u, dirThread = %u\n", __FUNCTION__, gCIS.dirCount, gCIS.dirThreads);
580 }
581 }
582
583 if (hfsplus && (gCIS.fileCount != gCIS.fileThreads)) {
584 RcdError(gScavGlobals, E_IncorrectNumThdRcd);
585 gScavGlobals->CBTStat |= S_Orphan;
586 if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
587 plog ("\t%s: fileCount = %u, fileThread = %u\n", __FUNCTION__, gCIS.fileCount, gCIS.fileThreads);
588 }
589 }
590
591 if (!hfsplus && (gCIS.fileThreads != gCIS.filesWithThreads)) {
592 RcdError(gScavGlobals, E_IncorrectNumThdRcd);
593 gScavGlobals->CBTStat |= S_Orphan;
594 if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
595 plog ("\t%s: fileThreads = %u, filesWithThread = %u\n", __FUNCTION__, gCIS.fileThreads, gCIS.filesWithThreads);
596 }
597 }
598
599 gScavGlobals->calculatedVCB->vcbEncodingsBitmap = gCIS.encodings;
600 gScavGlobals->calculatedVCB->vcbNextCatalogID = gCIS.nextCNID;
601 gScavGlobals->calculatedVCB->vcbFolderCount = gCIS.dirCount - 1;
602 gScavGlobals->calculatedVCB->vcbFileCount = gCIS.fileCount;
603 if (!hfsplus) {
604 gScavGlobals->calculatedVCB->vcbNmRtDirs = gCIS.rootDirCount;
605 gScavGlobals->calculatedVCB->vcbNmFls = gCIS.rootFileCount;
606 }
607
608 /*
609 * Check out the allocation map structure
610 */
611 err = BTMapChk(gScavGlobals, kCalculatedCatalogRefNum);
612 if (err) goto exit;
613
614 /*
615 * Make sure unused nodes in the B-tree are zero filled.
616 */
617 err = BTCheckUnusedNodes(gScavGlobals, kCalculatedCatalogRefNum, &gScavGlobals->CBTStat);
618 if (err) goto exit;
619
620 /*
621 * Compare BTree header record on disk with scavenger's BTree header record
622 */
623 err = CmpBTH(gScavGlobals, kCalculatedCatalogRefNum);
624 if (err) goto exit;
625
626 /*
627 * Compare BTree map on disk with scavenger's BTree map
628 */
629 err = CmpBTM(gScavGlobals, kCalculatedCatalogRefNum);
630
631 if (hfsplus) {
632 if (scanflag == 0) {
633 (void) CheckHardLinks(gCIS.hardLinkRef);
634
635 /* If any unrepairable corruption was detected for file
636 * hard links, stop the verification process by returning
637 * negative value.
638 */
639 if (gScavGlobals->CatStat & S_LinkErrNoRepair) {
640 err = -1;
641 goto exit;
642 }
643 }
644 }
645
646 exit:
647 if (hfsplus)
648 HardLinkCheckEnd(gCIS.hardLinkRef);
649
650 return (err);
651 }
652
653 /*
654 * CheckCatalogRecord - verify a catalog record
655 *
656 * Called in leaf-order for every leaf record in the Catalog B-tree
657 */
658 static int
659 CheckCatalogRecord(SGlobPtr GPtr, const HFSPlusCatalogKey *key, const CatalogRecord *rec, UInt16 reclen)
660 {
661 int result = 0;
662 Boolean isHFSPlus;
663
664 isHFSPlus = VolumeObjectIsHFSPlus( );
665 ++gScavGlobals->itemsProcessed;
666
667 if (!isHFSPlus)
668 return CheckCatalogRecord_HFS((HFSCatalogKey *)key, rec, reclen);
669
670 gScavGlobals->CNType = rec->recordType;
671
672 switch (rec->recordType) {
673 case kHFSPlusFolderRecord:
674 ++gCIS.dirCount;
675 if (reclen != sizeof(HFSPlusCatalogFolder)){
676 RcdError(gScavGlobals, E_LenDir);
677 result = E_LenDir;
678 break;
679 }
680 if (key->parentID != gCIS.parentID) {
681 result = CaptureMissingThread(key->parentID, key);
682 if (result) break;
683 /* Pretend thread record was there */
684 ++gCIS.dirThreads;
685 gCIS.parentID = key->parentID;
686 }
687 result = CheckDirectory(key, (HFSPlusCatalogFolder *)rec);
688 break;
689
690 case kHFSPlusFileRecord:
691 ++gCIS.fileCount;
692 if (reclen != sizeof(HFSPlusCatalogFile)){
693 RcdError(gScavGlobals, E_LenFil);
694 result = E_LenFil;
695 break;
696 }
697 if (key->parentID != gCIS.parentID) {
698 result = CaptureMissingThread(key->parentID, key);
699 if (result) break;
700 /* Pretend thread record was there */
701 ++gCIS.dirThreads;
702 gCIS.parentID = key->parentID;
703 }
704 result = CheckFile(key, (HFSPlusCatalogFile *)rec);
705 break;
706
707 case kHFSPlusFolderThreadRecord:
708 ++gCIS.dirThreads;
709 gCIS.parentID = key->parentID;
710 /* Fall through */
711
712 case kHFSPlusFileThreadRecord:
713 if (rec->recordType == kHFSPlusFileThreadRecord)
714 ++gCIS.fileThreads;
715
716 if (reclen > sizeof(HFSPlusCatalogThread) ||
717 reclen < sizeof(HFSPlusCatalogThread) - sizeof(HFSUniStr255)) {
718 RcdError(gScavGlobals, E_LenThd);
719 result = E_LenThd;
720 break;
721 } else if (reclen == sizeof(HFSPlusCatalogThread)) {
722 gScavGlobals->VeryMinorErrorsStat |= S_BloatedThreadRecordFound;
723 }
724 result = CheckThread(key, (HFSPlusCatalogThread *)rec);
725 break;
726
727 default:
728 RcdError(gScavGlobals, E_CatRec);
729 result = E_CatRec;
730 }
731
732 return (result);
733 }
734
735 /*
736 * CheckCatalogRecord_HFS - verify an HFS catalog record
737 *
738 * Called in leaf-order for every leaf record in the Catalog B-tree
739 */
740 static int
741 CheckCatalogRecord_HFS(const HFSCatalogKey *key, const CatalogRecord *rec, UInt16 reclen)
742 {
743 int result = 0;
744
745 gScavGlobals->CNType = rec->recordType;
746
747 switch (rec->recordType) {
748 case kHFSFolderRecord:
749 ++gCIS.dirCount;
750 if (key->parentID == kHFSRootFolderID )
751 ++gCIS.rootDirCount;
752 if (reclen != sizeof(HFSCatalogFolder)){
753 RcdError(gScavGlobals, E_LenDir);
754 result = E_LenDir;
755 break;
756 }
757 if (key->parentID != gCIS.parentID) {
758 result = CaptureMissingThread(key->parentID, (HFSPlusCatalogKey *)key);
759 if (result) break;
760 /* Pretend thread record was there */
761 ++gCIS.dirThreads;
762 gCIS.parentID = key->parentID;
763 }
764 result = CheckDirectory_HFS(key, (HFSCatalogFolder *)rec);
765 break;
766
767 case kHFSFileRecord:
768 ++gCIS.fileCount;
769 if (key->parentID == kHFSRootFolderID )
770 ++gCIS.rootFileCount;
771 if (reclen != sizeof(HFSCatalogFile)){
772 RcdError(gScavGlobals, E_LenFil);
773 result = E_LenFil;
774 break;
775 }
776 if (key->parentID != gCIS.parentID) {
777 result = CaptureMissingThread(key->parentID, (HFSPlusCatalogKey *)key);
778 if (result) break;
779 /* Pretend thread record was there */
780 ++gCIS.dirThreads;
781 gCIS.parentID = key->parentID;
782 }
783 result = CheckFile_HFS(key, (HFSCatalogFile *)rec);
784 break;
785
786 case kHFSFolderThreadRecord:
787 ++gCIS.dirThreads;
788 gCIS.parentID = key->parentID;
789 /* Fall through */
790 case kHFSFileThreadRecord:
791 if (rec->recordType == kHFSFileThreadRecord)
792 ++gCIS.fileThreads;
793
794 if (reclen != sizeof(HFSCatalogThread)) {
795 RcdError(gScavGlobals, E_LenThd);
796 result = E_LenThd;
797 break;
798 }
799 result = CheckThread_HFS(key, (HFSCatalogThread *)rec);
800 break;
801
802
803 default:
804 RcdError(gScavGlobals, E_CatRec);
805 result = E_CatRec;
806 }
807
808 return (result);
809 }
810
811 /*
812 * CheckDirectory - verify a catalog directory record
813 *
814 * Also collects info for later processing.
815 * Called in leaf-order for every directory record in the Catalog B-tree
816 */
817 static int
818 CheckDirectory(const HFSPlusCatalogKey * key, const HFSPlusCatalogFolder * dir)
819 {
820 UInt32 dirID;
821 int result = 0;
822
823 dirID = dir->folderID;
824
825 /* Directory cannot have these two flags set */
826 if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
827 RcdError(gScavGlobals, E_CatalogFlagsNotZero);
828 gScavGlobals->CBTStat |= S_ReservedNotZero;
829 }
830
831 RecordXAttrBits(gScavGlobals, dir->flags, dir->folderID, kCalculatedCatalogRefNum);
832 #if DEBUG_XATTR
833 plog ("%s: Record folderID=%d for prime modulus calculations\n", __FUNCTION__, dir->folderID);
834 #endif
835
836 if (dirID < kHFSFirstUserCatalogNodeID &&
837 dirID != kHFSRootFolderID) {
838 RcdError(gScavGlobals, E_InvalidID);
839 return (E_InvalidID);
840 }
841 if (dirID >= gCIS.nextCNID )
842 gCIS.nextCNID = dirID + 1;
843
844 gCIS.encodings |= (u_int64_t)(1ULL << MapEncodingToIndex(dir->textEncoding & 0x7F));
845
846 CheckBSDInfo(key, &dir->bsdInfo, true);
847
848 CheckCatalogName(key->nodeName.length, &key->nodeName.unicode[0], key->parentID, false);
849
850 /* Keep track of the directory inodes found */
851 if (dir->flags & kHFSHasLinkChainMask) {
852 gScavGlobals->calculated_dirinodes++;
853 }
854
855 return (result);
856 }
857
858 /*
859 * CheckFile - verify a HFS+ catalog file record
860 * - sanity check values
861 * - collect info for later processing
862 *
863 * Called in leaf-order for every file record in the Catalog B-tree
864 */
865 static int
866 CheckFile(const HFSPlusCatalogKey * key, const HFSPlusCatalogFile * file)
867 {
868 UInt32 fileID;
869 UInt32 blocks;
870 UInt64 bytes;
871 int result = 0;
872 int islink = 0;
873 int isjrnl = 0;
874 size_t len;
875 unsigned char filename[256 * 3];
876
877 (void) utf_encodestr(key->nodeName.unicode,
878 key->nodeName.length * 2,
879 filename, &len, sizeof(filename));
880 filename[len] = '\0';
881
882 RecordXAttrBits(gScavGlobals, file->flags, file->fileID, kCalculatedCatalogRefNum);
883 #if DEBUG_XATTR
884 plog ("%s: Record fileID=%d for prime modulus calculations\n", __FUNCTION__, file->fileID);
885 #endif
886
887 fileID = file->fileID;
888 if (fileID < kHFSFirstUserCatalogNodeID) {
889 RcdError(gScavGlobals, E_InvalidID);
890 result = E_InvalidID;
891 return (result);
892 }
893 if (fileID >= gCIS.nextCNID )
894 gCIS.nextCNID = fileID + 1;
895
896 gCIS.encodings |= (u_int64_t)(1ULL << MapEncodingToIndex(file->textEncoding & 0x7F));
897
898 CheckBSDInfo(key, &file->bsdInfo, false);
899
900 /* check out data fork extent info */
901 result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
902 file->dataFork.extents, &blocks);
903 if (result != noErr)
904 return (result);
905
906 if (file->dataFork.totalBlocks != blocks) {
907 result = RecordBadAllocation(key->parentID, filename, kDataFork,
908 file->dataFork.totalBlocks, blocks);
909 if (result)
910 return (result);
911 } else {
912 bytes = (UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize;
913 if (file->dataFork.logicalSize > bytes) {
914 result = RecordTruncation(key->parentID, filename, kDataFork,
915 file->dataFork.logicalSize, bytes);
916 if (result)
917 return (result);
918 }
919 }
920 /* check out resource fork extent info */
921 result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
922 file->resourceFork.extents, &blocks);
923 if (result != noErr)
924 return (result);
925
926 if (file->resourceFork.totalBlocks != blocks) {
927 result = RecordBadAllocation(key->parentID, filename, kRsrcFork,
928 file->resourceFork.totalBlocks, blocks);
929 if (result)
930 return (result);
931 } else {
932 bytes = (UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize;
933 if (file->resourceFork.logicalSize > bytes) {
934 result = RecordTruncation(key->parentID, filename, kRsrcFork,
935 file->resourceFork.logicalSize, bytes);
936 if (result)
937 return (result);
938 }
939 }
940
941 /* Collect indirect link info for later */
942 if (file->userInfo.fdType == kHardLinkFileType &&
943 file->userInfo.fdCreator == kHFSPlusCreator) {
944 islink = 1;
945 CaptureHardLink(gCIS.hardLinkRef, file);
946 }
947
948 CheckCatalogName(key->nodeName.length, &key->nodeName.unicode[0], key->parentID, false);
949
950 /* Keep track of the directory hard links found */
951 if ((file->flags & kHFSHasLinkChainMask) &&
952 ((file->userInfo.fdType == kHFSAliasType) ||
953 (file->userInfo.fdCreator == kHFSAliasCreator)) &&
954 (key->parentID != gScavGlobals->filelink_priv_dir_id &&
955 key->parentID != gScavGlobals->dirlink_priv_dir_id)) {
956 gScavGlobals->calculated_dirlinks++;
957 islink = 1;
958 }
959
960 /* For non-journaled filesystems, the cached journal file IDs will be 0 */
961 if (file->fileID &&
962 (file->fileID == gScavGlobals->journal_file_id ||
963 file->fileID == gScavGlobals->jib_file_id)) {
964 isjrnl = 1;
965 }
966
967 if (islink == 0) {
968 if (file->flags & kHFSHasLinkChainMask &&
969 (gScavGlobals->filelink_priv_dir_id != key->parentID &&
970 gScavGlobals->dirlink_priv_dir_id != key->parentID)) {
971 RepairOrderPtr p;
972 fsckPrint(gScavGlobals->context, E_LinkChainNonLink, file->fileID);
973 p = AllocMinorRepairOrder(gScavGlobals, 0);
974 if (p) {
975 p->type = E_LinkChainNonLink;
976 p->correct = 0;
977 p->incorrect = 0;
978 p->parid = file->fileID;
979 p->hint = 0;
980 } else {
981 result = memFullErr;
982 }
983 gScavGlobals->CatStat |= S_LinkErrRepair;
984 }
985
986 if (((file->bsdInfo.fileMode & S_IFMT) == S_IFREG) &&
987 gScavGlobals->filelink_priv_dir_id != key->parentID &&
988 file->bsdInfo.special.linkCount > 1 &&
989 isjrnl == 0) {
990 RepairOrderPtr p;
991 char badstr[16];
992 fsckPrint(gScavGlobals->context, E_FileLinkCountError, file->fileID);
993 snprintf(badstr, sizeof(badstr), "%u", file->bsdInfo.special.linkCount);
994 fsckPrint(gScavGlobals->context, E_BadValue, "1", badstr);
995
996 p = AllocMinorRepairOrder(gScavGlobals, 0);
997 if (p) {
998 p->type = E_FileLinkCountError;
999 p->correct = 1;
1000 p->incorrect = file->bsdInfo.special.linkCount;
1001 p->parid = file->fileID;
1002 p->hint = 0;
1003 } else {
1004 result = memFullErr;
1005 }
1006 gScavGlobals->CatStat |= S_LinkErrRepair;
1007 }
1008 /*
1009 * Check for symlinks.
1010 * Currently, d_check_slink is 0x1000, so -D 0x1000 on the command line.
1011 */
1012 if ((cur_debug_level & d_check_slink) != 0) {
1013 if (((file->bsdInfo.fileMode & S_IFMT) == S_IFLNK) ||
1014 file->userInfo.fdType == kSymLinkFileType ||
1015 file->userInfo.fdCreator == kSymLinkCreator) {
1016 // Okay, it claims to be a symlink, at least somehow.
1017 // Check all the info
1018 if (((file->bsdInfo.fileMode & S_IFMT) != S_IFLNK) ||
1019 file->userInfo.fdType != kSymLinkFileType ||
1020 file->userInfo.fdCreator != kSymLinkCreator) {
1021 fsckPrint(gScavGlobals->context, E_BadSymLink, file->fileID);
1022 // Should find a way to print out the path, no?
1023 }
1024 if (file->dataFork.logicalSize > PATH_MAX) {
1025 fsckPrint(gScavGlobals->context, E_BadSymLinkLength, file->fileID, (unsigned int)file->dataFork.logicalSize, (unsigned int)PATH_MAX);
1026 printSymLinkName(gScavGlobals, file->fileID);
1027 } else {
1028 /*
1029 * Reading is hard.
1030 * It's made easier by PATH_MAX being so small, so we can assume
1031 * (for now) that the file is entirely in the 8 extents in the catalog
1032 * record. (In most cases, it'll be only one extent; in the worst
1033 * case, it will only be 2, at least until PATH_MAX is increased.)
1034 */
1035 uint8_t *dataBuffer = malloc(file->dataFork.totalBlocks * gScavGlobals->calculatedVCB->vcbBlockSize + 1);
1036
1037 if (dataBuffer == NULL) {
1038 plog("Unable to allocate %llu bytes for reading symlink", file->dataFork.logicalSize);
1039 } else {
1040 char *curPtr = (char*)dataBuffer;
1041 size_t nread = 0;
1042 size_t dataLen;
1043 HFSPlusExtentDescriptor *ep = (HFSPlusExtentDescriptor*)&file->dataFork.extents[0];
1044
1045 while (nread < file->dataFork.logicalSize) {
1046 Buf_t *bufp;
1047 int rv = CacheRead(&fscache, ep->startBlock * (off_t)gScavGlobals->calculatedVCB->vcbBlockSize, ep->blockCount * gScavGlobals->calculatedVCB->vcbBlockSize, &bufp);
1048 if (rv) {
1049 abort(); // do something better
1050 }
1051 memcpy(curPtr, bufp->Buffer, bufp->Length);
1052 curPtr += bufp->Length;
1053 nread += bufp->Length;
1054 CacheRelease(&fscache, bufp, 0);
1055 }
1056 dataLen = strnlen((char*)dataBuffer, file->dataFork.totalBlocks * gScavGlobals->calculatedVCB->vcbBlockSize);
1057 if (dataLen != file->dataFork.logicalSize) {
1058 fsckPrint(gScavGlobals->context, E_BadSymLinkLength, file->fileID, (unsigned int)dataLen, (unsigned int)file->dataFork.logicalSize);
1059 printSymLinkName(gScavGlobals, file->fileID);
1060 if (debug)
1061 plog("Symlink for file id %u has bad data length\n", file->fileID);
1062 }
1063 free(dataBuffer);
1064 }
1065 }
1066 }
1067 }
1068 }
1069 if (islink == 1 && file->dataFork.totalBlocks != 0) {
1070 fsckPrint(gScavGlobals->context, E_LinkHasData, file->fileID);
1071 gScavGlobals->CatStat |= S_LinkErrNoRepair;
1072 }
1073
1074 return (result);
1075 }
1076
1077 /*
1078 * CheckThread - verify a catalog thread
1079 *
1080 * Called in leaf-order for every thread record in the Catalog B-tree
1081 */
1082 static int
1083 CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread)
1084 {
1085 int result = 0;
1086
1087 if (key->nodeName.length != 0) {
1088 RcdError(gScavGlobals, E_ThdKey);
1089 return (E_ThdKey);
1090 }
1091
1092 result = CheckCatalogName(thread->nodeName.length, &thread->nodeName.unicode[0],
1093 thread->parentID, true);
1094 if (result != noErr) {
1095 RcdError(gScavGlobals, E_ThdCN);
1096 return (E_ThdCN);
1097 }
1098
1099 if (key->parentID < kHFSFirstUserCatalogNodeID &&
1100 key->parentID != kHFSRootParentID &&
1101 key->parentID != kHFSRootFolderID) {
1102 RcdError(gScavGlobals, E_InvalidID);
1103 return (E_InvalidID);
1104 }
1105
1106 if (thread->parentID == kHFSRootParentID) {
1107 if (key->parentID != kHFSRootFolderID) {
1108 RcdError(gScavGlobals, E_InvalidID);
1109 return (E_InvalidID);
1110 }
1111 } else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
1112 thread->parentID != kHFSRootFolderID) {
1113 RcdError(gScavGlobals, E_InvalidID);
1114 return (E_InvalidID);
1115 }
1116
1117 return (0);
1118 }
1119
1120 /*
1121 * CheckDirectory - verify an HFS catalog directory record
1122 *
1123 * Also collects info for later processing.
1124 * Called in leaf-order for every directory record in the Catalog B-tree
1125 */
1126 static int
1127 CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir)
1128 {
1129 UInt32 dirID;
1130 int result = 0;
1131
1132 dirID = dir->folderID;
1133
1134 /* Directory cannot have these two flags set */
1135 if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
1136 RcdError(gScavGlobals, E_CatalogFlagsNotZero);
1137 gScavGlobals->CBTStat |= S_ReservedNotZero;
1138 }
1139
1140 if (dirID < kHFSFirstUserCatalogNodeID &&
1141 dirID != kHFSRootFolderID) {
1142 RcdError(gScavGlobals, E_InvalidID);
1143 return (E_InvalidID);
1144 }
1145 if (dirID >= gCIS.nextCNID )
1146 gCIS.nextCNID = dirID + 1;
1147
1148 CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
1149
1150 return (result);
1151 }
1152
1153 /*
1154 * CheckFile_HFS - verify a HFS catalog file record
1155 * - sanity check values
1156 * - collect info for later processing
1157 *
1158 * Called in b-tree leaf order for every HFS file
1159 * record in the Catalog B-tree.
1160 */
1161 static int
1162 CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file)
1163 {
1164 UInt32 fileID;
1165 UInt32 blocks;
1166 char idstr[20];
1167 int result = 0;
1168
1169 if (file->flags & kHFSThreadExistsMask)
1170 ++gCIS.filesWithThreads;
1171
1172 /* 3843017 : Check for reserved field removed to support new bits in future */
1173 if ((file->dataStartBlock) ||
1174 (file->rsrcStartBlock) ||
1175 (file->reserved))
1176 {
1177 RcdError(gScavGlobals, E_CatalogFlagsNotZero);
1178 gScavGlobals->CBTStat |= S_ReservedNotZero;
1179 }
1180
1181 fileID = file->fileID;
1182 if (fileID < kHFSFirstUserCatalogNodeID) {
1183 RcdError(gScavGlobals, E_InvalidID);
1184 result = E_InvalidID;
1185 return (result);
1186 }
1187 if (fileID >= gCIS.nextCNID )
1188 gCIS.nextCNID = fileID + 1;
1189
1190 /* check out data fork extent info */
1191 result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
1192 file->dataExtents, &blocks);
1193 if (result != noErr)
1194 return (result);
1195 if (file->dataPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
1196 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1197 fsckPrint(gScavGlobals->context, E_PEOF, idstr);
1198 return (noErr); /* we don't fix this, ignore the error */
1199 }
1200 if (file->dataLogicalSize > file->dataPhysicalSize) {
1201 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1202 fsckPrint(gScavGlobals->context, E_LEOF, idstr);
1203 return (noErr); /* we don't fix this, ignore the error */
1204 }
1205
1206 /* check out resource fork extent info */
1207 result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
1208 file->rsrcExtents, &blocks);
1209 if (result != noErr)
1210 return (result);
1211 if (file->rsrcPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
1212 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1213 fsckPrint(gScavGlobals->context, E_PEOF, idstr);
1214 return (noErr); /* we don't fix this, ignore the error */
1215 }
1216 if (file->rsrcLogicalSize > file->rsrcPhysicalSize) {
1217 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1218 fsckPrint(gScavGlobals->context, E_LEOF, idstr);
1219 return (noErr); /* we don't fix this, ignore the error */
1220 }
1221 #if 1
1222 /* Keeping handle in globals of file ID's for HFS volume only */
1223 if (PtrAndHand(&file->fileID, (Handle)gScavGlobals->validFilesList, sizeof(UInt32) ) )
1224 return (R_NoMem);
1225 #endif
1226 CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
1227
1228 return (result);
1229 }
1230
1231 /*
1232 * CheckThread - verify a catalog thread
1233 *
1234 * Called in leaf-order for every thread record in the Catalog B-tree
1235 */
1236 static int
1237 CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread)
1238 {
1239 int result = 0;
1240
1241 if (key->nodeName[0] != 0) {
1242 RcdError(gScavGlobals, E_ThdKey);
1243 return (E_ThdKey);
1244 }
1245
1246 result = CheckCatalogName_HFS(thread->nodeName[0], &thread->nodeName[1],
1247 thread->parentID, true);
1248 if (result != noErr) {
1249 RcdError(gScavGlobals, E_ThdCN);
1250 return (E_ThdCN);
1251 }
1252
1253 if (key->parentID < kHFSFirstUserCatalogNodeID &&
1254 key->parentID != kHFSRootParentID &&
1255 key->parentID != kHFSRootFolderID) {
1256 RcdError(gScavGlobals, E_InvalidID);
1257 return (E_InvalidID);
1258 }
1259
1260 if (thread->parentID == kHFSRootParentID) {
1261 if (key->parentID != kHFSRootFolderID) {
1262 RcdError(gScavGlobals, E_InvalidID);
1263 return (E_InvalidID);
1264 }
1265 } else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
1266 thread->parentID != kHFSRootFolderID) {
1267 RcdError(gScavGlobals, E_InvalidID);
1268 return (E_InvalidID);
1269 }
1270
1271 return (0);
1272 }
1273
1274
1275 /* File types from BSD Mode */
1276 #define FT_MASK 0170000 /* Mask of file type. */
1277 #define FT_FIFO 0010000 /* Named pipe (fifo). */
1278 #define FT_CHR 0020000 /* Character device. */
1279 #define FT_DIR 0040000 /* Directory file. */
1280 #define FT_BLK 0060000 /* Block device. */
1281 #define FT_REG 0100000 /* Regular file. */
1282 #define FT_LNK 0120000 /* Symbolic link. */
1283 #define FT_SOCK 0140000 /* BSD domain socket. */
1284
1285 /*
1286 * CheckBSDInfo - Check BSD Permissions data
1287 * (HFS Plus volumes only)
1288 *
1289 * if repairable then log the error and create a repair order
1290 */
1291 static void
1292 CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir)
1293 {
1294
1295 Boolean reset = false;
1296
1297 /* skip uninitialized BSD info */
1298 if (bsdInfo->fileMode == 0)
1299 return;
1300
1301 switch (bsdInfo->fileMode & FT_MASK) {
1302 case FT_DIR:
1303 if (!isdir)
1304 reset = true;
1305 break;
1306 case FT_REG:
1307 case FT_BLK:
1308 case FT_CHR:
1309 case FT_LNK:
1310 case FT_SOCK:
1311 case FT_FIFO:
1312 if (isdir)
1313 reset = true;
1314 break;
1315 default:
1316 reset = true;
1317 }
1318
1319 if (reset) {
1320 RepairOrderPtr p;
1321 int n;
1322
1323 gScavGlobals->TarBlock = bsdInfo->fileMode & FT_MASK;
1324 RcdError(gScavGlobals, E_InvalidPermissions);
1325
1326 n = CatalogNameSize( (CatalogName *) &key->nodeName, true );
1327
1328 p = AllocMinorRepairOrder(gScavGlobals, n);
1329 if (p == NULL) return;
1330
1331 CopyCatalogName((const CatalogName *)&key->nodeName,
1332 (CatalogName*)&p->name, true);
1333
1334 p->type = E_InvalidPermissions;
1335 p->correct = 0;
1336 p->incorrect = bsdInfo->fileMode;
1337 p->parid = key->parentID;
1338 p->hint = 0;
1339
1340 gScavGlobals->CatStat |= S_Permissions;
1341 }
1342 }
1343
1344 /*
1345 * Validate a Unicode filename for HFS+ volumes
1346 *
1347 * check character count
1348 * check for illegal names
1349 *
1350 * if repairable then log the error and create a repair order
1351 */
1352 static int
1353 CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars, u_int32_t parentID, Boolean thread)
1354 {
1355 OSErr result;
1356 u_int16_t * myPtr;
1357 RepairOrderPtr roPtr;
1358 int myLength;
1359 CatalogName newName;
1360
1361 if ((charCount == 0) || (charCount > kHFSPlusMaxFileNameChars))
1362 return( E_CName );
1363
1364 // only do the remaining checks for files or directories
1365 if ( thread )
1366 return( noErr );
1367
1368 // look for objects with illegal names of "." or "..". We only do this for
1369 // file or folder catalog records (the thread records will be taken care of
1370 // in the repair routines).
1371 if ( charCount < 3 && *uniChars == 0x2E )
1372 {
1373 if ( charCount == 1 || (charCount == 2 && *(uniChars + 1) == 0x2E) )
1374 {
1375 fsckPrint(gScavGlobals->context, E_IllegalName);
1376 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1377 plog( "\tillegal name is 0x" );
1378 PrintName( charCount, (UInt8 *) uniChars, true );
1379 }
1380
1381 // get a new name to use when we rename the file system object
1382 result = UniqueDotName( gScavGlobals, &newName, parentID,
1383 ((charCount == 1) ? true : false), true );
1384 if ( result != noErr )
1385 return( noErr );
1386
1387 // we will copy the old and new names to our RepairOrder. The names will
1388 // look like this:
1389 // 2 byte length of old name
1390 // unicode characters for old name
1391 // 2 byte length of new name
1392 // unicode characters for new name
1393 myLength = (charCount + 1) * 2; // bytes needed for old name
1394 myLength += ((newName.ustr.length + 1) * 2); // bytes needed for new name
1395
1396 roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1397 if ( roPtr == NULL )
1398 return( noErr );
1399
1400 myPtr = (u_int16_t *) &roPtr->name;
1401 *myPtr++ = charCount; // copy in length of old name and bump past it
1402 CopyMemory( uniChars, myPtr, (charCount * 2) ); // copy in old name
1403 myPtr += charCount; // bump past old name
1404 *myPtr++ = newName.ustr.length; // copy in length of new name and bump past it
1405 CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); // copy in new name
1406 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1407 plog( "\treplacement name is 0x" );
1408 PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
1409 }
1410
1411 roPtr->type = E_IllegalName;
1412 roPtr->parid = parentID;
1413 gScavGlobals->CatStat |= S_IllName;
1414 return( E_IllegalName );
1415 }
1416 }
1417
1418 // look for Unicode decomposition errors in file system object names created before Jaguar (10.2)
1419 if ( FixDecomps( charCount, uniChars, &newName.ustr ) )
1420 {
1421 fsckPrint(gScavGlobals->context, E_IllegalName);
1422 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1423 plog( "\tillegal name is 0x" );
1424 PrintName( charCount, (UInt8 *) uniChars, true );
1425 }
1426
1427 // we will copy the old and new names to our RepairOrder. The names will
1428 // look like this:
1429 // 2 byte length of old name
1430 // unicode characters for old name
1431 // 2 byte length of new name
1432 // unicode characters for new name
1433 myLength = (charCount + 1) * 2; // bytes needed for old name
1434 myLength += ((newName.ustr.length + 1) * 2); // bytes needed for new name
1435
1436 roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1437 if ( roPtr == NULL )
1438 return( noErr );
1439
1440 myPtr = (u_int16_t *) &roPtr->name;
1441 *myPtr++ = charCount; // copy in length of old name and bump past it
1442 CopyMemory( uniChars, myPtr, (charCount * 2) ); // copy in old name
1443 myPtr += charCount; // bump past old name
1444 *myPtr++ = newName.ustr.length; // copy in length of new name and bump past it
1445 CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); // copy in new name
1446 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1447 plog( "\treplacement name is 0x" );
1448 PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
1449 }
1450
1451 roPtr->type = E_IllegalName;
1452 roPtr->parid = parentID;
1453 gScavGlobals->CatStat |= S_IllName;
1454 return( E_IllegalName );
1455 }
1456
1457 return( noErr );
1458 }
1459
1460
1461 /*
1462 * Validate an HFS filename
1463 *
1464 * check character count
1465 * check for illegal names
1466 *
1467 * if repairable then log the error and create a repair order
1468 */
1469 static int
1470 CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename, u_int32_t parentID, Boolean thread)
1471 {
1472 u_char * myPtr;
1473 RepairOrderPtr roPtr;
1474 int myLength;
1475 CatalogName newName;
1476
1477 if ((charCount == 0) || (charCount > kHFSMaxFileNameChars))
1478 return( E_CName );
1479
1480 // only do the remaining checks for files or directories
1481 if ( thread )
1482 return( noErr );
1483
1484 // look for objects with illegal names of "." or "..". We only do this for
1485 // file or folder catalog records (the thread records will be taken care of
1486 // in the repair routines).
1487 if ( charCount < 3 && *filename == 0x2E )
1488 {
1489 if ( charCount == 1 || (charCount == 2 && *(filename + 1) == 0x2E) )
1490 {
1491 OSErr result;
1492 fsckPrint(gScavGlobals->context, E_IllegalName);
1493 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1494 plog( "\tillegal name is 0x" );
1495 PrintName( charCount, filename, false );
1496 }
1497
1498 // get a new name to use when we rename the file system object
1499 result = UniqueDotName( gScavGlobals, &newName, parentID,
1500 ((charCount == 1) ? true : false), false );
1501 if ( result != noErr )
1502 return( noErr );
1503
1504 // we will copy the old and new names to our RepairOrder. The names will
1505 // look like this:
1506 // 1 byte length of old name
1507 // characters for old name
1508 // 1 byte length of new name
1509 // characters for new name
1510 myLength = charCount + 1; // bytes needed for old name
1511 myLength += (newName.pstr[0] + 1); // bytes needed for new name
1512 roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1513 if ( roPtr == NULL )
1514 return( noErr );
1515
1516 myPtr = (u_char *)&roPtr->name[0];
1517 *myPtr++ = charCount; // copy in length of old name and bump past it
1518 CopyMemory( filename, myPtr, charCount );
1519 myPtr += charCount; // bump past old name
1520 *myPtr++ = newName.pstr[0]; // copy in length of new name and bump past it
1521 CopyMemory( &newName.pstr[1], myPtr, newName.pstr[0] ); // copy in new name
1522 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1523 plog( "\treplacement name is 0x" );
1524 PrintName( newName.pstr[0], &newName.pstr[1], false );
1525 }
1526
1527 roPtr->type = E_IllegalName;
1528 roPtr->parid = parentID;
1529 gScavGlobals->CatStat |= S_IllName;
1530 return( E_IllegalName );
1531 }
1532 }
1533
1534 return( noErr );
1535 }
1536
1537
1538 /*------------------------------------------------------------------------------
1539 UniqueDotName: figure out a unique name we can use to rename a file system
1540 object that has the illegal name of "." or ".."
1541 ------------------------------------------------------------------------------*/
1542 static OSErr
1543 UniqueDotName( SGlobPtr GPtr,
1544 CatalogName * theNewNamePtr,
1545 UInt32 theParID,
1546 Boolean isSingleDotName,
1547 Boolean isHFSPlus )
1548 {
1549 u_char newChar;
1550 OSErr result;
1551 size_t nameLen;
1552 UInt16 recSize;
1553 SFCB * fcbPtr;
1554 u_char * myPtr;
1555 CatalogRecord record;
1556 CatalogKey catKey;
1557 u_char dotName[] = {'d', 'o', 't', 'd', 'o', 't', 0x0d, 0x00};
1558
1559 fcbPtr = GPtr->calculatedCatalogFCB;
1560
1561 // create key with new name
1562 if ( isSingleDotName )
1563 myPtr = &dotName[3];
1564 else
1565 myPtr = &dotName[0];
1566
1567 nameLen = strlen((char *) myPtr );
1568 if ( isHFSPlus )
1569 {
1570 int i;
1571 theNewNamePtr->ustr.length = nameLen;
1572 for ( i = 0; i < theNewNamePtr->ustr.length; i++ )
1573 theNewNamePtr->ustr.unicode[ i ] = (u_int16_t) *(myPtr + i);
1574 }
1575 else
1576 {
1577 theNewNamePtr->pstr[0] = nameLen;
1578 memcpy( &theNewNamePtr->pstr[1], myPtr, nameLen );
1579 }
1580
1581 // if the name is already in use we will try appending ascii characters
1582 // from '0' (0x30) up to '~' (0x7E)
1583 for ( newChar = 0x30; newChar < 0x7F; newChar++ )
1584 {
1585 // make sure new name isn't already there
1586 BuildCatalogKey( theParID, theNewNamePtr, isHFSPlus, &catKey );
1587 result = SearchBTreeRecord( fcbPtr, &catKey, kNoHint, NULL, &record, &recSize, NULL );
1588 if ( result != noErr )
1589 return( noErr );
1590
1591 // new name is already there, try another
1592 if ( isHFSPlus )
1593 {
1594 theNewNamePtr->ustr.unicode[ nameLen ] = (u_int16_t) newChar;
1595 theNewNamePtr->ustr.length = nameLen + 1;
1596 }
1597 else
1598 {
1599 theNewNamePtr->pstr[ 0 ] = nameLen + 1;
1600 theNewNamePtr->pstr[ nameLen + 1 ] = newChar;
1601 }
1602 }
1603
1604 return( -1 );
1605
1606 } /* UniqueDotName */
1607
1608 /* Function: RecordBadAllocation
1609 *
1610 * Description:
1611 * Record a repair to adjust a file or extended attribute's allocation size.
1612 * This could also trigger a truncation if the new block count isn't large
1613 * enough to cover the current LEOF.
1614 *
1615 * Note that it stores different values and prints different error message
1616 * for file and extended attribute.
1617 * For files -
1618 * E_PEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1619 * Prints filename.
1620 * For extended attributes -
1621 * E_PEOAttr, fileID, attribute name, forkType (kEAData).
1622 * Prints attribute name and filename. Since the attribute name is
1623 * passed as parameter, it needs to lookup the filename.
1624 *
1625 * Input:
1626 * For files -
1627 * parID - parent ID of file
1628 * filename - name of the file
1629 * forkType - type of fork (kDataFork/kRsrcFork)
1630 * For extended attributes -
1631 * parID - fileID for attribute
1632 * filename - name of the attribute
1633 * forkType - kEAData
1634 * Common inputs -
1635 * oldBlkCnt - Incorrect block count
1636 * newBlkCnt - Correct block count
1637 * Output:
1638 * On success, zero.
1639 * On failure, non-zero.
1640 * R_NoMem - out of memory
1641 * E_PEOF - Bad allocation error on plain HFS volume
1642 */
1643 int
1644 RecordBadAllocation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt32 oldBlkCnt, UInt32 newBlkCnt)
1645 {
1646 RepairOrderPtr p;
1647 char goodstr[16];
1648 char badstr[16];
1649 size_t n;
1650 Boolean isHFSPlus;
1651 int result;
1652 char *real_filename;
1653 unsigned int filenamelen;
1654
1655 isHFSPlus = VolumeObjectIsHFSPlus( );
1656 if (forkType == kEAData) {
1657 /* Print attribute name and filename for extended attribute */
1658 filenamelen = NAME_MAX * 3;
1659 real_filename = malloc(filenamelen);
1660 if (!real_filename) {
1661 return (R_NoMem);
1662 }
1663
1664 /* Get the name of the file */
1665 result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
1666 real_filename, &filenamelen, NULL);
1667 if (result) {
1668 /* If error while looking up filename, default to print file ID */
1669 sprintf(real_filename, "id = %u", parID);
1670 }
1671
1672 fsckPrint(gScavGlobals->context, E_PEOAttr, filename, real_filename);
1673 free(real_filename);
1674 } else {
1675 fsckPrint(gScavGlobals->context, E_PEOF, filename);
1676 }
1677 sprintf(goodstr, "%d", newBlkCnt);
1678 sprintf(badstr, "%d", oldBlkCnt);
1679 fsckPrint(gScavGlobals->context, E_BadValue, goodstr, badstr);
1680
1681 /* Only HFS+ is repaired here */
1682 if ( !isHFSPlus )
1683 return (E_PEOF);
1684
1685 n = strlen((char *)filename);
1686 p = AllocMinorRepairOrder(gScavGlobals, n + 1);
1687 if (p == NULL)
1688 return (R_NoMem);
1689
1690 if (forkType == kEAData) {
1691 p->type = E_PEOAttr;
1692 } else {
1693 p->type = E_PEOF;
1694 }
1695 p->forkType = forkType;
1696 p->incorrect = oldBlkCnt;
1697 p->correct = newBlkCnt;
1698 p->hint = 0;
1699 p->parid = parID;
1700 p->name[0] = n; /* pascal string */
1701 CopyMemory(filename, &p->name[1], n);
1702
1703 gScavGlobals->CatStat |= S_FileAllocation;
1704 return (0);
1705 }
1706
1707 /* Function: RecordTruncation
1708 *
1709 * Description:
1710 * Record a repair to trucate a file's logical size.
1711 *
1712 * Note that it stores different error values and prints
1713 * different error message for file and extended attribute.
1714 * For files -
1715 * E_LEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1716 * Prints filename.
1717 * For extended attributes -
1718 * E_LEOAttr, fileID, attribute name, forkType (kEAData).
1719 * Prints attribute name and filename. Since the attribute name is
1720 * passed as parameter, it needs to lookup the filename.
1721 *
1722 * Input:
1723 * For files -
1724 * parID - parent ID of file
1725 * filename - name of the file
1726 * forkType - type of fork (kDataFork/kRsrcFork)
1727 * For extended attributes -
1728 * parID - fileID for attribute
1729 * filename - name of the attribute
1730 * forkType - kEAData
1731 * Common inputs -
1732 * oldSize - Incorrect logical size
1733 * newSize - Correct logical size
1734 * Output:
1735 * On success, zero.
1736 * On failure, non-zero.
1737 * R_NoMem - out of memory
1738 * E_LEOF - Truncation error on plain HFS volume
1739 */
1740 int
1741 RecordTruncation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt64 oldSize, UInt64 newSize)
1742 {
1743 RepairOrderPtr p;
1744 char oldSizeStr[48];
1745 char newSizeStr[48];
1746 size_t n;
1747 Boolean isHFSPlus;
1748 int result;
1749 char *real_filename;
1750 unsigned int filenamelen;
1751
1752 isHFSPlus = VolumeObjectIsHFSPlus( );
1753 if (forkType == kEAData) {
1754 /* Print attribute name and filename for extended attribute */
1755 filenamelen = NAME_MAX * 3;
1756 real_filename = malloc(filenamelen);
1757 if (!real_filename) {
1758 return (R_NoMem);
1759 }
1760
1761 /* Get the name of the file */
1762 result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
1763 real_filename, &filenamelen, NULL);
1764 if (result) {
1765 /* If error while looking up filename, default to print file ID */
1766 sprintf(real_filename, "id = %u", parID);
1767 }
1768
1769 fsckPrint(gScavGlobals->context, E_LEOAttr, filename, real_filename);
1770 free(real_filename);
1771 } else {
1772 fsckPrint(gScavGlobals->context, E_LEOF, filename);
1773 }
1774 sprintf(oldSizeStr, "%qd", oldSize);
1775 sprintf(newSizeStr, "%qd", newSize);
1776 fsckPrint(gScavGlobals->context, E_BadValue, newSizeStr, oldSizeStr);
1777
1778 /* Only HFS+ is repaired here */
1779 if ( !isHFSPlus )
1780 return (E_LEOF);
1781
1782 n = strlen((char *)filename);
1783 p = AllocMinorRepairOrder(gScavGlobals, n + 1);
1784 if (p == NULL)
1785 return (R_NoMem);
1786
1787 if (forkType == kEAData) {
1788 p->type = E_LEOAttr;
1789 } else {
1790 p->type = E_LEOF;
1791 }
1792 p->forkType = forkType;
1793 p->incorrect = oldSize;
1794 p->correct = newSize;
1795 p->hint = 0;
1796 p->parid = parID;
1797 p->name[0] = n; /* pascal string */
1798 CopyMemory(filename, &p->name[1], n);
1799
1800 gScavGlobals->CatStat |= S_FileAllocation;
1801 return (0);
1802 }
1803
1804
1805 /*
1806 * CaptureMissingThread
1807 *
1808 * Capture info for a missing thread record so it
1809 * can be repaired later. The next key is saved
1810 * so that the Catalog Hierarchy check can proceed.
1811 * The thread PID/NAME are initialized during the
1812 * Catalog Hierarchy check phase.
1813 */
1814 static int
1815 CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey)
1816 {
1817 MissingThread *mtp;
1818 Boolean isHFSPlus;
1819
1820 isHFSPlus = VolumeObjectIsHFSPlus( );
1821
1822 fsckPrint(gScavGlobals->context, E_NoThd, threadID);
1823
1824 /* Only HFS+ missing threads are repaired here */
1825 if ( !isHFSPlus)
1826 return (E_NoThd);
1827
1828 mtp = (MissingThread *) AllocateClearMemory(sizeof(MissingThread));
1829 if (mtp == NULL)
1830 return (R_NoMem);
1831
1832 /* add it to the list of missing threads */
1833 mtp->link = gScavGlobals->missingThreadList;
1834 gScavGlobals->missingThreadList = mtp;
1835
1836 mtp->threadID = threadID;
1837 CopyMemory(nextKey, &mtp->nextKey, nextKey->keyLength + 2);
1838
1839 if (gScavGlobals->RepLevel == repairLevelNoProblemsFound)
1840 gScavGlobals->RepLevel = repairLevelVolumeRecoverable;
1841
1842 gScavGlobals->CatStat |= S_MissingThread;
1843 return (noErr);
1844 }
1845
1846
1847 /*
1848 FixDecomps. Originally written by Peter Edberg for use in fsck_hfs.
1849
1850 If inFilename needs updating and the function was able to do this without
1851 overflowing the 255-character limit, it returns 1 (true) and outFIlename
1852 contains the update file. If inFilename did not need updating, or an update
1853 would overflow the limit, the function returns 0 (false) and the contents of
1854 outFilename are undefined.
1855
1856 Function implementation:
1857
1858 Characters that don't require any special handling have combining class 0 and do
1859 not begin a decomposition sequence (of 1-3 characters) that needs updating. For
1860 these characters, the function just copies them from inFilename to outFilename
1861 and sets the pointer outNameCombSeqPtr to NULL (when this pointer is not NULL,
1862 it points to the beginning of a sequence of combining marks that continues up to
1863 the current character; if the current character is combining, it may need to be
1864 reordered into that sequence). The copying operation in cheap, and postponing it
1865 until we know the filename needs modification would make the code much more
1866 complicated.
1867
1868 This copying operation may be invoked from many places in the code, some deeply
1869 nested - any time the code determines that the current character needs no
1870 special handling. For this reason it has a label (CopyBaseChar) and is located
1871 at the end of the character processing loop; various places in the code use goto
1872 statements to jump to it (this is a situation where they are justified).
1873
1874 The main function loop has 4 sections.
1875
1876 First, it quickly determines if the high 12 bits of the character indicate that
1877 it is in a range that has neither nonzero combining class nor any decomposition
1878 sequences that need updating. If so, the code jumps straight to CopyBaseChar.
1879
1880 Second, the code determines if the character is part of a sequence that needs
1881 updating. It checks if the current character has a corresponding action in the
1882 replaceData array. If so, depending on the action, it may need to check for
1883 additional matching characters in inFilename. If the sequence of 1-3 characters
1884 is successfully matched, then a replacement sequence of 1-3 characters is
1885 inserted at the corresponding position in outFilename. While this replacement
1886 sequence is being inserted, each character must be checked to see if it has
1887 nonzero combining class and needs reordering (some replacement sequences consist
1888 entirely of combining characters and may interact with combining characters in
1889 the filename before the updated sequence).
1890
1891 Third, the code handles characters whose high-order 12 bits indicated that some
1892 action was required, but were not part of sequences that needed updating (these
1893 may include characters that were examined in the second part but were part of
1894 sequences that did not completely match, so there are also goto fallthroughs to
1895 this code - labeled CheckCombClass - from the second part). These characters
1896 have to be checked for nonzero combining class; if so, they are reordered as
1897 necessary. Each time a new nonzero class character is encountered, it is added
1898 to outFIlename at the correct point in any active combining character sequence
1899 (with other characters in the sequence moved as necessary), so the sequence
1900 pointed to by outNameCombSeqPtr is always in correct order up to the current
1901 character.
1902
1903 Finally, the fourth part has the default handlers to just copy characters to
1904 outFilename.
1905
1906 */
1907 static Boolean
1908 FixDecomps( u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename )
1909 {
1910 // input filename: address of curr input char,
1911 const u_int16_t * inNamePtr = inFilename;
1912 // and of last input char.
1913 const u_int16_t * inNameLastPtr = &inFilename[charCount - 1];
1914 // output filename buffer: addr of next output char,
1915 u_int16_t * outNamePtr = outFilename->unicode;
1916 // and of last possible output char.
1917 u_int16_t * outNameLastPtr = &outFilename->unicode[kHFSPlusMaxFileNameChars - 1];
1918 u_int16_t * outNameCombSeqPtr = NULL; // if non-NULL, start of output combining seq we are processing.
1919 u_int32_t maxClassValueInSeq = 0;
1920 Boolean didModifyName = 0;
1921
1922 while (inNamePtr <= inNameLastPtr) {
1923 u_int16_t shiftUniChar; // this must be 16 bits for the kShiftUniCharOffset wraparound to work
1924 int32_t rangeIndex;
1925 u_int32_t shiftUniCharLo;
1926 u_int32_t replDataIndex;
1927 u_int32_t currCharClass;
1928
1929 shiftUniChar = *inNamePtr + kShiftUniCharOffset;
1930 if ( shiftUniChar >= kShiftUniCharLimit )
1931 goto CopyBaseChar;
1932 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
1933 if ( rangeIndex < 0 )
1934 goto CopyBaseChar;
1935 shiftUniCharLo = shiftUniChar & kLoFieldMask;
1936 replDataIndex = replaceRanges[rangeIndex][shiftUniCharLo];
1937
1938 if ( replDataIndex > 0 ) {
1939 // we have a possible substitution (replDataIndex != 0)
1940 const u_int16_t * replDataPtr;
1941 u_int32_t action;
1942 u_int32_t copyCount = 0;
1943
1944 replDataPtr = &replaceData[replDataIndex];
1945 action = *replDataPtr++;
1946 switch (action) {
1947 case kReplaceCurWithTwo:
1948 case kReplaceCurWithThree:
1949 inNamePtr++;
1950 copyCount = (action == kReplaceCurWithTwo)? 2: 3;
1951 break;
1952 // the next 3 cases can have a first char or replacement char with nonzero combining class
1953 case kIfNextOneMatchesReplaceAllWithOne:
1954 case kIfNextOneMatchesReplaceAllWithTwo:
1955 if (inNamePtr + 1 <= inNameLastPtr && *(inNamePtr + 1) == *replDataPtr++) {
1956 inNamePtr += 2;
1957 copyCount = (action == kIfNextOneMatchesReplaceAllWithOne)? 1: 2;
1958 } else {
1959 // No substitution; check for comb class & copy char
1960 goto CheckCombClass;
1961 }
1962 break;
1963 case kIfNextTwoMatchReplaceAllWithOne:
1964 if ( inNamePtr + 2 <= inNameLastPtr &&
1965 *(inNamePtr + 1) == *replDataPtr++ &&
1966 *(inNamePtr + 2) == *replDataPtr++)
1967 {
1968 inNamePtr += 3;
1969 copyCount = 1;
1970 } else {
1971 // No substitution; check for comb class & copy char
1972 goto CheckCombClass;
1973 }
1974 break;
1975 }
1976
1977 // now copy copyCount chars (1-3) from replDataPtr to output, checking for comb class etc.
1978 if (outNamePtr + copyCount - 1 > outNameLastPtr) {
1979 didModifyName = 0;
1980 break;
1981 }
1982 while (copyCount-- > 0) {
1983 currCharClass = 0;
1984 shiftUniChar = *replDataPtr + kShiftUniCharOffset;
1985 if ( shiftUniChar < kShiftUniCharLimit ) {
1986 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
1987 if (rangeIndex >= 0) {
1988 shiftUniCharLo = shiftUniChar & kLoFieldMask;
1989 currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
1990 }
1991 }
1992 // This part is similar to CheckCombClass below, which has more detailed
1993 // comments; see them for info.
1994 if ( currCharClass == 0 ) {
1995 outNameCombSeqPtr = NULL;
1996 *outNamePtr++ = *replDataPtr++;
1997 } else if ( outNameCombSeqPtr == NULL ) {
1998 outNameCombSeqPtr = outNamePtr;
1999 maxClassValueInSeq = currCharClass;
2000 *outNamePtr++ = *replDataPtr++;
2001 } else if ( currCharClass >= maxClassValueInSeq ) {
2002 // Sequence is already in correct order with current char,
2003 // just update maxClassValueInSeq
2004 maxClassValueInSeq = currCharClass;
2005 *outNamePtr++ = *replDataPtr++;
2006 } else if ( outNamePtr - outNameCombSeqPtr == 1) {
2007 // Here we know we need to reorder.
2008 // If the sequence is two chars, just interchange them
2009 *outNamePtr++ = *outNameCombSeqPtr;
2010 *outNameCombSeqPtr = *replDataPtr++;
2011 } else {
2012 // General reordering case for three or more chars.
2013 u_int16_t * outNameCombCharPtr;
2014 u_int32_t combCharClass;
2015
2016 outNameCombCharPtr = outNamePtr++;
2017 while (outNameCombCharPtr > outNameCombSeqPtr) {
2018 shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
2019 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
2020 shiftUniCharLo = shiftUniChar & kLoFieldMask;
2021 combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2022 if (combCharClass <= currCharClass)
2023 break;
2024 *outNameCombCharPtr = *(outNameCombCharPtr - 1);
2025 outNameCombCharPtr--;
2026 }
2027 *outNameCombCharPtr = *replDataPtr++;
2028 }
2029 }
2030 didModifyName = 1;
2031 continue;
2032 } /* end of replDataIndex > 0 */
2033
2034 CheckCombClass:
2035 // check for combining class
2036 currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2037 if ( currCharClass == 0 ) {
2038 goto CopyBaseChar;
2039 }
2040 if ( outNameCombSeqPtr == NULL ) {
2041 // The current char is the first of a possible sequence of chars
2042 // with nonzero combining class. Initialize sequence stuff, then
2043 // just copy char to output.
2044 outNameCombSeqPtr = outNamePtr;
2045 maxClassValueInSeq = currCharClass;
2046 goto CopyChar;
2047 }
2048 if ( currCharClass >= maxClassValueInSeq ) {
2049 // The sequence of chars with nonzero combining class is already
2050 // in correct order through the current char; just update the max
2051 // class value found in the sequence.
2052 maxClassValueInSeq = currCharClass;
2053 goto CopyChar;
2054 }
2055
2056 // This char is at least the second in a sequence of chars with
2057 // nonzero combining class in the output buffer; outNameCombSeqPtr
2058 // points to the first in the sequence. Need to put the current
2059 // char into the correct place in the sequence (previous chars in
2060 // the sequence are already in correct order, but the current char
2061 // is out of place).
2062
2063 // First make sure there is room for the new char
2064 if (outNamePtr > outNameLastPtr) {
2065 didModifyName = 0;
2066 break;
2067 }
2068
2069 if (outNamePtr - outNameCombSeqPtr == 1) {
2070 // If the sequence is two chars, just interchange them
2071 *outNamePtr++ = *outNameCombSeqPtr;
2072 *outNameCombSeqPtr = *inNamePtr++;
2073 } else {
2074 // General case: Starting at previous end of sequence, move chars to
2075 // next position in string as long as their class is higher than current
2076 // char; insert current char where we stop. We could cache the
2077 // combining classes instead of re-determining them, but having multiple
2078 // combining marks is rare enough that it wouldn't be worth the overhead.
2079 // At least we don't have to recheck shiftUniChar < kShiftUniCharLimit,
2080 // rangeIndex != 0, etc.)
2081 u_int16_t * outNameCombCharPtr;
2082 u_int32_t combCharClass;
2083
2084 outNameCombCharPtr = outNamePtr++;
2085 while (outNameCombCharPtr > outNameCombSeqPtr) {
2086 shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
2087 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
2088 shiftUniCharLo = shiftUniChar & kLoFieldMask;
2089 combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2090 if (combCharClass <= currCharClass)
2091 break;
2092 *outNameCombCharPtr = *(outNameCombCharPtr - 1);
2093 outNameCombCharPtr--;
2094 }
2095 *outNameCombCharPtr = *inNamePtr++;
2096 }
2097 didModifyName = 1;
2098 continue;
2099
2100 CopyBaseChar:
2101 outNameCombSeqPtr = NULL;
2102 CopyChar:
2103 // nothing special happens with this char, just copy to output
2104 if (outNamePtr <= outNameLastPtr) {
2105 *outNamePtr++ = *inNamePtr++;
2106 } else {
2107 didModifyName = 0;
2108 break;
2109 }
2110 } /* end of while( inNamePtr <= inNameLastPtr ) */
2111
2112 if (didModifyName) {
2113 outFilename->length = outNamePtr - outFilename->unicode;
2114 }
2115 return didModifyName;
2116
2117 } /* FixDecomps */
2118