/* * 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. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * fsck_pcfs -- routines for manipulating clusters. */ #include #include #include #include #include #include #include #include #include #include #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; /* * Internal statistics */ 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) { /* silent failure for bogus clusters */ 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) { /* silent failure for bogus clusters */ 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 int askAboutFreeing(int32_t clusterNum) { /* * If it is not OkayToRelink, we haven't already printed the size * of the orphaned chain. */ if (!OkayToRelink) printOrphanInfo(clusterNum); /* * If we are in preen mode, preenBail won't return. */ preenBail("Need user confirmation to free orphaned chain.\n"); (void) printf( gettext("Free the allocation units in the orphaned chain ? " "(y/n) ")); return (yes()); } static int askAboutRelink(int32_t clusterNum) { /* * Display the size of the chain for the user to consider. */ printOrphanInfo(clusterNum); /* * If we are in preen mode, preenBail won't return. */ preenBail("Need user confirmation to re-link orphaned chain.\n"); (void) printf(gettext("Re-link orphaned chain into file system ? " "(y/n) ")); return (yes()); } static int isHidden(int32_t clusterNum) { /* silent failure for bogus clusters */ 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) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (0); return ((InUse[clusterNum - FIRST_CLUSTER] != NULL) && (InUse[clusterNum - FIRST_CLUSTER]->dirent != NULL)); } /* * Caller's may request that we cache the data from a readCluster. * The xxxClusterxxxCachexxx routines handle looking for cached data * or initially caching the data. * * XXX - facilitate releasing cached data for low memory situations. */ 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); } /* * getcluster * Get cluster bytes off the disk. We always read those bytes into * the same static buffer. If the caller wants its own copy of the * data it'll have to make its own copy. We'll return all the data * read, even if it's short of a full cluster. This is for future use * when we might want to relocate any salvagable data from bad clusters. */ 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); } } /* * It's cheaper to allocate a lot at a time; malloc overhead pushes * you over the brink much more quickly if you don't. * This numbers seems to be a fair trade-off between reduced malloc overhead * and additional overhead by over-allocating. */ #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); } /* Should be called with verified arguments */ 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; } } /* * Allocate entries in our sparse array of cluster information. * Returns non-zero if the structure already has been allocated * (for those keeping score at home). * * The template parameter, if non-NULL, is used to facilitate sharing * the ClusterInfo nodes for the clusters belonging to the same file. * The first call to allocInUse for a new file should have *template * set to 0; on return, *template then points to the newly allocated * ClusterInfo. Second and further calls keep the same value * in *template and that ClusterInfo ndoe is then used for all * entries in the file. Code that modifies the ClusterInfo nodes * should take care proper sharing semantics are maintained (i.e., * copy-on-write using cloneClusterInfo()) * * The ClusterInfo used in the template is guaranted to be in use in * at least one other cluster as we never return a value if we didn't * set it first. So we can overwrite it without the possibility of a leak. */ 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) { /* silent failure for bogus clusters */ 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) { /* silent failure for bogus clusters */ 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) { /* silent failure for bogus clusters */ 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) { /* silent failure for bogus clusters */ 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; /* silent failure for bogus clusters */ 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)) { /* * This catches the bad sectors found * during thorough verify that have never been * allocated to a file. Without this check, we * count these guys as free. */ BadClusterCount++; markBadInFAT(c); } else if (isHidden(c)) { HiddenClusterCount++; } else if (isInUse(c)) { AllocedClusterCount++; } else { FreeClusterCount++; } } } /* * summarizeFAT * Mark orphans without directory entries as allocated. * XXX - these chains should be reclaimed! * XXX - merge this routine with countClusters (same loop, duh.) */ 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); /* * Traverse the full meta-data tree to talley what clusters * are in use. 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_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) { /* silent failure for bogus clusters */ 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) { /* silent failure for bogus clusters */ 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; /* silent failure for bogus clusters */ 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); /* * There are two scenarios. One is that we truncated the * directory in the very beginning. The other is that we * truncated it in the middle or at the end. In the first * scenario, the secondToLast argument is not a valid cluster * (it's zero), and so we actually need to change the start * cluster for the directory to this new start cluster. In * the second scenario, the secondToLast cluster we received * as an argument needs to be pointed at the new end of * directory. */ 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); } /* * truncAtCluster * Given a directory entry and a cluster number, search through * the cluster chain for the entry and make the cluster previous * to the given cluster in the chain the last cluster in the file. * The number of orphaned bytes is returned. For a chain that's * a directory we need to do some special handling, since we'll be * getting rid of the end of directory notice by truncating. */ 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) { /* * We didn't find the cluster they wanted to trunc at * anywhere in the entry's chain. So we'll leave the * entry alone, and return a negative value so they * can know something is wrong. */ 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) { /* * We don't really know what the size of a directory is * but it is important for us to know if this truncation * results in an orphan with any size. The value we * return from this routine for a normal file is the * number of bytes left in the chain. For a directory * we can't be exact, and the caller doesn't really * expect us to be. For a directory the caller only * cares if there are zero bytes left or more than * zero bytes left. We'll return 1 to indicate * more than zero. */ if ((follow = nextInChain(follow)) != 0) return (1); else return (0); } /* * newSize should always be smaller than the old one, since * we are decreasing the number of clusters allocated to the file. */ 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); /* * If the truncation fails, (which ought not to happen), * there's no need to go any further, we just return * a null value for the new directory entry pointer. */ remainder = truncAtCluster(fd, dp, endCluster); if (remainder < 0) return (ndp); if (!dir && isBad) { /* * Subtract out the bad cluster from the remaining size * We always assume the cluster being deleted from the * file is full size, but that might not be the case * for the last cluster of the file, so that is why * we check for negative remainder value. */ remainder -= TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; if (remainder < 0) remainder = 0; } /* * Build a new directory entry for the rest of the chain. * Later, if the user okays it, we'll link this entry into the * root directory. The new entry will start out as a * copy of the truncated entry. */ 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); } /* * splitChain() * * split a cluster allocation chain into two cluster chains * around a given cluster (problemCluster). This results in two * separate directory entries; the original (dp), and one we hope * to create and return a pointer to to the caller (*newdp). * This second entry is the orphan chain, and it may end up in * the root directory as a FILEnnnn.CHK file. We also return the * starting cluster of the orphan chain to the caller (*orphanStart). */ 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); } } /* * freeOrphan * * User has requested that an orphaned cluster chain be freed back * into the file area. */ static void freeOrphan(int32_t c) { int32_t n; /* * Free the directory entry we explicitly created for * the orphaned clusters. */ if (InUse[c - FIRST_CLUSTER]->dirent != NULL) free(InUse[c - FIRST_CLUSTER]->dirent); /* * Then mark the clusters themselves as available. */ do { n = nextInChain(c); markFreeInFAT(c); markFree(c); c = n; } while (c != 0); } /* * Rewrite the InUse field for a cluster chain. Can be used on a partial * chain if provided with a stopAtCluster. */ 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) { /* silent failure for bogus clusters */ if (clusterNum < FIRST_CLUSTER || clusterNum > LastCluster) return (-1); if (isInUse(clusterNum)) { return (extractSize(InUse[clusterNum - FIRST_CLUSTER]->dirent)); } else { return (-1); } } /* * linkOrphan * * User has requested that an orphaned cluster chain be brought back * into the file system. So we have to make a new directory entry * in the root directory and point it at the cluster chain. */ 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")); } /* * A cluster isn't really InUse() unless it is referenced, * so if newEnt is NULL here, we are in effect using markInUse() * to note that the cluster is NOT in use. */ redoInUse(fd, start, newEnt, 0); } /* * relinkCreatedOrphans * * While marking clusters as bad, we can create orphan cluster * chains. Since we were the ones doing the marking, we were able to * keep track of the orphans we created. Now we want to go through * all those chains and either get them back into the file system or * free them depending on the user's input. */ 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); } } } /* * relinkFATOrphans * * We want to find orphans not represented in the meta-data. * These are chains marked in the FAT as being in use but * not referenced anywhere by any directory entries. * We'll go through the whole FAT and mark the first cluster * in any such chain as an orphan. Then we can just use * the relinkCreatedOrphans routine to get them back into the * file system or free'ed depending on the user's input. */ 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) { /* * We found a loop. Eradicate it by changing * the last cluster in the loop to be last * in the chain instead instead of pointing * back to the first cluster. */ markLastInFAT(prev); } } static void sharedChainError(int fd, int32_t clusterNum, struct pcdir *badEntry) { /* * If we have shared clusters, it is either because the * cluster somehow got assigned to multiple files and/or * because of a loop in the cluster chain. In either * case we want to truncate the offending file at the * cluster of contention. Then, we will want to run * through the remainder of the chain. If we find ourselves * back at the top, we will know there is a loop in the * FAT we need to remove. */ if (Verbose) (void) fprintf(stderr, gettext("Truncating chain due to duplicate allocation of " "unit %d.\n"), clusterNum); /* * Note that we don't orphan anything here, because the duplicate * part of the chain may be part of another valid chain. */ (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() truncates the current guy and * then makes an orphan chain out of the remaining * clusters. When we come back from the split * we'll want to continue looking for bad clusters * in the orphan chain. */ splitChain(fd, dp, c, &orphanEntry, &orphanStartCluster); /* * There is a chance that we weren't able or weren't * required to make a directory entry for the * remaining clusters. In that case we won't go * on, because we couldn't make any more splits * anyway. */ if (orphanEntry == NULL) break; c = orphanStartCluster; dp = orphanEntry; continue; } c = nextInChain(c); } } int32_t nextInChain(int32_t currentCluster) { int32_t nextCluster; /* silent failure for bogus clusters */ if (currentCluster < FIRST_CLUSTER || currentCluster > LastCluster) return (0); /* * Look up FAT entry of next link in cluster chain, * if this one is the last one return 0 as the next link. */ nextCluster = readFATEntry(currentCluster); if (nextCluster < FIRST_CLUSTER || nextCluster > LastCluster) return (0); return (nextCluster); } /* * findImpactedCluster * * Called when someone modifies what they believe might be a cached * cluster entry, but when they only have a directory entry pointer * and not the cluster number. We have to go dig up what cluster * they are modifying. */ int32_t findImpactedCluster(struct pcdir *modified) { CachedCluster *loop; /* * Check to see if it's in the root directory first */ 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; } /* * Guess it wasn't cached after all... */ 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) { /* silent failure for bogus clusters */ 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; /* silent failure for bogus clusters */ 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 is newly allocated (refcnt <= 1) we must fill in the fields. * If Cl has different fields, we must clone it. */ 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; /* * Return cl as the template to use for other clusters in * this file */ if (template) *template = cl; } return (CLINFO_NEWLY_ALLOCED); } void markClusterModified(int32_t clusterNum) { CachedCluster *c; if (clusterNum == FAKE_ROOTDIR_CLUST) { RootDirModified = 1; return; } /* silent failure for bogus clusters */ 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); } } /* * readCluster * caller wants to read cluster clusterNum. We should return * a pointer to the read data in "data", and fill in the number * of bytes read in "datasize". If shouldCache is non-zero * we should allocate cache space to the cluster, otherwise we * just return a pointer to a buffer we re-use whenever cacheing * is not requested. */ 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); /* * Caller requested we NOT cache the data from this read. * So, we just return a pointer to the common data buffer. */ if (shouldCache == 0) { *data = newBuf; return (rv); } /* * Caller requested we cache the data from this read. * So, if we have some data, add it to the cache by * copying it out of the common buffer into new storage. */ 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); } /* * Progress meter, display a '.' for every 1000 clusters * processed. We don't want to display this when * we are in verbose mode; verbose mode progress is * shown by displaying each file name as it is found. */ if (!Verbose && clusterCount % 1000 == 0) (void) printf("."); } (void) printf(gettext("..done\n")); } void scanAndFixMetadata(int fd) { /* * First we initialize a few things. */ makeUseTable(); getReadyToSearch(fd); createCHKNameList(fd); /* * Make initial scan, taking into account any effect that * the bad clusters we may have already discovered have * on meta-data. We may break up some cluster chains * during this period. The relinkCreatedOrphans() call * will then give the user the chance to recover stuff * we've created. */ (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); /* * Clear our usage table and go back over everything, this * time including looking for clusters floating free in the FAT. * This may include clusters the user chose to free during the * relink phase. */ 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); }