HardLinkCheck.c   [plain text]


/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * @APPLE_LICENSE_HEADER_END@
 */

#include "Scavenger.h"

#define FILE_ID_COUNT	10
#define kBadLinkID 0xffffffff

/* info saved for each indirect link encountered */
struct IndirectLinkInfo {
	UInt32	linkID;
	UInt32	linkCount;
	UInt32  linkFileID[FILE_ID_COUNT];
};

struct HardLinkInfo {
	UInt32	privDirID;
	UInt32	linkSlots;
	UInt32	slotsUsed;
	SGlobPtr globals;
	struct IndirectLinkInfo linkInfo[1];
};


HFSPlusCatalogKey gMetaDataDirKey = {
	48,		/* key length */
	2,		/* parent directory ID */
	{
		21,	/* number of unicode characters */
		{
			'\0','\0','\0','\0',
			'H','F','S','+',' ',
			'P','r','i','v','a','t','e',' ',
			'D','a','t','a'
		}
	}
};


/* private local routines */
static int  GetPrivateDir(SGlobPtr gp, CatalogRecord *rec);
static int  RecordOrphanINode(SGlobPtr gp, UInt32 parID, char * filename);
static int  RecordBadLinkCount(SGlobPtr gp, UInt32 parID, char * filename,
                                int badcnt, int goodcnt);

/*
 * HardLinkCheckBegin
 *
 * Get ready to capture indirect link info.
 * Called before iterating over all the catalog leaf nodes.
 */
int
HardLinkCheckBegin(SGlobPtr gp, void** cookie)
{
	struct HardLinkInfo *info;
	CatalogRecord rec;
	UInt32 folderID;
	int entries, slots;

	if (GetPrivateDir(gp, &rec) == 0 && rec.hfsPlusFolder.valence != 0) {
		entries = rec.hfsPlusFolder.valence;
		folderID = rec.hfsPlusFolder.folderID;
	} else {
		entries = 0;
		folderID = 0;
	};

	slots = entries ? entries + 10 : 100;

	info = (struct HardLinkInfo *) AllocateClearMemory(
		sizeof(struct HardLinkInfo) + sizeof(struct IndirectLinkInfo) * slots);

	info->privDirID = folderID;
	info->linkSlots = slots;
	info->slotsUsed = 0;
	info->globals = gp;
	
	* cookie = info;
	return (0);
}

/*
 * HardLinkCheckEnd
 *
 * Dispose of captured data.
 * Called after calling CheckHardLinks.
 */
void
HardLinkCheckEnd(void * cookie)
{
#if 0
	struct HardLinkInfo * info = (struct HardLinkInfo *) cookie;
	struct IndirectLinkInfo *linkInfo;
	int i, j;

	linkInfo = &info->linkInfo[0];
	for (i = 0; i < info->slotsUsed; ++linkInfo, ++i) {
		printf(" link %d has %d links: ", linkInfo->linkID, linkInfo->linkCount);
		for (j = 0; j < linkInfo->linkCount; ++j)
			printf(", %d", linkInfo->linkFileID[j]);
		printf("\n");
	}
#endif
	if (cookie)
		DisposeMemory(cookie);
}

/*
 * CaptureHardLink
 *
 * Capture indirect link info.
 * Called for every indirect link in the catalog.
 */
void
CaptureHardLink(void * cookie, UInt32 linkID, UInt32 fileID)
{
	struct HardLinkInfo * info = (struct HardLinkInfo *) cookie;
	struct IndirectLinkInfo *linkInfo;
	int linkCount;
	int i;

	if (info == NULL)
		return;

	if (linkID == 0)
		linkID = kBadLinkID;	/* reported later */

	linkInfo = &info->linkInfo[0];
	for (i = 0; i < info->linkSlots; ++linkInfo, ++i) {
		if (linkInfo->linkID == kBadLinkID)
			continue;
		if (linkInfo->linkID == linkID || linkInfo->linkID == 0) {
			linkCount = linkInfo->linkCount;
			++linkInfo->linkCount;
			if (linkCount < FILE_ID_COUNT)
				linkInfo->linkFileID[linkCount] = fileID;
			if (linkInfo->linkID == 0) {
				linkInfo->linkID = linkID;
				++info->slotsUsed;
			}
			break;
		}
	}
}

/*
 * CheckHardLinks
 *
 * Check indirect node link counts aginst the indirect
 * links that were found. There are 4 possible problems
 * that can occur.
 *  1. orphaned indirect node (i.e. no links found)
 *  2. orphaned indirect link (i.e. indirect node missing)
 *  3. incorrect link count
 *  4. indirect link id was 0 (i.e. link id wasn't preserved)
 */
int
CheckHardLinks(void *cookie)
{
	struct HardLinkInfo *info = (struct HardLinkInfo *)cookie;
	SGlobPtr gp;
	UInt32              folderID;
	UInt32 linkID;
	SFCB              * fcb;
	CatalogRecord       rec;
	HFSPlusCatalogKey * keyp;
	BTreeIterator       iterator;
	FSBufferDescriptor  btrec;
	UInt16              reclen;
	size_t              len;
	int                 i;
	int linkCount;
	int prefixlen;
	int result;
	struct IndirectLinkInfo * linkInfo;
	char filename[64];

	/* All done if no hard links exist. */
	if (info == NULL || (info->privDirID == 0 && info->slotsUsed == 0))
		return (0);

	gp = info->globals;
	PrintStatus(gp, M_MultiLinkChk, 0);

	folderID = info->privDirID;
	linkInfo = &info->linkInfo[0];
	fcb = gp->calculatedCatalogFCB;
	prefixlen = strlen(HFS_INODE_PREFIX);
	ClearMemory(&iterator, sizeof(iterator));
	keyp = (HFSPlusCatalogKey*)&iterator.key;
	btrec.bufferAddress = &rec;
	btrec.itemCount = 1;
	btrec.itemSize = sizeof(rec);
	/*
	 * position iterator at folder thread record
	 * (i.e. one record before first child)
	 */
	ClearMemory(&iterator, sizeof(iterator));
	BuildCatalogKey(folderID, NULL, true, (CatalogKey *)keyp);
	result = BTSearchRecord(fcb, &iterator, kInvalidMRUCacheKey, &btrec,
				&reclen, &iterator);
	if (result) goto exit;

	/* Visit all the children in private directory. */
	for (;;) {
		result = BTIterateRecord(fcb, kBTreeNextRecord, &iterator,
					&btrec, &reclen);
		if (result || keyp->parentID != folderID)
			break;
		if (rec.recordType != kHFSPlusFileRecord)
			continue;

		(void) utf_encodestr(keyp->nodeName.unicode,
					keyp->nodeName.length * 2,
					filename, &len);
		filename[len] = '\0';
		
		if ( 	(strstr(filename, HFS_DELETE_PREFIX) == filename) &&
				(gp->calculatedVCB->vcbDriverWriteRef != -1) ) {
				
			RecordOrphanINode(gp, folderID, filename);
			continue;		
		}
				
		if (strstr(filename, HFS_INODE_PREFIX) != filename)
			continue;
		
		linkID = atol(&filename[prefixlen]);
		linkCount = rec.hfsPlusFile.bsdInfo.special.linkCount;

		/* look for matching links */
		for (i = 0; i < info->slotsUsed; ++i) {
			if (linkID == linkInfo[i].linkID) {
#if 0
				printf("   found match for link %d\n", linkID);
#endif
				if (linkCount != linkInfo[i].linkCount)
					RecordBadLinkCount(gp, folderID, filename,
					                   linkCount, linkInfo[i].linkCount);
				linkInfo[i].linkID = 0;
				break;
			}
		}

		/* no match means this is an orphan indirect node */
		if (i == info->slotsUsed)
			RecordOrphanINode(gp, folderID, filename);

		filename[0] = '\0';
	}

	/*
	 * Any remaining indirect links are orphans.
	 *
	 * TBD: what to do with them...
	 */
#if 0
	if (gp->logLevel >= kDebugLog) {
	    for (i = 0; i < info->slotsUsed; ++i) {
		if (linkInfo[i].linkID == kBadLinkID) {
		    printf("missing link number (copied under Mac OS 9 ?)\n");
		    /*
		     * To do: loop through each file ID and report
		     */
		} else if (linkInfo[i].linkID != 0) {
		    printf("\torphaned link (indirect node %d missing)\n", linkInfo[i].linkID);
		}
	}
#endif

exit:
	return (result);
}

/*
 * GetPrivateDir
 *
 * Get catalog entry for the "HFS+ Private Data" directory.
 * The indirect nodes are stored in this directory.
 */
static int
GetPrivateDir(SGlobPtr gp, CatalogRecord * rec)
{
	HFSPlusCatalogKey * keyp;
	BTreeIterator       iterator;
	FSBufferDescriptor  btrec;
	UInt16              reclen;
	int                 result;

	if (!gp->isHFSPlus)
		return (-1);

	ClearMemory(&iterator, sizeof(iterator));
	keyp = (HFSPlusCatalogKey*)&iterator.key;

	btrec.bufferAddress = rec;
	btrec.itemCount = 1;
	btrec.itemSize = sizeof(CatalogRecord);

	/* look up record for HFS+ private folder */
	ClearMemory(&iterator, sizeof(iterator));
	CopyMemory(&gMetaDataDirKey, keyp, sizeof(gMetaDataDirKey));
	result = BTSearchRecord(gp->calculatedCatalogFCB, &iterator,
	                        kInvalidMRUCacheKey, &btrec, &reclen, &iterator);

	return (result);
}

/*
 * RecordOrphanINode
 *
 * Record a repair to delete an orphaned indirect node.
 */
static int
RecordOrphanINode(SGlobPtr gp, UInt32 parID, char* filename)
{
	RepairOrderPtr p;
	int n;
	
	PrintError(gp, E_UnlinkedFile, 1, filename);
	
	n = strlen(filename);
	p = AllocMinorRepairOrder(gp, n + 1);
	if (p == NULL)
		return (R_NoMem);

	p->type = E_UnlinkedFile;
	p->correct = 0;
	p->incorrect = 0;
	p->hint = 0;
	p->parid = parID;
	p->name[0] = n;  /* pascal string */
	CopyMemory(filename, &p->name[1], n);

	gp->CatStat |= S_UnlinkedFile;
	return (noErr);
}


/* 
 * RecordBadLinkCount
 *
 * Record a repair to adjust an indirect node's link count.
 */
static int
RecordBadLinkCount(SGlobPtr gp, UInt32 parID, char * filename,
                   int badcnt, int goodcnt)
{
	RepairOrderPtr p;
	char goodstr[16];
	char badstr[16];
	int n;
	
	PrintError(gp, E_InvalidLinkCount, 1, filename);
	sprintf(goodstr, "%d", goodcnt);
	sprintf(badstr, "%d", badcnt);
	PrintError(gp, E_BadValue, 2, goodstr, badstr);

	n = strlen(filename);
	p = AllocMinorRepairOrder(gp, n + 1);
	if (p == NULL)
		return (R_NoMem);

	p->type = E_InvalidLinkCount;
	p->incorrect = badcnt;
	p->correct = goodcnt;
	p->hint = 0;
	p->parid = parID;
	p->name[0] = n;  /* pascal string */
	CopyMemory(filename, &p->name[1], n);

	gp->CatStat |= S_LinkCount;
	return (0);
}