/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1999,2000 by Sun Microsystems, Inc. * All rights reserved. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * fsck_pcfs -- routines for manipulating directories. */ #include #include #include #include #include #include #include #include #include #include #include #include #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; /* * We import these routines from the pc_subr.c file in the kernel. */ extern void pc_tvtopct(timestruc_t *, struct pctime *); extern int pc_validchar(char); /* * We have a bunch of routines for handling CHK names. A CHK name is * simply a file name of the form "FILEnnnn.CHK", where the n's are the * digits in the numbers from 1 to 9999. There are always four digits * used, leading zeros are added as necessary. * * We use CHK names to link orphaned cluster chains back into the file * system's root directory under an auspicious name so that the user * may be able to recover some of their data. * * We use these routines to ensure CHK names we use don't conflict * with any already present in the file system. */ 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) { /* silent failure on bogus value */ 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; /* * Sometimes caller doesn't care about keeping track of the path */ if (thePath == NULL) return; /* * Prepend / */ if (*theLen < MAXPATHLEN) *(thePath + (*theLen)++) = '/'; /* * Print out the file name part, but only up to the first * space. */ while (*theLen < MAXPATHLEN && i < PCFNAMESIZE) { /* * When we start seeing spaces we assume that's the * end of the interesting characters in the name. */ if ((dp->pcd_filename[i] == ' ') || !(pc_validchar(dp->pcd_filename[i]))) break; *(thePath + (*theLen)++) = dp->pcd_filename[i++]; } /* * Leave now, if we don't have an extension (or room for one) */ if ((dp->pcd_ext[i] == ' ') || ((*theLen) >= MAXPATHLEN) || (!(pc_validchar(dp->pcd_ext[i])))) return; /* * Tack on the extension */ *(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]); } } /* * sanityCheckSize * Make sure the size in the directory entry matches what is * actually allocated. If there is a mismatch, orphan all * the allocated clusters. Returns SIZE_MATCHED if everything matches * up, TRUNCATED to indicate truncation was necessary. */ 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)); } /* * Use splitChain() to make an orphan that is the entire allocation * chain. */ 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); } /* * Do a sanity check on the file size in the directory entry. * This may create an orphaned cluster chain. */ if (sanityCheckSize(fd, dp, count, isDir, startAt, fullPathName, &orphanEntry) == TRUNCATED) { /* * The pre-existing directory entry has been truncated, * so the chain associated with it no longer has any * bad clusters. Instead, the new orphan has them. */ 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); /* * Make a copy of the name at this point. We may want it to * note the original source of an orphaned cluster. */ 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) { /* * XXX eventually depth should be passed to this routine just * as it is with storeInfoAboutEntry(). If it isn't zero, then * we've got a bogus directory entry. */ 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) { /* * We support these searching operations: * * PCFS_FIND_ATTR * look for the first file with a certain attribute * (e.g, find all hidden files) * PCFS_FIND_STATUS * look for the first file with a certain status * (e.g., the file has been marked deleted; making * its directory entry reusable) * PCFS_FIND_CHKS * look for all files with short names of the form * FILENNNN.CHK. These are the file names we give * to chains of orphaned clusters we relink into the * file system. This find facility allows us to seek * out all existing files of this naming form so that * we may create unique file names for new orphans. */ 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); } } /* * visitNodes() * * This is the main workhouse routine for traversing pcfs metadata. * There isn't a lot to the metadata. Basically there is a root * directory somewhere (either in its own special place outside the * data area or in a data cluster). The root directory (and all other * directories) are filled with a number of fixed size entries. An * entry has the filename and extension, the file's attributes, the * file's size, and the starting data cluster of the storage allocated * to the file. To determine which clusters are assigned to the file, * you start at the starting cluster entry in the FAT, and follow the * chain of entries in the FAT. * * Arguments are: * fd * descriptor for accessing the raw file system data * currentCluster * original caller supplies the initial starting cluster, * subsequent recursive calls are made with updated * cluster numbers for the sub-directories. * dirData * pointer to the directory data bytes * dirDataLen * size of the whole buffer of data bytes (usually it is * the size of a cluster, but the root directory on * FAT12/16 is not necessarily the same size as a cluster). * depth * original caller should set it to zero (assuming they are * starting from the root directory). This number is used to * change the indentation of file names presented as debug info. * descend * boolean indicates if we should descend into subdirectories. * operation * what, if any, matching should be performed. * The PCFS_TRAVERSE_ALL operation is a depth first traversal * of all nodes in the metadata tree, that tracks all the * clusters in use (according to the meta-data, at least) * matchRequired * value to be matched (if any) * found * output parameter * used to return pointer to a directory entry that matches * the search requirement * original caller should pass in a pointer to a NULL pointer. * lastDirCluster * output parameter * if no match found, last cluster num of starting directory * dirEnd * output parameter * if no match found, return parameter stores pointer to where * new directory entry could be appended to existing directory * recordPath * output parameter * as files are discovered, and directories traversed, this * buffer is used to store the current full path name. * pathLen * output parameter * this is in the integer length of the current full path name. */ 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; /* * A directory entry where the first character of the name is * PCD_UNUSED indicates the end of the directory. */ while ((uchar_t *)dp < dirData->bytes + dirDataLen && dp->pcd_filename[0] != PCD_UNUSED) { /* * Handle the special case find operations. */ searchChecks(dp, operation, matchRequired, found); if (*found) break; /* * Are we looking at part of a long file name entry? * If so, we may need to note the start of the name. * We don't do any further processing of long file * name entries. * * We also skip deleted entries and the '.' and '..' * entries. */ 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] == '.')) { /* * XXX - if we were within a long name, then * its existence is bogus, because it is not * attached to any real file. */ 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) { /* * We reached the end of directory before the end of * our provided data (a cluster). That means this cluster * is the last one in this directory's chain. It also * means we've just looked at the last directory entry. */ *lastDirCluster = currentCluster; *dirEnd = dp; return; } /* * If there is more to the directory we'll go get it otherwise we * are done traversing this directory. */ 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; } } /* * traverseFromRoot() * For use with 12 and 16 bit FATs that have a root directory outside * of the file system. This is a general purpose routine that * can be used simply to visit all of the nodes in the metadata or * to find the first instance of something, e.g., the first directory * entry where the file is marked deleted. * * Inputs are described in the commentary for visitNodes() above. */ 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); } /* * traverseDir() * For use with all FATs outside of the initial root directory on * 12 and 16 bit FAT file systems. This is a general purpose routine * that can be used simply to visit all of the nodes in the metadata or * to find the first instance of something, e.g., the first directory * entry where the file is marked deleted. * * Unique Input is: * startAt * starting cluster of the directory * * This is the cluster that is the first one in this directory. * We read it right away, so we can provide it as data to visitNodes(). * Note that we cache this cluster as we read it, because it is * metadata and we cache all metadata. By doing so, we can * keep pointers to directory entries for quickly moving around and * fixing up any problems we find. Of course if we get a big * filesystem with a huge amount of metadata we may be hosed, as * we'll likely run out of memory. * * I believe in the future this will have to be addressed. It * may be possible to do more of the processing of problems * within directories as they are cached, so that when memory * runs short we can free cached directories we are already * finished visiting. * * The remainder of inputs are described in visitNodes() comments. */ 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; /* * Allocate an array to keep a bit map of the integer * values used in CHK names. */ if ((CHKsList = (uchar_t *)calloc(1, idivceil(MAXCHKVAL, NBBY))) == NULL) { OkayToRelink = 0; return; } /* * Search the root directory for all the files with names of * the form FILEXXXX.CHK. The root directory is an area * outside of the file space on FAT12 and FAT16 file systems. * On FAT32 file systems, the root directory is in a file * area cluster just like any other directory. */ 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; /* * First off, try to find an erased entry in the root * directory. The root directory is an area outside of the * file space on FAT12 and FAT16 file systems. On FAT32 file * systems, the root directory is in a file area cluster just * like any other directory. */ 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 we found a deleted file in the directory we'll overwrite * that entry. */ if (deletedEntry) return (deletedEntry); /* * If there is room at the end of the existing directory, we * should place the new entry there. */ if (appendPoint) return (appendPoint); /* * XXX need to grow the directory */ 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(timestruc_t *ts) { struct timeval tv; if (gettimeofday(&tv, NULL) == 0) { ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000; } else { /* Failed to get time, set create time to the Solaris epoch */ ts->tv_sec = 0; ts->tv_nsec = 0; } } /* * FAT file systems store the following time information in a directory * entry: * creation time * creation date * last access date * last modify time * last modify date * * No access time is kept. */ static void updateDirEnt_CreatTime(struct pcdir *dp) { timestruc_t ts; getNow(&ts); pc_tvtopct(&ts, &(dp->pcd_crtime)); /* sets creation time/date */ dp->pcd_crtime_msec = 0; markClusterModified(findImpactedCluster(dp)); } static void updateDirEnt_ModTimes(struct pcdir *dp) { timestruc_t ts; getNow(&ts); pc_tvtopct(&ts, &(dp->pcd_mtime)); /* sets modification time/date */ dp->pcd_ladate = dp->pcd_mtime.pct_date; /* sets access 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); } /* * FAT12 and FAT16 have a root directory outside the normal file space, * so we have separate routines for finding and reading the root directory. */ static off64_t seekRootDirectory(int fd) { off64_t seekto; /* * The RootDir immediately follows the FATs, which in * turn immediately follow the reserved sectors. */ 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)); }