]> git.saurik.com Git - apple/hfs.git/blob - fsck_hfs/dfalib/CatalogCheck.c
hfs-556.100.11.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 if (debug)
1039 plog("Unable to allocate %llu bytes for reading symlink", file->dataFork.logicalSize);
1040 } else {
1041 char *curPtr = (char*)dataBuffer;
1042 size_t nread = 0;
1043 size_t dataLen;
1044 HFSPlusExtentDescriptor *ep = (HFSPlusExtentDescriptor*)&file->dataFork.extents[0];
1045
1046 while (nread < file->dataFork.logicalSize) {
1047 Buf_t *bufp;
1048 int rv = CacheRead(&fscache, ep->startBlock * (off_t)gScavGlobals->calculatedVCB->vcbBlockSize, ep->blockCount * gScavGlobals->calculatedVCB->vcbBlockSize, &bufp);
1049 if (rv) {
1050 abort(); // do something better
1051 }
1052 memcpy(curPtr, bufp->Buffer, bufp->Length);
1053 curPtr += bufp->Length;
1054 nread += bufp->Length;
1055 CacheRelease(&fscache, bufp, 0);
1056 }
1057 dataLen = strnlen((char*)dataBuffer, file->dataFork.totalBlocks * gScavGlobals->calculatedVCB->vcbBlockSize);
1058 if (dataLen != file->dataFork.logicalSize) {
1059 fsckPrint(gScavGlobals->context, E_BadSymLinkLength, file->fileID, (unsigned int)dataLen, (unsigned int)file->dataFork.logicalSize);
1060 printSymLinkName(gScavGlobals, file->fileID);
1061 if (debug)
1062 plog("Symlink for file id %u has bad data length\n", file->fileID);
1063 }
1064 free(dataBuffer);
1065 }
1066 }
1067 }
1068 }
1069 }
1070 if (islink == 1 && file->dataFork.totalBlocks != 0) {
1071 fsckPrint(gScavGlobals->context, E_LinkHasData, file->fileID);
1072 gScavGlobals->CatStat |= S_LinkErrNoRepair;
1073 }
1074
1075 return (result);
1076 }
1077
1078 /*
1079 * CheckThread - verify a catalog thread
1080 *
1081 * Called in leaf-order for every thread record in the Catalog B-tree
1082 */
1083 static int
1084 CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread)
1085 {
1086 int result = 0;
1087
1088 if (key->nodeName.length != 0) {
1089 RcdError(gScavGlobals, E_ThdKey);
1090 return (E_ThdKey);
1091 }
1092
1093 result = CheckCatalogName(thread->nodeName.length, &thread->nodeName.unicode[0],
1094 thread->parentID, true);
1095 if (result != noErr) {
1096 RcdError(gScavGlobals, E_ThdCN);
1097 return (E_ThdCN);
1098 }
1099
1100 if (key->parentID < kHFSFirstUserCatalogNodeID &&
1101 key->parentID != kHFSRootParentID &&
1102 key->parentID != kHFSRootFolderID) {
1103 RcdError(gScavGlobals, E_InvalidID);
1104 return (E_InvalidID);
1105 }
1106
1107 if (thread->parentID == kHFSRootParentID) {
1108 if (key->parentID != kHFSRootFolderID) {
1109 RcdError(gScavGlobals, E_InvalidID);
1110 return (E_InvalidID);
1111 }
1112 } else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
1113 thread->parentID != kHFSRootFolderID) {
1114 RcdError(gScavGlobals, E_InvalidID);
1115 return (E_InvalidID);
1116 }
1117
1118 return (0);
1119 }
1120
1121 /*
1122 * CheckDirectory - verify an HFS catalog directory record
1123 *
1124 * Also collects info for later processing.
1125 * Called in leaf-order for every directory record in the Catalog B-tree
1126 */
1127 static int
1128 CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir)
1129 {
1130 UInt32 dirID;
1131 int result = 0;
1132
1133 dirID = dir->folderID;
1134
1135 /* Directory cannot have these two flags set */
1136 if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
1137 RcdError(gScavGlobals, E_CatalogFlagsNotZero);
1138 gScavGlobals->CBTStat |= S_ReservedNotZero;
1139 }
1140
1141 if (dirID < kHFSFirstUserCatalogNodeID &&
1142 dirID != kHFSRootFolderID) {
1143 RcdError(gScavGlobals, E_InvalidID);
1144 return (E_InvalidID);
1145 }
1146 if (dirID >= gCIS.nextCNID )
1147 gCIS.nextCNID = dirID + 1;
1148
1149 CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
1150
1151 return (result);
1152 }
1153
1154 /*
1155 * CheckFile_HFS - verify a HFS catalog file record
1156 * - sanity check values
1157 * - collect info for later processing
1158 *
1159 * Called in b-tree leaf order for every HFS file
1160 * record in the Catalog B-tree.
1161 */
1162 static int
1163 CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file)
1164 {
1165 UInt32 fileID;
1166 UInt32 blocks;
1167 char idstr[20];
1168 int result = 0;
1169
1170 if (file->flags & kHFSThreadExistsMask)
1171 ++gCIS.filesWithThreads;
1172
1173 /* 3843017 : Check for reserved field removed to support new bits in future */
1174 if ((file->dataStartBlock) ||
1175 (file->rsrcStartBlock) ||
1176 (file->reserved))
1177 {
1178 RcdError(gScavGlobals, E_CatalogFlagsNotZero);
1179 gScavGlobals->CBTStat |= S_ReservedNotZero;
1180 }
1181
1182 fileID = file->fileID;
1183 if (fileID < kHFSFirstUserCatalogNodeID) {
1184 RcdError(gScavGlobals, E_InvalidID);
1185 result = E_InvalidID;
1186 return (result);
1187 }
1188 if (fileID >= gCIS.nextCNID )
1189 gCIS.nextCNID = fileID + 1;
1190
1191 /* check out data fork extent info */
1192 result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
1193 file->dataExtents, &blocks);
1194 if (result != noErr)
1195 return (result);
1196 if (file->dataPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
1197 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1198 fsckPrint(gScavGlobals->context, E_PEOF, idstr);
1199 return (noErr); /* we don't fix this, ignore the error */
1200 }
1201 if (file->dataLogicalSize > file->dataPhysicalSize) {
1202 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1203 fsckPrint(gScavGlobals->context, E_LEOF, idstr);
1204 return (noErr); /* we don't fix this, ignore the error */
1205 }
1206
1207 /* check out resource fork extent info */
1208 result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
1209 file->rsrcExtents, &blocks);
1210 if (result != noErr)
1211 return (result);
1212 if (file->rsrcPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
1213 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1214 fsckPrint(gScavGlobals->context, E_PEOF, idstr);
1215 return (noErr); /* we don't fix this, ignore the error */
1216 }
1217 if (file->rsrcLogicalSize > file->rsrcPhysicalSize) {
1218 snprintf (idstr, sizeof(idstr), "id=%u", fileID);
1219 fsckPrint(gScavGlobals->context, E_LEOF, idstr);
1220 return (noErr); /* we don't fix this, ignore the error */
1221 }
1222 #if 1
1223 /* Keeping handle in globals of file ID's for HFS volume only */
1224 if (PtrAndHand(&file->fileID, (Handle)gScavGlobals->validFilesList, sizeof(UInt32) ) )
1225 return (R_NoMem);
1226 #endif
1227 CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
1228
1229 return (result);
1230 }
1231
1232 /*
1233 * CheckThread - verify a catalog thread
1234 *
1235 * Called in leaf-order for every thread record in the Catalog B-tree
1236 */
1237 static int
1238 CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread)
1239 {
1240 int result = 0;
1241
1242 if (key->nodeName[0] != 0) {
1243 RcdError(gScavGlobals, E_ThdKey);
1244 return (E_ThdKey);
1245 }
1246
1247 result = CheckCatalogName_HFS(thread->nodeName[0], &thread->nodeName[1],
1248 thread->parentID, true);
1249 if (result != noErr) {
1250 RcdError(gScavGlobals, E_ThdCN);
1251 return (E_ThdCN);
1252 }
1253
1254 if (key->parentID < kHFSFirstUserCatalogNodeID &&
1255 key->parentID != kHFSRootParentID &&
1256 key->parentID != kHFSRootFolderID) {
1257 RcdError(gScavGlobals, E_InvalidID);
1258 return (E_InvalidID);
1259 }
1260
1261 if (thread->parentID == kHFSRootParentID) {
1262 if (key->parentID != kHFSRootFolderID) {
1263 RcdError(gScavGlobals, E_InvalidID);
1264 return (E_InvalidID);
1265 }
1266 } else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
1267 thread->parentID != kHFSRootFolderID) {
1268 RcdError(gScavGlobals, E_InvalidID);
1269 return (E_InvalidID);
1270 }
1271
1272 return (0);
1273 }
1274
1275
1276 /* File types from BSD Mode */
1277 #define FT_MASK 0170000 /* Mask of file type. */
1278 #define FT_FIFO 0010000 /* Named pipe (fifo). */
1279 #define FT_CHR 0020000 /* Character device. */
1280 #define FT_DIR 0040000 /* Directory file. */
1281 #define FT_BLK 0060000 /* Block device. */
1282 #define FT_REG 0100000 /* Regular file. */
1283 #define FT_LNK 0120000 /* Symbolic link. */
1284 #define FT_SOCK 0140000 /* BSD domain socket. */
1285
1286 /*
1287 * CheckBSDInfo - Check BSD Permissions data
1288 * (HFS Plus volumes only)
1289 *
1290 * if repairable then log the error and create a repair order
1291 */
1292 static void
1293 CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir)
1294 {
1295
1296 Boolean reset = false;
1297
1298 /* skip uninitialized BSD info */
1299 if (bsdInfo->fileMode == 0)
1300 return;
1301
1302 switch (bsdInfo->fileMode & FT_MASK) {
1303 case FT_DIR:
1304 if (!isdir)
1305 reset = true;
1306 break;
1307 case FT_REG:
1308 case FT_BLK:
1309 case FT_CHR:
1310 case FT_LNK:
1311 case FT_SOCK:
1312 case FT_FIFO:
1313 if (isdir)
1314 reset = true;
1315 break;
1316 default:
1317 reset = true;
1318 }
1319
1320 if (reset) {
1321 RepairOrderPtr p;
1322 int n;
1323
1324 gScavGlobals->TarBlock = bsdInfo->fileMode & FT_MASK;
1325 RcdError(gScavGlobals, E_InvalidPermissions);
1326
1327 n = CatalogNameSize( (CatalogName *) &key->nodeName, true );
1328
1329 p = AllocMinorRepairOrder(gScavGlobals, n);
1330 if (p == NULL) return;
1331
1332 CopyCatalogName((const CatalogName *)&key->nodeName,
1333 (CatalogName*)&p->name, true);
1334
1335 p->type = E_InvalidPermissions;
1336 p->correct = 0;
1337 p->incorrect = bsdInfo->fileMode;
1338 p->parid = key->parentID;
1339 p->hint = 0;
1340
1341 gScavGlobals->CatStat |= S_Permissions;
1342 }
1343 }
1344
1345 /*
1346 * Validate a Unicode filename for HFS+ volumes
1347 *
1348 * check character count
1349 * check for illegal names
1350 *
1351 * if repairable then log the error and create a repair order
1352 */
1353 static int
1354 CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars, u_int32_t parentID, Boolean thread)
1355 {
1356 OSErr result;
1357 u_int16_t * myPtr;
1358 RepairOrderPtr roPtr;
1359 int myLength;
1360 CatalogName newName;
1361
1362 if ((charCount == 0) || (charCount > kHFSPlusMaxFileNameChars))
1363 return( E_CName );
1364
1365 // only do the remaining checks for files or directories
1366 if ( thread )
1367 return( noErr );
1368
1369 // look for objects with illegal names of "." or "..". We only do this for
1370 // file or folder catalog records (the thread records will be taken care of
1371 // in the repair routines).
1372 if ( charCount < 3 && *uniChars == 0x2E )
1373 {
1374 if ( charCount == 1 || (charCount == 2 && *(uniChars + 1) == 0x2E) )
1375 {
1376 fsckPrint(gScavGlobals->context, E_IllegalName);
1377 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1378 plog( "\tillegal name is 0x" );
1379 PrintName( charCount, (UInt8 *) uniChars, true );
1380 }
1381
1382 // get a new name to use when we rename the file system object
1383 result = UniqueDotName( gScavGlobals, &newName, parentID,
1384 ((charCount == 1) ? true : false), true );
1385 if ( result != noErr )
1386 return( noErr );
1387
1388 // we will copy the old and new names to our RepairOrder. The names will
1389 // look like this:
1390 // 2 byte length of old name
1391 // unicode characters for old name
1392 // 2 byte length of new name
1393 // unicode characters for new name
1394 myLength = (charCount + 1) * 2; // bytes needed for old name
1395 myLength += ((newName.ustr.length + 1) * 2); // bytes needed for new name
1396
1397 roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1398 if ( roPtr == NULL )
1399 return( noErr );
1400
1401 myPtr = (u_int16_t *) &roPtr->name;
1402 *myPtr++ = charCount; // copy in length of old name and bump past it
1403 CopyMemory( uniChars, myPtr, (charCount * 2) ); // copy in old name
1404 myPtr += charCount; // bump past old name
1405 *myPtr++ = newName.ustr.length; // copy in length of new name and bump past it
1406 CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); // copy in new name
1407 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1408 plog( "\treplacement name is 0x" );
1409 PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
1410 }
1411
1412 roPtr->type = E_IllegalName;
1413 roPtr->parid = parentID;
1414 gScavGlobals->CatStat |= S_IllName;
1415 return( E_IllegalName );
1416 }
1417 }
1418
1419 // look for Unicode decomposition errors in file system object names created before Jaguar (10.2)
1420 if ( FixDecomps( charCount, uniChars, &newName.ustr ) )
1421 {
1422 fsckPrint(gScavGlobals->context, E_IllegalName);
1423 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1424 plog( "\tillegal name is 0x" );
1425 PrintName( charCount, (UInt8 *) uniChars, true );
1426 }
1427
1428 // we will copy the old and new names to our RepairOrder. The names will
1429 // look like this:
1430 // 2 byte length of old name
1431 // unicode characters for old name
1432 // 2 byte length of new name
1433 // unicode characters for new name
1434 myLength = (charCount + 1) * 2; // bytes needed for old name
1435 myLength += ((newName.ustr.length + 1) * 2); // bytes needed for new name
1436
1437 roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1438 if ( roPtr == NULL )
1439 return( noErr );
1440
1441 myPtr = (u_int16_t *) &roPtr->name;
1442 *myPtr++ = charCount; // copy in length of old name and bump past it
1443 CopyMemory( uniChars, myPtr, (charCount * 2) ); // copy in old name
1444 myPtr += charCount; // bump past old name
1445 *myPtr++ = newName.ustr.length; // copy in length of new name and bump past it
1446 CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); // copy in new name
1447 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1448 plog( "\treplacement name is 0x" );
1449 PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
1450 }
1451
1452 roPtr->type = E_IllegalName;
1453 roPtr->parid = parentID;
1454 gScavGlobals->CatStat |= S_IllName;
1455 return( E_IllegalName );
1456 }
1457
1458 return( noErr );
1459 }
1460
1461
1462 /*
1463 * Validate an HFS filename
1464 *
1465 * check character count
1466 * check for illegal names
1467 *
1468 * if repairable then log the error and create a repair order
1469 */
1470 static int
1471 CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename, u_int32_t parentID, Boolean thread)
1472 {
1473 u_char * myPtr;
1474 RepairOrderPtr roPtr;
1475 int myLength;
1476 CatalogName newName;
1477
1478 if ((charCount == 0) || (charCount > kHFSMaxFileNameChars))
1479 return( E_CName );
1480
1481 // only do the remaining checks for files or directories
1482 if ( thread )
1483 return( noErr );
1484
1485 // look for objects with illegal names of "." or "..". We only do this for
1486 // file or folder catalog records (the thread records will be taken care of
1487 // in the repair routines).
1488 if ( charCount < 3 && *filename == 0x2E )
1489 {
1490 if ( charCount == 1 || (charCount == 2 && *(filename + 1) == 0x2E) )
1491 {
1492 OSErr result;
1493 fsckPrint(gScavGlobals->context, E_IllegalName);
1494 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1495 plog( "\tillegal name is 0x" );
1496 PrintName( charCount, filename, false );
1497 }
1498
1499 // get a new name to use when we rename the file system object
1500 result = UniqueDotName( gScavGlobals, &newName, parentID,
1501 ((charCount == 1) ? true : false), false );
1502 if ( result != noErr )
1503 return( noErr );
1504
1505 // we will copy the old and new names to our RepairOrder. The names will
1506 // look like this:
1507 // 1 byte length of old name
1508 // characters for old name
1509 // 1 byte length of new name
1510 // characters for new name
1511 myLength = charCount + 1; // bytes needed for old name
1512 myLength += (newName.pstr[0] + 1); // bytes needed for new name
1513 roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
1514 if ( roPtr == NULL )
1515 return( noErr );
1516
1517 myPtr = (u_char *)&roPtr->name[0];
1518 *myPtr++ = charCount; // copy in length of old name and bump past it
1519 CopyMemory( filename, myPtr, charCount );
1520 myPtr += charCount; // bump past old name
1521 *myPtr++ = newName.pstr[0]; // copy in length of new name and bump past it
1522 CopyMemory( &newName.pstr[1], myPtr, newName.pstr[0] ); // copy in new name
1523 if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
1524 plog( "\treplacement name is 0x" );
1525 PrintName( newName.pstr[0], &newName.pstr[1], false );
1526 }
1527
1528 roPtr->type = E_IllegalName;
1529 roPtr->parid = parentID;
1530 gScavGlobals->CatStat |= S_IllName;
1531 return( E_IllegalName );
1532 }
1533 }
1534
1535 return( noErr );
1536 }
1537
1538
1539 /*------------------------------------------------------------------------------
1540 UniqueDotName: figure out a unique name we can use to rename a file system
1541 object that has the illegal name of "." or ".."
1542 ------------------------------------------------------------------------------*/
1543 static OSErr
1544 UniqueDotName( SGlobPtr GPtr,
1545 CatalogName * theNewNamePtr,
1546 UInt32 theParID,
1547 Boolean isSingleDotName,
1548 Boolean isHFSPlus )
1549 {
1550 u_char newChar;
1551 OSErr result;
1552 size_t nameLen;
1553 UInt16 recSize;
1554 SFCB * fcbPtr;
1555 u_char * myPtr;
1556 CatalogRecord record;
1557 CatalogKey catKey;
1558 u_char dotName[] = {'d', 'o', 't', 'd', 'o', 't', 0x0d, 0x00};
1559
1560 fcbPtr = GPtr->calculatedCatalogFCB;
1561
1562 // create key with new name
1563 if ( isSingleDotName )
1564 myPtr = &dotName[3];
1565 else
1566 myPtr = &dotName[0];
1567
1568 nameLen = strlen((char *) myPtr );
1569 if ( isHFSPlus )
1570 {
1571 int i;
1572 theNewNamePtr->ustr.length = nameLen;
1573 for ( i = 0; i < theNewNamePtr->ustr.length; i++ )
1574 theNewNamePtr->ustr.unicode[ i ] = (u_int16_t) *(myPtr + i);
1575 }
1576 else
1577 {
1578 theNewNamePtr->pstr[0] = nameLen;
1579 memcpy( &theNewNamePtr->pstr[1], myPtr, nameLen );
1580 }
1581
1582 // if the name is already in use we will try appending ascii characters
1583 // from '0' (0x30) up to '~' (0x7E)
1584 for ( newChar = 0x30; newChar < 0x7F; newChar++ )
1585 {
1586 // make sure new name isn't already there
1587 BuildCatalogKey( theParID, theNewNamePtr, isHFSPlus, &catKey );
1588 result = SearchBTreeRecord( fcbPtr, &catKey, kNoHint, NULL, &record, &recSize, NULL );
1589 if ( result != noErr )
1590 return( noErr );
1591
1592 // new name is already there, try another
1593 if ( isHFSPlus )
1594 {
1595 theNewNamePtr->ustr.unicode[ nameLen ] = (u_int16_t) newChar;
1596 theNewNamePtr->ustr.length = nameLen + 1;
1597 }
1598 else
1599 {
1600 theNewNamePtr->pstr[ 0 ] = nameLen + 1;
1601 theNewNamePtr->pstr[ nameLen + 1 ] = newChar;
1602 }
1603 }
1604
1605 return( -1 );
1606
1607 } /* UniqueDotName */
1608
1609 /* Function: RecordBadAllocation
1610 *
1611 * Description:
1612 * Record a repair to adjust a file or extended attribute's allocation size.
1613 * This could also trigger a truncation if the new block count isn't large
1614 * enough to cover the current LEOF.
1615 *
1616 * Note that it stores different values and prints different error message
1617 * for file and extended attribute.
1618 * For files -
1619 * E_PEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1620 * Prints filename.
1621 * For extended attributes -
1622 * E_PEOAttr, fileID, attribute name, forkType (kEAData).
1623 * Prints attribute name and filename. Since the attribute name is
1624 * passed as parameter, it needs to lookup the filename.
1625 *
1626 * Input:
1627 * For files -
1628 * parID - parent ID of file
1629 * filename - name of the file
1630 * forkType - type of fork (kDataFork/kRsrcFork)
1631 * For extended attributes -
1632 * parID - fileID for attribute
1633 * filename - name of the attribute
1634 * forkType - kEAData
1635 * Common inputs -
1636 * oldBlkCnt - Incorrect block count
1637 * newBlkCnt - Correct block count
1638 * Output:
1639 * On success, zero.
1640 * On failure, non-zero.
1641 * R_NoMem - out of memory
1642 * E_PEOF - Bad allocation error on plain HFS volume
1643 */
1644 int
1645 RecordBadAllocation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt32 oldBlkCnt, UInt32 newBlkCnt)
1646 {
1647 RepairOrderPtr p;
1648 char goodstr[16];
1649 char badstr[16];
1650 size_t n;
1651 Boolean isHFSPlus;
1652 int result;
1653 char *real_filename;
1654 unsigned int filenamelen;
1655
1656 isHFSPlus = VolumeObjectIsHFSPlus( );
1657 if (forkType == kEAData) {
1658 /* Print attribute name and filename for extended attribute */
1659 filenamelen = NAME_MAX * 3;
1660 real_filename = malloc(filenamelen);
1661 if (!real_filename) {
1662 return (R_NoMem);
1663 }
1664
1665 /* Get the name of the file */
1666 result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
1667 real_filename, &filenamelen, NULL);
1668 if (result) {
1669 /* If error while looking up filename, default to print file ID */
1670 sprintf(real_filename, "id = %u", parID);
1671 }
1672
1673 fsckPrint(gScavGlobals->context, E_PEOAttr, filename, real_filename);
1674 free(real_filename);
1675 } else {
1676 fsckPrint(gScavGlobals->context, E_PEOF, filename);
1677 }
1678 sprintf(goodstr, "%d", newBlkCnt);
1679 sprintf(badstr, "%d", oldBlkCnt);
1680 fsckPrint(gScavGlobals->context, E_BadValue, goodstr, badstr);
1681
1682 /* Only HFS+ is repaired here */
1683 if ( !isHFSPlus )
1684 return (E_PEOF);
1685
1686 n = strlen((char *)filename);
1687 p = AllocMinorRepairOrder(gScavGlobals, n + 1);
1688 if (p == NULL)
1689 return (R_NoMem);
1690
1691 if (forkType == kEAData) {
1692 p->type = E_PEOAttr;
1693 } else {
1694 p->type = E_PEOF;
1695 }
1696 p->forkType = forkType;
1697 p->incorrect = oldBlkCnt;
1698 p->correct = newBlkCnt;
1699 p->hint = 0;
1700 p->parid = parID;
1701 p->name[0] = n; /* pascal string */
1702 CopyMemory(filename, &p->name[1], n);
1703
1704 gScavGlobals->CatStat |= S_FileAllocation;
1705 return (0);
1706 }
1707
1708 /* Function: RecordTruncation
1709 *
1710 * Description:
1711 * Record a repair to trucate a file's logical size.
1712 *
1713 * Note that it stores different error values and prints
1714 * different error message for file and extended attribute.
1715 * For files -
1716 * E_LEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1717 * Prints filename.
1718 * For extended attributes -
1719 * E_LEOAttr, fileID, attribute name, forkType (kEAData).
1720 * Prints attribute name and filename. Since the attribute name is
1721 * passed as parameter, it needs to lookup the filename.
1722 *
1723 * Input:
1724 * For files -
1725 * parID - parent ID of file
1726 * filename - name of the file
1727 * forkType - type of fork (kDataFork/kRsrcFork)
1728 * For extended attributes -
1729 * parID - fileID for attribute
1730 * filename - name of the attribute
1731 * forkType - kEAData
1732 * Common inputs -
1733 * oldSize - Incorrect logical size
1734 * newSize - Correct logical size
1735 * Output:
1736 * On success, zero.
1737 * On failure, non-zero.
1738 * R_NoMem - out of memory
1739 * E_LEOF - Truncation error on plain HFS volume
1740 */
1741 int
1742 RecordTruncation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt64 oldSize, UInt64 newSize)
1743 {
1744 RepairOrderPtr p;
1745 char oldSizeStr[48];
1746 char newSizeStr[48];
1747 size_t n;
1748 Boolean isHFSPlus;
1749 int result;
1750 char *real_filename;
1751 unsigned int filenamelen;
1752
1753 isHFSPlus = VolumeObjectIsHFSPlus( );
1754 if (forkType == kEAData) {
1755 /* Print attribute name and filename for extended attribute */
1756 filenamelen = NAME_MAX * 3;
1757 real_filename = malloc(filenamelen);
1758 if (!real_filename) {
1759 return (R_NoMem);
1760 }
1761
1762 /* Get the name of the file */
1763 result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
1764 real_filename, &filenamelen, NULL);
1765 if (result) {
1766 /* If error while looking up filename, default to print file ID */
1767 sprintf(real_filename, "id = %u", parID);
1768 }
1769
1770 fsckPrint(gScavGlobals->context, E_LEOAttr, filename, real_filename);
1771 free(real_filename);
1772 } else {
1773 fsckPrint(gScavGlobals->context, E_LEOF, filename);
1774 }
1775 sprintf(oldSizeStr, "%qd", oldSize);
1776 sprintf(newSizeStr, "%qd", newSize);
1777 fsckPrint(gScavGlobals->context, E_BadValue, newSizeStr, oldSizeStr);
1778
1779 /* Only HFS+ is repaired here */
1780 if ( !isHFSPlus )
1781 return (E_LEOF);
1782
1783 n = strlen((char *)filename);
1784 p = AllocMinorRepairOrder(gScavGlobals, n + 1);
1785 if (p == NULL)
1786 return (R_NoMem);
1787
1788 if (forkType == kEAData) {
1789 p->type = E_LEOAttr;
1790 } else {
1791 p->type = E_LEOF;
1792 }
1793 p->forkType = forkType;
1794 p->incorrect = oldSize;
1795 p->correct = newSize;
1796 p->hint = 0;
1797 p->parid = parID;
1798 p->name[0] = n; /* pascal string */
1799 CopyMemory(filename, &p->name[1], n);
1800
1801 gScavGlobals->CatStat |= S_FileAllocation;
1802 return (0);
1803 }
1804
1805
1806 /*
1807 * CaptureMissingThread
1808 *
1809 * Capture info for a missing thread record so it
1810 * can be repaired later. The next key is saved
1811 * so that the Catalog Hierarchy check can proceed.
1812 * The thread PID/NAME are initialized during the
1813 * Catalog Hierarchy check phase.
1814 */
1815 static int
1816 CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey)
1817 {
1818 MissingThread *mtp;
1819 Boolean isHFSPlus;
1820
1821 isHFSPlus = VolumeObjectIsHFSPlus( );
1822
1823 fsckPrint(gScavGlobals->context, E_NoThd, threadID);
1824
1825 /* Only HFS+ missing threads are repaired here */
1826 if ( !isHFSPlus)
1827 return (E_NoThd);
1828
1829 mtp = (MissingThread *) AllocateClearMemory(sizeof(MissingThread));
1830 if (mtp == NULL)
1831 return (R_NoMem);
1832
1833 /* add it to the list of missing threads */
1834 mtp->link = gScavGlobals->missingThreadList;
1835 gScavGlobals->missingThreadList = mtp;
1836
1837 mtp->threadID = threadID;
1838 CopyMemory(nextKey, &mtp->nextKey, nextKey->keyLength + 2);
1839
1840 if (gScavGlobals->RepLevel == repairLevelNoProblemsFound)
1841 gScavGlobals->RepLevel = repairLevelVolumeRecoverable;
1842
1843 gScavGlobals->CatStat |= S_MissingThread;
1844 return (noErr);
1845 }
1846
1847
1848 /*
1849 FixDecomps. Originally written by Peter Edberg for use in fsck_hfs.
1850
1851 If inFilename needs updating and the function was able to do this without
1852 overflowing the 255-character limit, it returns 1 (true) and outFIlename
1853 contains the update file. If inFilename did not need updating, or an update
1854 would overflow the limit, the function returns 0 (false) and the contents of
1855 outFilename are undefined.
1856
1857 Function implementation:
1858
1859 Characters that don't require any special handling have combining class 0 and do
1860 not begin a decomposition sequence (of 1-3 characters) that needs updating. For
1861 these characters, the function just copies them from inFilename to outFilename
1862 and sets the pointer outNameCombSeqPtr to NULL (when this pointer is not NULL,
1863 it points to the beginning of a sequence of combining marks that continues up to
1864 the current character; if the current character is combining, it may need to be
1865 reordered into that sequence). The copying operation in cheap, and postponing it
1866 until we know the filename needs modification would make the code much more
1867 complicated.
1868
1869 This copying operation may be invoked from many places in the code, some deeply
1870 nested - any time the code determines that the current character needs no
1871 special handling. For this reason it has a label (CopyBaseChar) and is located
1872 at the end of the character processing loop; various places in the code use goto
1873 statements to jump to it (this is a situation where they are justified).
1874
1875 The main function loop has 4 sections.
1876
1877 First, it quickly determines if the high 12 bits of the character indicate that
1878 it is in a range that has neither nonzero combining class nor any decomposition
1879 sequences that need updating. If so, the code jumps straight to CopyBaseChar.
1880
1881 Second, the code determines if the character is part of a sequence that needs
1882 updating. It checks if the current character has a corresponding action in the
1883 replaceData array. If so, depending on the action, it may need to check for
1884 additional matching characters in inFilename. If the sequence of 1-3 characters
1885 is successfully matched, then a replacement sequence of 1-3 characters is
1886 inserted at the corresponding position in outFilename. While this replacement
1887 sequence is being inserted, each character must be checked to see if it has
1888 nonzero combining class and needs reordering (some replacement sequences consist
1889 entirely of combining characters and may interact with combining characters in
1890 the filename before the updated sequence).
1891
1892 Third, the code handles characters whose high-order 12 bits indicated that some
1893 action was required, but were not part of sequences that needed updating (these
1894 may include characters that were examined in the second part but were part of
1895 sequences that did not completely match, so there are also goto fallthroughs to
1896 this code - labeled CheckCombClass - from the second part). These characters
1897 have to be checked for nonzero combining class; if so, they are reordered as
1898 necessary. Each time a new nonzero class character is encountered, it is added
1899 to outFIlename at the correct point in any active combining character sequence
1900 (with other characters in the sequence moved as necessary), so the sequence
1901 pointed to by outNameCombSeqPtr is always in correct order up to the current
1902 character.
1903
1904 Finally, the fourth part has the default handlers to just copy characters to
1905 outFilename.
1906
1907 */
1908 static Boolean
1909 FixDecomps( u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename )
1910 {
1911 // input filename: address of curr input char,
1912 const u_int16_t * inNamePtr = inFilename;
1913 // and of last input char.
1914 const u_int16_t * inNameLastPtr = &inFilename[charCount - 1];
1915 // output filename buffer: addr of next output char,
1916 u_int16_t * outNamePtr = outFilename->unicode;
1917 // and of last possible output char.
1918 u_int16_t * outNameLastPtr = &outFilename->unicode[kHFSPlusMaxFileNameChars - 1];
1919 u_int16_t * outNameCombSeqPtr = NULL; // if non-NULL, start of output combining seq we are processing.
1920 u_int32_t maxClassValueInSeq = 0;
1921 Boolean didModifyName = 0;
1922
1923 while (inNamePtr <= inNameLastPtr) {
1924 u_int16_t shiftUniChar; // this must be 16 bits for the kShiftUniCharOffset wraparound to work
1925 int32_t rangeIndex;
1926 u_int32_t shiftUniCharLo;
1927 u_int32_t replDataIndex;
1928 u_int32_t currCharClass;
1929
1930 shiftUniChar = *inNamePtr + kShiftUniCharOffset;
1931 if ( shiftUniChar >= kShiftUniCharLimit )
1932 goto CopyBaseChar;
1933 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
1934 if ( rangeIndex < 0 )
1935 goto CopyBaseChar;
1936 shiftUniCharLo = shiftUniChar & kLoFieldMask;
1937 replDataIndex = replaceRanges[rangeIndex][shiftUniCharLo];
1938
1939 if ( replDataIndex > 0 ) {
1940 // we have a possible substitution (replDataIndex != 0)
1941 const u_int16_t * replDataPtr;
1942 u_int32_t action;
1943 u_int32_t copyCount = 0;
1944
1945 replDataPtr = &replaceData[replDataIndex];
1946 action = *replDataPtr++;
1947 switch (action) {
1948 case kReplaceCurWithTwo:
1949 case kReplaceCurWithThree:
1950 inNamePtr++;
1951 copyCount = (action == kReplaceCurWithTwo)? 2: 3;
1952 break;
1953 // the next 3 cases can have a first char or replacement char with nonzero combining class
1954 case kIfNextOneMatchesReplaceAllWithOne:
1955 case kIfNextOneMatchesReplaceAllWithTwo:
1956 if (inNamePtr + 1 <= inNameLastPtr && *(inNamePtr + 1) == *replDataPtr++) {
1957 inNamePtr += 2;
1958 copyCount = (action == kIfNextOneMatchesReplaceAllWithOne)? 1: 2;
1959 } else {
1960 // No substitution; check for comb class & copy char
1961 goto CheckCombClass;
1962 }
1963 break;
1964 case kIfNextTwoMatchReplaceAllWithOne:
1965 if ( inNamePtr + 2 <= inNameLastPtr &&
1966 *(inNamePtr + 1) == *replDataPtr++ &&
1967 *(inNamePtr + 2) == *replDataPtr++)
1968 {
1969 inNamePtr += 3;
1970 copyCount = 1;
1971 } else {
1972 // No substitution; check for comb class & copy char
1973 goto CheckCombClass;
1974 }
1975 break;
1976 }
1977
1978 // now copy copyCount chars (1-3) from replDataPtr to output, checking for comb class etc.
1979 if (outNamePtr + copyCount - 1 > outNameLastPtr) {
1980 didModifyName = 0;
1981 break;
1982 }
1983 while (copyCount-- > 0) {
1984 currCharClass = 0;
1985 shiftUniChar = *replDataPtr + kShiftUniCharOffset;
1986 if ( shiftUniChar < kShiftUniCharLimit ) {
1987 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
1988 if (rangeIndex >= 0) {
1989 shiftUniCharLo = shiftUniChar & kLoFieldMask;
1990 currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
1991 }
1992 }
1993 // This part is similar to CheckCombClass below, which has more detailed
1994 // comments; see them for info.
1995 if ( currCharClass == 0 ) {
1996 outNameCombSeqPtr = NULL;
1997 *outNamePtr++ = *replDataPtr++;
1998 } else if ( outNameCombSeqPtr == NULL ) {
1999 outNameCombSeqPtr = outNamePtr;
2000 maxClassValueInSeq = currCharClass;
2001 *outNamePtr++ = *replDataPtr++;
2002 } else if ( currCharClass >= maxClassValueInSeq ) {
2003 // Sequence is already in correct order with current char,
2004 // just update maxClassValueInSeq
2005 maxClassValueInSeq = currCharClass;
2006 *outNamePtr++ = *replDataPtr++;
2007 } else if ( outNamePtr - outNameCombSeqPtr == 1) {
2008 // Here we know we need to reorder.
2009 // If the sequence is two chars, just interchange them
2010 *outNamePtr++ = *outNameCombSeqPtr;
2011 *outNameCombSeqPtr = *replDataPtr++;
2012 } else {
2013 // General reordering case for three or more chars.
2014 u_int16_t * outNameCombCharPtr;
2015 u_int32_t combCharClass;
2016
2017 outNameCombCharPtr = outNamePtr++;
2018 while (outNameCombCharPtr > outNameCombSeqPtr) {
2019 shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
2020 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
2021 shiftUniCharLo = shiftUniChar & kLoFieldMask;
2022 combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2023 if (combCharClass <= currCharClass)
2024 break;
2025 *outNameCombCharPtr = *(outNameCombCharPtr - 1);
2026 outNameCombCharPtr--;
2027 }
2028 *outNameCombCharPtr = *replDataPtr++;
2029 }
2030 }
2031 didModifyName = 1;
2032 continue;
2033 } /* end of replDataIndex > 0 */
2034
2035 CheckCombClass:
2036 // check for combining class
2037 currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2038 if ( currCharClass == 0 ) {
2039 goto CopyBaseChar;
2040 }
2041 if ( outNameCombSeqPtr == NULL ) {
2042 // The current char is the first of a possible sequence of chars
2043 // with nonzero combining class. Initialize sequence stuff, then
2044 // just copy char to output.
2045 outNameCombSeqPtr = outNamePtr;
2046 maxClassValueInSeq = currCharClass;
2047 goto CopyChar;
2048 }
2049 if ( currCharClass >= maxClassValueInSeq ) {
2050 // The sequence of chars with nonzero combining class is already
2051 // in correct order through the current char; just update the max
2052 // class value found in the sequence.
2053 maxClassValueInSeq = currCharClass;
2054 goto CopyChar;
2055 }
2056
2057 // This char is at least the second in a sequence of chars with
2058 // nonzero combining class in the output buffer; outNameCombSeqPtr
2059 // points to the first in the sequence. Need to put the current
2060 // char into the correct place in the sequence (previous chars in
2061 // the sequence are already in correct order, but the current char
2062 // is out of place).
2063
2064 // First make sure there is room for the new char
2065 if (outNamePtr > outNameLastPtr) {
2066 didModifyName = 0;
2067 break;
2068 }
2069
2070 if (outNamePtr - outNameCombSeqPtr == 1) {
2071 // If the sequence is two chars, just interchange them
2072 *outNamePtr++ = *outNameCombSeqPtr;
2073 *outNameCombSeqPtr = *inNamePtr++;
2074 } else {
2075 // General case: Starting at previous end of sequence, move chars to
2076 // next position in string as long as their class is higher than current
2077 // char; insert current char where we stop. We could cache the
2078 // combining classes instead of re-determining them, but having multiple
2079 // combining marks is rare enough that it wouldn't be worth the overhead.
2080 // At least we don't have to recheck shiftUniChar < kShiftUniCharLimit,
2081 // rangeIndex != 0, etc.)
2082 u_int16_t * outNameCombCharPtr;
2083 u_int32_t combCharClass;
2084
2085 outNameCombCharPtr = outNamePtr++;
2086 while (outNameCombCharPtr > outNameCombSeqPtr) {
2087 shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
2088 rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
2089 shiftUniCharLo = shiftUniChar & kLoFieldMask;
2090 combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
2091 if (combCharClass <= currCharClass)
2092 break;
2093 *outNameCombCharPtr = *(outNameCombCharPtr - 1);
2094 outNameCombCharPtr--;
2095 }
2096 *outNameCombCharPtr = *inNamePtr++;
2097 }
2098 didModifyName = 1;
2099 continue;
2100
2101 CopyBaseChar:
2102 outNameCombSeqPtr = NULL;
2103 CopyChar:
2104 // nothing special happens with this char, just copy to output
2105 if (outNamePtr <= outNameLastPtr) {
2106 *outNamePtr++ = *inNamePtr++;
2107 } else {
2108 didModifyName = 0;
2109 break;
2110 }
2111 } /* end of while( inNamePtr <= inNameLastPtr ) */
2112
2113 if (didModifyName) {
2114 outFilename->length = outNamePtr - outFilename->unicode;
2115 }
2116 return didModifyName;
2117
2118 } /* FixDecomps */
2119