#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libintl.h>
#include <errno.h>
#include <sys/dktp/fdisk.h>
#include <sys/fs/pc_fs.h>
#include <sys/fs/pc_dir.h>
#include <sys/fs/pc_label.h>
#include "getresponse.h"
#include "pcfs_common.h"
#include "fsck_pcfs.h"
extern ClusterContents TheRootDir;
extern off64_t FirstClusterOffset;
extern off64_t PartitionOffset;
extern int32_t BytesPerCluster;
extern int32_t TotalClusters;
extern int32_t LastCluster;
extern int32_t RootDirSize;
extern int32_t FATSize;
extern bpb_t TheBIOSParameterBlock;
extern short FATEntrySize;
extern int RootDirModified;
extern int OkayToRelink;
extern int ReadOnly;
extern int IsFAT32;
extern int Verbose;
static struct pcdir BlankPCDIR;
static CachedCluster *ClusterCache;
static ClusterInfo **InUse;
static int32_t ReservedClusterCount;
static int32_t AllocedClusterCount;
static int32_t FreeClusterCount;
static int32_t BadClusterCount;
static int32_t CachedClusterCount;
int32_t HiddenClusterCount;
int32_t FileClusterCount;
int32_t DirClusterCount;
int32_t HiddenFileCount;
int32_t FileCount;
int32_t DirCount;
static int32_t orphanSizeLookup(int32_t clusterNum);
static void
freeNameInfo(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) {
if (InUse[clusterNum - FIRST_CLUSTER]->path->references > 1) {
InUse[clusterNum - FIRST_CLUSTER]->path->references--;
} else {
free(InUse[clusterNum - FIRST_CLUSTER]->path->fullName);
free(InUse[clusterNum - FIRST_CLUSTER]->path);
}
InUse[clusterNum - FIRST_CLUSTER]->path = NULL;
}
}
static void
printOrphanPath(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
if (InUse[clusterNum - FIRST_CLUSTER]->path != NULL) {
(void) printf(gettext("\nOrphaned allocation units originally "
"allocated to:\n"));
(void) printf("%s\n",
InUse[clusterNum - FIRST_CLUSTER]->path->fullName);
freeNameInfo(clusterNum);
} else {
(void) printf(gettext("\nOrphaned allocation units originally "
"allocated to an unknown file or directory:\n"));
(void) printf(gettext("Orphaned chain begins with allocation "
"unit %d.\n"), clusterNum);
}
}
static void
printOrphanSize(int32_t clusterNum)
{
int32_t size = orphanSizeLookup(clusterNum);
if (size > 0) {
(void) printf(gettext("%d bytes in the orphaned chain of "
"allocation units.\n"), size);
if (Verbose) {
(void) printf(gettext("[Starting at allocation "
"unit %d]\n"), clusterNum);
}
}
}
static void
printOrphanInfo(int32_t clusterNum)
{
printOrphanPath(clusterNum);
printOrphanSize(clusterNum);
}
static bool
askAboutFreeing(int32_t clusterNum)
{
if (!OkayToRelink)
printOrphanInfo(clusterNum);
preenBail("Need user confirmation to free orphaned chain.\n");
(void) printf(
gettext("Free the allocation units in the orphaned chain ? "
"(y/n) "));
if (AlwaysYes)
return (true);
if (AlwaysNo)
return (false);
return (yes());
}
static bool
askAboutRelink(int32_t clusterNum)
{
printOrphanInfo(clusterNum);
preenBail("Need user confirmation to re-link orphaned chain.\n");
(void) printf(gettext("Re-link orphaned chain into file system ? "
"(y/n) "));
if (AlwaysYes)
return (true);
if (AlwaysNo)
return (false);
return (yes());
}
static int
isHidden(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (0);
if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
return (0);
return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_HIDDEN);
}
static int
isInUse(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (0);
return ((InUse[clusterNum - FIRST_CLUSTER] != NULL) &&
(InUse[clusterNum - FIRST_CLUSTER]->dirent != NULL));
}
static CachedCluster *
findClusterCacheEntry(int32_t clusterNum)
{
CachedCluster *loop = ClusterCache;
while (loop != NULL) {
if (loop->clusterNum == clusterNum)
return (loop);
loop = loop->next;
}
return (NULL);
}
static uchar_t *
findClusterDataInTheCache(int32_t clusterNum)
{
CachedCluster *loop = ClusterCache;
while (loop) {
if (loop->clusterNum == clusterNum)
return (loop->clusterData.bytes);
loop = loop->next;
}
return (NULL);
}
static uchar_t *
addToCache(int32_t clusterNum, uchar_t *buf, int32_t *datasize)
{
CachedCluster *new;
uchar_t *cp;
if ((new = (CachedCluster *)malloc(sizeof (CachedCluster))) == NULL) {
perror(gettext("No memory for cached cluster info"));
return (buf);
}
new->clusterNum = clusterNum;
new->modified = 0;
if ((cp = (uchar_t *)calloc(1, BytesPerCluster)) == NULL) {
perror(gettext("No memory for cached copy of cluster"));
free(new);
return (buf);
}
(void) memcpy(cp, buf, *datasize);
new->clusterData.bytes = cp;
if (Verbose) {
(void) fprintf(stderr,
gettext("Allocation unit %d cached.\n"), clusterNum);
}
if (ClusterCache == NULL) {
ClusterCache = new;
new->next = NULL;
} else if (new->clusterNum < ClusterCache->clusterNum) {
new->next = ClusterCache;
ClusterCache = new;
} else {
CachedCluster *loop = ClusterCache;
CachedCluster *trailer = NULL;
while (loop && new->clusterNum > loop->clusterNum) {
trailer = loop;
loop = loop->next;
}
trailer->next = new;
if (loop) {
new->next = loop;
} else {
new->next = NULL;
}
}
CachedClusterCount++;
return (new->clusterData.bytes);
}
static int
seekCluster(int fd, int32_t clusterNum)
{
off64_t seekto;
int saveError;
seekto = FirstClusterOffset +
((off64_t)clusterNum - FIRST_CLUSTER) * BytesPerCluster;
if (lseek64(fd, seekto, SEEK_SET) != seekto) {
saveError = errno;
(void) fprintf(stderr,
gettext("Seek to Allocation unit #%d failed: "),
clusterNum);
(void) fprintf(stderr, strerror(saveError));
(void) fprintf(stderr, "\n");
return (0);
}
return (1);
}
static int
getCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize)
{
static uchar_t *clusterBuffer = NULL;
int saveError;
int try;
*datasize = 0;
*data = NULL;
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (RDCLUST_BADINPUT);
if (clusterBuffer == NULL &&
(clusterBuffer = (uchar_t *)malloc(BytesPerCluster)) == NULL) {
perror(gettext("No memory for a cluster data buffer"));
return (RDCLUST_MEMERR);
}
for (try = 0; try < RDCLUST_MAX_RETRY; try++) {
if (!seekCluster(fd, clusterNum))
return (RDCLUST_FAIL);
if ((*datasize = read(fd, clusterBuffer, BytesPerCluster)) ==
BytesPerCluster) {
*data = clusterBuffer;
return (RDCLUST_GOOD);
}
}
if (*datasize >= 0) {
*data = clusterBuffer;
(void) fprintf(stderr,
gettext("Short read of allocation unit #%d\n"), clusterNum);
} else {
saveError = errno;
(void) fprintf(stderr, "Allocation unit %d:", clusterNum);
(void) fprintf(stderr, strerror(saveError));
(void) fprintf(stderr, "\n");
}
return (RDCLUST_FAIL);
}
static void
writeCachedCluster(int fd, CachedCluster *clustInfo)
{
ssize_t bytesWritten;
if (ReadOnly)
return;
if (Verbose)
(void) fprintf(stderr,
gettext("Allocation unit %d modified.\n"),
clustInfo->clusterNum);
if (seekCluster(fd, clustInfo->clusterNum) == 0)
return;
if ((bytesWritten = write(fd, clustInfo->clusterData.bytes,
BytesPerCluster)) != BytesPerCluster) {
if (bytesWritten < 0) {
perror(gettext("Failed to write modified "
"allocation unit"));
} else {
(void) fprintf(stderr,
gettext("Short write of allocation unit %d\n"),
clustInfo->clusterNum);
}
(void) close(fd);
exit(13);
}
}
#define CHUNKSIZE 1024
static ClusterInfo *pool;
static ClusterInfo *
newClusterInfo(void)
{
ClusterInfo *ret;
if (pool == NULL) {
int i;
pool = (ClusterInfo *)malloc(sizeof (ClusterInfo) * CHUNKSIZE);
if (pool == NULL) {
perror(
gettext("Out of memory for cluster information"));
exit(9);
}
for (i = 0; i < CHUNKSIZE - 1; i++)
pool[i].nextfree = &pool[i+1];
pool[CHUNKSIZE-1].nextfree = NULL;
}
ret = pool;
pool = pool->nextfree;
memset(ret, 0, sizeof (*ret));
return (ret);
}
static ClusterInfo *
cloneClusterInfo(int32_t clusterNum)
{
ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER];
if (cl->refcnt > 1) {
ClusterInfo *newCl = newClusterInfo();
cl->refcnt--;
*newCl = *cl;
newCl->refcnt = 1;
if (newCl->path)
newCl->path->references++;
InUse[clusterNum - FIRST_CLUSTER] = newCl;
}
return (InUse[clusterNum - FIRST_CLUSTER]);
}
static void
updateFlags(int32_t clusterNum, int newflags)
{
ClusterInfo *cl = InUse[clusterNum - FIRST_CLUSTER];
if (cl->flags != newflags && cl->refcnt > 1)
cl = cloneClusterInfo(clusterNum);
cl->flags = newflags;
}
static void
freeClusterInfo(ClusterInfo *old)
{
if (--old->refcnt <= 0) {
if (old->path && --old->path->references <= 0) {
free(old->path->fullName);
free(old->path);
}
old->nextfree = pool;
pool = old;
}
}
static int
allocInUse(int32_t clusterNum, ClusterInfo **template)
{
ClusterInfo *newCl;
if (InUse[clusterNum - FIRST_CLUSTER] != NULL)
return (CLINFO_PREVIOUSLY_ALLOCED);
if (template != NULL && *template != NULL)
newCl = *template;
else {
newCl = newClusterInfo();
if (template)
*template = newCl;
}
InUse[clusterNum - FIRST_CLUSTER] = newCl;
newCl->refcnt++;
return (CLINFO_NEWLY_ALLOCED);
}
static void
markFree(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
if (InUse[clusterNum - FIRST_CLUSTER]) {
if (InUse[clusterNum - FIRST_CLUSTER]->saved)
free(InUse[clusterNum - FIRST_CLUSTER]->saved);
freeClusterInfo(InUse[clusterNum - FIRST_CLUSTER]);
InUse[clusterNum - FIRST_CLUSTER] = NULL;
}
}
static void
markOrphan(int fd, int32_t clusterNum, struct pcdir *dp)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
(void) markInUse(fd, clusterNum, dp, NULL, 0, VISIBLE, NULL);
if (InUse[clusterNum - FIRST_CLUSTER] != NULL)
updateFlags(clusterNum,
InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_ORPHAN);
}
static void
markBad(int32_t clusterNum, uchar_t *recovered, int32_t recoveredLen)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
(void) allocInUse(clusterNum, NULL);
if (recoveredLen) {
(void) cloneClusterInfo(clusterNum);
InUse[clusterNum - FIRST_CLUSTER]->saved = recovered;
}
updateFlags(clusterNum,
InUse[clusterNum - FIRST_CLUSTER]->flags | CLINFO_BAD);
BadClusterCount++;
if (Verbose)
(void) fprintf(stderr,
gettext("Allocation unit %d marked bad.\n"), clusterNum);
}
static void
clearOrphan(int32_t c)
{
if (c < FIRST_CLUSTER || c > LastCluster)
return;
if (InUse[c - FIRST_CLUSTER] != NULL)
updateFlags(c,
InUse[c - FIRST_CLUSTER]->flags & ~CLINFO_ORPHAN);
}
static void
clearInUse(int32_t c)
{
ClusterInfo **clp;
if (c < FIRST_CLUSTER || c > LastCluster)
return;
clp = &InUse[c - FIRST_CLUSTER];
if (*clp != NULL) {
freeClusterInfo(*clp);
*clp = NULL;
}
}
static void
clearAllClusters_InUse()
{
int32_t cc;
for (cc = FIRST_CLUSTER; cc < LastCluster; cc++) {
clearInUse(cc);
}
}
static void
makeUseTable(void)
{
if (InUse != NULL) {
clearAllClusters_InUse();
return;
}
if ((InUse = (ClusterInfo **)
calloc(TotalClusters, sizeof (ClusterInfo *))) == NULL) {
perror(gettext("No memory for internal table"));
exit(9);
}
}
static void
countClusters(void)
{
int32_t c;
BadClusterCount = HiddenClusterCount =
AllocedClusterCount = FreeClusterCount = 0;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
if (badInFAT(c)) {
BadClusterCount++;
} else if (isMarkedBad(c)) {
BadClusterCount++;
markBadInFAT(c);
} else if (isHidden(c)) {
HiddenClusterCount++;
} else if (isInUse(c)) {
AllocedClusterCount++;
} else {
FreeClusterCount++;
}
}
}
static void
summarizeFAT(int fd)
{
int32_t c;
ClusterInfo *tmpl = NULL;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
if (!freeInFAT(c) && !badInFAT(c) && !reservedInFAT(c) &&
!isInUse(c)) {
(void) markInUse(fd, c, &BlankPCDIR, NULL, 0, VISIBLE,
&tmpl);
}
}
}
static void
getReadyToSearch(int fd)
{
getFAT(fd);
if (!IsFAT32)
getRootDirectory(fd);
}
static char PathName[MAXPATHLEN];
static void
summarize(int fd, int includeFAT)
{
struct pcdir *ignorep1, *ignorep2 = NULL;
int32_t ignore32;
char ignore;
int pathlen;
ReservedClusterCount = 0;
AllocedClusterCount = 0;
HiddenClusterCount = 0;
FileClusterCount = 0;
FreeClusterCount = 0;
DirClusterCount = 0;
BadClusterCount = 0;
HiddenFileCount = 0;
FileCount = 0;
DirCount = 0;
ignorep1 = ignorep2 = NULL;
ignore = '\0';
PathName[0] = '\0';
pathlen = 0;
getReadyToSearch(fd);
if (!IsFAT32) {
traverseFromRoot(fd, 0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL,
ignore, &ignorep1, &ignore32, &ignorep2, PathName,
&pathlen);
} else {
DirCount++;
traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
0, PCFS_VISIT_SUBDIRS, PCFS_TRAVERSE_ALL, ignore,
&ignorep1, &ignore32, &ignorep2, PathName, &pathlen);
}
if (includeFAT)
summarizeFAT(fd);
countClusters();
}
int
isMarkedBad(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (0);
if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
return (0);
return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_BAD);
}
static int
isMarkedOrphan(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (0);
if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
return (0);
return (InUse[clusterNum - FIRST_CLUSTER]->flags & CLINFO_ORPHAN);
}
static void
orphanChain(int fd, int32_t c, struct pcdir *ndp)
{
ClusterInfo *tmpl = NULL;
if (c < FIRST_CLUSTER || c > LastCluster)
return;
clearInUse(c);
markOrphan(fd, c, ndp);
c = nextInChain(c);
while (c != 0) {
clearInUse(c);
clearOrphan(c);
(void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, &tmpl);
c = nextInChain(c);
}
}
static int32_t
findAFreeCluster(int32_t startAt)
{
int32_t look = startAt;
for (;;) {
if (freeInFAT(look)) {
break;
}
if (look == LastCluster)
look = FIRST_CLUSTER;
else
look++;
if (look == startAt)
break;
}
if (look != startAt)
return (look);
else
return (0);
}
static void
setEndOfDirectory(struct pcdir *dp)
{
dp->pcd_filename[0] = PCD_UNUSED;
}
static void
emergencyEndOfDirectory(int fd, int32_t secondToLast)
{
ClusterContents dirdata;
int32_t dirdatasize = 0;
if (readCluster(fd, secondToLast, &(dirdata.bytes), &dirdatasize,
RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
(void) fprintf(stderr,
gettext("Unable to read allocation unit %d.\n"),
secondToLast);
(void) fprintf(stderr,
gettext("Cannot allocate a new allocation unit to hold an"
" end-of-directory marker.\nCannot access allocation unit"
" to overwrite existing directory entry with\nthe marker."
" Needed directory truncation has failed. Giving up.\n"));
(void) close(fd);
exit(11);
}
setEndOfDirectory(dirdata.dirp);
markClusterModified(secondToLast);
}
static void
makeNewEndOfDirectory(struct pcdir *entry, int32_t secondToLast,
int32_t newCluster, ClusterContents *newData)
{
setEndOfDirectory(newData->dirp);
markClusterModified(newCluster);
if (secondToLast == 0) {
updateDirEnt_Start(entry, newCluster);
} else {
writeFATEntry(secondToLast, newCluster);
}
markLastInFAT(newCluster);
}
static void
createNewEndOfDirectory(int fd, struct pcdir *entry, int32_t secondToLast)
{
ClusterContents dirdata;
int32_t dirdatasize = 0;
int32_t freeCluster;
if (((freeCluster = findAFreeCluster(secondToLast)) != 0)) {
if (readCluster(fd, freeCluster, &(dirdata.bytes),
&dirdatasize, RDCLUST_DO_CACHE) == RDCLUST_GOOD) {
if (Verbose) {
(void) fprintf(stderr,
gettext("Grabbed allocation unit #%d "
"for truncated\ndirectory's new end "
"of directory.\n"), freeCluster);
}
makeNewEndOfDirectory(entry, secondToLast,
freeCluster, &dirdata);
return;
}
}
if (secondToLast == 0) {
if (freeCluster == 0) {
(void) fprintf(stderr, gettext("File system full.\n"));
} else {
(void) fprintf(stderr,
gettext("Unable to read allocation unit %d.\n"),
freeCluster);
}
(void) fprintf(stderr,
gettext("Cannot allocate a new allocation unit to hold "
"an end-of-directory marker.\nNo existing directory "
"entries can be overwritten with the marker,\n"
"the only unit allocated to the directory is "
"inaccessible.\nNeeded directory truncation has failed. "
"Giving up.\n"));
(void) close(fd);
exit(11);
}
emergencyEndOfDirectory(fd, secondToLast);
}
static int64_t
truncAtCluster(int fd, struct pcdir *entry, int32_t cluster)
{
uint32_t oldSize, newSize;
int32_t prev, count, follow;
int dir = (entry->pcd_attr & PCA_DIR);
prev = 0; count = 0;
follow = extractStartCluster(entry);
while (follow != cluster && follow >= FIRST_CLUSTER &&
follow <= LastCluster) {
prev = follow;
count++;
follow = nextInChain(follow);
}
if (follow != cluster) {
return (-1);
}
if (Verbose) {
(void) fprintf(stderr,
gettext("Chain truncation at unit #%d\n"), cluster);
}
if (!dir) {
oldSize = extractSize(entry);
newSize = count *
TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector;
if (newSize == 0)
updateDirEnt_Start(entry, 0);
} else {
newSize = 0;
}
updateDirEnt_Size(entry, newSize);
if (dir) {
createNewEndOfDirectory(fd, entry, prev);
} else if (prev != 0) {
markLastInFAT(prev);
}
if (dir) {
if ((follow = nextInChain(follow)) != 0)
return (1);
else
return (0);
}
return ((int64_t)oldSize - (int64_t)newSize);
}
static struct pcdir *
updateOrphanedChainMetadata(int fd, struct pcdir *dp, int32_t endCluster,
int isBad)
{
struct pcdir *ndp = NULL;
int64_t remainder;
char *newName = NULL;
int chosenName;
int dir = (dp->pcd_attr & PCA_DIR);
remainder = truncAtCluster(fd, dp, endCluster);
if (remainder < 0)
return (ndp);
if (!dir && isBad) {
remainder -= TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector;
if (remainder < 0)
remainder = 0;
}
if ((remainder != 0) &&
((newName = nextAvailableCHKName(&chosenName)) != NULL) &&
((ndp = newDirEnt(dp)) != NULL)) {
if (Verbose) {
if (dir)
(void) fprintf(stderr,
gettext("Orphaned directory chain.\n"));
else
(void) fprintf(stderr,
gettext("Orphaned chain, %u bytes.\n"),
(uint32_t)remainder);
}
if (!dir)
updateDirEnt_Size(ndp, (uint32_t)remainder);
if (isBad)
updateDirEnt_Start(ndp, nextInChain(endCluster));
else
updateDirEnt_Start(ndp, endCluster);
updateDirEnt_Name(ndp, newName);
addEntryToCHKList(chosenName);
}
return (ndp);
}
void
splitChain(int fd, struct pcdir *dp, int32_t problemCluster,
struct pcdir **newdp, int32_t *orphanStart)
{
struct pcdir *ndp = NULL;
int isBad = isMarkedBad(problemCluster);
ndp = updateOrphanedChainMetadata(fd, dp, problemCluster, isBad);
*newdp = ndp;
clearInUse(problemCluster);
if (isBad) {
clearOrphan(problemCluster);
*orphanStart = nextInChain(problemCluster);
orphanChain(fd, *orphanStart, ndp);
markBadInFAT(problemCluster);
} else {
*orphanStart = problemCluster;
orphanChain(fd, problemCluster, ndp);
}
}
static void
freeOrphan(int32_t c)
{
int32_t n;
if (InUse[c - FIRST_CLUSTER]->dirent != NULL)
free(InUse[c - FIRST_CLUSTER]->dirent);
do {
n = nextInChain(c);
markFreeInFAT(c);
markFree(c);
c = n;
} while (c != 0);
}
static void
redoInUse(int fd, int32_t c, struct pcdir *ndp, int32_t stopAtCluster)
{
while (c && c != stopAtCluster) {
clearInUse(c);
(void) markInUse(fd, c, ndp, NULL, 0, VISIBLE, NULL);
c = nextInChain(c);
}
}
static struct pcdir *
orphanDirEntLookup(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (NULL);
if (isInUse(clusterNum)) {
return (InUse[clusterNum - FIRST_CLUSTER]->dirent);
} else {
return (NULL);
}
}
static int32_t
orphanSizeLookup(int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (-1);
if (isInUse(clusterNum)) {
return (extractSize(InUse[clusterNum - FIRST_CLUSTER]->dirent));
} else {
return (-1);
}
}
static void
linkOrphan(int fd, int32_t start)
{
struct pcdir *newEnt = NULL;
struct pcdir *dp;
if ((dp = orphanDirEntLookup(start)) != NULL) {
newEnt = addRootDirEnt(fd, dp);
} else {
(void) printf(gettext("Re-link of orphaned chain failed."
" Allocation units will remain orphaned.\n"));
}
redoInUse(fd, start, newEnt, 0);
}
static void
relinkCreatedOrphans(int fd)
{
int32_t c;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
if (isMarkedOrphan(c)) {
if (OkayToRelink && askAboutRelink(c)) {
linkOrphan(fd, c);
} else if (askAboutFreeing(c)) {
freeOrphan(c);
}
clearOrphan(c);
}
}
}
static void
relinkFATOrphans(int fd)
{
struct pcdir *ndp = NULL;
int32_t cc, c, n;
int32_t bpc, newSize;
char *newName;
int chosenName;
for (c = FIRST_CLUSTER; c < LastCluster; c++) {
if (freeInFAT(c) || badInFAT(c) ||
reservedInFAT(c) || isInUse(c))
continue;
cc = 1;
n = c;
while (n = nextInChain(n))
cc++;
bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector;
newSize = cc * bpc;
if (((newName = nextAvailableCHKName(&chosenName)) != NULL) &&
((ndp = newDirEnt(NULL)) != NULL)) {
updateDirEnt_Size(ndp, newSize);
updateDirEnt_Start(ndp, c);
updateDirEnt_Name(ndp, newName);
addEntryToCHKList(chosenName);
}
orphanChain(fd, c, ndp);
}
relinkCreatedOrphans(fd);
}
static void
relinkOrphans(int fd)
{
relinkCreatedOrphans(fd);
relinkFATOrphans(fd);
}
static void
checkForFATLoop(int32_t clusterNum)
{
int32_t prev = clusterNum;
int32_t follow;
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
follow = nextInChain(clusterNum);
while (follow != clusterNum && follow >= FIRST_CLUSTER &&
follow <= LastCluster) {
prev = follow;
follow = nextInChain(follow);
}
if (follow == clusterNum) {
markLastInFAT(prev);
}
}
static void
sharedChainError(int fd, int32_t clusterNum, struct pcdir *badEntry)
{
if (Verbose)
(void) fprintf(stderr,
gettext("Truncating chain due to duplicate allocation of "
"unit %d.\n"), clusterNum);
(void) truncAtCluster(fd, badEntry, clusterNum);
checkForFATLoop(clusterNum);
}
void
truncChainWithBadCluster(int fd, struct pcdir *dp, int32_t startCluster)
{
struct pcdir *orphanEntry;
int32_t orphanStartCluster;
int32_t c = startCluster;
while (c != 0) {
if (isMarkedBad(c)) {
splitChain(fd, dp, c,
&orphanEntry, &orphanStartCluster);
if (orphanEntry == NULL)
break;
c = orphanStartCluster;
dp = orphanEntry;
continue;
}
c = nextInChain(c);
}
}
int32_t
nextInChain(int32_t currentCluster)
{
int32_t nextCluster;
if (currentCluster < FIRST_CLUSTER || currentCluster > LastCluster)
return (0);
nextCluster = readFATEntry(currentCluster);
if (nextCluster < FIRST_CLUSTER || nextCluster > LastCluster)
return (0);
return (nextCluster);
}
int32_t
findImpactedCluster(struct pcdir *modified)
{
CachedCluster *loop;
if (!IsFAT32 && ((uchar_t *)modified >= TheRootDir.bytes) &&
((uchar_t *)modified < TheRootDir.bytes + RootDirSize))
return (FAKE_ROOTDIR_CLUST);
loop = ClusterCache;
while (loop) {
if (((uchar_t *)modified >= loop->clusterData.bytes) &&
((uchar_t *)modified <
(loop->clusterData.bytes + BytesPerCluster))) {
return (loop->clusterNum);
}
loop = loop->next;
}
return (0);
}
void
writeClusterMods(int fd)
{
CachedCluster *loop = ClusterCache;
while (loop) {
if (loop->modified)
writeCachedCluster(fd, loop);
loop = loop->next;
}
}
void
squirrelPath(struct nameinfo *pathInfo, int32_t clusterNum)
{
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
if (InUse[clusterNum - FIRST_CLUSTER] == NULL)
return;
InUse[clusterNum - FIRST_CLUSTER]->path = pathInfo;
}
int
markInUse(int fd, int32_t clusterNum, struct pcdir *referencer, struct
pcdir *longRef, int32_t longStartCluster, int isHiddenFile,
ClusterInfo **template)
{
int alreadyMarked;
ClusterInfo *cl;
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return (CLINFO_NEWLY_ALLOCED);
alreadyMarked = allocInUse(clusterNum, template);
if ((alreadyMarked == CLINFO_PREVIOUSLY_ALLOCED) &&
(isInUse(clusterNum))) {
sharedChainError(fd, clusterNum, referencer);
return (CLINFO_PREVIOUSLY_ALLOCED);
}
cl = InUse[clusterNum - FIRST_CLUSTER];
if (cl->refcnt <= 1 || cl->dirent != referencer ||
cl->longent != longRef ||
cl->longEntStartClust != longStartCluster) {
if (cl->refcnt > 1)
cl = cloneClusterInfo(clusterNum);
cl->dirent = referencer;
cl->longent = longRef;
cl->longEntStartClust = longStartCluster;
if (isHiddenFile)
cl->flags |= CLINFO_HIDDEN;
if (template)
*template = cl;
}
return (CLINFO_NEWLY_ALLOCED);
}
void
markClusterModified(int32_t clusterNum)
{
CachedCluster *c;
if (clusterNum == FAKE_ROOTDIR_CLUST) {
RootDirModified = 1;
return;
}
if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster)
return;
if (c = findClusterCacheEntry(clusterNum)) {
c->modified = 1;
} else {
(void) fprintf(stderr,
gettext("Unexpected internal error: "
"Missing cache entry [%d]\n"), clusterNum);
exit(10);
}
}
int
readCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize,
int shouldCache)
{
uchar_t *newBuf;
int rv;
*data = NULL;
if ((*data = findClusterDataInTheCache(clusterNum)) != NULL) {
*datasize = BytesPerCluster;
return (RDCLUST_GOOD);
}
rv = getCluster(fd, clusterNum, &newBuf, datasize);
if (rv != RDCLUST_GOOD)
return (rv);
if (shouldCache == 0) {
*data = newBuf;
return (rv);
}
if (*datasize > 0)
*data = addToCache(clusterNum, newBuf, datasize);
return (rv);
}
void
findBadClusters(int fd)
{
int32_t clusterCount;
int32_t datasize;
uchar_t *data;
BadClusterCount = 0;
makeUseTable();
(void) printf(gettext("** Scanning allocation units\n"));
for (clusterCount = FIRST_CLUSTER;
clusterCount < LastCluster; clusterCount++) {
if (readCluster(fd, clusterCount,
&data, &datasize, RDCLUST_DONT_CACHE) < 0) {
if (Verbose)
(void) fprintf(stderr,
gettext(
"\nUnreadable allocation unit %d.\n"),
clusterCount);
markBad(clusterCount, data, datasize);
}
if (!Verbose && clusterCount % 1000 == 0)
(void) printf(".");
}
(void) printf(gettext("..done\n"));
}
void
scanAndFixMetadata(int fd)
{
makeUseTable();
getReadyToSearch(fd);
createCHKNameList(fd);
(void) printf(gettext("** Scanning file system meta-data\n"));
summarize(fd, NO_FAT_IN_SUMMARY);
if (Verbose)
printSummary(stderr);
(void) printf(gettext("** Correcting any meta-data discrepancies\n"));
relinkCreatedOrphans(fd);
makeUseTable();
summarize(fd, INCLUDE_FAT_IN_SUMMARY);
relinkOrphans(fd);
}
void
printSummary(FILE *outDest)
{
(void) fprintf(outDest,
gettext("%llu bytes.\n"),
(uint64_t)
TotalClusters * TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector);
(void) fprintf(outDest,
gettext("%llu bytes in bad sectors.\n"),
(uint64_t)
BadClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector);
(void) fprintf(outDest,
gettext("%llu bytes in %d directories.\n"),
(uint64_t)
DirClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector, DirCount);
if (HiddenClusterCount) {
(void) fprintf(outDest,
gettext("%llu bytes in %d hidden files.\n"),
(uint64_t)HiddenClusterCount *
TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector,
HiddenFileCount);
}
(void) fprintf(outDest,
gettext("%llu bytes in %d files.\n"),
(uint64_t)
FileClusterCount * TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector, FileCount);
(void) fprintf(outDest,
gettext("%llu bytes free.\n"), (uint64_t)FreeClusterCount *
TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector);
(void) fprintf(outDest,
gettext("%d bytes per allocation unit.\n"),
TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector);
(void) fprintf(outDest,
gettext("%d total allocation units.\n"), TotalClusters);
if (ReservedClusterCount)
(void) fprintf(outDest,
gettext("%d reserved allocation units.\n"),
ReservedClusterCount);
(void) fprintf(outDest,
gettext("%d available allocation units.\n"), FreeClusterCount);
}