#include "Scavenger.h"
#include <sys/stat.h>
#define DEBUG_HARDLINKCHECK 0
#define LINKINFO_INIT 0x01
#define LINKINFO_CHECK 0x02
struct IndirectLinkInfo {
UInt32 linkID;
UInt32 linkCount;
UInt32 flags;
struct HardLinkList *list;
};
#define VISITED_INODE_ID 1
struct HardLinkInfo {
UInt32 privDirID;
SGlobPtr globals;
PrimeBuckets *fileBucket;
};
struct HardLinkList {
UInt32 prev;
UInt32 fileID;
UInt32 next;
};
HFSPlusCatalogKey gMetaDataDirKey = {
48,
2,
{
21,
{
'\0','\0','\0','\0',
'H','F','S','+',' ',
'P','r','i','v','a','t','e',' ',
'D','a','t','a'
}
}
};
static int GetPrivateDir(SGlobPtr gp, CatalogRecord *rec);
static int RecordOrphanOpenUnlink(SGlobPtr gp, UInt32 parID, unsigned char * filename);
static int RecordBadHardLinkChainFirst(SGlobPtr, UInt32, UInt32, UInt32);
static int RecordBadHardLinkNext(SGlobPtr gp, UInt32 fileID, UInt32 is, UInt32 shouldbe);
static int RecordBadHardLinkPrev(SGlobPtr gp, UInt32 fileID, UInt32 is, UInt32 shouldbe);
static int RecordBadLinkCount(SGlobPtr gp, UInt32 inodeID, UInt32 is, UInt32 shouldbe) ;
static int RecordOrphanLink(SGlobPtr gp, Boolean isdir, UInt32 linkID);
static int RecordOrphanInode(SGlobPtr gp, Boolean isdir, UInt32 inodeID);
static void hash_insert(UInt32 linkID, int totalSlots, int slotsUsed, struct IndirectLinkInfo *linkInfo);
static struct IndirectLinkInfo * hash_search(UInt32 linkID, int totalSlots, int slotsUsed, struct IndirectLinkInfo *linkInfo);
static int
chain_compare(const void *a1, const void *a2) {
struct HardLinkList *left = (struct HardLinkList*)a1;
struct HardLinkList *right = (struct HardLinkList*)a2;
return (left->prev - right->prev);
}
static int
find_id(struct HardLinkList *list, int nel, int id)
{
int i;
for (i = 0; i < nel; i++) {
if (list[i].fileID == id)
return i;
}
return 0;
}
static int
tsort(struct HardLinkList *list, int nel)
{
struct HardLinkList *tmp;
int cur_indx, tmp_indx = 0;
int rv = 0;
tmp = calloc(sizeof(struct HardLinkList), nel);
if (tmp == NULL) {
rv = ENOMEM;
goto done;
}
qsort(list, nel, sizeof(list[0]), chain_compare);
for (cur_indx = 0; cur_indx < nel; cur_indx++) {
int i;
if (list[cur_indx].fileID == 0)
continue;
tmp[tmp_indx++] = list[cur_indx];
list[cur_indx].fileID = 0;
for (i = tmp[tmp_indx-1].next; i != 0; ) {
int j = find_id(list, nel, i);
if (j == 0) {
i = 0;
} else {
tmp[tmp_indx++] = list[j];
list[j].fileID = 0;
i = tmp[tmp_indx-1].next;
}
}
}
memcpy(list, tmp, nel * sizeof(struct HardLinkList));
done:
if (tmp) {
free(tmp);
}
return rv;
}
static int
CheckHardLinkList(SGlobPtr gp, UInt32 inodeID, struct HardLinkList *list, int calc_link_count, UInt32 firstID)
{
int retval;
int indx;
if (list == NULL) {
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("\tCheckHardLinkList: list=NULL for inodeID = %u\n", inodeID);
}
return ENOMEM;
}
if (calc_link_count > 1) {
retval = tsort(list, calc_link_count);
if (retval) {
goto done;
}
}
if (list[0].prev != 0) {
RecordBadHardLinkPrev(gp, list[0].fileID, list[0].prev, 0);
}
if (list[0].fileID != firstID) {
RecordBadHardLinkChainFirst(gp, inodeID, firstID, list[0].fileID);
}
for (indx = 1; indx < calc_link_count; indx++) {
if (list[indx-1].next != list[indx].fileID) {
RecordBadHardLinkNext(gp, list[indx-1].fileID, list[indx-1].next, list[indx].fileID);
}
if (list[indx].prev != list[indx-1].fileID) {
RecordBadHardLinkPrev(gp, list[indx].fileID, list[indx].prev, list[indx-1].fileID);
}
}
if (list[calc_link_count-1].next != 0) {
RecordBadHardLinkNext(gp, list[calc_link_count-1].fileID, list[calc_link_count-1].next, 0);
}
done:
#if DEBUG_HARDLINKCHECK
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
for (indx = 0; indx < calc_link_count; indx++) {
fplog(stderr, "CNID %u: #%u: <%u, %u, %u>\n", inodeID, indx, list[indx].prev, list[indx].fileID, list[indx].next);
}
}
#endif
return 0;
}
int
HardLinkCheckBegin(SGlobPtr gp, void** cookie)
{
struct HardLinkInfo *info;
CatalogRecord rec;
UInt32 folderID;
if (GetPrivateDir(gp, &rec) == 0) {
folderID = rec.hfsPlusFolder.folderID;
} else {
folderID = 0;
}
info = (struct HardLinkInfo *) malloc(sizeof(struct HardLinkInfo));
if (info == NULL) {
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("hardLinkCheckBegin: malloc(%zu) failed\n", sizeof(struct HardLinkInfo));
}
return 1;
}
info->privDirID = folderID;
info->globals = gp;
gp->filelink_priv_dir_id = folderID;
info->fileBucket = calloc(1, sizeof(PrimeBuckets));
if (info->fileBucket == NULL) {
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("HardLinkCheckBegin: prime bucket allocation failed\n");
}
}
* cookie = info;
return (0);
}
void
HardLinkCheckEnd(void * cookie)
{
if (cookie) {
struct HardLinkInfo * infoPtr;
infoPtr = (struct HardLinkInfo *) cookie;
if (infoPtr->fileBucket) {
free(infoPtr->fileBucket);
infoPtr->fileBucket = NULL;
}
DisposeMemory(cookie);
}
}
#define FILELINK_HASH_SIZE 257
struct filelink_hash {
UInt32 link_ref_num;
UInt32 found_link_count;
UInt32 calc_link_count;
struct filelink_hash *next;
};
struct filelink_hash **filelink_head = NULL;
UInt32 filelink_entry_count = 0;
static struct filelink_hash *filelink_hash_search(UInt32 link_ref_num)
{
struct filelink_hash *cur;
if (filelink_head == NULL) {
return NULL;
}
cur = filelink_head[link_ref_num % FILELINK_HASH_SIZE];
while (cur) {
if (link_ref_num == cur->link_ref_num) {
break;
}
cur = cur->next;
}
return cur;
}
static struct filelink_hash *filelink_hash_insert(UInt32 link_ref_num)
{
struct filelink_hash *cur;
cur = malloc(sizeof(struct filelink_hash));
if (!cur) {
return cur;
}
cur->link_ref_num = link_ref_num;
cur->found_link_count = 0;
cur->calc_link_count = 0;
cur->next = filelink_head[link_ref_num % FILELINK_HASH_SIZE];
filelink_head[link_ref_num % FILELINK_HASH_SIZE] = cur;
filelink_entry_count++;
return cur;
}
static int filelink_hash_link(UInt32 link_ref_num)
{
struct filelink_hash *cur;
if (filelink_head == NULL) {
filelink_head = calloc(FILELINK_HASH_SIZE, sizeof(struct filelink_hash *));
if (filelink_head == NULL) {
return ENOMEM;
}
}
cur = filelink_hash_search(link_ref_num);
if (!cur) {
cur = filelink_hash_insert(link_ref_num);
}
if (cur) {
cur->calc_link_count++;
return 0;
}
return ENOMEM;
}
int filelink_hash_inode(UInt32 link_ref_num, UInt32 linkCount)
{
struct filelink_hash *cur;
if (filelink_head == NULL) {
filelink_head = calloc(FILELINK_HASH_SIZE, sizeof(struct filelink_hash *));
if (filelink_head == NULL) {
return ENOMEM;
}
}
cur = filelink_hash_search(link_ref_num);
if (!cur) {
cur = filelink_hash_insert(link_ref_num);
}
if (cur) {
cur->found_link_count = linkCount;
return 0;
}
return ENOMEM;
}
static void filelink_hash_destroy(void)
{
int i;
struct filelink_hash *cur;
for (i = 0; i < FILELINK_HASH_SIZE; i++) {
while (filelink_head[i]) {
cur = filelink_head[i];
filelink_head[i] = cur->next;
free (cur);
}
}
free(filelink_head);
filelink_head = NULL;
filelink_entry_count = 0;
}
void
CaptureHardLink(void *cookie, const HFSPlusCatalogFile *file)
{
struct HardLinkInfo * info = (struct HardLinkInfo *) cookie;
if ((info->fileBucket == NULL) ||
(((file->flags & kHFSHasLinkChainMask) == 0) &&
(file->hl_prevLinkID == 0) &&
(file->hl_nextLinkID == 0))) {
filelink_hash_link(file->hl_linkReference);
} else {
hardlink_add_bucket(info->fileBucket, file->hl_linkReference,
file->fileID);
if ((file->flags & kHFSHasLinkChainMask) == 0) {
record_link_badchain(info->globals, false);
}
}
return;
}
int
RepairHardLinkChains(SGlobPtr gp, Boolean isdir)
{
int result = 0;
struct IndirectLinkInfo *linkInfo = NULL;
CatalogRecord rec;
HFSPlusCatalogKey *keyp;
BTreeIterator iterator;
FSBufferDescriptor btrec;
UInt16 reclen;
UInt32 linkID, inodeID;
UInt32 metadirid;
SFCB *fcb;
size_t prefixlen;
int slotsUsed = 0, slots = 0;
char *prefixName;
UInt32 folderID;
UInt32 link_ref_num;
int entries;
UInt32 flags;
if (isdir) {
metadirid = gp->dirlink_priv_dir_id;
prefixlen = strlen(HFS_DIRINODE_PREFIX);
prefixName = HFS_DIRINODE_PREFIX;
} else {
metadirid = gp->filelink_priv_dir_id;
prefixlen = strlen(HFS_INODE_PREFIX);
prefixName = HFS_INODE_PREFIX;
}
if (metadirid == 0) {
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
if (isdir) {
plog ("\tPrivate directory for dirlinks not found. Stopping repairs.\n");
} else {
plog ("\tPrivate directory for filelinks not found. Stopping repairs.\n");
}
}
result = ENOENT;
goto done;
}
if (GetPrivateDir(gp, &rec) == 0 && rec.hfsPlusFolder.valence != 0) {
entries = rec.hfsPlusFolder.valence + 10;
folderID = rec.hfsPlusFolder.folderID;
} else {
entries = 1000;
folderID = 0;
}
for (slots = 1; slots <= entries; slots <<= 1)
continue;
if (slots < (entries + (entries/3)))
slots <<= 1;
linkInfo = calloc(slots, sizeof(struct IndirectLinkInfo));
if (linkInfo == NULL) {
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("RepairHardLinkChains: calloc(%d, %zu) failed\n", slots, sizeof(struct IndirectLinkInfo));
}
result = ENOMEM;
goto done;
}
fcb = gp->calculatedCatalogFCB;
ClearMemory(&iterator, sizeof(iterator));
keyp = (HFSPlusCatalogKey*)&iterator.key;
BuildCatalogKey(kHFSRootFolderID, NULL, true, (CatalogKey*)keyp);
btrec.bufferAddress = &rec;
btrec.itemCount = 1;
btrec.itemSize = sizeof(rec);
entries = 0;
for (result = BTIterateRecord(fcb, kBTreeFirstRecord, &iterator, &btrec, &reclen);
result == 0;
result = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &btrec, &reclen)) {
HFSPlusCatalogFile *file = &rec.hfsPlusFile;
Boolean islink = false;
if (rec.recordType != kHFSPlusFileRecord)
continue;
if (isdir) {
if (((file->userInfo.fdType == kHFSAliasType) ||
(file->userInfo.fdCreator == kHFSAliasCreator) ||
(file->userInfo.fdFlags & kIsAlias)) &&
(keyp->parentID != gp->filelink_priv_dir_id) &&
((file->hl_linkReference >= kHFSFirstUserCatalogNodeID) ||
(file->flags & kHFSHasLinkChainMask))) {
flags = rec.hfsPlusFile.flags;
islink = true;
}
} else if (file->userInfo.fdType == kHardLinkFileType &&
file->userInfo.fdCreator == kHFSPlusCreator) {
flags = rec.hfsPlusFile.flags;
islink = true;
}
if (islink) {
struct IndirectLinkInfo *li = NULL;
struct HardLinkList *tlist = NULL;
int i;
int count;
linkID = file->fileID;
inodeID = file->bsdInfo.special.iNodeNum;
if ((flags & kHFSHasLinkChainMask) == 0) {
record_link_badflags(gp, linkID, isdir, flags,
flags | kHFSHasLinkChainMask);
}
if (isdir) {
if ((file->bsdInfo.ownerFlags & UF_IMMUTABLE) == 0) {
record_dirlink_badownerflags(gp, file->fileID,
file->bsdInfo.ownerFlags,
file->bsdInfo.ownerFlags | UF_IMMUTABLE, true);
}
if ((file->userInfo.fdType != kHFSAliasType) ||
(file->userInfo.fdCreator != kHFSAliasCreator) ||
((file->userInfo.fdFlags & kIsAlias) == 0)) {
record_link_badfinderinfo(gp, file->fileID, true);
}
}
li = hash_search(inodeID, slots, slotsUsed, linkInfo);
if (li) {
li->linkCount++;
} else {
entries++;
hash_insert(inodeID, slots, slotsUsed++, linkInfo);
li = hash_search(inodeID, slots, slotsUsed, linkInfo);
}
if (li == NULL) {
result = ENOENT;
goto done;
}
count = li->linkCount - 1;
if ((count % 10) == 0) {
tlist = realloc(li->list, (count + 10) * sizeof(struct HardLinkList));
if (tlist == NULL) {
free(li->list);
li->list = NULL;
result = ENOMEM;
goto done;
} else {
li->list = tlist; for (i = count; i < (count + 10); i++) {
memset(&li->list[i], 0, sizeof(li->list[i]));
}
}
}
if (li->list) {
li->list[count].fileID = linkID;
li->list[count].prev = file->hl_prevLinkID;
li->list[count].next = file->hl_nextLinkID;
}
}
}
if (result == btNotFound)
result = 0;
if (result) {
goto done;
}
ClearMemory(&iterator, sizeof(iterator));
keyp = (HFSPlusCatalogKey*)&iterator.key;
BuildCatalogKey(metadirid, NULL, true, (CatalogKey*)keyp);
btrec.bufferAddress = &rec;
btrec.itemCount = 1;
btrec.itemSize = sizeof(rec);
for (result = BTSearchRecord(fcb, &iterator, kInvalidMRUCacheKey, &btrec, &reclen, &iterator);
result == 0;
result = BTIterateRecord(fcb, kBTreeNextRecord, &iterator, &btrec, &reclen)) {
unsigned char filename[64];
size_t len;
struct IndirectLinkInfo *li = NULL;
if (rec.recordType == kHFSPlusFolderThreadRecord ||
rec.recordType == kHFSPlusFileThreadRecord)
continue;
if (keyp->parentID != metadirid)
break;
if ((isdir && rec.recordType != kHFSPlusFolderRecord) ||
(!isdir && rec.recordType != kHFSPlusFileRecord))
continue;
(void)utf_encodestr(keyp->nodeName.unicode,
keyp->nodeName.length * 2,
filename, &len, sizeof(filename));
filename[len] = 0;
if (strstr((char*)filename, prefixName) != (char*)filename)
continue;
if (isdir) {
inodeID = rec.hfsPlusFolder.folderID;
link_ref_num = 0;
flags = rec.hfsPlusFolder.flags;
li = hash_search(inodeID, slots, slotsUsed, linkInfo);
} else {
inodeID = rec.hfsPlusFile.fileID;
link_ref_num = atol((char*)&filename[prefixlen]);
flags = rec.hfsPlusFile.flags;
li = hash_search(link_ref_num, slots, slotsUsed, linkInfo);
}
if ((flags & kHFSHasLinkChainMask) == 0) {
record_inode_badflags(gp, inodeID, isdir, flags,
flags | kHFSHasLinkChainMask, true);
}
if (li) {
UInt32 first_link_id = 0;
uint32_t linkCount = 0;
result = get_first_link_id(gp, &rec, inodeID, isdir, &first_link_id);
if (result != 0) {
if (fsckGetVerbosity(gp->context) >= kDebugLog)
plog("\tError getting first link ID for inode = %u (result=%d)\n", inodeID, result);
}
result = CheckHardLinkList(gp, inodeID, li->list, li->linkCount, first_link_id);
linkCount = isdir ? rec.hfsPlusFolder.bsdInfo.special.linkCount : rec.hfsPlusFile.bsdInfo.special.linkCount;
if (linkCount != li->linkCount) {
RecordBadLinkCount(gp, inodeID, linkCount, li->linkCount);
}
li->flags |= LINKINFO_CHECK;
entries--;
} else {
RecordOrphanInode(gp, isdir, inodeID);
}
}
if (result == btNotFound) {
result = 0;
}
if (result) {
goto done;
}
if (entries) {
int i, j;
for (i = 0; i < slots; i++) {
if ((linkInfo[i].flags & LINKINFO_INIT) &&
((linkInfo[i].flags & LINKINFO_CHECK) == 0)) {
for (j = 0; j < linkInfo[i].linkCount; j++) {
RecordOrphanLink(gp, isdir, linkInfo[i].list[j].fileID);
}
}
}
}
done:
if (linkInfo) {
int i;
for (i = 0; i < slots; i++) {
if (linkInfo[i].list)
free(linkInfo[i].list);
}
free(linkInfo);
}
return result;
}
int
CheckHardLinks(void *cookie)
{
struct HardLinkInfo *info = (struct HardLinkInfo *)cookie;
SGlobPtr gp;
UInt32 folderID;
SFCB * fcb;
CatalogRecord rec;
HFSPlusCatalogKey * keyp;
BTreeIterator iterator;
FSBufferDescriptor btrec;
UInt16 reclen;
size_t len;
size_t prefixlen;
int result;
unsigned char filename[64];
PrimeBuckets *catBucket;
if (info == NULL)
return (0);
gp = info->globals;
fsckPrint(gp->context, hfsHardLinkCheck);
folderID = info->privDirID;
catBucket = calloc(1, sizeof(PrimeBuckets));
if (catBucket == NULL) {
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("CheckHardLinks: calloc(1, %zu) failed\n", sizeof(PrimeBuckets));
}
result = ENOMEM;
goto exit;
}
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);
ClearMemory(&iterator, sizeof(iterator));
BuildCatalogKey(folderID, NULL, true, (CatalogKey *)keyp);
result = BTSearchRecord(fcb, &iterator, kInvalidMRUCacheKey, &btrec,
&reclen, &iterator);
if ((result != 0) && (result != btNotFound)) {
goto exit;
}
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, sizeof(filename));
filename[len] = '\0';
if ((strstr((char *)filename, HFS_DELETE_PREFIX) == (char *)filename) &&
(fsckGetVerbosity(gp->context) == kDebugLog)) {
RecordOrphanOpenUnlink(gp, folderID, filename);
continue;
}
if (strstr((char *)filename, HFS_INODE_PREFIX) != (char *)filename)
continue;
result = inode_check(gp, catBucket, (CatalogRecord*)&rec, (CatalogKey*)keyp, false);
if (result) {
break;
}
filename[0] = '\0';
}
if (result == btNotFound) {
result = 0;
}
if ((result == 0) && (info->fileBucket != NULL)) {
result = compare_prime_buckets(catBucket, info->fileBucket);
if (result) {
record_link_badchain(gp, false);
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("\tfilelink prime buckets do not match\n");
}
goto exit;
}
}
if (filelink_entry_count) {
int i;
struct filelink_hash *cur;
if (fsckGetVerbosity(gp->context) >= kDebugLog) {
plog("\tCheckHardLinks: found %u pre-Leopard file inodes.\n", filelink_entry_count);
}
for (i = 0; i < FILELINK_HASH_SIZE; i++) {
cur = filelink_head[i];
while (cur) {
if ((cur->found_link_count == 0) ||
(cur->calc_link_count == 0) ||
(cur->found_link_count != cur->calc_link_count)) {
record_link_badchain(gp, false);
goto exit;
}
cur = cur->next;
}
}
}
exit:
if (filelink_entry_count) {
filelink_hash_destroy();
}
if (catBucket)
free(catBucket);
return (result);
}
static int
GetPrivateDir(SGlobPtr gp, CatalogRecord * rec)
{
HFSPlusCatalogKey * keyp;
BTreeIterator iterator;
FSBufferDescriptor btrec;
UInt16 reclen;
int result;
Boolean isHFSPlus;
isHFSPlus = VolumeObjectIsHFSPlus( );
if (!isHFSPlus)
return (-1);
ClearMemory(&iterator, sizeof(iterator));
keyp = (HFSPlusCatalogKey*)&iterator.key;
btrec.bufferAddress = rec;
btrec.itemCount = 1;
btrec.itemSize = sizeof(CatalogRecord);
ClearMemory(&iterator, sizeof(iterator));
CopyMemory(&gMetaDataDirKey, keyp, sizeof(gMetaDataDirKey));
result = BTSearchRecord(gp->calculatedCatalogFCB, &iterator,
kInvalidMRUCacheKey, &btrec, &reclen, &iterator);
return (result);
}
static int
RecordOrphanLink(SGlobPtr gp, Boolean isdir, UInt32 linkID)
{
RepairOrderPtr p;
fsckPrint(gp->context, isdir ? E_OrphanDirLink : E_OrphanFileLink, linkID);
p = AllocMinorRepairOrder(gp, 0);
if (p == NULL)
return ENOMEM;
p->type = isdir ? E_OrphanDirLink : E_OrphanFileLink;
p->parid = linkID;
gp->CatStat |= S_LinkErrRepair;
return 0;
}
static int
RecordOrphanInode(SGlobPtr gp, Boolean isdir, UInt32 inodeID)
{
RepairOrderPtr p;
fsckPrint(gp->context, isdir ? E_OrphanDirInode : E_OrphanFileInode, inodeID);
p = AllocMinorRepairOrder(gp, 0);
if (p == NULL)
return ENOMEM;
p->type = isdir ? E_OrphanDirInode : E_OrphanFileInode;
p->parid = inodeID;
gp->CatStat |= S_LinkErrRepair;
return 0;
}
static int
RecordOrphanOpenUnlink(SGlobPtr gp, UInt32 parID, unsigned char* filename)
{
RepairOrderPtr p;
size_t n;
fsckPrint(gp->context, E_UnlinkedFile, filename);
n = strlen((char *)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;
CopyMemory(filename, &p->name[1], n);
gp->CatStat |= S_UnlinkedFile;
return (noErr);
}
static int
RecordBadHardLinkChainFirst(SGlobPtr gp, UInt32 fileID, UInt32 is, UInt32 shouldbe)
{
RepairOrderPtr p;
char goodstr[16], badstr[16];
fsckPrint(gp->context, E_InvalidLinkChainFirst, fileID);
sprintf(goodstr, "%u", shouldbe);
sprintf(badstr, "%u", is);
fsckPrint(gp->context, E_BadValue, goodstr, badstr);
p = AllocMinorRepairOrder(gp, 0);
if (p == NULL) {
return (ENOMEM);
}
p->type = E_InvalidLinkChainFirst;
p->incorrect = is;
p->correct = shouldbe;
p->hint = 0;
p->parid = fileID; gp->CatStat |= S_LinkErrRepair;
return (0);
}
static int
RecordBadHardLinkPrev(SGlobPtr gp, UInt32 fileID, UInt32 is, UInt32 shouldbe)
{
RepairOrderPtr p;
char goodstr[16], badstr[16];
fsckPrint(gp->context, E_InvalidLinkChainPrev, fileID);
sprintf(goodstr, "%u", shouldbe);
sprintf(badstr, "%u", is);
fsckPrint(gp->context, E_BadValue, goodstr, badstr);
p = AllocMinorRepairOrder(gp, 0);
if (p == NULL)
return (R_NoMem);
p->type = E_InvalidLinkChainPrev;
p->incorrect = is;
p->correct = shouldbe;
p->hint = 0;
p->parid = fileID; gp->CatStat |= S_LinkCount;
return (0);
}
static int
RecordBadHardLinkNext(SGlobPtr gp, UInt32 fileID, UInt32 is, UInt32 shouldbe)
{
RepairOrderPtr p;
char goodstr[16], badstr[16];
fsckPrint(gp->context, E_InvalidLinkChainNext, fileID);
sprintf(goodstr, "%u", shouldbe);
sprintf(badstr, "%u", is);
fsckPrint(gp->context, E_BadValue, goodstr, badstr);
p = AllocMinorRepairOrder(gp, 0);
if (p == NULL)
return (R_NoMem);
p->type = E_InvalidLinkChainNext;
p->incorrect = is;
p->correct = shouldbe;
p->hint = 0;
p->parid = fileID; gp->CatStat |= S_LinkCount;
return (0);
}
static int
RecordBadLinkCount(SGlobPtr gp, UInt32 inodeID, UInt32 is, UInt32 shouldbe)
{
RepairOrderPtr p;
char goodstr[16], badstr[16];
fsckPrint(gp->context, E_InvalidLinkCount, inodeID);
sprintf(goodstr, "%u", shouldbe);
sprintf(badstr, "%u", is);
fsckPrint(gp->context, E_BadValue, goodstr, badstr);
p = AllocMinorRepairOrder(gp, 0);
if (p == NULL)
return (R_NoMem);
p->type = E_InvalidLinkCount;
p->incorrect = is;
p->correct = shouldbe;
p->hint = 0;
p->parid = inodeID; return (0);
}
static void
hash_insert(UInt32 linkID, int totalSlots, int slotsUsed, struct IndirectLinkInfo *linkInfo)
{
int i, last;
i = linkID & (totalSlots - 1);
last = (i + (totalSlots-1)) % totalSlots;
while ((i != last) &&
(linkInfo[i].flags & LINKINFO_INIT) &&
(linkInfo[i].linkID != linkID)) {
i = (i + 1) % totalSlots;
}
if ((linkInfo[i].flags & LINKINFO_INIT) == 0) {
if (linkInfo[i].list) {
plog ("hash: overwriting data! (old:%u, new:%u)\n", linkInfo[i].linkID, linkID);
exit(13);
}
linkInfo[i].flags |= LINKINFO_INIT;
linkInfo[i].linkID = linkID;
linkInfo[i].linkCount = 1;
} else if (linkInfo[i].linkID == linkID) {
plog("hash: duplicate insert! (%d)\n", linkID);
exit(13);
} else {
plog("hash table full (%d entries) \n", slotsUsed);
exit(14);
}
}
static struct IndirectLinkInfo *
hash_search(UInt32 linkID, int totalSlots, int slotsUsed, struct IndirectLinkInfo *linkInfo)
{
int i, last;
int p = 1;
i = linkID & (totalSlots - 1);
last = (i + (slotsUsed-1)) % totalSlots;
while ((i != last) &&
(linkInfo[i].flags & LINKINFO_INIT) &&
(linkInfo[i].linkID != linkID)) {
i = (i + 1) % totalSlots;
++p;
}
if ((linkInfo[i].flags & LINKINFO_INIT) &&
(linkInfo[i].linkID == linkID)) {
return (&linkInfo[i]);
} else {
return (NULL);
}
}