2 * Copyright (c) 2000-2009 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include "Scavenger.h"
25 #include "DecompDataEnums.h"
26 #include "DecompData.h"
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
);
34 * information collected when visiting catalog records
36 struct CatalogIterationSummary
{
38 UInt32 rootDirCount
; /* hfs only */
39 UInt32 rootFileCount
; /* hfs only */
43 UInt32 filesWithThreads
; /* hfs only */
50 /* Globals used during Catalog record checks */
51 struct CatalogIterationSummary gCIS
;
53 SGlobPtr gScavGlobals
;
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
);
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
);
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
);
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
);
75 static int CaptureMissingThread(UInt32 threadID
, const HFSPlusCatalogKey
*nextKey
);
76 static OSErr
UniqueDotName( SGlobPtr GPtr
,
77 CatalogName
* theNewNamePtr
,
79 Boolean isSingleDotName
,
81 static Boolean
FixDecomps( u_int16_t charCount
, const u_int16_t
*inFilename
, HFSUniStr255
*outFilename
);
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.
87 struct folderCountInfo
{
91 struct folderCountInfo
*next
;
95 * Print a symbolic link name given the fileid
98 printSymLinkName(SGlobPtr GPtr
, UInt32 fid
)
100 char pathname
[PATH_MAX
+1], filename
[PATH_MAX
+1];
101 unsigned int path_len
= sizeof(pathname
), fname_len
= sizeof(filename
);
104 if (GetFileNamePathByID(GPtr
, fid
, pathname
, &path_len
, filename
, &fname_len
, &status
) == 0) {
105 fsckPrint(GPtr
->context
, E_BadSymLinkName
, pathname
);
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.
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
122 CountFolderRecords(HFSPlusCatalogKey
*myKey
, HFSPlusCatalogFolder
*folder
, SGlobPtr GPtr
)
124 SFCB
*fcb
= GPtr
->calculatedCatalogFCB
;
126 BTreeIterator iterator
;
127 FSBufferDescriptor btRecord
;
129 HFSPlusCatalogFolder catRecord
;
130 HFSPlusCatalogFile catFile
;
132 HFSPlusCatalogKey
*key
;
133 UInt16 recordSize
= 0;
134 UInt32 folderCount
= 0;
136 ClearMemory(&iterator
, sizeof(iterator
));
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
);
144 for (err
= BTSearchRecord(fcb
, &iterator
, kNoHint
, &btRecord
, &recordSize
, &iterator
);
146 err
= BTIterateRecord(fcb
, kBTreeNextRecord
, &iterator
, &btRecord
, &recordSize
)) {
147 switch (catRecord
.catRecord
.recordType
) {
148 case kHFSPlusFolderThreadRecord
:
149 case kHFSPlusFileThreadRecord
:
152 if (key
->parentID
!= folder
->folderID
)
154 if (catRecord
.catRecord
.recordType
== kHFSPlusFolderRecord
) {
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.
167 if (err
== btNotFound
)
170 if (folderCount
!= folder
->folderCount
) {
171 err
= RcdFCntErr( GPtr
,
182 releaseFolderCountInfo(struct folderCountInfo
*fcip
, int numFolders
)
186 for (i
= 0; i
< numFolders
; i
++) {
187 struct folderCountInfo
*f
= &fcip
[i
];
191 struct folderCountInfo
*t
= f
->next
;
199 static struct folderCountInfo
*
200 findFolderEntry(struct folderCountInfo
*fcip
, int numFolders
, UInt32 fid
)
202 struct folderCountInfo
*retval
= NULL
;
205 indx
= fid
% numFolders
; // Slot index
207 retval
= &fcip
[indx
];
208 if (retval
->folderID
== fid
) {
211 while (retval
->next
!= NULL
) {
212 retval
= retval
->next
;
213 if (retval
->folderID
== fid
)
221 static struct folderCountInfo
*
222 addFolderEntry(struct folderCountInfo
*fcip
, int numFolders
, UInt32 fid
)
224 struct folderCountInfo
*retval
= NULL
;
227 indx
= fid
% numFolders
;
228 retval
= &fcip
[indx
];
230 if (retval
->folderID
== fid
)
232 while (retval
->folderID
!= 0) {
233 if (retval
->next
== NULL
) {
234 retval
->next
= calloc(1, sizeof(struct folderCountInfo
));
235 if (retval
->next
== NULL
) {
239 retval
= retval
->next
;
240 } else if (retval
->folderID
== fid
) {
243 retval
= retval
->next
;
246 retval
->folderID
= fid
;
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).
260 folderCountAdd(struct folderCountInfo
*fcip
, int numFolders
, UInt32 parentID
, UInt32 folderID
, UInt32 count
)
263 struct folderCountInfo
*curp
= NULL
;
266 /* Only add directories represented by folder record to the cache */
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.
274 curp
= findFolderEntry(fcip
, numFolders
, folderID
);
276 curp
= addFolderEntry(fcip
, numFolders
, folderID
);
282 curp
->recordedCount
= count
;
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.
290 curp
= findFolderEntry(fcip
, numFolders
, parentID
);
292 curp
= addFolderEntry(fcip
, numFolders
, parentID
);
298 curp
->computedCount
++;
305 * CheckFolderCount - Verify the folderCount fields of the HFSPlusCatalogFolder records
306 * in the catalog BTree. This is currently only done for HFSX.
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.
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.
324 CheckFolderCount( SGlobPtr GPtr
)
328 BTreeIterator iterator
;
329 FSBufferDescriptor btRecord
;
330 HFSPlusCatalogKey
*key
;
332 HFSPlusCatalogFolder catRecord
;
333 HFSPlusCatalogFile catFile
;
335 UInt16 recordSize
= 0;
336 struct folderCountInfo
*fcip
= NULL
;
338 ClearMemory(&iterator
, sizeof(iterator
));
339 if (!VolumeObjectIsHFSX(GPtr
)) {
343 if (GPtr
->calculatedVCB
== NULL
) {
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.
354 numFolders
= GPtr
->calculatedVCB
->vcbFolderCount
+ 2;
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.
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.
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); })
376 fcip
= LCALLOC(numFolders
, sizeof(*fcip
), MAXCACHEMEM
);
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
);
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
396 for (err
= BTIterateRecord(GPtr
->calculatedCatalogFCB
, kBTreeFirstRecord
,
397 &iterator
, &btRecord
, &recordSize
);
399 err
= BTIterateRecord(GPtr
->calculatedCatalogFCB
, kBTreeNextRecord
,
400 &iterator
, &btRecord
, &recordSize
)) {
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
,
408 catRecord
.catRecord
.flags
| kHFSHasFolderCountMask
,
409 catRecord
.catRecord
.flags
,
410 catRecord
.catRecord
.folderID
);
415 if (folderCountAdd(fcip
, numFolders
,
417 catRecord
.catRecord
.folderID
,
418 catRecord
.catRecord
.folderCount
)) {
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.
428 releaseFolderCountInfo(fcip
, numFolders
);
433 err
= CountFolderRecords(key
, &catRecord
.catRecord
, GPtr
);
438 case kHFSPlusFileRecord
:
439 /* If this file record is a directory hard link, count
440 * it towards our folder count calculations.
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()
454 if (folderCountAdd(fcip
, numFolders
,
455 key
->parentID
, 0, 0)) {
456 /* See above for why we release & restart */
457 releaseFolderCountInfo(fcip
, numFolders
);
467 if (err
== btNotFound
)
468 err
= 0; // We hit the end of the file, which is okay
469 if (err
== 0 && fcip
!= NULL
) {
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.)
477 for (i
= 0; i
< numFolders
; i
++) {
478 struct folderCountInfo
*curp
;
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
487 if (curp
->recordedCount
!= curp
->computedCount
) {
488 /* RcdFCntErr requests a repair order to correct the folder count */
489 err
= RcdFCntErr( GPtr
,
503 releaseFolderCountInfo(fcip
, numFolders
);
510 * CheckCatalogBTree - Verifies the catalog B-tree structure
512 * Causes CheckCatalogRecord to be called for every leaf record
515 CheckCatalogBTree( SGlobPtr GPtr
)
521 hfsplus
= VolumeObjectIsHFSPlus( );
523 ClearMemory(&gCIS
, sizeof(gCIS
));
524 gCIS
.parentID
= kHFSRootParentID
;
525 gCIS
.nextCNID
= kHFSFirstUserCatalogNodeID
;
528 /* Initialize check for file hard links */
529 HardLinkCheckBegin(gScavGlobals
, &gCIS
.hardLinkRef
);
531 /* Initialize check for directory hard links */
532 dirhardlink_init(gScavGlobals
);
535 GPtr
->journal_file_id
= GPtr
->jib_file_id
= 0;
537 if (CheckIfJournaled(GPtr
, true)) {
544 #define HFS_JOURNAL_FILE ".journal"
545 #define HFS_JOURNAL_INFO ".journal_info_block"
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
;
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
;
565 /* for compatibility, init these globals */
566 gScavGlobals
->TarID
= kHFSCatalogFileID
;
567 GetVolumeObjectBlockNum( &gScavGlobals
->TarBlock
);
570 * Check out the BTree structure
572 err
= BTCheck(gScavGlobals
, kCalculatedCatalogRefNum
, (CheckLeafRecordProcPtr
)CheckCatalogRecord
);
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
);
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
);
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
);
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
;
604 gScavGlobals
->calculatedVCB
->vcbNmRtDirs
= gCIS
.rootDirCount
;
605 gScavGlobals
->calculatedVCB
->vcbNmFls
= gCIS
.rootFileCount
;
609 * Check out the allocation map structure
611 err
= BTMapChk(gScavGlobals
, kCalculatedCatalogRefNum
);
615 * Make sure unused nodes in the B-tree are zero filled.
617 err
= BTCheckUnusedNodes(gScavGlobals
, kCalculatedCatalogRefNum
, &gScavGlobals
->CBTStat
);
621 * Compare BTree header record on disk with scavenger's BTree header record
623 err
= CmpBTH(gScavGlobals
, kCalculatedCatalogRefNum
);
627 * Compare BTree map on disk with scavenger's BTree map
629 err
= CmpBTM(gScavGlobals
, kCalculatedCatalogRefNum
);
633 (void) CheckHardLinks(gCIS
.hardLinkRef
);
635 /* If any unrepairable corruption was detected for file
636 * hard links, stop the verification process by returning
639 if (gScavGlobals
->CatStat
& S_LinkErrNoRepair
) {
648 HardLinkCheckEnd(gCIS
.hardLinkRef
);
654 * CheckCatalogRecord - verify a catalog record
656 * Called in leaf-order for every leaf record in the Catalog B-tree
659 CheckCatalogRecord(SGlobPtr GPtr
, const HFSPlusCatalogKey
*key
, const CatalogRecord
*rec
, UInt16 reclen
)
664 isHFSPlus
= VolumeObjectIsHFSPlus( );
665 ++gScavGlobals
->itemsProcessed
;
668 return CheckCatalogRecord_HFS((HFSCatalogKey
*)key
, rec
, reclen
);
670 gScavGlobals
->CNType
= rec
->recordType
;
672 switch (rec
->recordType
) {
673 case kHFSPlusFolderRecord
:
675 if (reclen
!= sizeof(HFSPlusCatalogFolder
)){
676 RcdError(gScavGlobals
, E_LenDir
);
680 if (key
->parentID
!= gCIS
.parentID
) {
681 result
= CaptureMissingThread(key
->parentID
, key
);
683 /* Pretend thread record was there */
685 gCIS
.parentID
= key
->parentID
;
687 result
= CheckDirectory(key
, (HFSPlusCatalogFolder
*)rec
);
690 case kHFSPlusFileRecord
:
692 if (reclen
!= sizeof(HFSPlusCatalogFile
)){
693 RcdError(gScavGlobals
, E_LenFil
);
697 if (key
->parentID
!= gCIS
.parentID
) {
698 result
= CaptureMissingThread(key
->parentID
, key
);
700 /* Pretend thread record was there */
702 gCIS
.parentID
= key
->parentID
;
704 result
= CheckFile(key
, (HFSPlusCatalogFile
*)rec
);
707 case kHFSPlusFolderThreadRecord
:
709 gCIS
.parentID
= key
->parentID
;
712 case kHFSPlusFileThreadRecord
:
713 if (rec
->recordType
== kHFSPlusFileThreadRecord
)
716 if (reclen
> sizeof(HFSPlusCatalogThread
) ||
717 reclen
< sizeof(HFSPlusCatalogThread
) - sizeof(HFSUniStr255
)) {
718 RcdError(gScavGlobals
, E_LenThd
);
721 } else if (reclen
== sizeof(HFSPlusCatalogThread
)) {
722 gScavGlobals
->VeryMinorErrorsStat
|= S_BloatedThreadRecordFound
;
724 result
= CheckThread(key
, (HFSPlusCatalogThread
*)rec
);
728 RcdError(gScavGlobals
, E_CatRec
);
736 * CheckCatalogRecord_HFS - verify an HFS catalog record
738 * Called in leaf-order for every leaf record in the Catalog B-tree
741 CheckCatalogRecord_HFS(const HFSCatalogKey
*key
, const CatalogRecord
*rec
, UInt16 reclen
)
745 gScavGlobals
->CNType
= rec
->recordType
;
747 switch (rec
->recordType
) {
748 case kHFSFolderRecord
:
750 if (key
->parentID
== kHFSRootFolderID
)
752 if (reclen
!= sizeof(HFSCatalogFolder
)){
753 RcdError(gScavGlobals
, E_LenDir
);
757 if (key
->parentID
!= gCIS
.parentID
) {
758 result
= CaptureMissingThread(key
->parentID
, (HFSPlusCatalogKey
*)key
);
760 /* Pretend thread record was there */
762 gCIS
.parentID
= key
->parentID
;
764 result
= CheckDirectory_HFS(key
, (HFSCatalogFolder
*)rec
);
769 if (key
->parentID
== kHFSRootFolderID
)
770 ++gCIS
.rootFileCount
;
771 if (reclen
!= sizeof(HFSCatalogFile
)){
772 RcdError(gScavGlobals
, E_LenFil
);
776 if (key
->parentID
!= gCIS
.parentID
) {
777 result
= CaptureMissingThread(key
->parentID
, (HFSPlusCatalogKey
*)key
);
779 /* Pretend thread record was there */
781 gCIS
.parentID
= key
->parentID
;
783 result
= CheckFile_HFS(key
, (HFSCatalogFile
*)rec
);
786 case kHFSFolderThreadRecord
:
788 gCIS
.parentID
= key
->parentID
;
790 case kHFSFileThreadRecord
:
791 if (rec
->recordType
== kHFSFileThreadRecord
)
794 if (reclen
!= sizeof(HFSCatalogThread
)) {
795 RcdError(gScavGlobals
, E_LenThd
);
799 result
= CheckThread_HFS(key
, (HFSCatalogThread
*)rec
);
804 RcdError(gScavGlobals
, E_CatRec
);
812 * CheckDirectory - verify a catalog directory record
814 * Also collects info for later processing.
815 * Called in leaf-order for every directory record in the Catalog B-tree
818 CheckDirectory(const HFSPlusCatalogKey
* key
, const HFSPlusCatalogFolder
* dir
)
823 dirID
= dir
->folderID
;
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
;
831 RecordXAttrBits(gScavGlobals
, dir
->flags
, dir
->folderID
, kCalculatedCatalogRefNum
);
833 plog ("%s: Record folderID=%d for prime modulus calculations\n", __FUNCTION__
, dir
->folderID
);
836 if (dirID
< kHFSFirstUserCatalogNodeID
&&
837 dirID
!= kHFSRootFolderID
) {
838 RcdError(gScavGlobals
, E_InvalidID
);
839 return (E_InvalidID
);
841 if (dirID
>= gCIS
.nextCNID
)
842 gCIS
.nextCNID
= dirID
+ 1;
844 gCIS
.encodings
|= (u_int64_t
)(1ULL << MapEncodingToIndex(dir
->textEncoding
& 0x7F));
846 CheckBSDInfo(key
, &dir
->bsdInfo
, true);
848 CheckCatalogName(key
->nodeName
.length
, &key
->nodeName
.unicode
[0], key
->parentID
, false);
850 /* Keep track of the directory inodes found */
851 if (dir
->flags
& kHFSHasLinkChainMask
) {
852 gScavGlobals
->calculated_dirinodes
++;
859 * CheckFile - verify a HFS+ catalog file record
860 * - sanity check values
861 * - collect info for later processing
863 * Called in leaf-order for every file record in the Catalog B-tree
866 CheckFile(const HFSPlusCatalogKey
* key
, const HFSPlusCatalogFile
* file
)
875 unsigned char filename
[256 * 3];
877 (void) utf_encodestr(key
->nodeName
.unicode
,
878 key
->nodeName
.length
* 2,
879 filename
, &len
, sizeof(filename
));
880 filename
[len
] = '\0';
882 RecordXAttrBits(gScavGlobals
, file
->flags
, file
->fileID
, kCalculatedCatalogRefNum
);
884 plog ("%s: Record fileID=%d for prime modulus calculations\n", __FUNCTION__
, file
->fileID
);
887 fileID
= file
->fileID
;
888 if (fileID
< kHFSFirstUserCatalogNodeID
) {
889 RcdError(gScavGlobals
, E_InvalidID
);
890 result
= E_InvalidID
;
893 if (fileID
>= gCIS
.nextCNID
)
894 gCIS
.nextCNID
= fileID
+ 1;
896 gCIS
.encodings
|= (u_int64_t
)(1ULL << MapEncodingToIndex(file
->textEncoding
& 0x7F));
898 CheckBSDInfo(key
, &file
->bsdInfo
, false);
900 /* check out data fork extent info */
901 result
= CheckFileExtents(gScavGlobals
, file
->fileID
, kDataFork
, NULL
,
902 file
->dataFork
.extents
, &blocks
);
906 if (file
->dataFork
.totalBlocks
!= blocks
) {
907 result
= RecordBadAllocation(key
->parentID
, filename
, kDataFork
,
908 file
->dataFork
.totalBlocks
, blocks
);
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
);
920 /* check out resource fork extent info */
921 result
= CheckFileExtents(gScavGlobals
, file
->fileID
, kRsrcFork
, NULL
,
922 file
->resourceFork
.extents
, &blocks
);
926 if (file
->resourceFork
.totalBlocks
!= blocks
) {
927 result
= RecordBadAllocation(key
->parentID
, filename
, kRsrcFork
,
928 file
->resourceFork
.totalBlocks
, blocks
);
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
);
941 /* Collect indirect link info for later */
942 if (file
->userInfo
.fdType
== kHardLinkFileType
&&
943 file
->userInfo
.fdCreator
== kHFSPlusCreator
) {
945 CaptureHardLink(gCIS
.hardLinkRef
, file
);
948 CheckCatalogName(key
->nodeName
.length
, &key
->nodeName
.unicode
[0], key
->parentID
, false);
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
++;
960 /* For non-journaled filesystems, the cached journal file IDs will be 0 */
962 (file
->fileID
== gScavGlobals
->journal_file_id
||
963 file
->fileID
== gScavGlobals
->jib_file_id
)) {
968 if (file
->flags
& kHFSHasLinkChainMask
&&
969 (gScavGlobals
->filelink_priv_dir_id
!= key
->parentID
&&
970 gScavGlobals
->dirlink_priv_dir_id
!= key
->parentID
)) {
972 fsckPrint(gScavGlobals
->context
, E_LinkChainNonLink
, file
->fileID
);
973 p
= AllocMinorRepairOrder(gScavGlobals
, 0);
975 p
->type
= E_LinkChainNonLink
;
978 p
->parid
= file
->fileID
;
983 gScavGlobals
->CatStat
|= S_LinkErrRepair
;
986 if (((file
->bsdInfo
.fileMode
& S_IFMT
) == S_IFREG
) &&
987 gScavGlobals
->filelink_priv_dir_id
!= key
->parentID
&&
988 file
->bsdInfo
.special
.linkCount
> 1 &&
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
);
996 p
= AllocMinorRepairOrder(gScavGlobals
, 0);
998 p
->type
= E_FileLinkCountError
;
1000 p
->incorrect
= file
->bsdInfo
.special
.linkCount
;
1001 p
->parid
= file
->fileID
;
1004 result
= memFullErr
;
1006 gScavGlobals
->CatStat
|= S_LinkErrRepair
;
1009 * Check for symlinks.
1010 * Currently, d_check_slink is 0x1000, so -D 0x1000 on the command line.
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?
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
);
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.)
1035 uint8_t *dataBuffer
= malloc(file
->dataFork
.totalBlocks
* gScavGlobals
->calculatedVCB
->vcbBlockSize
+ 1);
1037 if (dataBuffer
== NULL
) {
1039 plog("Unable to allocate %llu bytes for reading symlink", file
->dataFork
.logicalSize
);
1041 char *curPtr
= (char*)dataBuffer
;
1044 HFSPlusExtentDescriptor
*ep
= (HFSPlusExtentDescriptor
*)&file
->dataFork
.extents
[0];
1046 while (nread
< file
->dataFork
.logicalSize
) {
1048 int rv
= CacheRead(&fscache
, ep
->startBlock
* (off_t
)gScavGlobals
->calculatedVCB
->vcbBlockSize
, ep
->blockCount
* gScavGlobals
->calculatedVCB
->vcbBlockSize
, &bufp
);
1050 abort(); // do something better
1052 memcpy(curPtr
, bufp
->Buffer
, bufp
->Length
);
1053 curPtr
+= bufp
->Length
;
1054 nread
+= bufp
->Length
;
1055 CacheRelease(&fscache
, bufp
, 0);
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
);
1062 plog("Symlink for file id %u has bad data length\n", file
->fileID
);
1070 if (islink
== 1 && file
->dataFork
.totalBlocks
!= 0) {
1071 fsckPrint(gScavGlobals
->context
, E_LinkHasData
, file
->fileID
);
1072 gScavGlobals
->CatStat
|= S_LinkErrNoRepair
;
1079 * CheckThread - verify a catalog thread
1081 * Called in leaf-order for every thread record in the Catalog B-tree
1084 CheckThread(const HFSPlusCatalogKey
* key
, const HFSPlusCatalogThread
* thread
)
1088 if (key
->nodeName
.length
!= 0) {
1089 RcdError(gScavGlobals
, E_ThdKey
);
1093 result
= CheckCatalogName(thread
->nodeName
.length
, &thread
->nodeName
.unicode
[0],
1094 thread
->parentID
, true);
1095 if (result
!= noErr
) {
1096 RcdError(gScavGlobals
, E_ThdCN
);
1100 if (key
->parentID
< kHFSFirstUserCatalogNodeID
&&
1101 key
->parentID
!= kHFSRootParentID
&&
1102 key
->parentID
!= kHFSRootFolderID
) {
1103 RcdError(gScavGlobals
, E_InvalidID
);
1104 return (E_InvalidID
);
1107 if (thread
->parentID
== kHFSRootParentID
) {
1108 if (key
->parentID
!= kHFSRootFolderID
) {
1109 RcdError(gScavGlobals
, E_InvalidID
);
1110 return (E_InvalidID
);
1112 } else if (thread
->parentID
< kHFSFirstUserCatalogNodeID
&&
1113 thread
->parentID
!= kHFSRootFolderID
) {
1114 RcdError(gScavGlobals
, E_InvalidID
);
1115 return (E_InvalidID
);
1122 * CheckDirectory - verify an HFS catalog directory record
1124 * Also collects info for later processing.
1125 * Called in leaf-order for every directory record in the Catalog B-tree
1128 CheckDirectory_HFS(const HFSCatalogKey
* key
, const HFSCatalogFolder
* dir
)
1133 dirID
= dir
->folderID
;
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
;
1141 if (dirID
< kHFSFirstUserCatalogNodeID
&&
1142 dirID
!= kHFSRootFolderID
) {
1143 RcdError(gScavGlobals
, E_InvalidID
);
1144 return (E_InvalidID
);
1146 if (dirID
>= gCIS
.nextCNID
)
1147 gCIS
.nextCNID
= dirID
+ 1;
1149 CheckCatalogName_HFS(key
->nodeName
[0], &key
->nodeName
[1], key
->parentID
, false);
1155 * CheckFile_HFS - verify a HFS catalog file record
1156 * - sanity check values
1157 * - collect info for later processing
1159 * Called in b-tree leaf order for every HFS file
1160 * record in the Catalog B-tree.
1163 CheckFile_HFS(const HFSCatalogKey
* key
, const HFSCatalogFile
* file
)
1170 if (file
->flags
& kHFSThreadExistsMask
)
1171 ++gCIS
.filesWithThreads
;
1173 /* 3843017 : Check for reserved field removed to support new bits in future */
1174 if ((file
->dataStartBlock
) ||
1175 (file
->rsrcStartBlock
) ||
1178 RcdError(gScavGlobals
, E_CatalogFlagsNotZero
);
1179 gScavGlobals
->CBTStat
|= S_ReservedNotZero
;
1182 fileID
= file
->fileID
;
1183 if (fileID
< kHFSFirstUserCatalogNodeID
) {
1184 RcdError(gScavGlobals
, E_InvalidID
);
1185 result
= E_InvalidID
;
1188 if (fileID
>= gCIS
.nextCNID
)
1189 gCIS
.nextCNID
= fileID
+ 1;
1191 /* check out data fork extent info */
1192 result
= CheckFileExtents(gScavGlobals
, file
->fileID
, kDataFork
, NULL
,
1193 file
->dataExtents
, &blocks
);
1194 if (result
!= noErr
)
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 */
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 */
1207 /* check out resource fork extent info */
1208 result
= CheckFileExtents(gScavGlobals
, file
->fileID
, kRsrcFork
, NULL
,
1209 file
->rsrcExtents
, &blocks
);
1210 if (result
!= noErr
)
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 */
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 */
1223 /* Keeping handle in globals of file ID's for HFS volume only */
1224 if (PtrAndHand(&file
->fileID
, (Handle
)gScavGlobals
->validFilesList
, sizeof(UInt32
) ) )
1227 CheckCatalogName_HFS(key
->nodeName
[0], &key
->nodeName
[1], key
->parentID
, false);
1233 * CheckThread - verify a catalog thread
1235 * Called in leaf-order for every thread record in the Catalog B-tree
1238 CheckThread_HFS(const HFSCatalogKey
* key
, const HFSCatalogThread
* thread
)
1242 if (key
->nodeName
[0] != 0) {
1243 RcdError(gScavGlobals
, E_ThdKey
);
1247 result
= CheckCatalogName_HFS(thread
->nodeName
[0], &thread
->nodeName
[1],
1248 thread
->parentID
, true);
1249 if (result
!= noErr
) {
1250 RcdError(gScavGlobals
, E_ThdCN
);
1254 if (key
->parentID
< kHFSFirstUserCatalogNodeID
&&
1255 key
->parentID
!= kHFSRootParentID
&&
1256 key
->parentID
!= kHFSRootFolderID
) {
1257 RcdError(gScavGlobals
, E_InvalidID
);
1258 return (E_InvalidID
);
1261 if (thread
->parentID
== kHFSRootParentID
) {
1262 if (key
->parentID
!= kHFSRootFolderID
) {
1263 RcdError(gScavGlobals
, E_InvalidID
);
1264 return (E_InvalidID
);
1266 } else if (thread
->parentID
< kHFSFirstUserCatalogNodeID
&&
1267 thread
->parentID
!= kHFSRootFolderID
) {
1268 RcdError(gScavGlobals
, E_InvalidID
);
1269 return (E_InvalidID
);
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. */
1287 * CheckBSDInfo - Check BSD Permissions data
1288 * (HFS Plus volumes only)
1290 * if repairable then log the error and create a repair order
1293 CheckBSDInfo(const HFSPlusCatalogKey
* key
, const HFSPlusBSDInfo
* bsdInfo
, int isdir
)
1296 Boolean reset
= false;
1298 /* skip uninitialized BSD info */
1299 if (bsdInfo
->fileMode
== 0)
1302 switch (bsdInfo
->fileMode
& FT_MASK
) {
1324 gScavGlobals
->TarBlock
= bsdInfo
->fileMode
& FT_MASK
;
1325 RcdError(gScavGlobals
, E_InvalidPermissions
);
1327 n
= CatalogNameSize( (CatalogName
*) &key
->nodeName
, true );
1329 p
= AllocMinorRepairOrder(gScavGlobals
, n
);
1330 if (p
== NULL
) return;
1332 CopyCatalogName((const CatalogName
*)&key
->nodeName
,
1333 (CatalogName
*)&p
->name
, true);
1335 p
->type
= E_InvalidPermissions
;
1337 p
->incorrect
= bsdInfo
->fileMode
;
1338 p
->parid
= key
->parentID
;
1341 gScavGlobals
->CatStat
|= S_Permissions
;
1346 * Validate a Unicode filename for HFS+ volumes
1348 * check character count
1349 * check for illegal names
1351 * if repairable then log the error and create a repair order
1354 CheckCatalogName(u_int16_t charCount
, const u_int16_t
*uniChars
, u_int32_t parentID
, Boolean thread
)
1358 RepairOrderPtr roPtr
;
1360 CatalogName newName
;
1362 if ((charCount
== 0) || (charCount
> kHFSPlusMaxFileNameChars
))
1365 // only do the remaining checks for files or directories
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 )
1374 if ( charCount
== 1 || (charCount
== 2 && *(uniChars
+ 1) == 0x2E) )
1376 fsckPrint(gScavGlobals
->context
, E_IllegalName
);
1377 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1378 plog( "\tillegal name is 0x" );
1379 PrintName( charCount
, (UInt8
*) uniChars
, true );
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
)
1388 // we will copy the old and new names to our RepairOrder. The names will
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
1397 roPtr
= AllocMinorRepairOrder( gScavGlobals
, myLength
);
1398 if ( roPtr
== NULL
)
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 );
1412 roPtr
->type
= E_IllegalName
;
1413 roPtr
->parid
= parentID
;
1414 gScavGlobals
->CatStat
|= S_IllName
;
1415 return( E_IllegalName
);
1419 // look for Unicode decomposition errors in file system object names created before Jaguar (10.2)
1420 if ( FixDecomps( charCount
, uniChars
, &newName
.ustr
) )
1422 fsckPrint(gScavGlobals
->context
, E_IllegalName
);
1423 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1424 plog( "\tillegal name is 0x" );
1425 PrintName( charCount
, (UInt8
*) uniChars
, true );
1428 // we will copy the old and new names to our RepairOrder. The names will
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
1437 roPtr
= AllocMinorRepairOrder( gScavGlobals
, myLength
);
1438 if ( roPtr
== NULL
)
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 );
1452 roPtr
->type
= E_IllegalName
;
1453 roPtr
->parid
= parentID
;
1454 gScavGlobals
->CatStat
|= S_IllName
;
1455 return( E_IllegalName
);
1463 * Validate an HFS filename
1465 * check character count
1466 * check for illegal names
1468 * if repairable then log the error and create a repair order
1471 CheckCatalogName_HFS(u_int16_t charCount
, const u_char
*filename
, u_int32_t parentID
, Boolean thread
)
1474 RepairOrderPtr roPtr
;
1476 CatalogName newName
;
1478 if ((charCount
== 0) || (charCount
> kHFSMaxFileNameChars
))
1481 // only do the remaining checks for files or directories
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 )
1490 if ( charCount
== 1 || (charCount
== 2 && *(filename
+ 1) == 0x2E) )
1493 fsckPrint(gScavGlobals
->context
, E_IllegalName
);
1494 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1495 plog( "\tillegal name is 0x" );
1496 PrintName( charCount
, filename
, false );
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
)
1505 // we will copy the old and new names to our RepairOrder. The names will
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
)
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 );
1528 roPtr
->type
= E_IllegalName
;
1529 roPtr
->parid
= parentID
;
1530 gScavGlobals
->CatStat
|= S_IllName
;
1531 return( E_IllegalName
);
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 ------------------------------------------------------------------------------*/
1544 UniqueDotName( SGlobPtr GPtr
,
1545 CatalogName
* theNewNamePtr
,
1547 Boolean isSingleDotName
,
1556 CatalogRecord record
;
1558 u_char dotName
[] = {'d', 'o', 't', 'd', 'o', 't', 0x0d, 0x00};
1560 fcbPtr
= GPtr
->calculatedCatalogFCB
;
1562 // create key with new name
1563 if ( isSingleDotName
)
1564 myPtr
= &dotName
[3];
1566 myPtr
= &dotName
[0];
1568 nameLen
= strlen((char *) myPtr
);
1572 theNewNamePtr
->ustr
.length
= nameLen
;
1573 for ( i
= 0; i
< theNewNamePtr
->ustr
.length
; i
++ )
1574 theNewNamePtr
->ustr
.unicode
[ i
] = (u_int16_t
) *(myPtr
+ i
);
1578 theNewNamePtr
->pstr
[0] = nameLen
;
1579 memcpy( &theNewNamePtr
->pstr
[1], myPtr
, nameLen
);
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
++ )
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
)
1592 // new name is already there, try another
1595 theNewNamePtr
->ustr
.unicode
[ nameLen
] = (u_int16_t
) newChar
;
1596 theNewNamePtr
->ustr
.length
= nameLen
+ 1;
1600 theNewNamePtr
->pstr
[ 0 ] = nameLen
+ 1;
1601 theNewNamePtr
->pstr
[ nameLen
+ 1 ] = newChar
;
1607 } /* UniqueDotName */
1609 /* Function: RecordBadAllocation
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.
1616 * Note that it stores different values and prints different error message
1617 * for file and extended attribute.
1619 * E_PEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
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.
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
1636 * oldBlkCnt - Incorrect block count
1637 * newBlkCnt - Correct block count
1640 * On failure, non-zero.
1641 * R_NoMem - out of memory
1642 * E_PEOF - Bad allocation error on plain HFS volume
1645 RecordBadAllocation(UInt32 parID
, unsigned char * filename
, UInt32 forkType
, UInt32 oldBlkCnt
, UInt32 newBlkCnt
)
1653 char *real_filename
;
1654 unsigned int filenamelen
;
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
) {
1665 /* Get the name of the file */
1666 result
= GetFileNamePathByID(gScavGlobals
, parID
, NULL
, NULL
,
1667 real_filename
, &filenamelen
, NULL
);
1669 /* If error while looking up filename, default to print file ID */
1670 sprintf(real_filename
, "id = %u", parID
);
1673 fsckPrint(gScavGlobals
->context
, E_PEOAttr
, filename
, real_filename
);
1674 free(real_filename
);
1676 fsckPrint(gScavGlobals
->context
, E_PEOF
, filename
);
1678 sprintf(goodstr
, "%d", newBlkCnt
);
1679 sprintf(badstr
, "%d", oldBlkCnt
);
1680 fsckPrint(gScavGlobals
->context
, E_BadValue
, goodstr
, badstr
);
1682 /* Only HFS+ is repaired here */
1686 n
= strlen((char *)filename
);
1687 p
= AllocMinorRepairOrder(gScavGlobals
, n
+ 1);
1691 if (forkType
== kEAData
) {
1692 p
->type
= E_PEOAttr
;
1696 p
->forkType
= forkType
;
1697 p
->incorrect
= oldBlkCnt
;
1698 p
->correct
= newBlkCnt
;
1701 p
->name
[0] = n
; /* pascal string */
1702 CopyMemory(filename
, &p
->name
[1], n
);
1704 gScavGlobals
->CatStat
|= S_FileAllocation
;
1708 /* Function: RecordTruncation
1711 * Record a repair to trucate a file's logical size.
1713 * Note that it stores different error values and prints
1714 * different error message for file and extended attribute.
1716 * E_LEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
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.
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
1733 * oldSize - Incorrect logical size
1734 * newSize - Correct logical size
1737 * On failure, non-zero.
1738 * R_NoMem - out of memory
1739 * E_LEOF - Truncation error on plain HFS volume
1742 RecordTruncation(UInt32 parID
, unsigned char * filename
, UInt32 forkType
, UInt64 oldSize
, UInt64 newSize
)
1745 char oldSizeStr
[48];
1746 char newSizeStr
[48];
1750 char *real_filename
;
1751 unsigned int filenamelen
;
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
) {
1762 /* Get the name of the file */
1763 result
= GetFileNamePathByID(gScavGlobals
, parID
, NULL
, NULL
,
1764 real_filename
, &filenamelen
, NULL
);
1766 /* If error while looking up filename, default to print file ID */
1767 sprintf(real_filename
, "id = %u", parID
);
1770 fsckPrint(gScavGlobals
->context
, E_LEOAttr
, filename
, real_filename
);
1771 free(real_filename
);
1773 fsckPrint(gScavGlobals
->context
, E_LEOF
, filename
);
1775 sprintf(oldSizeStr
, "%qd", oldSize
);
1776 sprintf(newSizeStr
, "%qd", newSize
);
1777 fsckPrint(gScavGlobals
->context
, E_BadValue
, newSizeStr
, oldSizeStr
);
1779 /* Only HFS+ is repaired here */
1783 n
= strlen((char *)filename
);
1784 p
= AllocMinorRepairOrder(gScavGlobals
, n
+ 1);
1788 if (forkType
== kEAData
) {
1789 p
->type
= E_LEOAttr
;
1793 p
->forkType
= forkType
;
1794 p
->incorrect
= oldSize
;
1795 p
->correct
= newSize
;
1798 p
->name
[0] = n
; /* pascal string */
1799 CopyMemory(filename
, &p
->name
[1], n
);
1801 gScavGlobals
->CatStat
|= S_FileAllocation
;
1807 * CaptureMissingThread
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.
1816 CaptureMissingThread(UInt32 threadID
, const HFSPlusCatalogKey
*nextKey
)
1821 isHFSPlus
= VolumeObjectIsHFSPlus( );
1823 fsckPrint(gScavGlobals
->context
, E_NoThd
, threadID
);
1825 /* Only HFS+ missing threads are repaired here */
1829 mtp
= (MissingThread
*) AllocateClearMemory(sizeof(MissingThread
));
1833 /* add it to the list of missing threads */
1834 mtp
->link
= gScavGlobals
->missingThreadList
;
1835 gScavGlobals
->missingThreadList
= mtp
;
1837 mtp
->threadID
= threadID
;
1838 CopyMemory(nextKey
, &mtp
->nextKey
, nextKey
->keyLength
+ 2);
1840 if (gScavGlobals
->RepLevel
== repairLevelNoProblemsFound
)
1841 gScavGlobals
->RepLevel
= repairLevelVolumeRecoverable
;
1843 gScavGlobals
->CatStat
|= S_MissingThread
;
1849 FixDecomps. Originally written by Peter Edberg for use in fsck_hfs.
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.
1857 Function implementation:
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
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).
1875 The main function loop has 4 sections.
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.
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).
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
1904 Finally, the fourth part has the default handlers to just copy characters to
1909 FixDecomps( u_int16_t charCount
, const u_int16_t
*inFilename
, HFSUniStr255
*outFilename
)
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;
1923 while (inNamePtr
<= inNameLastPtr
) {
1924 u_int16_t shiftUniChar
; // this must be 16 bits for the kShiftUniCharOffset wraparound to work
1926 u_int32_t shiftUniCharLo
;
1927 u_int32_t replDataIndex
;
1928 u_int32_t currCharClass
;
1930 shiftUniChar
= *inNamePtr
+ kShiftUniCharOffset
;
1931 if ( shiftUniChar
>= kShiftUniCharLimit
)
1933 rangeIndex
= classAndReplIndex
[shiftUniChar
>> kLoFieldBitSize
];
1934 if ( rangeIndex
< 0 )
1936 shiftUniCharLo
= shiftUniChar
& kLoFieldMask
;
1937 replDataIndex
= replaceRanges
[rangeIndex
][shiftUniCharLo
];
1939 if ( replDataIndex
> 0 ) {
1940 // we have a possible substitution (replDataIndex != 0)
1941 const u_int16_t
* replDataPtr
;
1943 u_int32_t copyCount
= 0;
1945 replDataPtr
= &replaceData
[replDataIndex
];
1946 action
= *replDataPtr
++;
1948 case kReplaceCurWithTwo
:
1949 case kReplaceCurWithThree
:
1951 copyCount
= (action
== kReplaceCurWithTwo
)? 2: 3;
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
++) {
1958 copyCount
= (action
== kIfNextOneMatchesReplaceAllWithOne
)? 1: 2;
1960 // No substitution; check for comb class & copy char
1961 goto CheckCombClass
;
1964 case kIfNextTwoMatchReplaceAllWithOne
:
1965 if ( inNamePtr
+ 2 <= inNameLastPtr
&&
1966 *(inNamePtr
+ 1) == *replDataPtr
++ &&
1967 *(inNamePtr
+ 2) == *replDataPtr
++)
1972 // No substitution; check for comb class & copy char
1973 goto CheckCombClass
;
1978 // now copy copyCount chars (1-3) from replDataPtr to output, checking for comb class etc.
1979 if (outNamePtr
+ copyCount
- 1 > outNameLastPtr
) {
1983 while (copyCount
-- > 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
];
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
++;
2013 // General reordering case for three or more chars.
2014 u_int16_t
* outNameCombCharPtr
;
2015 u_int32_t combCharClass
;
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
)
2025 *outNameCombCharPtr
= *(outNameCombCharPtr
- 1);
2026 outNameCombCharPtr
--;
2028 *outNameCombCharPtr
= *replDataPtr
++;
2033 } /* end of replDataIndex > 0 */
2036 // check for combining class
2037 currCharClass
= combClassRanges
[rangeIndex
][shiftUniCharLo
];
2038 if ( currCharClass
== 0 ) {
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
;
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
;
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).
2064 // First make sure there is room for the new char
2065 if (outNamePtr
> outNameLastPtr
) {
2070 if (outNamePtr
- outNameCombSeqPtr
== 1) {
2071 // If the sequence is two chars, just interchange them
2072 *outNamePtr
++ = *outNameCombSeqPtr
;
2073 *outNameCombSeqPtr
= *inNamePtr
++;
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
;
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
)
2093 *outNameCombCharPtr
= *(outNameCombCharPtr
- 1);
2094 outNameCombCharPtr
--;
2096 *outNameCombCharPtr
= *inNamePtr
++;
2102 outNameCombSeqPtr
= NULL
;
2104 // nothing special happens with this char, just copy to output
2105 if (outNamePtr
<= outNameLastPtr
) {
2106 *outNamePtr
++ = *inNamePtr
++;
2111 } /* end of while( inNamePtr <= inNameLastPtr ) */
2113 if (didModifyName
) {
2114 outFilename
->length
= outNamePtr
- outFilename
->unicode
;
2116 return didModifyName
;