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
) {
1038 plog("Unable to allocate %llu bytes for reading symlink", file
->dataFork
.logicalSize
);
1040 char *curPtr
= (char*)dataBuffer
;
1043 HFSPlusExtentDescriptor
*ep
= (HFSPlusExtentDescriptor
*)&file
->dataFork
.extents
[0];
1045 while (nread
< file
->dataFork
.logicalSize
) {
1047 int rv
= CacheRead(&fscache
, ep
->startBlock
* (off_t
)gScavGlobals
->calculatedVCB
->vcbBlockSize
, ep
->blockCount
* gScavGlobals
->calculatedVCB
->vcbBlockSize
, &bufp
);
1049 abort(); // do something better
1051 memcpy(curPtr
, bufp
->Buffer
, bufp
->Length
);
1052 curPtr
+= bufp
->Length
;
1053 nread
+= bufp
->Length
;
1054 CacheRelease(&fscache
, bufp
, 0);
1056 dataLen
= strnlen((char*)dataBuffer
, file
->dataFork
.totalBlocks
* gScavGlobals
->calculatedVCB
->vcbBlockSize
);
1057 if (dataLen
!= file
->dataFork
.logicalSize
) {
1058 fsckPrint(gScavGlobals
->context
, E_BadSymLinkLength
, file
->fileID
, (unsigned int)dataLen
, (unsigned int)file
->dataFork
.logicalSize
);
1059 printSymLinkName(gScavGlobals
, file
->fileID
);
1061 plog("Symlink for file id %u has bad data length\n", file
->fileID
);
1069 if (islink
== 1 && file
->dataFork
.totalBlocks
!= 0) {
1070 fsckPrint(gScavGlobals
->context
, E_LinkHasData
, file
->fileID
);
1071 gScavGlobals
->CatStat
|= S_LinkErrNoRepair
;
1078 * CheckThread - verify a catalog thread
1080 * Called in leaf-order for every thread record in the Catalog B-tree
1083 CheckThread(const HFSPlusCatalogKey
* key
, const HFSPlusCatalogThread
* thread
)
1087 if (key
->nodeName
.length
!= 0) {
1088 RcdError(gScavGlobals
, E_ThdKey
);
1092 result
= CheckCatalogName(thread
->nodeName
.length
, &thread
->nodeName
.unicode
[0],
1093 thread
->parentID
, true);
1094 if (result
!= noErr
) {
1095 RcdError(gScavGlobals
, E_ThdCN
);
1099 if (key
->parentID
< kHFSFirstUserCatalogNodeID
&&
1100 key
->parentID
!= kHFSRootParentID
&&
1101 key
->parentID
!= kHFSRootFolderID
) {
1102 RcdError(gScavGlobals
, E_InvalidID
);
1103 return (E_InvalidID
);
1106 if (thread
->parentID
== kHFSRootParentID
) {
1107 if (key
->parentID
!= kHFSRootFolderID
) {
1108 RcdError(gScavGlobals
, E_InvalidID
);
1109 return (E_InvalidID
);
1111 } else if (thread
->parentID
< kHFSFirstUserCatalogNodeID
&&
1112 thread
->parentID
!= kHFSRootFolderID
) {
1113 RcdError(gScavGlobals
, E_InvalidID
);
1114 return (E_InvalidID
);
1121 * CheckDirectory - verify an HFS catalog directory record
1123 * Also collects info for later processing.
1124 * Called in leaf-order for every directory record in the Catalog B-tree
1127 CheckDirectory_HFS(const HFSCatalogKey
* key
, const HFSCatalogFolder
* dir
)
1132 dirID
= dir
->folderID
;
1134 /* Directory cannot have these two flags set */
1135 if ((dir
->flags
& (kHFSFileLockedMask
| kHFSThreadExistsMask
)) != 0) {
1136 RcdError(gScavGlobals
, E_CatalogFlagsNotZero
);
1137 gScavGlobals
->CBTStat
|= S_ReservedNotZero
;
1140 if (dirID
< kHFSFirstUserCatalogNodeID
&&
1141 dirID
!= kHFSRootFolderID
) {
1142 RcdError(gScavGlobals
, E_InvalidID
);
1143 return (E_InvalidID
);
1145 if (dirID
>= gCIS
.nextCNID
)
1146 gCIS
.nextCNID
= dirID
+ 1;
1148 CheckCatalogName_HFS(key
->nodeName
[0], &key
->nodeName
[1], key
->parentID
, false);
1154 * CheckFile_HFS - verify a HFS catalog file record
1155 * - sanity check values
1156 * - collect info for later processing
1158 * Called in b-tree leaf order for every HFS file
1159 * record in the Catalog B-tree.
1162 CheckFile_HFS(const HFSCatalogKey
* key
, const HFSCatalogFile
* file
)
1169 if (file
->flags
& kHFSThreadExistsMask
)
1170 ++gCIS
.filesWithThreads
;
1172 /* 3843017 : Check for reserved field removed to support new bits in future */
1173 if ((file
->dataStartBlock
) ||
1174 (file
->rsrcStartBlock
) ||
1177 RcdError(gScavGlobals
, E_CatalogFlagsNotZero
);
1178 gScavGlobals
->CBTStat
|= S_ReservedNotZero
;
1181 fileID
= file
->fileID
;
1182 if (fileID
< kHFSFirstUserCatalogNodeID
) {
1183 RcdError(gScavGlobals
, E_InvalidID
);
1184 result
= E_InvalidID
;
1187 if (fileID
>= gCIS
.nextCNID
)
1188 gCIS
.nextCNID
= fileID
+ 1;
1190 /* check out data fork extent info */
1191 result
= CheckFileExtents(gScavGlobals
, file
->fileID
, kDataFork
, NULL
,
1192 file
->dataExtents
, &blocks
);
1193 if (result
!= noErr
)
1195 if (file
->dataPhysicalSize
> ((UInt64
)blocks
* (UInt64
)gScavGlobals
->calculatedVCB
->vcbBlockSize
)) {
1196 snprintf (idstr
, sizeof(idstr
), "id=%u", fileID
);
1197 fsckPrint(gScavGlobals
->context
, E_PEOF
, idstr
);
1198 return (noErr
); /* we don't fix this, ignore the error */
1200 if (file
->dataLogicalSize
> file
->dataPhysicalSize
) {
1201 snprintf (idstr
, sizeof(idstr
), "id=%u", fileID
);
1202 fsckPrint(gScavGlobals
->context
, E_LEOF
, idstr
);
1203 return (noErr
); /* we don't fix this, ignore the error */
1206 /* check out resource fork extent info */
1207 result
= CheckFileExtents(gScavGlobals
, file
->fileID
, kRsrcFork
, NULL
,
1208 file
->rsrcExtents
, &blocks
);
1209 if (result
!= noErr
)
1211 if (file
->rsrcPhysicalSize
> ((UInt64
)blocks
* (UInt64
)gScavGlobals
->calculatedVCB
->vcbBlockSize
)) {
1212 snprintf (idstr
, sizeof(idstr
), "id=%u", fileID
);
1213 fsckPrint(gScavGlobals
->context
, E_PEOF
, idstr
);
1214 return (noErr
); /* we don't fix this, ignore the error */
1216 if (file
->rsrcLogicalSize
> file
->rsrcPhysicalSize
) {
1217 snprintf (idstr
, sizeof(idstr
), "id=%u", fileID
);
1218 fsckPrint(gScavGlobals
->context
, E_LEOF
, idstr
);
1219 return (noErr
); /* we don't fix this, ignore the error */
1222 /* Keeping handle in globals of file ID's for HFS volume only */
1223 if (PtrAndHand(&file
->fileID
, (Handle
)gScavGlobals
->validFilesList
, sizeof(UInt32
) ) )
1226 CheckCatalogName_HFS(key
->nodeName
[0], &key
->nodeName
[1], key
->parentID
, false);
1232 * CheckThread - verify a catalog thread
1234 * Called in leaf-order for every thread record in the Catalog B-tree
1237 CheckThread_HFS(const HFSCatalogKey
* key
, const HFSCatalogThread
* thread
)
1241 if (key
->nodeName
[0] != 0) {
1242 RcdError(gScavGlobals
, E_ThdKey
);
1246 result
= CheckCatalogName_HFS(thread
->nodeName
[0], &thread
->nodeName
[1],
1247 thread
->parentID
, true);
1248 if (result
!= noErr
) {
1249 RcdError(gScavGlobals
, E_ThdCN
);
1253 if (key
->parentID
< kHFSFirstUserCatalogNodeID
&&
1254 key
->parentID
!= kHFSRootParentID
&&
1255 key
->parentID
!= kHFSRootFolderID
) {
1256 RcdError(gScavGlobals
, E_InvalidID
);
1257 return (E_InvalidID
);
1260 if (thread
->parentID
== kHFSRootParentID
) {
1261 if (key
->parentID
!= kHFSRootFolderID
) {
1262 RcdError(gScavGlobals
, E_InvalidID
);
1263 return (E_InvalidID
);
1265 } else if (thread
->parentID
< kHFSFirstUserCatalogNodeID
&&
1266 thread
->parentID
!= kHFSRootFolderID
) {
1267 RcdError(gScavGlobals
, E_InvalidID
);
1268 return (E_InvalidID
);
1275 /* File types from BSD Mode */
1276 #define FT_MASK 0170000 /* Mask of file type. */
1277 #define FT_FIFO 0010000 /* Named pipe (fifo). */
1278 #define FT_CHR 0020000 /* Character device. */
1279 #define FT_DIR 0040000 /* Directory file. */
1280 #define FT_BLK 0060000 /* Block device. */
1281 #define FT_REG 0100000 /* Regular file. */
1282 #define FT_LNK 0120000 /* Symbolic link. */
1283 #define FT_SOCK 0140000 /* BSD domain socket. */
1286 * CheckBSDInfo - Check BSD Permissions data
1287 * (HFS Plus volumes only)
1289 * if repairable then log the error and create a repair order
1292 CheckBSDInfo(const HFSPlusCatalogKey
* key
, const HFSPlusBSDInfo
* bsdInfo
, int isdir
)
1295 Boolean reset
= false;
1297 /* skip uninitialized BSD info */
1298 if (bsdInfo
->fileMode
== 0)
1301 switch (bsdInfo
->fileMode
& FT_MASK
) {
1323 gScavGlobals
->TarBlock
= bsdInfo
->fileMode
& FT_MASK
;
1324 RcdError(gScavGlobals
, E_InvalidPermissions
);
1326 n
= CatalogNameSize( (CatalogName
*) &key
->nodeName
, true );
1328 p
= AllocMinorRepairOrder(gScavGlobals
, n
);
1329 if (p
== NULL
) return;
1331 CopyCatalogName((const CatalogName
*)&key
->nodeName
,
1332 (CatalogName
*)&p
->name
, true);
1334 p
->type
= E_InvalidPermissions
;
1336 p
->incorrect
= bsdInfo
->fileMode
;
1337 p
->parid
= key
->parentID
;
1340 gScavGlobals
->CatStat
|= S_Permissions
;
1345 * Validate a Unicode filename for HFS+ volumes
1347 * check character count
1348 * check for illegal names
1350 * if repairable then log the error and create a repair order
1353 CheckCatalogName(u_int16_t charCount
, const u_int16_t
*uniChars
, u_int32_t parentID
, Boolean thread
)
1357 RepairOrderPtr roPtr
;
1359 CatalogName newName
;
1361 if ((charCount
== 0) || (charCount
> kHFSPlusMaxFileNameChars
))
1364 // only do the remaining checks for files or directories
1368 // look for objects with illegal names of "." or "..". We only do this for
1369 // file or folder catalog records (the thread records will be taken care of
1370 // in the repair routines).
1371 if ( charCount
< 3 && *uniChars
== 0x2E )
1373 if ( charCount
== 1 || (charCount
== 2 && *(uniChars
+ 1) == 0x2E) )
1375 fsckPrint(gScavGlobals
->context
, E_IllegalName
);
1376 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1377 plog( "\tillegal name is 0x" );
1378 PrintName( charCount
, (UInt8
*) uniChars
, true );
1381 // get a new name to use when we rename the file system object
1382 result
= UniqueDotName( gScavGlobals
, &newName
, parentID
,
1383 ((charCount
== 1) ? true : false), true );
1384 if ( result
!= noErr
)
1387 // we will copy the old and new names to our RepairOrder. The names will
1389 // 2 byte length of old name
1390 // unicode characters for old name
1391 // 2 byte length of new name
1392 // unicode characters for new name
1393 myLength
= (charCount
+ 1) * 2; // bytes needed for old name
1394 myLength
+= ((newName
.ustr
.length
+ 1) * 2); // bytes needed for new name
1396 roPtr
= AllocMinorRepairOrder( gScavGlobals
, myLength
);
1397 if ( roPtr
== NULL
)
1400 myPtr
= (u_int16_t
*) &roPtr
->name
;
1401 *myPtr
++ = charCount
; // copy in length of old name and bump past it
1402 CopyMemory( uniChars
, myPtr
, (charCount
* 2) ); // copy in old name
1403 myPtr
+= charCount
; // bump past old name
1404 *myPtr
++ = newName
.ustr
.length
; // copy in length of new name and bump past it
1405 CopyMemory( newName
.ustr
.unicode
, myPtr
, (newName
.ustr
.length
* 2) ); // copy in new name
1406 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1407 plog( "\treplacement name is 0x" );
1408 PrintName( newName
.ustr
.length
, (UInt8
*) &newName
.ustr
.unicode
, true );
1411 roPtr
->type
= E_IllegalName
;
1412 roPtr
->parid
= parentID
;
1413 gScavGlobals
->CatStat
|= S_IllName
;
1414 return( E_IllegalName
);
1418 // look for Unicode decomposition errors in file system object names created before Jaguar (10.2)
1419 if ( FixDecomps( charCount
, uniChars
, &newName
.ustr
) )
1421 fsckPrint(gScavGlobals
->context
, E_IllegalName
);
1422 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1423 plog( "\tillegal name is 0x" );
1424 PrintName( charCount
, (UInt8
*) uniChars
, true );
1427 // we will copy the old and new names to our RepairOrder. The names will
1429 // 2 byte length of old name
1430 // unicode characters for old name
1431 // 2 byte length of new name
1432 // unicode characters for new name
1433 myLength
= (charCount
+ 1) * 2; // bytes needed for old name
1434 myLength
+= ((newName
.ustr
.length
+ 1) * 2); // bytes needed for new name
1436 roPtr
= AllocMinorRepairOrder( gScavGlobals
, myLength
);
1437 if ( roPtr
== NULL
)
1440 myPtr
= (u_int16_t
*) &roPtr
->name
;
1441 *myPtr
++ = charCount
; // copy in length of old name and bump past it
1442 CopyMemory( uniChars
, myPtr
, (charCount
* 2) ); // copy in old name
1443 myPtr
+= charCount
; // bump past old name
1444 *myPtr
++ = newName
.ustr
.length
; // copy in length of new name and bump past it
1445 CopyMemory( newName
.ustr
.unicode
, myPtr
, (newName
.ustr
.length
* 2) ); // copy in new name
1446 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1447 plog( "\treplacement name is 0x" );
1448 PrintName( newName
.ustr
.length
, (UInt8
*) &newName
.ustr
.unicode
, true );
1451 roPtr
->type
= E_IllegalName
;
1452 roPtr
->parid
= parentID
;
1453 gScavGlobals
->CatStat
|= S_IllName
;
1454 return( E_IllegalName
);
1462 * Validate an HFS filename
1464 * check character count
1465 * check for illegal names
1467 * if repairable then log the error and create a repair order
1470 CheckCatalogName_HFS(u_int16_t charCount
, const u_char
*filename
, u_int32_t parentID
, Boolean thread
)
1473 RepairOrderPtr roPtr
;
1475 CatalogName newName
;
1477 if ((charCount
== 0) || (charCount
> kHFSMaxFileNameChars
))
1480 // only do the remaining checks for files or directories
1484 // look for objects with illegal names of "." or "..". We only do this for
1485 // file or folder catalog records (the thread records will be taken care of
1486 // in the repair routines).
1487 if ( charCount
< 3 && *filename
== 0x2E )
1489 if ( charCount
== 1 || (charCount
== 2 && *(filename
+ 1) == 0x2E) )
1492 fsckPrint(gScavGlobals
->context
, E_IllegalName
);
1493 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1494 plog( "\tillegal name is 0x" );
1495 PrintName( charCount
, filename
, false );
1498 // get a new name to use when we rename the file system object
1499 result
= UniqueDotName( gScavGlobals
, &newName
, parentID
,
1500 ((charCount
== 1) ? true : false), false );
1501 if ( result
!= noErr
)
1504 // we will copy the old and new names to our RepairOrder. The names will
1506 // 1 byte length of old name
1507 // characters for old name
1508 // 1 byte length of new name
1509 // characters for new name
1510 myLength
= charCount
+ 1; // bytes needed for old name
1511 myLength
+= (newName
.pstr
[0] + 1); // bytes needed for new name
1512 roPtr
= AllocMinorRepairOrder( gScavGlobals
, myLength
);
1513 if ( roPtr
== NULL
)
1516 myPtr
= (u_char
*)&roPtr
->name
[0];
1517 *myPtr
++ = charCount
; // copy in length of old name and bump past it
1518 CopyMemory( filename
, myPtr
, charCount
);
1519 myPtr
+= charCount
; // bump past old name
1520 *myPtr
++ = newName
.pstr
[0]; // copy in length of new name and bump past it
1521 CopyMemory( &newName
.pstr
[1], myPtr
, newName
.pstr
[0] ); // copy in new name
1522 if ( fsckGetVerbosity(gScavGlobals
->context
) >= kDebugLog
) {
1523 plog( "\treplacement name is 0x" );
1524 PrintName( newName
.pstr
[0], &newName
.pstr
[1], false );
1527 roPtr
->type
= E_IllegalName
;
1528 roPtr
->parid
= parentID
;
1529 gScavGlobals
->CatStat
|= S_IllName
;
1530 return( E_IllegalName
);
1538 /*------------------------------------------------------------------------------
1539 UniqueDotName: figure out a unique name we can use to rename a file system
1540 object that has the illegal name of "." or ".."
1541 ------------------------------------------------------------------------------*/
1543 UniqueDotName( SGlobPtr GPtr
,
1544 CatalogName
* theNewNamePtr
,
1546 Boolean isSingleDotName
,
1555 CatalogRecord record
;
1557 u_char dotName
[] = {'d', 'o', 't', 'd', 'o', 't', 0x0d, 0x00};
1559 fcbPtr
= GPtr
->calculatedCatalogFCB
;
1561 // create key with new name
1562 if ( isSingleDotName
)
1563 myPtr
= &dotName
[3];
1565 myPtr
= &dotName
[0];
1567 nameLen
= strlen((char *) myPtr
);
1571 theNewNamePtr
->ustr
.length
= nameLen
;
1572 for ( i
= 0; i
< theNewNamePtr
->ustr
.length
; i
++ )
1573 theNewNamePtr
->ustr
.unicode
[ i
] = (u_int16_t
) *(myPtr
+ i
);
1577 theNewNamePtr
->pstr
[0] = nameLen
;
1578 memcpy( &theNewNamePtr
->pstr
[1], myPtr
, nameLen
);
1581 // if the name is already in use we will try appending ascii characters
1582 // from '0' (0x30) up to '~' (0x7E)
1583 for ( newChar
= 0x30; newChar
< 0x7F; newChar
++ )
1585 // make sure new name isn't already there
1586 BuildCatalogKey( theParID
, theNewNamePtr
, isHFSPlus
, &catKey
);
1587 result
= SearchBTreeRecord( fcbPtr
, &catKey
, kNoHint
, NULL
, &record
, &recSize
, NULL
);
1588 if ( result
!= noErr
)
1591 // new name is already there, try another
1594 theNewNamePtr
->ustr
.unicode
[ nameLen
] = (u_int16_t
) newChar
;
1595 theNewNamePtr
->ustr
.length
= nameLen
+ 1;
1599 theNewNamePtr
->pstr
[ 0 ] = nameLen
+ 1;
1600 theNewNamePtr
->pstr
[ nameLen
+ 1 ] = newChar
;
1606 } /* UniqueDotName */
1608 /* Function: RecordBadAllocation
1611 * Record a repair to adjust a file or extended attribute's allocation size.
1612 * This could also trigger a truncation if the new block count isn't large
1613 * enough to cover the current LEOF.
1615 * Note that it stores different values and prints different error message
1616 * for file and extended attribute.
1618 * E_PEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1620 * For extended attributes -
1621 * E_PEOAttr, fileID, attribute name, forkType (kEAData).
1622 * Prints attribute name and filename. Since the attribute name is
1623 * passed as parameter, it needs to lookup the filename.
1627 * parID - parent ID of file
1628 * filename - name of the file
1629 * forkType - type of fork (kDataFork/kRsrcFork)
1630 * For extended attributes -
1631 * parID - fileID for attribute
1632 * filename - name of the attribute
1633 * forkType - kEAData
1635 * oldBlkCnt - Incorrect block count
1636 * newBlkCnt - Correct block count
1639 * On failure, non-zero.
1640 * R_NoMem - out of memory
1641 * E_PEOF - Bad allocation error on plain HFS volume
1644 RecordBadAllocation(UInt32 parID
, unsigned char * filename
, UInt32 forkType
, UInt32 oldBlkCnt
, UInt32 newBlkCnt
)
1652 char *real_filename
;
1653 unsigned int filenamelen
;
1655 isHFSPlus
= VolumeObjectIsHFSPlus( );
1656 if (forkType
== kEAData
) {
1657 /* Print attribute name and filename for extended attribute */
1658 filenamelen
= NAME_MAX
* 3;
1659 real_filename
= malloc(filenamelen
);
1660 if (!real_filename
) {
1664 /* Get the name of the file */
1665 result
= GetFileNamePathByID(gScavGlobals
, parID
, NULL
, NULL
,
1666 real_filename
, &filenamelen
, NULL
);
1668 /* If error while looking up filename, default to print file ID */
1669 sprintf(real_filename
, "id = %u", parID
);
1672 fsckPrint(gScavGlobals
->context
, E_PEOAttr
, filename
, real_filename
);
1673 free(real_filename
);
1675 fsckPrint(gScavGlobals
->context
, E_PEOF
, filename
);
1677 sprintf(goodstr
, "%d", newBlkCnt
);
1678 sprintf(badstr
, "%d", oldBlkCnt
);
1679 fsckPrint(gScavGlobals
->context
, E_BadValue
, goodstr
, badstr
);
1681 /* Only HFS+ is repaired here */
1685 n
= strlen((char *)filename
);
1686 p
= AllocMinorRepairOrder(gScavGlobals
, n
+ 1);
1690 if (forkType
== kEAData
) {
1691 p
->type
= E_PEOAttr
;
1695 p
->forkType
= forkType
;
1696 p
->incorrect
= oldBlkCnt
;
1697 p
->correct
= newBlkCnt
;
1700 p
->name
[0] = n
; /* pascal string */
1701 CopyMemory(filename
, &p
->name
[1], n
);
1703 gScavGlobals
->CatStat
|= S_FileAllocation
;
1707 /* Function: RecordTruncation
1710 * Record a repair to trucate a file's logical size.
1712 * Note that it stores different error values and prints
1713 * different error message for file and extended attribute.
1715 * E_LEOF, parentID, filename, forkType (kDataFork/kRsrcFork).
1717 * For extended attributes -
1718 * E_LEOAttr, fileID, attribute name, forkType (kEAData).
1719 * Prints attribute name and filename. Since the attribute name is
1720 * passed as parameter, it needs to lookup the filename.
1724 * parID - parent ID of file
1725 * filename - name of the file
1726 * forkType - type of fork (kDataFork/kRsrcFork)
1727 * For extended attributes -
1728 * parID - fileID for attribute
1729 * filename - name of the attribute
1730 * forkType - kEAData
1732 * oldSize - Incorrect logical size
1733 * newSize - Correct logical size
1736 * On failure, non-zero.
1737 * R_NoMem - out of memory
1738 * E_LEOF - Truncation error on plain HFS volume
1741 RecordTruncation(UInt32 parID
, unsigned char * filename
, UInt32 forkType
, UInt64 oldSize
, UInt64 newSize
)
1744 char oldSizeStr
[48];
1745 char newSizeStr
[48];
1749 char *real_filename
;
1750 unsigned int filenamelen
;
1752 isHFSPlus
= VolumeObjectIsHFSPlus( );
1753 if (forkType
== kEAData
) {
1754 /* Print attribute name and filename for extended attribute */
1755 filenamelen
= NAME_MAX
* 3;
1756 real_filename
= malloc(filenamelen
);
1757 if (!real_filename
) {
1761 /* Get the name of the file */
1762 result
= GetFileNamePathByID(gScavGlobals
, parID
, NULL
, NULL
,
1763 real_filename
, &filenamelen
, NULL
);
1765 /* If error while looking up filename, default to print file ID */
1766 sprintf(real_filename
, "id = %u", parID
);
1769 fsckPrint(gScavGlobals
->context
, E_LEOAttr
, filename
, real_filename
);
1770 free(real_filename
);
1772 fsckPrint(gScavGlobals
->context
, E_LEOF
, filename
);
1774 sprintf(oldSizeStr
, "%"PRId64
"", oldSize
);
1775 sprintf(newSizeStr
, "%"PRId64
"", newSize
);
1776 fsckPrint(gScavGlobals
->context
, E_BadValue
, newSizeStr
, oldSizeStr
);
1778 /* Only HFS+ is repaired here */
1782 n
= strlen((char *)filename
);
1783 p
= AllocMinorRepairOrder(gScavGlobals
, n
+ 1);
1787 if (forkType
== kEAData
) {
1788 p
->type
= E_LEOAttr
;
1792 p
->forkType
= forkType
;
1793 p
->incorrect
= oldSize
;
1794 p
->correct
= newSize
;
1797 p
->name
[0] = n
; /* pascal string */
1798 CopyMemory(filename
, &p
->name
[1], n
);
1800 gScavGlobals
->CatStat
|= S_FileAllocation
;
1806 * CaptureMissingThread
1808 * Capture info for a missing thread record so it
1809 * can be repaired later. The next key is saved
1810 * so that the Catalog Hierarchy check can proceed.
1811 * The thread PID/NAME are initialized during the
1812 * Catalog Hierarchy check phase.
1815 CaptureMissingThread(UInt32 threadID
, const HFSPlusCatalogKey
*nextKey
)
1820 isHFSPlus
= VolumeObjectIsHFSPlus( );
1822 fsckPrint(gScavGlobals
->context
, E_NoThd
, threadID
);
1824 /* Only HFS+ missing threads are repaired here */
1828 mtp
= (MissingThread
*) AllocateClearMemory(sizeof(MissingThread
));
1832 /* add it to the list of missing threads */
1833 mtp
->link
= gScavGlobals
->missingThreadList
;
1834 gScavGlobals
->missingThreadList
= mtp
;
1836 mtp
->threadID
= threadID
;
1837 CopyMemory(nextKey
, &mtp
->nextKey
, nextKey
->keyLength
+ 2);
1839 if (gScavGlobals
->RepLevel
== repairLevelNoProblemsFound
)
1840 gScavGlobals
->RepLevel
= repairLevelVolumeRecoverable
;
1842 gScavGlobals
->CatStat
|= S_MissingThread
;
1848 FixDecomps. Originally written by Peter Edberg for use in fsck_hfs.
1850 If inFilename needs updating and the function was able to do this without
1851 overflowing the 255-character limit, it returns 1 (true) and outFIlename
1852 contains the update file. If inFilename did not need updating, or an update
1853 would overflow the limit, the function returns 0 (false) and the contents of
1854 outFilename are undefined.
1856 Function implementation:
1858 Characters that don't require any special handling have combining class 0 and do
1859 not begin a decomposition sequence (of 1-3 characters) that needs updating. For
1860 these characters, the function just copies them from inFilename to outFilename
1861 and sets the pointer outNameCombSeqPtr to NULL (when this pointer is not NULL,
1862 it points to the beginning of a sequence of combining marks that continues up to
1863 the current character; if the current character is combining, it may need to be
1864 reordered into that sequence). The copying operation in cheap, and postponing it
1865 until we know the filename needs modification would make the code much more
1868 This copying operation may be invoked from many places in the code, some deeply
1869 nested - any time the code determines that the current character needs no
1870 special handling. For this reason it has a label (CopyBaseChar) and is located
1871 at the end of the character processing loop; various places in the code use goto
1872 statements to jump to it (this is a situation where they are justified).
1874 The main function loop has 4 sections.
1876 First, it quickly determines if the high 12 bits of the character indicate that
1877 it is in a range that has neither nonzero combining class nor any decomposition
1878 sequences that need updating. If so, the code jumps straight to CopyBaseChar.
1880 Second, the code determines if the character is part of a sequence that needs
1881 updating. It checks if the current character has a corresponding action in the
1882 replaceData array. If so, depending on the action, it may need to check for
1883 additional matching characters in inFilename. If the sequence of 1-3 characters
1884 is successfully matched, then a replacement sequence of 1-3 characters is
1885 inserted at the corresponding position in outFilename. While this replacement
1886 sequence is being inserted, each character must be checked to see if it has
1887 nonzero combining class and needs reordering (some replacement sequences consist
1888 entirely of combining characters and may interact with combining characters in
1889 the filename before the updated sequence).
1891 Third, the code handles characters whose high-order 12 bits indicated that some
1892 action was required, but were not part of sequences that needed updating (these
1893 may include characters that were examined in the second part but were part of
1894 sequences that did not completely match, so there are also goto fallthroughs to
1895 this code - labeled CheckCombClass - from the second part). These characters
1896 have to be checked for nonzero combining class; if so, they are reordered as
1897 necessary. Each time a new nonzero class character is encountered, it is added
1898 to outFIlename at the correct point in any active combining character sequence
1899 (with other characters in the sequence moved as necessary), so the sequence
1900 pointed to by outNameCombSeqPtr is always in correct order up to the current
1903 Finally, the fourth part has the default handlers to just copy characters to
1908 FixDecomps( u_int16_t charCount
, const u_int16_t
*inFilename
, HFSUniStr255
*outFilename
)
1910 // input filename: address of curr input char,
1911 const u_int16_t
* inNamePtr
= inFilename
;
1912 // and of last input char.
1913 const u_int16_t
* inNameLastPtr
= &inFilename
[charCount
- 1];
1914 // output filename buffer: addr of next output char,
1915 u_int16_t
* outNamePtr
= outFilename
->unicode
;
1916 // and of last possible output char.
1917 u_int16_t
* outNameLastPtr
= &outFilename
->unicode
[kHFSPlusMaxFileNameChars
- 1];
1918 u_int16_t
* outNameCombSeqPtr
= NULL
; // if non-NULL, start of output combining seq we are processing.
1919 u_int32_t maxClassValueInSeq
= 0;
1920 Boolean didModifyName
= 0;
1922 while (inNamePtr
<= inNameLastPtr
) {
1923 u_int16_t shiftUniChar
; // this must be 16 bits for the kShiftUniCharOffset wraparound to work
1925 u_int32_t shiftUniCharLo
;
1926 u_int32_t replDataIndex
;
1927 u_int32_t currCharClass
;
1929 shiftUniChar
= *inNamePtr
+ kShiftUniCharOffset
;
1930 if ( shiftUniChar
>= kShiftUniCharLimit
)
1932 rangeIndex
= classAndReplIndex
[shiftUniChar
>> kLoFieldBitSize
];
1933 if ( rangeIndex
< 0 )
1935 shiftUniCharLo
= shiftUniChar
& kLoFieldMask
;
1936 replDataIndex
= replaceRanges
[rangeIndex
][shiftUniCharLo
];
1938 if ( replDataIndex
> 0 ) {
1939 // we have a possible substitution (replDataIndex != 0)
1940 const u_int16_t
* replDataPtr
;
1942 u_int32_t copyCount
= 0;
1944 replDataPtr
= &replaceData
[replDataIndex
];
1945 action
= *replDataPtr
++;
1947 case kReplaceCurWithTwo
:
1948 case kReplaceCurWithThree
:
1950 copyCount
= (action
== kReplaceCurWithTwo
)? 2: 3;
1952 // the next 3 cases can have a first char or replacement char with nonzero combining class
1953 case kIfNextOneMatchesReplaceAllWithOne
:
1954 case kIfNextOneMatchesReplaceAllWithTwo
:
1955 if (inNamePtr
+ 1 <= inNameLastPtr
&& *(inNamePtr
+ 1) == *replDataPtr
++) {
1957 copyCount
= (action
== kIfNextOneMatchesReplaceAllWithOne
)? 1: 2;
1959 // No substitution; check for comb class & copy char
1960 goto CheckCombClass
;
1963 case kIfNextTwoMatchReplaceAllWithOne
:
1964 if ( inNamePtr
+ 2 <= inNameLastPtr
&&
1965 *(inNamePtr
+ 1) == *replDataPtr
++ &&
1966 *(inNamePtr
+ 2) == *replDataPtr
++)
1971 // No substitution; check for comb class & copy char
1972 goto CheckCombClass
;
1977 // now copy copyCount chars (1-3) from replDataPtr to output, checking for comb class etc.
1978 if (outNamePtr
+ copyCount
- 1 > outNameLastPtr
) {
1982 while (copyCount
-- > 0) {
1984 shiftUniChar
= *replDataPtr
+ kShiftUniCharOffset
;
1985 if ( shiftUniChar
< kShiftUniCharLimit
) {
1986 rangeIndex
= classAndReplIndex
[shiftUniChar
>> kLoFieldBitSize
];
1987 if (rangeIndex
>= 0) {
1988 shiftUniCharLo
= shiftUniChar
& kLoFieldMask
;
1989 currCharClass
= combClassRanges
[rangeIndex
][shiftUniCharLo
];
1992 // This part is similar to CheckCombClass below, which has more detailed
1993 // comments; see them for info.
1994 if ( currCharClass
== 0 ) {
1995 outNameCombSeqPtr
= NULL
;
1996 *outNamePtr
++ = *replDataPtr
++;
1997 } else if ( outNameCombSeqPtr
== NULL
) {
1998 outNameCombSeqPtr
= outNamePtr
;
1999 maxClassValueInSeq
= currCharClass
;
2000 *outNamePtr
++ = *replDataPtr
++;
2001 } else if ( currCharClass
>= maxClassValueInSeq
) {
2002 // Sequence is already in correct order with current char,
2003 // just update maxClassValueInSeq
2004 maxClassValueInSeq
= currCharClass
;
2005 *outNamePtr
++ = *replDataPtr
++;
2006 } else if ( outNamePtr
- outNameCombSeqPtr
== 1) {
2007 // Here we know we need to reorder.
2008 // If the sequence is two chars, just interchange them
2009 *outNamePtr
++ = *outNameCombSeqPtr
;
2010 *outNameCombSeqPtr
= *replDataPtr
++;
2012 // General reordering case for three or more chars.
2013 u_int16_t
* outNameCombCharPtr
;
2014 u_int32_t combCharClass
;
2016 outNameCombCharPtr
= outNamePtr
++;
2017 while (outNameCombCharPtr
> outNameCombSeqPtr
) {
2018 shiftUniChar
= *(outNameCombCharPtr
- 1) + kShiftUniCharOffset
;
2019 rangeIndex
= classAndReplIndex
[shiftUniChar
>> kLoFieldBitSize
];
2020 shiftUniCharLo
= shiftUniChar
& kLoFieldMask
;
2021 combCharClass
= combClassRanges
[rangeIndex
][shiftUniCharLo
];
2022 if (combCharClass
<= currCharClass
)
2024 *outNameCombCharPtr
= *(outNameCombCharPtr
- 1);
2025 outNameCombCharPtr
--;
2027 *outNameCombCharPtr
= *replDataPtr
++;
2032 } /* end of replDataIndex > 0 */
2035 // check for combining class
2036 currCharClass
= combClassRanges
[rangeIndex
][shiftUniCharLo
];
2037 if ( currCharClass
== 0 ) {
2040 if ( outNameCombSeqPtr
== NULL
) {
2041 // The current char is the first of a possible sequence of chars
2042 // with nonzero combining class. Initialize sequence stuff, then
2043 // just copy char to output.
2044 outNameCombSeqPtr
= outNamePtr
;
2045 maxClassValueInSeq
= currCharClass
;
2048 if ( currCharClass
>= maxClassValueInSeq
) {
2049 // The sequence of chars with nonzero combining class is already
2050 // in correct order through the current char; just update the max
2051 // class value found in the sequence.
2052 maxClassValueInSeq
= currCharClass
;
2056 // This char is at least the second in a sequence of chars with
2057 // nonzero combining class in the output buffer; outNameCombSeqPtr
2058 // points to the first in the sequence. Need to put the current
2059 // char into the correct place in the sequence (previous chars in
2060 // the sequence are already in correct order, but the current char
2061 // is out of place).
2063 // First make sure there is room for the new char
2064 if (outNamePtr
> outNameLastPtr
) {
2069 if (outNamePtr
- outNameCombSeqPtr
== 1) {
2070 // If the sequence is two chars, just interchange them
2071 *outNamePtr
++ = *outNameCombSeqPtr
;
2072 *outNameCombSeqPtr
= *inNamePtr
++;
2074 // General case: Starting at previous end of sequence, move chars to
2075 // next position in string as long as their class is higher than current
2076 // char; insert current char where we stop. We could cache the
2077 // combining classes instead of re-determining them, but having multiple
2078 // combining marks is rare enough that it wouldn't be worth the overhead.
2079 // At least we don't have to recheck shiftUniChar < kShiftUniCharLimit,
2080 // rangeIndex != 0, etc.)
2081 u_int16_t
* outNameCombCharPtr
;
2082 u_int32_t combCharClass
;
2084 outNameCombCharPtr
= outNamePtr
++;
2085 while (outNameCombCharPtr
> outNameCombSeqPtr
) {
2086 shiftUniChar
= *(outNameCombCharPtr
- 1) + kShiftUniCharOffset
;
2087 rangeIndex
= classAndReplIndex
[shiftUniChar
>> kLoFieldBitSize
];
2088 shiftUniCharLo
= shiftUniChar
& kLoFieldMask
;
2089 combCharClass
= combClassRanges
[rangeIndex
][shiftUniCharLo
];
2090 if (combCharClass
<= currCharClass
)
2092 *outNameCombCharPtr
= *(outNameCombCharPtr
- 1);
2093 outNameCombCharPtr
--;
2095 *outNameCombCharPtr
= *inNamePtr
++;
2101 outNameCombSeqPtr
= NULL
;
2103 // nothing special happens with this char, just copy to output
2104 if (outNamePtr
<= outNameLastPtr
) {
2105 *outNamePtr
++ = *inNamePtr
++;
2110 } /* end of while( inNamePtr <= inNameLastPtr ) */
2112 if (didModifyName
) {
2113 outFilename
->length
= outNamePtr
- outFilename
->unicode
;
2115 return didModifyName
;