#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libintl.h>
#include <ctype.h>
#include <time.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/byteorder.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 "pcfs_common.h"
#include "fsck_pcfs.h"
extern int32_t HiddenClusterCount;
extern int32_t FileClusterCount;
extern int32_t DirClusterCount;
extern int32_t HiddenFileCount;
extern int32_t LastCluster;
extern int32_t FileCount;
extern int32_t BadCount;
extern int32_t DirCount;
extern int32_t FATSize;
extern off64_t PartitionOffset;
extern bpb_t TheBIOSParameterBlock;
extern int ReadOnly;
extern int IsFAT32;
extern int Verbose;
static uchar_t *CHKsList = NULL;
ClusterContents TheRootDir;
int32_t RootDirSize;
int RootDirModified;
int OkayToRelink = 1;
static int
hasCHKName(struct pcdir *dp)
{
return (dp->pcd_filename[CHKNAME_F] == 'F' &&
dp->pcd_filename[CHKNAME_I] == 'I' &&
dp->pcd_filename[CHKNAME_L] == 'L' &&
dp->pcd_filename[CHKNAME_E] == 'E' &&
isdigit(dp->pcd_filename[CHKNAME_THOUSANDS]) &&
isdigit(dp->pcd_filename[CHKNAME_HUNDREDS]) &&
isdigit(dp->pcd_filename[CHKNAME_TENS]) &&
isdigit(dp->pcd_filename[CHKNAME_ONES]) &&
dp->pcd_ext[CHKNAME_C] == 'C' &&
dp->pcd_ext[CHKNAME_H] == 'H' &&
dp->pcd_ext[CHKNAME_K] == 'K');
}
void
addEntryToCHKList(int chkNumber)
{
if (chkNumber < 0 || chkNumber > MAXCHKVAL)
return;
CHKsList[chkNumber / NBBY] |= (1 << (chkNumber % NBBY));
}
static void
addToCHKList(struct pcdir *dp)
{
int chknum;
chknum = 1000 * (dp->pcd_filename[CHKNAME_THOUSANDS] - '0');
chknum += 100 * (dp->pcd_filename[CHKNAME_HUNDREDS] - '0');
chknum += 10 * (dp->pcd_filename[CHKNAME_TENS] - '0');
chknum += (dp->pcd_filename[CHKNAME_ONES] - '0');
addEntryToCHKList(chknum);
}
static int
inUseCHKName(int chkNumber)
{
return (CHKsList[chkNumber / NBBY] & (1 << (chkNumber % NBBY)));
}
static void
appendToPath(struct pcdir *dp, char *thePath, int *theLen)
{
int i = 0;
if (thePath == NULL)
return;
if (*theLen < MAXPATHLEN)
*(thePath + (*theLen)++) = '/';
while (*theLen < MAXPATHLEN && i < PCFNAMESIZE) {
if ((dp->pcd_filename[i] == ' ') ||
!(pc_validchar(dp->pcd_filename[i])))
break;
*(thePath + (*theLen)++) = dp->pcd_filename[i++];
}
if ((dp->pcd_ext[i] == ' ') || ((*theLen) >= MAXPATHLEN) ||
(!(pc_validchar(dp->pcd_ext[i]))))
return;
*(thePath + (*theLen)++) = '.';
i = 0;
while ((*theLen < MAXPATHLEN) && (i < PCFEXTSIZE)) {
if ((dp->pcd_ext[i] == ' ') || !(pc_validchar(dp->pcd_ext[i])))
break;
*(thePath + (*theLen)++) = dp->pcd_ext[i++];
}
}
static void
printName(FILE *outDest, struct pcdir *dp)
{
int i;
for (i = 0; i < PCFNAMESIZE; i++) {
if ((dp->pcd_filename[i] == ' ') ||
!(pc_validchar(dp->pcd_filename[i])))
break;
(void) fprintf(outDest, "%c", dp->pcd_filename[i]);
}
(void) fprintf(outDest, ".");
for (i = 0; i < PCFEXTSIZE; i++) {
if (!(pc_validchar(dp->pcd_ext[i])))
break;
(void) fprintf(outDest, "%c", dp->pcd_ext[i]);
}
}
static int
sanityCheckSize(int fd, struct pcdir *dp, int32_t actualClusterCount,
int isDir, int32_t startCluster, struct nameinfo *fullPathName,
struct pcdir **orphanEntry)
{
uint32_t sizeFromDir;
int32_t ignorei = 0;
int64_t bpc;
bpc = TheBIOSParameterBlock.bpb.sectors_per_cluster *
TheBIOSParameterBlock.bpb.bytes_per_sector;
sizeFromDir = extractSize(dp);
if (isDir) {
if (sizeFromDir == 0)
return (SIZE_MATCHED);
} else {
if ((sizeFromDir > ((actualClusterCount - 1) * bpc)) &&
(sizeFromDir <= (actualClusterCount * bpc)))
return (SIZE_MATCHED);
}
if (fullPathName != NULL) {
fullPathName->references++;
(void) fprintf(stderr, "%s\n", fullPathName->fullName);
}
squirrelPath(fullPathName, startCluster);
(void) fprintf(stderr,
gettext("Truncating chain due to incorrect size "
"in directory. Size from directory = %u bytes,\n"), sizeFromDir);
if (actualClusterCount == 0) {
(void) fprintf(stderr,
gettext("Zero bytes are allocated to the file.\n"));
} else {
(void) fprintf(stderr,
gettext("Allocated size in range %llu - %llu bytes.\n"),
((actualClusterCount - 1) * bpc) + 1,
(actualClusterCount * bpc));
}
splitChain(fd, dp, startCluster, orphanEntry, &ignorei);
return (TRUNCATED);
}
static int
noteUsage(int fd, int32_t startAt, struct pcdir *dp, struct pcdir *lp,
int32_t longEntryStartCluster, int isHidden, int isDir,
struct nameinfo *fullPathName)
{
struct pcdir *orphanEntry;
int32_t chain = startAt;
int32_t count = 0;
int savePathNextIteration = 0;
int haveBad = 0;
ClusterInfo *tmpl = NULL;
while ((chain >= FIRST_CLUSTER) && (chain <= LastCluster)) {
if ((markInUse(fd, chain, dp, lp, longEntryStartCluster,
isHidden ? HIDDEN : VISIBLE, &tmpl))
!= CLINFO_NEWLY_ALLOCED)
break;
count++;
if (savePathNextIteration == 1) {
savePathNextIteration = 0;
if (fullPathName != NULL)
fullPathName->references++;
squirrelPath(fullPathName, chain);
}
if (isMarkedBad(chain)) {
haveBad = 1;
savePathNextIteration = 1;
}
if (isHidden)
HiddenClusterCount++;
else if (isDir)
DirClusterCount++;
else
FileClusterCount++;
chain = nextInChain(chain);
}
if (sanityCheckSize(fd, dp, count, isDir, startAt,
fullPathName, &orphanEntry) == TRUNCATED) {
if (haveBad > 0) {
truncChainWithBadCluster(fd, orphanEntry, startAt);
}
haveBad = 0;
}
return (haveBad);
}
static void
storeInfoAboutEntry(int fd, struct pcdir *dp, struct pcdir *ldp, int depth,
int32_t longEntryStartCluster, char *fullPath, int *fullLen)
{
struct nameinfo *pathCopy;
int32_t start;
int haveBad;
int hidden = (dp->pcd_attr & PCA_HIDDEN || dp->pcd_attr & PCA_SYSTEM);
int dir = (dp->pcd_attr & PCA_DIR);
int i;
if (hidden)
HiddenFileCount++;
else if (dir)
DirCount++;
else
FileCount++;
appendToPath(dp, fullPath, fullLen);
if ((pathCopy =
(struct nameinfo *)malloc(sizeof (struct nameinfo))) != NULL) {
if ((pathCopy->fullName =
(char *)malloc(*fullLen + 1)) != NULL) {
pathCopy->references = 0;
(void) strncpy(pathCopy->fullName, fullPath, *fullLen);
pathCopy->fullName[*fullLen] = '\0';
} else {
free(pathCopy);
pathCopy = NULL;
}
}
if (Verbose) {
for (i = 0; i < depth; i++)
(void) fprintf(stderr, " ");
if (hidden)
(void) fprintf(stderr, "[");
else if (dir)
(void) fprintf(stderr, "|_");
else
(void) fprintf(stderr, gettext("(%06d) "), FileCount);
printName(stderr, dp);
if (hidden)
(void) fprintf(stderr, "]");
(void) fprintf(stderr,
gettext(", %u bytes, start cluster %d"),
extractSize(dp), extractStartCluster(dp));
(void) fprintf(stderr, "\n");
}
start = extractStartCluster(dp);
haveBad = noteUsage(fd, start, dp, ldp, longEntryStartCluster,
hidden, dir, pathCopy);
if (haveBad > 0) {
if (dir && pathCopy->fullName != NULL) {
(void) fprintf(stderr,
gettext("Adjusting for bad allocation units in "
"the meta-data of:\n "));
(void) fprintf(stderr, pathCopy->fullName);
(void) fprintf(stderr, "\n");
}
truncChainWithBadCluster(fd, dp, start);
}
if ((pathCopy != NULL) && (pathCopy->references == 0)) {
free(pathCopy->fullName);
free(pathCopy);
}
}
static void
storeInfoAboutLabel(struct pcdir *dp)
{
if (Verbose) {
(void) fprintf(stderr, gettext("** "));
printName(stderr, dp);
(void) fprintf(stderr, gettext(" **\n"));
}
}
static void
searchChecks(struct pcdir *dp, int operation, char matchRequired,
struct pcdir **found)
{
if (operation == PCFS_FIND_ATTR && dp->pcd_attr == matchRequired) {
*found = dp;
} else if (operation == PCFS_FIND_STATUS &&
dp->pcd_filename[0] == matchRequired) {
*found = dp;
} else if (operation == PCFS_FIND_CHKS && hasCHKName(dp)) {
addToCHKList(dp);
}
}
static void
catalogEntry(int fd, struct pcdir *dp, struct pcdir *longdp,
int32_t currentCluster, int depth, char *recordPath, int *pathLen)
{
if (dp->pcd_attr & PCA_LABEL) {
storeInfoAboutLabel(dp);
} else {
storeInfoAboutEntry(fd, dp, longdp, depth, currentCluster,
recordPath, pathLen);
}
}
static void
visitNodes(int fd, int32_t currentCluster, ClusterContents *dirData,
int32_t dirDataLen, int depth, int descend, int operation,
char matchRequired, struct pcdir **found, int32_t *lastDirCluster,
struct pcdir **dirEnd, char *recordPath, int *pathLen)
{
struct pcdir *longdp = NULL;
struct pcdir *dp;
int32_t longStart;
int withinLongName = 0;
int saveLen = *pathLen;
dp = dirData->dirp;
while ((uchar_t *)dp < dirData->bytes + dirDataLen &&
dp->pcd_filename[0] != PCD_UNUSED) {
searchChecks(dp, operation, matchRequired, found);
if (*found)
break;
if ((dp->pcd_attr & PCDL_LFN_BITS) == PCDL_LFN_BITS) {
if (!withinLongName) {
withinLongName++;
longStart = currentCluster;
longdp = dp;
}
dp++;
continue;
} else if ((dp->pcd_filename[0] == PCD_ERASED) ||
(dp->pcd_filename[0] == '.')) {
withinLongName = 0;
dp++;
continue;
}
withinLongName = 0;
if (operation == PCFS_TRAVERSE_ALL)
catalogEntry(fd, dp, longdp, longStart, depth,
recordPath, pathLen);
longdp = NULL;
longStart = 0;
if (dp->pcd_attr & PCA_DIR && descend == PCFS_VISIT_SUBDIRS) {
traverseDir(fd, extractStartCluster(dp), depth + 1,
descend, operation, matchRequired, found,
lastDirCluster, dirEnd, recordPath, pathLen);
if (*found)
break;
}
dp++;
*pathLen = saveLen;
}
if (*found)
return;
if ((uchar_t *)dp < dirData->bytes + dirDataLen) {
*lastDirCluster = currentCluster;
*dirEnd = dp;
return;
}
if ((currentCluster == FAKE_ROOTDIR_CLUST) ||
(lastInFAT(currentCluster))) {
*lastDirCluster = currentCluster;
return;
} else {
traverseDir(fd, nextInChain(currentCluster),
depth, descend, operation, matchRequired,
found, lastDirCluster, dirEnd, recordPath, pathLen);
*pathLen = saveLen;
}
}
void
traverseFromRoot(int fd, int depth, int descend, int operation,
char matchRequired, struct pcdir **found, int32_t *lastDirCluster,
struct pcdir **dirEnd, char *recordPath, int *pathLen)
{
visitNodes(fd, FAKE_ROOTDIR_CLUST, &TheRootDir, RootDirSize, depth,
descend, operation, matchRequired, found, lastDirCluster, dirEnd,
recordPath, pathLen);
}
void
traverseDir(int fd, int32_t startAt, int depth, int descend, int operation,
char matchRequired, struct pcdir **found, int32_t *lastDirCluster,
struct pcdir **dirEnd, char *recordPath, int *pathLen)
{
ClusterContents dirdata;
int32_t dirdatasize = 0;
if (startAt < FIRST_CLUSTER || startAt > LastCluster)
return;
if (readCluster(fd, startAt, &(dirdata.bytes), &dirdatasize,
RDCLUST_DO_CACHE) != RDCLUST_GOOD) {
(void) fprintf(stderr,
gettext("Unable to get more directory entries!\n"));
return;
}
if (operation == PCFS_TRAVERSE_ALL) {
if (Verbose)
(void) fprintf(stderr,
gettext("Directory traversal enters "
"allocation unit %d.\n"), startAt);
}
visitNodes(fd, startAt, &dirdata, dirdatasize, depth, descend,
operation, matchRequired, found, lastDirCluster, dirEnd,
recordPath, pathLen);
}
void
createCHKNameList(int fd)
{
struct pcdir *ignorep1, *ignorep2;
int32_t ignore32;
char *ignorecp = NULL;
char ignore = '\0';
int ignoreint = 0;
ignorep1 = ignorep2 = NULL;
if (!OkayToRelink || CHKsList != NULL)
return;
if ((CHKsList =
(uchar_t *)calloc(1, idivceil(MAXCHKVAL, NBBY))) == NULL) {
OkayToRelink = 0;
return;
}
if (!IsFAT32) {
traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS,
ignore, &ignorep1, &ignore32, &ignorep2, ignorecp,
&ignoreint);
} else {
DirCount++;
traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
0, PCFS_NO_SUBDIRS, PCFS_FIND_CHKS, ignore,
&ignorep1, &ignore32, &ignorep2, ignorecp, &ignoreint);
}
}
char *
nextAvailableCHKName(int *chosen)
{
static char nameBuf[PCFNAMESIZE];
int i;
if (!OkayToRelink)
return (NULL);
nameBuf[CHKNAME_F] = 'F';
nameBuf[CHKNAME_I] = 'I';
nameBuf[CHKNAME_L] = 'L';
nameBuf[CHKNAME_E] = 'E';
for (i = 1; i <= MAXCHKVAL; i++) {
if (!inUseCHKName(i))
break;
}
if (i <= MAXCHKVAL) {
nameBuf[CHKNAME_THOUSANDS] = '0' + (i / 1000);
nameBuf[CHKNAME_HUNDREDS] = '0' + ((i % 1000) / 100);
nameBuf[CHKNAME_TENS] = '0' + ((i % 100) / 10);
nameBuf[CHKNAME_ONES] = '0' + (i % 10);
*chosen = i;
return (nameBuf);
} else {
(void) fprintf(stderr,
gettext("Sorry, no names available for "
"relinking orphan chains!\n"));
OkayToRelink = 0;
return (NULL);
}
}
uint32_t
extractSize(struct pcdir *dp)
{
uint32_t returnMe;
read_32_bits((uchar_t *)&(dp->pcd_size), &returnMe);
return (returnMe);
}
int32_t
extractStartCluster(struct pcdir *dp)
{
uint32_t lo, hi;
if (IsFAT32) {
read_16_bits((uchar_t *)&(dp->un.pcd_scluster_hi), &hi);
read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
return ((int32_t)((hi << 16) | lo));
} else {
read_16_bits((uchar_t *)&(dp->pcd_scluster_lo), &lo);
return ((int32_t)lo);
}
}
static struct pcdir *
findAvailableRootDirEntSlot(int fd, int32_t *clusterWithSlot)
{
struct pcdir *deletedEntry = NULL;
struct pcdir *appendPoint = NULL;
char *ignorecp = NULL;
int ignore = 0;
*clusterWithSlot = 0;
if (!IsFAT32) {
traverseFromRoot(fd, 0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS,
PCD_ERASED, &deletedEntry, clusterWithSlot,
&appendPoint, ignorecp, &ignore);
} else {
DirCount++;
traverseDir(fd, TheBIOSParameterBlock.bpb32.root_dir_clust,
0, PCFS_NO_SUBDIRS, PCFS_FIND_STATUS, PCD_ERASED,
&deletedEntry, clusterWithSlot, &appendPoint, ignorecp,
&ignore);
}
if (deletedEntry)
return (deletedEntry);
if (appendPoint)
return (appendPoint);
return (NULL);
}
static void
insertDirEnt(struct pcdir *slot, struct pcdir *entry, int32_t clusterWithSlot)
{
(void) memcpy(slot, entry, sizeof (struct pcdir));
markClusterModified(clusterWithSlot);
}
static void
getNow(struct pctime *pctp, uchar_t *msec)
{
time_t now;
struct tm tm;
ushort_t tim, dat;
daylight = 0;
(void) time(&now);
(void) localtime_r(&now, &tm);
dat = (tm.tm_year - 80) << YEARSHIFT;
dat |= tm.tm_mon << MONSHIFT;
dat |= tm.tm_mday << DAYSHIFT;
tim = tm.tm_hour << HOURSHIFT;
tim |= tm.tm_min << MINSHIFT;
tim |= (tm.tm_sec / 2) << SECSHIFT;
if (dat < 80 || dat > 227)
dat = tim = 0;
pctp->pct_date = LE_16(dat);
pctp->pct_time = LE_16(tim);
if (msec)
*msec = (tm.tm_sec & 1) ? 100 : 0;
}
static void
updateDirEnt_CreatTime(struct pcdir *dp)
{
getNow(&dp->pcd_crtime, &dp->pcd_crtime_msec);
markClusterModified(findImpactedCluster(dp));
}
static void
updateDirEnt_ModTimes(struct pcdir *dp)
{
timestruc_t ts;
getNow(&dp->pcd_mtime, NULL);
dp->pcd_ladate = dp->pcd_mtime.pct_date;
dp->pcd_attr |= PCA_ARCH;
markClusterModified(findImpactedCluster(dp));
}
struct pcdir *
addRootDirEnt(int fd, struct pcdir *new)
{
struct pcdir *added;
int32_t inCluster;
if ((added = findAvailableRootDirEntSlot(fd, &inCluster)) != NULL) {
insertDirEnt(added, new, inCluster);
return (added);
}
return (NULL);
}
static off64_t
seekRootDirectory(int fd)
{
off64_t seekto;
seekto = (off64_t)TheBIOSParameterBlock.bpb.resv_sectors *
TheBIOSParameterBlock.bpb.bytes_per_sector +
(off64_t)FATSize * TheBIOSParameterBlock.bpb.num_fats +
(off64_t)PartitionOffset;
if (Verbose)
(void) fprintf(stderr,
gettext("Seeking root directory @%lld.\n"), seekto);
return (lseek64(fd, seekto, SEEK_SET));
}
void
getRootDirectory(int fd)
{
ssize_t bytesRead;
if (TheRootDir.bytes != NULL)
return;
else if ((TheRootDir.bytes = (uchar_t *)malloc(RootDirSize)) == NULL) {
mountSanityCheckFails();
perror(gettext("No memory for a copy of the root directory"));
(void) close(fd);
exit(8);
}
if (seekRootDirectory(fd) < 0) {
mountSanityCheckFails();
perror(gettext("Cannot seek to RootDir"));
(void) close(fd);
exit(8);
}
if (Verbose)
(void) fprintf(stderr,
gettext("Reading root directory.\n"));
if ((bytesRead = read(fd, TheRootDir.bytes, RootDirSize)) !=
RootDirSize) {
mountSanityCheckFails();
if (bytesRead < 0) {
perror(gettext("Cannot read a RootDir"));
} else {
(void) fprintf(stderr,
gettext("Short read of RootDir\n"));
}
(void) close(fd);
exit(8);
}
if (Verbose) {
(void) fprintf(stderr,
gettext("Dump of root dir's first 256 bytes.\n"));
header_for_dump();
dump_bytes(TheRootDir.bytes, 256);
}
}
void
writeRootDirMods(int fd)
{
ssize_t bytesWritten;
if (!TheRootDir.bytes) {
(void) fprintf(stderr,
gettext("Internal error: No Root directory to write\n"));
(void) close(fd);
exit(12);
}
if (!RootDirModified) {
if (Verbose) {
(void) fprintf(stderr,
gettext("No root directory changes need to "
"be written.\n"));
}
return;
}
if (ReadOnly)
return;
if (Verbose)
(void) fprintf(stderr,
gettext("Writing root directory.\n"));
if (seekRootDirectory(fd) < 0) {
perror(gettext("Cannot write the RootDir (seek failed)"));
(void) close(fd);
exit(12);
}
if ((bytesWritten = write(fd, TheRootDir.bytes, RootDirSize)) !=
RootDirSize) {
if (bytesWritten < 0) {
perror(gettext("Cannot write the RootDir"));
} else {
(void) fprintf(stderr,
gettext("Short write of root directory\n"));
}
(void) close(fd);
exit(12);
}
RootDirModified = 0;
}
struct pcdir *
newDirEnt(struct pcdir *copyme)
{
struct pcdir *ndp;
if ((ndp = (struct pcdir *)calloc(1, sizeof (struct pcdir))) == NULL) {
(void) fprintf(stderr, gettext("Out of memory to create a "
"new directory entry!\n"));
return (ndp);
}
if (copyme)
(void) memcpy(ndp, copyme, sizeof (struct pcdir));
ndp->pcd_ext[CHKNAME_C] = 'C';
ndp->pcd_ext[CHKNAME_H] = 'H';
ndp->pcd_ext[CHKNAME_K] = 'K';
updateDirEnt_CreatTime(ndp);
updateDirEnt_ModTimes(ndp);
return (ndp);
}
void
updateDirEnt_Size(struct pcdir *dp, uint32_t newSize)
{
uchar_t *p = (uchar_t *)&(dp->pcd_size);
store_32_bits(&p, newSize);
markClusterModified(findImpactedCluster(dp));
}
void
updateDirEnt_Start(struct pcdir *dp, int32_t newStart)
{
uchar_t *p = (uchar_t *)&(dp->pcd_scluster_lo);
store_16_bits(&p, newStart & 0xffff);
if (IsFAT32) {
p = (uchar_t *)&(dp->un.pcd_scluster_hi);
store_16_bits(&p, newStart >> 16);
}
markClusterModified(findImpactedCluster(dp));
}
void
updateDirEnt_Name(struct pcdir *dp, char *newName)
{
int i;
for (i = 0; i < PCFNAMESIZE; i++) {
if (*newName)
dp->pcd_filename[i] = *newName++;
else
dp->pcd_filename[i] = ' ';
}
markClusterModified(findImpactedCluster(dp));
}