Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic repair for broken directory hardlinks. #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 228 additions & 2 deletions fsck_hfs/dfalib/SRepair.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "Scavenger.h"
#include <unistd.h>
#include <inttypes.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stddef.h>
Expand Down Expand Up @@ -120,7 +121,6 @@ static int BuildThreadRec( CatalogKey * theKeyPtr, CatalogRecord * theRecPtr,
static int BuildFileRec(UInt16 fileType, UInt16 fileMode, UInt32 fileID, Boolean isHFSPlus, CatalogRecord *catRecord);
static void BuildAttributeKey(u_int32_t fileID, u_int32_t startBlock, unsigned char *attrName, u_int16_t attrNameLen, HFSPlusAttrKey *key);


OSErr RepairVolume( SGlobPtr GPtr )
{
OSErr err;
Expand Down Expand Up @@ -986,7 +986,7 @@ OSErr FixFileHardLinkFlag(SGlobPtr GPtr, RepairOrderPtr p)
Routine: FixPrivDirBadPerms - fix the permissions of the directory hard-link private dir

Input: GPtr -- pointer to scavenger global data
p -- poitner to a minor repair order
p -- pointer to a minor repair order

Output: function result:
0 -- no error
Expand Down Expand Up @@ -1029,6 +1029,223 @@ static OSErr FixPrivDirBadPerms(SGlobPtr GPtr, RepairOrderPtr p)
return retval;
}

/*
Routine: FixBadParentInode - move inode file/folder to metadata directory.

Input: GPtr -- pointer to scavenger global data
p -- pointer to a minor repair order

Output: function result:
0 -- no error
n -- error
*/

static OSErr FixBadParentInode(SGlobPtr GPtr, RepairOrderPtr p)
{
OSErr retval = noErr;
UInt32 hint;

CatalogName inodeName;

CatalogKey incorrectKey;
CatalogKey correctKey;

CatalogRecord currentRecord;
uint16_t currentRecordSize;

CatalogKey parentFolderKey;
CatalogRecord parentFolderRecord;
uint16_t parentFolderRecordSize;

/* This error only happens on HFS+ */
if ( !VolumeObjectIsHFSPlus() ) {
retval = IntError( GPtr, R_IntErr );
goto done;
}

inodeName.ustr = *((HFSUniStr255 *)p->name);

// Read record data using current (incorrect) key
BuildCatalogKey(p->incorrect, &inodeName, true, &incorrectKey);
retval = SearchBTreeRecord(GPtr->calculatedCatalogFCB, &incorrectKey, kNoHint, NULL, &currentRecord, &currentRecordSize, NULL);
if ( retval != noErr ) {
if ( retval == btNotFound ) {
retval = 0;
}
goto done;
}

// We do not want to ever loose the record, so we insert/replace the new one first.
BuildCatalogKey(p->correct, &inodeName, true, &correctKey);
retval = ReplaceBTreeRecord( GPtr->calculatedCatalogFCB, &correctKey, kNoHint, &currentRecord, currentRecordSize, &hint);
if ( retval == btNotFound ) {
// Could not replace because it was not there before, so insert it.
retval = InsertBTreeRecord( GPtr->calculatedCatalogFCB, &correctKey, &currentRecord, currentRecordSize, &hint);
}
if ( retval != noErr ) {
goto done;
}

// Now we delete the incorrect one.
retval = DeleteBTreeRecord( GPtr->calculatedCatalogFCB, &incorrectKey );
if ( retval != noErr ) {
goto done;
}

/* NOTE: At this point, the valence of one or both of the folders is
* most likely incorrect. To know which one, one has to count
* the items in the folders. This is one of the repair steps
* anyways so it is okay to just ignore this here and let the
* next repair pass handle it.
*/
GPtr->minorRepairFalseSuccess = true;

done:
if ( retval != noErr ) {
if ( fsckGetVerbosity(GPtr->context) >= kDebugLog ) {
plog( "\t%s - repair failed for type 0x%02X %d \n", __FUNCTION__, p->type, p->type );
}
}

return retval;
}

/*
Routine: FixDirInodeBadName - fix the name of a hard linked folder.

Input: GPtr -- pointer to scavenger global data
p -- pointer to a minor repair order

Output: function result:
0 -- no error
n -- error
*/

static OSErr FixDirInodeBadName(SGlobPtr GPtr, RepairOrderPtr p)
{
OSErr retval = 0;

SFCB *fcbPtr;
UInt32 hint;

size_t *incorrect_len;
char *incorrect;
char *correct;

CatalogKey currentKey;
CatalogRecord currentRecord;
UInt16 currentRecordSize;

UInt32 threadID;
CatalogKey threadKey;
CatalogRecord threadRecord;
UInt16 threadRecordSize;

CatalogKey parentKey;
CatalogRecord parentRecord;
UInt16 parentRecordSize;

UInt32 oldValence;
UInt32 oldFolderCount;


/* This error only ever happens on HFS+ */
if ( !VolumeObjectIsHFSPlus() ) {
goto done;
}

incorrect_len = (size_t *)p->name;
incorrect = (char *) p->name + sizeof(size_t);
correct = (char *) p->name + sizeof(size_t) + *incorrect_len + 1;

fcbPtr = GPtr->calculatedCatalogFCB;

retval = GetCatalogRecordByID( GPtr, (UInt32)p->parid, true, &currentKey, &currentRecord, &currentRecordSize );
if ( retval != noErr ) {
if ( retval == btNotFound ) {
/* If the record was not found this means that the record was
* deleted by an other repair order, return success since this
* means the name is no longer used. Make it a false success
* to be sure.
*/
GPtr->minorRepairFalseSuccess = true;
retval = 0;
}
goto done;
}

if ( currentRecord.recordType == kHFSPlusFolderRecord ) {
threadID = currentRecord.hfsPlusFolder.folderID;
} else if ( currentRecord.recordType == kHFSPlusFileRecord ) {
threadID = currentRecord.hfsPlusFile.fileID;
} else {
retval = IntError( GPtr, R_IntErr );
goto done;
}

// Is it a deleted link that was open when unmount happened?
if (strncmp(incorrect, HFS_DELETE_PREFIX, strlen(HFS_DELETE_PREFIX)) == 0) {
retval = DeleteBTreeRecord( fcbPtr, &currentKey );
if ( retval != noErr ) {
if ( fsckGetVerbosity(GPtr->context) >= kDebugLog ) {
plog( "\t%s - failed to delete inode record %" PRIu32 "\n", __FUNCTION__, p->parid );
}
goto done;
}

BuildCatalogKey( threadID, NULL, true, &threadKey );
retval = SearchBTreeRecord( fcbPtr, &threadKey, kNoHint, NULL, &threadRecord, &threadRecordSize, NULL );
if ( retval == noErr ) {
retval = DeleteBTreeRecord( fcbPtr, &threadKey );
if ( retval != noErr ) {
if ( fsckGetVerbosity(GPtr->context) >= kDebugLog ) {
plog( "\t%s - failed to delete thread record for folder ID %" PRIu32 "\n", __FUNCTION__, threadID );
}
goto done;
}
} else {
if ( fsckGetVerbosity(GPtr->context) >= kDebugLog ) {
plog( "\t%s - no thread record to delete for folder ID %" PRIu32 "\n", __FUNCTION__, threadID );
}
}

retval = GetCatalogRecordByID( GPtr, currentKey.hfsPlus.parentID, true, &parentKey, &parentRecord, &parentRecordSize );
if ( retval != noErr ) {
goto done;
}

oldValence = parentRecord.hfsPlusFolder.valence;
oldFolderCount = parentRecord.hfsPlusFolder.folderCount;

if ( parentRecord.hfsPlusFolder.valence > 0 ) {
parentRecord.hfsPlusFolder.valence--;
}
if ( currentRecord.recordType == kHFSPlusFolderRecord && (parentRecord.hfsPlusFolder.flags & kHFSHasFolderCountMask) && parentRecord.hfsPlusFolder.folderCount > 0 ) {
parentRecord.hfsPlusFolder.folderCount--;
}

if ( (parentRecord.hfsPlusFolder.valence != oldValence) || (parentRecord.hfsPlusFolder.folderCount != oldFolderCount) ) {
retval = ReplaceBTreeRecord( GPtr->calculatedCatalogFCB, &parentKey, kNoHint, &parentRecord, parentRecordSize, &hint );
if ( retval != noErr ) {
goto done;
}
} else {
}
} else { // not a pending delete, actually incorrect name, moving to lost+found.
retval = FixOrphanInode(GPtr, p);
goto done;
}

done:
if ( retval != noErr ) {
if ( fsckGetVerbosity(GPtr->context) >= kDebugLog ) {
plog( "\t%s - repair failed for type 0x%02X %d \n", __FUNCTION__, p->type, p->type );
}
}
return retval;
}


/*------------------------------------------------------------------------------
Routine: FixOrphanLink

Expand Down Expand Up @@ -1519,6 +1736,15 @@ static OSErr DoMinorOrders( SGlobPtr GPtr ) // the globals
err = FixPrivDirBadPerms(GPtr, p);
break;

case E_DirInodeBadParent:
case E_FileInodeBadParent:
err = FixBadParentInode(GPtr, p);
break;

case E_DirInodeBadName:
case E_FileInodeBadName:
err = FixDirInodeBadName(GPtr, p);

case E_InvalidLinkChainFirst:
err = FixBadLinkChainFirst(GPtr, p);
break;
Expand Down
Loading