#include "Scavenger.h"
#include "DecompDataEnums.h"
#include "DecompData.h"
extern int RcdFCntErr( SGlobPtr GPtr, OSErr type, UInt32 correct, UInt32 incorrect, HFSCatalogNodeID);
extern int RcdHsFldCntErr( SGlobPtr GPtr, OSErr type, UInt32 correct, UInt32 incorrect, HFSCatalogNodeID);
struct CatalogIterationSummary {
UInt32 parentID;
UInt32 rootDirCount;
UInt32 rootFileCount;
UInt32 dirCount;
UInt32 dirThreads;
UInt32 fileCount;
UInt32 filesWithThreads;
UInt32 fileThreads;
UInt32 nextCNID;
UInt64 encodings;
void * hardLinkRef;
};
struct CatalogIterationSummary gCIS;
SGlobPtr gScavGlobals;
static int CheckCatalogRecord(SGlobPtr GPtr, const HFSPlusCatalogKey *key,
const CatalogRecord *rec, UInt16 reclen);
static int CheckCatalogRecord_HFS(const HFSCatalogKey *key,
const CatalogRecord *rec, UInt16 reclen);
static int CheckDirectory(const HFSPlusCatalogKey * key, const HFSPlusCatalogFolder * dir);
static int CheckFile(const HFSPlusCatalogKey * key, const HFSPlusCatalogFile * file);
static int CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread);
static int CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir);
static int CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file);
static int CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread);
static void CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir);
static int CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars,
u_int32_t parentID, Boolean thread);
static int CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename,
u_int32_t parentID, Boolean thread);
static int CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey);
static OSErr UniqueDotName( SGlobPtr GPtr,
CatalogName * theNewNamePtr,
UInt32 theParID,
Boolean isSingleDotName,
Boolean isHFSPlus );
static Boolean FixDecomps( u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename );
struct folderCountInfo {
UInt32 folderID;
UInt32 recordedCount;
UInt32 computedCount;
struct folderCountInfo *next;
};
static OSErr
CountFolderRecords(HFSPlusCatalogKey *myKey, HFSPlusCatalogFolder *folder, SGlobPtr GPtr)
{
SFCB *fcb = GPtr->calculatedCatalogFCB;
OSErr err = 0;
BTreeIterator iterator;
FSBufferDescriptor btRecord;
union {
HFSPlusCatalogFolder catRecord;
HFSPlusCatalogFile catFile;
} catRecord;
HFSPlusCatalogKey *key;
UInt16 recordSize = 0;
UInt32 folderCount = 0;
ClearMemory(&iterator, sizeof(iterator));
key = (HFSPlusCatalogKey*)&iterator.key;
BuildCatalogKey(folder->folderID, NULL, true, (CatalogKey*)key);
btRecord.bufferAddress = &catRecord;
btRecord.itemCount = 1;
btRecord.itemSize = sizeof(catRecord);
for (err = BTSearchRecord(fcb, &iterator, kNoHint, &btRecord, &recordSize, &iterator);
err == 0;
err = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &btRecord, &recordSize)) {
switch (catRecord.catRecord.recordType) {
case kHFSPlusFolderThreadRecord:
case kHFSPlusFileThreadRecord:
continue;
}
if (key->parentID != folder->folderID)
break;
if (catRecord.catRecord.recordType == kHFSPlusFolderRecord) {
folderCount++;
} else if ((catRecord.catRecord.recordType == kHFSPlusFileRecord) &&
(catRecord.catFile.flags & kHFSHasLinkChainMask) &&
(catRecord.catFile.userInfo.fdType == kHFSAliasType) &&
(catRecord.catFile.userInfo.fdCreator == kHFSAliasCreator) &&
(key->parentID != GPtr->filelink_priv_dir_id)) {
folderCount++;
}
}
if (err == btNotFound)
err = 0;
if (err == 0) {
if (folderCount != folder->folderCount) {
err = RcdFCntErr( GPtr,
E_FldCount,
folderCount,
folder->folderCount,
folder->folderID);
}
}
return err;
}
static void
releaseFolderCountInfo(struct folderCountInfo *fcip, int numFolders)
{
int i;
for (i = 0; i < numFolders; i++) {
struct folderCountInfo *f = &fcip[i];
f = f->next;
while (f) {
struct folderCountInfo *t = f->next;
free(f);
f = t;
}
}
free(fcip);
}
static struct folderCountInfo *
findFolderEntry(struct folderCountInfo *fcip, int numFolders, UInt32 fid)
{
struct folderCountInfo *retval = NULL;
int indx;
indx = fid % numFolders;
retval = &fcip[indx];
if (retval->folderID == fid) {
goto done;
}
while (retval->next != NULL) {
retval = retval->next;
if (retval->folderID == fid)
goto done;
}
retval = NULL;
done:
return retval;
}
static struct folderCountInfo *
addFolderEntry(struct folderCountInfo *fcip, int numFolders, UInt32 fid)
{
struct folderCountInfo *retval = NULL;
int indx;
indx = fid % numFolders;
retval = &fcip[indx];
if (retval->folderID == fid)
goto done;
while (retval->folderID != 0) {
if (retval->next == NULL) {
retval->next = calloc(1, sizeof(struct folderCountInfo));
if (retval->next == NULL) {
retval = NULL;
goto done;
} else
retval = retval->next;
} else if (retval->folderID == fid) {
goto done;
} else
retval = retval->next;
}
retval->folderID = fid;
done:
return retval;
}
static int
folderCountAdd(struct folderCountInfo *fcip, int numFolders, UInt32 parentID, UInt32 folderID, UInt32 count)
{
int retval = 0;
struct folderCountInfo *curp = NULL;
if (folderID != 0) {
curp = findFolderEntry(fcip, numFolders, folderID);
if (curp == NULL) {
curp = addFolderEntry(fcip, numFolders, folderID);
if (curp == NULL) {
retval = ENOMEM;
goto done;
}
}
curp->recordedCount = count;
}
curp = findFolderEntry(fcip, numFolders, parentID);
if (curp == NULL) {
curp = addFolderEntry(fcip, numFolders, parentID);
if (curp == NULL) {
retval = ENOMEM;
goto done;
}
}
curp->computedCount++;
done:
return retval;
}
OSErr
CheckFolderCount( SGlobPtr GPtr )
{
OSErr err = 0;
int numFolders;
BTreeIterator iterator;
FSBufferDescriptor btRecord;
HFSPlusCatalogKey *key;
union {
HFSPlusCatalogFolder catRecord;
HFSPlusCatalogFile catFile;
} catRecord;
UInt16 recordSize = 0;
struct folderCountInfo *fcip = NULL;
ClearMemory(&iterator, sizeof(iterator));
if (!VolumeObjectIsHFSX(GPtr)) {
goto done;
}
if (GPtr->calculatedVCB == NULL) {
err = EINVAL;
goto done;
}
#if 0
numFolders = GPtr->calculatedVCB->vcbFolderCount + 2;
#else
numFolders = 257;
#endif
#define MAXCACHEMEM (5 * 1024 * 1024)
#define LCALLOC(c, s, l) \
({ __typeof(c) _count = (c); __typeof(s) _size = (s); __typeof(l) _lim = (l); \
((_count * _size) > _lim) ? NULL : calloc(_count, _size); })
fcip = LCALLOC(numFolders, sizeof(*fcip), MAXCACHEMEM);
#undef MAXCACHEMEM
#undef LCALLOC
restart:
key = (HFSPlusCatalogKey*)&iterator.key;
BuildCatalogKey(kHFSRootFolderID, NULL, true, (CatalogKey*)key);
btRecord.bufferAddress = &catRecord;
btRecord.itemCount = 1;
btRecord.itemSize = sizeof(catRecord);
for (err = BTIterateRecord(GPtr->calculatedCatalogFCB, kBTreeFirstRecord,
&iterator, &btRecord, &recordSize);
err == 0;
err = BTIterateRecord(GPtr->calculatedCatalogFCB, kBTreeNextRecord,
&iterator, &btRecord, &recordSize)) {
switch (catRecord.catRecord.recordType) {
case kHFSPlusFolderRecord:
if (!(catRecord.catRecord.flags & kHFSHasFolderCountMask)) {
err = RcdHsFldCntErr( GPtr,
E_HsFldCount,
catRecord.catRecord.flags | kHFSHasFolderCountMask,
catRecord.catRecord.flags,
catRecord.catRecord.folderID );
if (err != 0)
goto done;
}
if (fcip) {
if (folderCountAdd(fcip, numFolders,
key->parentID,
catRecord.catRecord.folderID,
catRecord.catRecord.folderCount)) {
releaseFolderCountInfo(fcip, numFolders);
fcip = NULL;
goto restart;
}
} else {
err = CountFolderRecords(key, &catRecord.catRecord, GPtr);
if (err != 0)
goto done;
}
break;
case kHFSPlusFileRecord:
if ((catRecord.catFile.flags & kHFSHasLinkChainMask) &&
(catRecord.catFile.userInfo.fdType == kHFSAliasType) &&
(catRecord.catFile.userInfo.fdCreator == kHFSAliasCreator) &&
(key->parentID != GPtr->filelink_priv_dir_id)) {
if (fcip) {
if (folderCountAdd(fcip, numFolders,
key->parentID, 0, 0)) {
releaseFolderCountInfo(fcip, numFolders);
fcip = NULL;
goto restart;
}
}
}
break;
}
}
if (err == btNotFound)
err = 0; if (err == 0 && fcip != NULL) {
int i;
for (i = 0; i < numFolders; i++) {
struct folderCountInfo *curp;
for (curp = &fcip[i]; curp; curp = curp->next) {
if (curp->folderID == 0) {
} else if (curp->folderID == kHFSRootParentID) {
continue;
} else {
if (curp->recordedCount != curp->computedCount) {
err = RcdFCntErr( GPtr,
E_FldCount,
curp->computedCount,
curp->recordedCount,
curp->folderID );
if (err != 0)
goto done;
}
}
}
}
}
done:
if (fcip) {
releaseFolderCountInfo(fcip, numFolders);
fcip = NULL;
}
return err;
}
OSErr
CheckCatalogBTree( SGlobPtr GPtr )
{
OSErr err;
int hfsplus;
gScavGlobals = GPtr;
hfsplus = VolumeObjectIsHFSPlus( );
ClearMemory(&gCIS, sizeof(gCIS));
gCIS.parentID = kHFSRootParentID;
gCIS.nextCNID = kHFSFirstUserCatalogNodeID;
if (hfsplus) {
HardLinkCheckBegin(gScavGlobals, &gCIS.hardLinkRef);
dirhardlink_init(gScavGlobals);
}
gScavGlobals->TarID = kHFSCatalogFileID;
GetVolumeObjectBlockNum( &gScavGlobals->TarBlock );
err = BTCheck(gScavGlobals, kCalculatedCatalogRefNum, (CheckLeafRecordProcPtr)CheckCatalogRecord);
if (err) goto exit;
if (gCIS.dirCount != gCIS.dirThreads) {
RcdError(gScavGlobals, E_IncorrectNumThdRcd);
gScavGlobals->CBTStat |= S_Orphan;
if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
plog ("\t%s: dirCount = %u, dirThread = %u\n", __FUNCTION__, gCIS.dirCount, gCIS.dirThreads);
}
}
if (hfsplus && (gCIS.fileCount != gCIS.fileThreads)) {
RcdError(gScavGlobals, E_IncorrectNumThdRcd);
gScavGlobals->CBTStat |= S_Orphan;
if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
plog ("\t%s: fileCount = %u, fileThread = %u\n", __FUNCTION__, gCIS.fileCount, gCIS.fileThreads);
}
}
if (!hfsplus && (gCIS.fileThreads != gCIS.filesWithThreads)) {
RcdError(gScavGlobals, E_IncorrectNumThdRcd);
gScavGlobals->CBTStat |= S_Orphan;
if (fsckGetVerbosity(gScavGlobals->context) >= kDebugLog) {
plog ("\t%s: fileThreads = %u, filesWithThread = %u\n", __FUNCTION__, gCIS.fileThreads, gCIS.filesWithThreads);
}
}
gScavGlobals->calculatedVCB->vcbEncodingsBitmap = gCIS.encodings;
gScavGlobals->calculatedVCB->vcbNextCatalogID = gCIS.nextCNID;
gScavGlobals->calculatedVCB->vcbFolderCount = gCIS.dirCount - 1;
gScavGlobals->calculatedVCB->vcbFileCount = gCIS.fileCount;
if (!hfsplus) {
gScavGlobals->calculatedVCB->vcbNmRtDirs = gCIS.rootDirCount;
gScavGlobals->calculatedVCB->vcbNmFls = gCIS.rootFileCount;
}
err = BTMapChk(gScavGlobals, kCalculatedCatalogRefNum);
if (err) goto exit;
err = BTCheckUnusedNodes(gScavGlobals, kCalculatedCatalogRefNum, &gScavGlobals->CBTStat);
if (err) goto exit;
err = CmpBTH(gScavGlobals, kCalculatedCatalogRefNum);
if (err) goto exit;
err = CmpBTM(gScavGlobals, kCalculatedCatalogRefNum);
if (hfsplus) {
(void) CheckHardLinks(gCIS.hardLinkRef);
if (gScavGlobals->CatStat & S_LinkErrNoRepair) {
err = -1;
goto exit;
}
}
exit:
if (hfsplus)
HardLinkCheckEnd(gCIS.hardLinkRef);
return (err);
}
static int
CheckCatalogRecord(SGlobPtr GPtr, const HFSPlusCatalogKey *key, const CatalogRecord *rec, UInt16 reclen)
{
int result = 0;
Boolean isHFSPlus;
isHFSPlus = VolumeObjectIsHFSPlus( );
++gScavGlobals->itemsProcessed;
if (!isHFSPlus)
return CheckCatalogRecord_HFS((HFSCatalogKey *)key, rec, reclen);
gScavGlobals->CNType = rec->recordType;
switch (rec->recordType) {
case kHFSPlusFolderRecord:
++gCIS.dirCount;
if (reclen != sizeof(HFSPlusCatalogFolder)){
RcdError(gScavGlobals, E_LenDir);
result = E_LenDir;
break;
}
if (key->parentID != gCIS.parentID) {
result = CaptureMissingThread(key->parentID, key);
if (result) break;
++gCIS.dirThreads;
gCIS.parentID = key->parentID;
}
result = CheckDirectory(key, (HFSPlusCatalogFolder *)rec);
break;
case kHFSPlusFileRecord:
++gCIS.fileCount;
if (reclen != sizeof(HFSPlusCatalogFile)){
RcdError(gScavGlobals, E_LenFil);
result = E_LenFil;
break;
}
if (key->parentID != gCIS.parentID) {
result = CaptureMissingThread(key->parentID, key);
if (result) break;
++gCIS.dirThreads;
gCIS.parentID = key->parentID;
}
result = CheckFile(key, (HFSPlusCatalogFile *)rec);
break;
case kHFSPlusFolderThreadRecord:
++gCIS.dirThreads;
gCIS.parentID = key->parentID;
case kHFSPlusFileThreadRecord:
if (rec->recordType == kHFSPlusFileThreadRecord)
++gCIS.fileThreads;
if (reclen > sizeof(HFSPlusCatalogThread) ||
reclen < sizeof(HFSPlusCatalogThread) - sizeof(HFSUniStr255)) {
RcdError(gScavGlobals, E_LenThd);
result = E_LenThd;
break;
} else if (reclen == sizeof(HFSPlusCatalogThread)) {
gScavGlobals->VeryMinorErrorsStat |= S_BloatedThreadRecordFound;
}
result = CheckThread(key, (HFSPlusCatalogThread *)rec);
break;
default:
RcdError(gScavGlobals, E_CatRec);
result = E_CatRec;
}
return (result);
}
static int
CheckCatalogRecord_HFS(const HFSCatalogKey *key, const CatalogRecord *rec, UInt16 reclen)
{
int result = 0;
gScavGlobals->CNType = rec->recordType;
switch (rec->recordType) {
case kHFSFolderRecord:
++gCIS.dirCount;
if (key->parentID == kHFSRootFolderID )
++gCIS.rootDirCount;
if (reclen != sizeof(HFSCatalogFolder)){
RcdError(gScavGlobals, E_LenDir);
result = E_LenDir;
break;
}
if (key->parentID != gCIS.parentID) {
result = CaptureMissingThread(key->parentID, (HFSPlusCatalogKey *)key);
if (result) break;
++gCIS.dirThreads;
gCIS.parentID = key->parentID;
}
result = CheckDirectory_HFS(key, (HFSCatalogFolder *)rec);
break;
case kHFSFileRecord:
++gCIS.fileCount;
if (key->parentID == kHFSRootFolderID )
++gCIS.rootFileCount;
if (reclen != sizeof(HFSCatalogFile)){
RcdError(gScavGlobals, E_LenFil);
result = E_LenFil;
break;
}
if (key->parentID != gCIS.parentID) {
result = CaptureMissingThread(key->parentID, (HFSPlusCatalogKey *)key);
if (result) break;
++gCIS.dirThreads;
gCIS.parentID = key->parentID;
}
result = CheckFile_HFS(key, (HFSCatalogFile *)rec);
break;
case kHFSFolderThreadRecord:
++gCIS.dirThreads;
gCIS.parentID = key->parentID;
case kHFSFileThreadRecord:
if (rec->recordType == kHFSFileThreadRecord)
++gCIS.fileThreads;
if (reclen != sizeof(HFSCatalogThread)) {
RcdError(gScavGlobals, E_LenThd);
result = E_LenThd;
break;
}
result = CheckThread_HFS(key, (HFSCatalogThread *)rec);
break;
default:
RcdError(gScavGlobals, E_CatRec);
result = E_CatRec;
}
return (result);
}
static int
CheckDirectory(const HFSPlusCatalogKey * key, const HFSPlusCatalogFolder * dir)
{
UInt32 dirID;
int result = 0;
dirID = dir->folderID;
if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
RcdError(gScavGlobals, E_CatalogFlagsNotZero);
gScavGlobals->CBTStat |= S_ReservedNotZero;
}
RecordXAttrBits(gScavGlobals, dir->flags, dir->folderID, kCalculatedCatalogRefNum);
#if DEBUG_XATTR
plog ("%s: Record folderID=%d for prime modulus calculations\n", __FUNCTION__, dir->folderID);
#endif
if (dirID < kHFSFirstUserCatalogNodeID &&
dirID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
if (dirID >= gCIS.nextCNID )
gCIS.nextCNID = dirID + 1;
gCIS.encodings |= (u_int64_t)(1ULL << MapEncodingToIndex(dir->textEncoding & 0x7F));
CheckBSDInfo(key, &dir->bsdInfo, true);
CheckCatalogName(key->nodeName.length, &key->nodeName.unicode[0], key->parentID, false);
if (dir->flags & kHFSHasLinkChainMask) {
gScavGlobals->calculated_dirinodes++;
}
return (result);
}
static int
CheckFile(const HFSPlusCatalogKey * key, const HFSPlusCatalogFile * file)
{
UInt32 fileID;
UInt32 blocks;
UInt64 bytes;
int result = 0;
size_t len;
unsigned char filename[256 * 3];
(void) utf_encodestr(key->nodeName.unicode,
key->nodeName.length * 2,
filename, &len, sizeof(filename));
filename[len] = '\0';
RecordXAttrBits(gScavGlobals, file->flags, file->fileID, kCalculatedCatalogRefNum);
#if DEBUG_XATTR
plog ("%s: Record fileID=%d for prime modulus calculations\n", __FUNCTION__, file->fileID);
#endif
fileID = file->fileID;
if (fileID < kHFSFirstUserCatalogNodeID) {
RcdError(gScavGlobals, E_InvalidID);
result = E_InvalidID;
return (result);
}
if (fileID >= gCIS.nextCNID )
gCIS.nextCNID = fileID + 1;
gCIS.encodings |= (u_int64_t)(1ULL << MapEncodingToIndex(file->textEncoding & 0x7F));
CheckBSDInfo(key, &file->bsdInfo, false);
result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
file->dataFork.extents, &blocks);
if (result != noErr)
return (result);
if (file->dataFork.totalBlocks != blocks) {
result = RecordBadAllocation(key->parentID, filename, kDataFork,
file->dataFork.totalBlocks, blocks);
if (result)
return (result);
} else {
bytes = (UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize;
if (file->dataFork.logicalSize > bytes) {
result = RecordTruncation(key->parentID, filename, kDataFork,
file->dataFork.logicalSize, bytes);
if (result)
return (result);
}
}
result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
file->resourceFork.extents, &blocks);
if (result != noErr)
return (result);
if (file->resourceFork.totalBlocks != blocks) {
result = RecordBadAllocation(key->parentID, filename, kRsrcFork,
file->resourceFork.totalBlocks, blocks);
if (result)
return (result);
} else {
bytes = (UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize;
if (file->resourceFork.logicalSize > bytes) {
result = RecordTruncation(key->parentID, filename, kRsrcFork,
file->resourceFork.logicalSize, bytes);
if (result)
return (result);
}
}
if (file->userInfo.fdType == kHardLinkFileType &&
file->userInfo.fdCreator == kHFSPlusCreator)
CaptureHardLink(gCIS.hardLinkRef, file);
CheckCatalogName(key->nodeName.length, &key->nodeName.unicode[0], key->parentID, false);
if ((file->flags & kHFSHasLinkChainMask) &&
((file->userInfo.fdType == kHFSAliasType) ||
(file->userInfo.fdCreator == kHFSAliasCreator))) {
gScavGlobals->calculated_dirlinks++;
}
return (result);
}
static int
CheckThread(const HFSPlusCatalogKey * key, const HFSPlusCatalogThread * thread)
{
int result = 0;
if (key->nodeName.length != 0) {
RcdError(gScavGlobals, E_ThdKey);
return (E_ThdKey);
}
result = CheckCatalogName(thread->nodeName.length, &thread->nodeName.unicode[0],
thread->parentID, true);
if (result != noErr) {
RcdError(gScavGlobals, E_ThdCN);
return (E_ThdCN);
}
if (key->parentID < kHFSFirstUserCatalogNodeID &&
key->parentID != kHFSRootParentID &&
key->parentID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
if (thread->parentID == kHFSRootParentID) {
if (key->parentID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
} else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
thread->parentID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
return (0);
}
static int
CheckDirectory_HFS(const HFSCatalogKey * key, const HFSCatalogFolder * dir)
{
UInt32 dirID;
int result = 0;
dirID = dir->folderID;
if ((dir->flags & (kHFSFileLockedMask | kHFSThreadExistsMask)) != 0) {
RcdError(gScavGlobals, E_CatalogFlagsNotZero);
gScavGlobals->CBTStat |= S_ReservedNotZero;
}
if (dirID < kHFSFirstUserCatalogNodeID &&
dirID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
if (dirID >= gCIS.nextCNID )
gCIS.nextCNID = dirID + 1;
CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
return (result);
}
static int
CheckFile_HFS(const HFSCatalogKey * key, const HFSCatalogFile * file)
{
UInt32 fileID;
UInt32 blocks;
char idstr[20];
int result = 0;
if (file->flags & kHFSThreadExistsMask)
++gCIS.filesWithThreads;
if ((file->dataStartBlock) ||
(file->rsrcStartBlock) ||
(file->reserved))
{
RcdError(gScavGlobals, E_CatalogFlagsNotZero);
gScavGlobals->CBTStat |= S_ReservedNotZero;
}
fileID = file->fileID;
if (fileID < kHFSFirstUserCatalogNodeID) {
RcdError(gScavGlobals, E_InvalidID);
result = E_InvalidID;
return (result);
}
if (fileID >= gCIS.nextCNID )
gCIS.nextCNID = fileID + 1;
result = CheckFileExtents(gScavGlobals, file->fileID, kDataFork, NULL,
file->dataExtents, &blocks);
if (result != noErr)
return (result);
if (file->dataPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
snprintf (idstr, sizeof(idstr), "id=%u", fileID);
fsckPrint(gScavGlobals->context, E_PEOF, idstr);
return (noErr);
}
if (file->dataLogicalSize > file->dataPhysicalSize) {
snprintf (idstr, sizeof(idstr), "id=%u", fileID);
fsckPrint(gScavGlobals->context, E_LEOF, idstr);
return (noErr);
}
result = CheckFileExtents(gScavGlobals, file->fileID, kRsrcFork, NULL,
file->rsrcExtents, &blocks);
if (result != noErr)
return (result);
if (file->rsrcPhysicalSize > ((UInt64)blocks * (UInt64)gScavGlobals->calculatedVCB->vcbBlockSize)) {
snprintf (idstr, sizeof(idstr), "id=%u", fileID);
fsckPrint(gScavGlobals->context, E_PEOF, idstr);
return (noErr);
}
if (file->rsrcLogicalSize > file->rsrcPhysicalSize) {
snprintf (idstr, sizeof(idstr), "id=%u", fileID);
fsckPrint(gScavGlobals->context, E_LEOF, idstr);
return (noErr);
}
#if 1
if (PtrAndHand(&file->fileID, (Handle)gScavGlobals->validFilesList, sizeof(UInt32) ) )
return (R_NoMem);
#endif
CheckCatalogName_HFS(key->nodeName[0], &key->nodeName[1], key->parentID, false);
return (result);
}
static int
CheckThread_HFS(const HFSCatalogKey * key, const HFSCatalogThread * thread)
{
int result = 0;
if (key->nodeName[0] != 0) {
RcdError(gScavGlobals, E_ThdKey);
return (E_ThdKey);
}
result = CheckCatalogName_HFS(thread->nodeName[0], &thread->nodeName[1],
thread->parentID, true);
if (result != noErr) {
RcdError(gScavGlobals, E_ThdCN);
return (E_ThdCN);
}
if (key->parentID < kHFSFirstUserCatalogNodeID &&
key->parentID != kHFSRootParentID &&
key->parentID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
if (thread->parentID == kHFSRootParentID) {
if (key->parentID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
} else if (thread->parentID < kHFSFirstUserCatalogNodeID &&
thread->parentID != kHFSRootFolderID) {
RcdError(gScavGlobals, E_InvalidID);
return (E_InvalidID);
}
return (0);
}
#define FT_MASK 0170000
#define FT_FIFO 0010000
#define FT_CHR 0020000
#define FT_DIR 0040000
#define FT_BLK 0060000
#define FT_REG 0100000
#define FT_LNK 0120000
#define FT_SOCK 0140000
static void
CheckBSDInfo(const HFSPlusCatalogKey * key, const HFSPlusBSDInfo * bsdInfo, int isdir)
{
Boolean reset = false;
if (bsdInfo->fileMode == 0)
return;
switch (bsdInfo->fileMode & FT_MASK) {
case FT_DIR:
if (!isdir)
reset = true;
break;
case FT_REG:
case FT_BLK:
case FT_CHR:
case FT_LNK:
case FT_SOCK:
case FT_FIFO:
if (isdir)
reset = true;
break;
default:
reset = true;
}
if (reset) {
RepairOrderPtr p;
int n;
gScavGlobals->TarBlock = bsdInfo->fileMode & FT_MASK;
RcdError(gScavGlobals, E_InvalidPermissions);
n = CatalogNameSize( (CatalogName *) &key->nodeName, true );
p = AllocMinorRepairOrder(gScavGlobals, n);
if (p == NULL) return;
CopyCatalogName((const CatalogName *)&key->nodeName,
(CatalogName*)&p->name, true);
p->type = E_InvalidPermissions;
p->correct = 0;
p->incorrect = bsdInfo->fileMode;
p->parid = key->parentID;
p->hint = 0;
gScavGlobals->CatStat |= S_Permissions;
}
}
static int
CheckCatalogName(u_int16_t charCount, const u_int16_t *uniChars, u_int32_t parentID, Boolean thread)
{
OSErr result;
u_int16_t * myPtr;
RepairOrderPtr roPtr;
int myLength;
CatalogName newName;
if ((charCount == 0) || (charCount > kHFSPlusMaxFileNameChars))
return( E_CName );
if ( thread )
return( noErr );
if ( charCount < 3 && *uniChars == 0x2E )
{
if ( charCount == 1 || (charCount == 2 && *(uniChars + 1) == 0x2E) )
{
fsckPrint(gScavGlobals->context, E_IllegalName);
if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
plog( "\tillegal name is 0x" );
PrintName( charCount, (UInt8 *) uniChars, true );
}
result = UniqueDotName( gScavGlobals, &newName, parentID,
((charCount == 1) ? true : false), true );
if ( result != noErr )
return( noErr );
myLength = (charCount + 1) * 2; myLength += ((newName.ustr.length + 1) * 2);
roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
if ( roPtr == NULL )
return( noErr );
myPtr = (u_int16_t *) &roPtr->name;
*myPtr++ = charCount; CopyMemory( uniChars, myPtr, (charCount * 2) ); myPtr += charCount; *myPtr++ = newName.ustr.length; CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
plog( "\treplacement name is 0x" );
PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
}
roPtr->type = E_IllegalName;
roPtr->parid = parentID;
gScavGlobals->CatStat |= S_IllName;
return( E_IllegalName );
}
}
if ( FixDecomps( charCount, uniChars, &newName.ustr ) )
{
fsckPrint(gScavGlobals->context, E_IllegalName);
if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
plog( "\tillegal name is 0x" );
PrintName( charCount, (UInt8 *) uniChars, true );
}
myLength = (charCount + 1) * 2; myLength += ((newName.ustr.length + 1) * 2);
roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
if ( roPtr == NULL )
return( noErr );
myPtr = (u_int16_t *) &roPtr->name;
*myPtr++ = charCount; CopyMemory( uniChars, myPtr, (charCount * 2) ); myPtr += charCount; *myPtr++ = newName.ustr.length; CopyMemory( newName.ustr.unicode, myPtr, (newName.ustr.length * 2) ); if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
plog( "\treplacement name is 0x" );
PrintName( newName.ustr.length, (UInt8 *) &newName.ustr.unicode, true );
}
roPtr->type = E_IllegalName;
roPtr->parid = parentID;
gScavGlobals->CatStat |= S_IllName;
return( E_IllegalName );
}
return( noErr );
}
static int
CheckCatalogName_HFS(u_int16_t charCount, const u_char *filename, u_int32_t parentID, Boolean thread)
{
u_char * myPtr;
RepairOrderPtr roPtr;
int myLength;
CatalogName newName;
if ((charCount == 0) || (charCount > kHFSMaxFileNameChars))
return( E_CName );
if ( thread )
return( noErr );
if ( charCount < 3 && *filename == 0x2E )
{
if ( charCount == 1 || (charCount == 2 && *(filename + 1) == 0x2E) )
{
OSErr result;
fsckPrint(gScavGlobals->context, E_IllegalName);
if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
plog( "\tillegal name is 0x" );
PrintName( charCount, filename, false );
}
result = UniqueDotName( gScavGlobals, &newName, parentID,
((charCount == 1) ? true : false), false );
if ( result != noErr )
return( noErr );
myLength = charCount + 1; myLength += (newName.pstr[0] + 1); roPtr = AllocMinorRepairOrder( gScavGlobals, myLength );
if ( roPtr == NULL )
return( noErr );
myPtr = (u_char *)&roPtr->name[0];
*myPtr++ = charCount; CopyMemory( filename, myPtr, charCount );
myPtr += charCount; *myPtr++ = newName.pstr[0]; CopyMemory( &newName.pstr[1], myPtr, newName.pstr[0] ); if ( fsckGetVerbosity(gScavGlobals->context) >= kDebugLog ) {
plog( "\treplacement name is 0x" );
PrintName( newName.pstr[0], &newName.pstr[1], false );
}
roPtr->type = E_IllegalName;
roPtr->parid = parentID;
gScavGlobals->CatStat |= S_IllName;
return( E_IllegalName );
}
}
return( noErr );
}
static OSErr
UniqueDotName( SGlobPtr GPtr,
CatalogName * theNewNamePtr,
UInt32 theParID,
Boolean isSingleDotName,
Boolean isHFSPlus )
{
u_char newChar;
OSErr result;
size_t nameLen;
UInt16 recSize;
SFCB * fcbPtr;
u_char * myPtr;
CatalogRecord record;
CatalogKey catKey;
u_char dotName[] = {'d', 'o', 't', 'd', 'o', 't', 0x0d, 0x00};
fcbPtr = GPtr->calculatedCatalogFCB;
if ( isSingleDotName )
myPtr = &dotName[3];
else
myPtr = &dotName[0];
nameLen = strlen((char *) myPtr );
if ( isHFSPlus )
{
int i;
theNewNamePtr->ustr.length = nameLen;
for ( i = 0; i < theNewNamePtr->ustr.length; i++ )
theNewNamePtr->ustr.unicode[ i ] = (u_int16_t) *(myPtr + i);
}
else
{
theNewNamePtr->pstr[0] = nameLen;
memcpy( &theNewNamePtr->pstr[1], myPtr, nameLen );
}
for ( newChar = 0x30; newChar < 0x7F; newChar++ )
{
BuildCatalogKey( theParID, theNewNamePtr, isHFSPlus, &catKey );
result = SearchBTreeRecord( fcbPtr, &catKey, kNoHint, NULL, &record, &recSize, NULL );
if ( result != noErr )
return( noErr );
if ( isHFSPlus )
{
theNewNamePtr->ustr.unicode[ nameLen ] = (u_int16_t) newChar;
theNewNamePtr->ustr.length = nameLen + 1;
}
else
{
theNewNamePtr->pstr[ 0 ] = nameLen + 1;
theNewNamePtr->pstr[ nameLen + 1 ] = newChar;
}
}
return( -1 );
}
int
RecordBadAllocation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt32 oldBlkCnt, UInt32 newBlkCnt)
{
RepairOrderPtr p;
char goodstr[16];
char badstr[16];
size_t n;
Boolean isHFSPlus;
int result;
char *real_filename;
unsigned int filenamelen;
isHFSPlus = VolumeObjectIsHFSPlus( );
if (forkType == kEAData) {
filenamelen = NAME_MAX * 3;
real_filename = malloc(filenamelen);
if (!real_filename) {
return (R_NoMem);
}
result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
real_filename, &filenamelen, NULL);
if (result) {
sprintf(real_filename, "id = %u", parID);
}
fsckPrint(gScavGlobals->context, E_PEOAttr, filename, real_filename);
free(real_filename);
} else {
fsckPrint(gScavGlobals->context, E_PEOF, filename);
}
sprintf(goodstr, "%d", newBlkCnt);
sprintf(badstr, "%d", oldBlkCnt);
fsckPrint(gScavGlobals->context, E_BadValue, goodstr, badstr);
if ( !isHFSPlus )
return (E_PEOF);
n = strlen((char *)filename);
p = AllocMinorRepairOrder(gScavGlobals, n + 1);
if (p == NULL)
return (R_NoMem);
if (forkType == kEAData) {
p->type = E_PEOAttr;
} else {
p->type = E_PEOF;
}
p->forkType = forkType;
p->incorrect = oldBlkCnt;
p->correct = newBlkCnt;
p->hint = 0;
p->parid = parID;
p->name[0] = n;
CopyMemory(filename, &p->name[1], n);
gScavGlobals->CatStat |= S_FileAllocation;
return (0);
}
int
RecordTruncation(UInt32 parID, unsigned char * filename, UInt32 forkType, UInt64 oldSize, UInt64 newSize)
{
RepairOrderPtr p;
char oldSizeStr[48];
char newSizeStr[48];
size_t n;
Boolean isHFSPlus;
int result;
char *real_filename;
unsigned int filenamelen;
isHFSPlus = VolumeObjectIsHFSPlus( );
if (forkType == kEAData) {
filenamelen = NAME_MAX * 3;
real_filename = malloc(filenamelen);
if (!real_filename) {
return (R_NoMem);
}
result = GetFileNamePathByID(gScavGlobals, parID, NULL, NULL,
real_filename, &filenamelen, NULL);
if (result) {
sprintf(real_filename, "id = %u", parID);
}
fsckPrint(gScavGlobals->context, E_LEOAttr, filename, real_filename);
free(real_filename);
} else {
fsckPrint(gScavGlobals->context, E_LEOF, filename);
}
sprintf(oldSizeStr, "%qd", oldSize);
sprintf(newSizeStr, "%qd", newSize);
fsckPrint(gScavGlobals->context, E_BadValue, newSizeStr, oldSizeStr);
if ( !isHFSPlus )
return (E_LEOF);
n = strlen((char *)filename);
p = AllocMinorRepairOrder(gScavGlobals, n + 1);
if (p == NULL)
return (R_NoMem);
if (forkType == kEAData) {
p->type = E_LEOAttr;
} else {
p->type = E_LEOF;
}
p->forkType = forkType;
p->incorrect = oldSize;
p->correct = newSize;
p->hint = 0;
p->parid = parID;
p->name[0] = n;
CopyMemory(filename, &p->name[1], n);
gScavGlobals->CatStat |= S_FileAllocation;
return (0);
}
static int
CaptureMissingThread(UInt32 threadID, const HFSPlusCatalogKey *nextKey)
{
MissingThread *mtp;
Boolean isHFSPlus;
isHFSPlus = VolumeObjectIsHFSPlus( );
fsckPrint(gScavGlobals->context, E_NoThd, threadID);
if ( !isHFSPlus)
return (E_NoThd);
mtp = (MissingThread *) AllocateClearMemory(sizeof(MissingThread));
if (mtp == NULL)
return (R_NoMem);
mtp->link = gScavGlobals->missingThreadList;
gScavGlobals->missingThreadList = mtp;
mtp->threadID = threadID;
CopyMemory(nextKey, &mtp->nextKey, nextKey->keyLength + 2);
if (gScavGlobals->RepLevel == repairLevelNoProblemsFound)
gScavGlobals->RepLevel = repairLevelVolumeRecoverable;
gScavGlobals->CatStat |= S_MissingThread;
return (noErr);
}
static Boolean
FixDecomps( u_int16_t charCount, const u_int16_t *inFilename, HFSUniStr255 *outFilename )
{
const u_int16_t * inNamePtr = inFilename;
const u_int16_t * inNameLastPtr = &inFilename[charCount - 1];
u_int16_t * outNamePtr = outFilename->unicode;
u_int16_t * outNameLastPtr = &outFilename->unicode[kHFSPlusMaxFileNameChars - 1];
u_int16_t * outNameCombSeqPtr = NULL; u_int32_t maxClassValueInSeq = 0;
Boolean didModifyName = 0;
while (inNamePtr <= inNameLastPtr) {
u_int16_t shiftUniChar; int32_t rangeIndex;
u_int32_t shiftUniCharLo;
u_int32_t replDataIndex;
u_int32_t currCharClass;
shiftUniChar = *inNamePtr + kShiftUniCharOffset;
if ( shiftUniChar >= kShiftUniCharLimit )
goto CopyBaseChar;
rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
if ( rangeIndex < 0 )
goto CopyBaseChar;
shiftUniCharLo = shiftUniChar & kLoFieldMask;
replDataIndex = replaceRanges[rangeIndex][shiftUniCharLo];
if ( replDataIndex > 0 ) {
const u_int16_t * replDataPtr;
u_int32_t action;
u_int32_t copyCount = 0;
replDataPtr = &replaceData[replDataIndex];
action = *replDataPtr++;
switch (action) {
case kReplaceCurWithTwo:
case kReplaceCurWithThree:
inNamePtr++;
copyCount = (action == kReplaceCurWithTwo)? 2: 3;
break;
case kIfNextOneMatchesReplaceAllWithOne:
case kIfNextOneMatchesReplaceAllWithTwo:
if (inNamePtr + 1 <= inNameLastPtr && *(inNamePtr + 1) == *replDataPtr++) {
inNamePtr += 2;
copyCount = (action == kIfNextOneMatchesReplaceAllWithOne)? 1: 2;
} else {
goto CheckCombClass;
}
break;
case kIfNextTwoMatchReplaceAllWithOne:
if ( inNamePtr + 2 <= inNameLastPtr &&
*(inNamePtr + 1) == *replDataPtr++ &&
*(inNamePtr + 2) == *replDataPtr++)
{
inNamePtr += 3;
copyCount = 1;
} else {
goto CheckCombClass;
}
break;
}
if (outNamePtr + copyCount - 1 > outNameLastPtr) {
didModifyName = 0;
break;
}
while (copyCount-- > 0) {
currCharClass = 0;
shiftUniChar = *replDataPtr + kShiftUniCharOffset;
if ( shiftUniChar < kShiftUniCharLimit ) {
rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
if (rangeIndex >= 0) {
shiftUniCharLo = shiftUniChar & kLoFieldMask;
currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
}
}
if ( currCharClass == 0 ) {
outNameCombSeqPtr = NULL;
*outNamePtr++ = *replDataPtr++;
} else if ( outNameCombSeqPtr == NULL ) {
outNameCombSeqPtr = outNamePtr;
maxClassValueInSeq = currCharClass;
*outNamePtr++ = *replDataPtr++;
} else if ( currCharClass >= maxClassValueInSeq ) {
maxClassValueInSeq = currCharClass;
*outNamePtr++ = *replDataPtr++;
} else if ( outNamePtr - outNameCombSeqPtr == 1) {
*outNamePtr++ = *outNameCombSeqPtr;
*outNameCombSeqPtr = *replDataPtr++;
} else {
u_int16_t * outNameCombCharPtr;
u_int32_t combCharClass;
outNameCombCharPtr = outNamePtr++;
while (outNameCombCharPtr > outNameCombSeqPtr) {
shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
shiftUniCharLo = shiftUniChar & kLoFieldMask;
combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
if (combCharClass <= currCharClass)
break;
*outNameCombCharPtr = *(outNameCombCharPtr - 1);
outNameCombCharPtr--;
}
*outNameCombCharPtr = *replDataPtr++;
}
}
didModifyName = 1;
continue;
}
CheckCombClass:
currCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
if ( currCharClass == 0 ) {
goto CopyBaseChar;
}
if ( outNameCombSeqPtr == NULL ) {
outNameCombSeqPtr = outNamePtr;
maxClassValueInSeq = currCharClass;
goto CopyChar;
}
if ( currCharClass >= maxClassValueInSeq ) {
maxClassValueInSeq = currCharClass;
goto CopyChar;
}
if (outNamePtr > outNameLastPtr) {
didModifyName = 0;
break;
}
if (outNamePtr - outNameCombSeqPtr == 1) {
*outNamePtr++ = *outNameCombSeqPtr;
*outNameCombSeqPtr = *inNamePtr++;
} else {
u_int16_t * outNameCombCharPtr;
u_int32_t combCharClass;
outNameCombCharPtr = outNamePtr++;
while (outNameCombCharPtr > outNameCombSeqPtr) {
shiftUniChar = *(outNameCombCharPtr - 1) + kShiftUniCharOffset;
rangeIndex = classAndReplIndex[shiftUniChar >> kLoFieldBitSize];
shiftUniCharLo = shiftUniChar & kLoFieldMask;
combCharClass = combClassRanges[rangeIndex][shiftUniCharLo];
if (combCharClass <= currCharClass)
break;
*outNameCombCharPtr = *(outNameCombCharPtr - 1);
outNameCombCharPtr--;
}
*outNameCombCharPtr = *inNamePtr++;
}
didModifyName = 1;
continue;
CopyBaseChar:
outNameCombSeqPtr = NULL;
CopyChar:
if (outNamePtr <= outNameLastPtr) {
*outNamePtr++ = *inNamePtr++;
} else {
didModifyName = 0;
break;
}
}
if (didModifyName) {
outFilename->length = outNamePtr - outFilename->unicode;
}
return didModifyName;
}