# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (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 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2024 MNX Cloud, Inc. # FSTYPE= pcfs LIBPROG= fsck ATTMK= $(LIBPROG) include ../../Makefile.fstype COMMONOBJS= pcfs_common.o getresponse.o COMMONSRCS= ../common/pcfs_common.c $(SRC)/common/util/getresponse.c FSCKOBJS= fsck_main.o bpb.o clusters.o fat.o dir.o FSCKSRCS= $(FSCKOBJS:%.o=%.c) # # Error injection module for debugging purposes # #DEBUGOBJS= inject.o #DEBUGSRCS= $(DEBUGOBJS:%.o=%.c) OBJS= $(FSCKOBJS) $(DEBUGOBJS) $(COMMONOBJS) SRCS= $(FSCKSRCS) $(DEBUGSRCS) $(COMMONSRCS) # for messaging catalog # POFILES= $(OBJS:%.o=%.po) POFILE= fsck.po catalog: $(POFILE) CPPFLAGS += -D_LARGEFILE64_SOURCE CPPFLAGS += -I../common CPPFLAGS += -I$(SRC)/common/util CERRWARN += -Wno-parentheses CERRWARN += -Wno-unused-variable CERRWARN += $(CNOWARN_UNINIT) $(LIBPROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) %.o : ../common/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) %.o : $(SRC)/common/util/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) $(POFILE): $(RM) $@ $(COMPILE.cpp) $(SRCS) > $(POFILE).i $(XGETTEXT) $(XGETFLAGS) $(POFILE).i sed "/^domain/d" messages.po > $@ $(RM) $(POFILE).i messages.po clean: $(RM) $(FSCKOBJS) $(DEBUGOBJS) $(COMMONOBJS) $(POFILE).i messages.po /* * 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) 2011 Gary Mills * Copyright 2024 MNX Cloud, Inc. */ /* * fsck_pcfs -- routines for manipulating the BPB (BIOS parameter block) * of the file system. */ #include #include #include #include #include #include #include #include #include #include #include "pcfs_common.h" #include "fsck_pcfs.h" #include "pcfs_bpb.h" 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 short FATEntrySize; extern bpb_t TheBIOSParameterBlock; extern int IsFAT32; extern int Verbose; static void computeFileAreaSize(void) { int32_t dataSectors; int32_t overhead; /* * Compute bytes/cluster for later reference */ BytesPerCluster = TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector; /* * First we'll find total number of sectors in the file area... */ if (TheBIOSParameterBlock.bpb.sectors_in_volume > 0) dataSectors = TheBIOSParameterBlock.bpb.sectors_in_volume; else dataSectors = TheBIOSParameterBlock.bpb.sectors_in_logical_volume; overhead = TheBIOSParameterBlock.bpb.resv_sectors; RootDirSize = TheBIOSParameterBlock.bpb.num_root_entries * sizeof (struct pcdir); overhead += RootDirSize / TheBIOSParameterBlock.bpb.bytes_per_sector; if (TheBIOSParameterBlock.bpb.sectors_per_fat) { /* * Good old FAT12 or FAT16 */ overhead += TheBIOSParameterBlock.bpb.num_fats * TheBIOSParameterBlock.bpb.sectors_per_fat; /* * Compute this for later - when we actually pull in a copy * of the FAT */ FATSize = TheBIOSParameterBlock.bpb.sectors_per_fat * TheBIOSParameterBlock.bpb.bytes_per_sector; } else { /* * FAT32 * I'm unsure if this is always going to work. At one * point during the creation of this program and mkfs_pcfs * it seemed that Windows had created an fs where it had * rounded big_sectors_per_fat up to a cluster boundary. * Later, though, I encountered a problem where I wasn't * finding the root directory because I was looking in the * wrong place by doing that same roundup. So, for now, * I'm backing off on the cluster boundary thing and just * believing what I am told. */ overhead += TheBIOSParameterBlock.bpb.num_fats * TheBIOSParameterBlock.bpb32.big_sectors_per_fat; /* * Compute this for later - when we actually pull in a copy * of the FAT */ FATSize = TheBIOSParameterBlock.bpb32.big_sectors_per_fat * TheBIOSParameterBlock.bpb.bytes_per_sector; } /* * Now change sectors to clusters. The computed value for * TotalClusters is persistent for the remainder of execution. */ dataSectors -= overhead; TotalClusters = dataSectors / TheBIOSParameterBlock.bpb.sectors_per_cluster; /* * Also need to compute last cluster and offset of the first cluster */ LastCluster = TotalClusters + FIRST_CLUSTER; FirstClusterOffset = overhead * TheBIOSParameterBlock.bpb.bytes_per_sector; FirstClusterOffset += PartitionOffset; /* * XXX this should probably be more sophisticated */ if (IsFAT32) FATEntrySize = 32; else { if (TotalClusters <= DOS_F12MAXC) FATEntrySize = 12; else FATEntrySize = 16; } if (Verbose) { (void) fprintf(stderr, gettext("Disk has a file area of %d " "allocation units,\neach with %d sectors = %llu " "bytes.\n"), TotalClusters, TheBIOSParameterBlock.bpb.sectors_per_cluster, (uint64_t)TotalClusters * TheBIOSParameterBlock.bpb.sectors_per_cluster * TheBIOSParameterBlock.bpb.bytes_per_sector); (void) fprintf(stderr, gettext("File system overhead of %d sectors.\n"), overhead); (void) fprintf(stderr, gettext("The last cluster is %d\n"), LastCluster); } } /* * XXX - right now we aren't attempting to fix anything that looks bad, * instead we just give up. */ void readBPB(int fd) { boot_sector_t ubpb; /* * The BPB is the first sector of the file system */ if (lseek64(fd, PartitionOffset, SEEK_SET) < 0) { mountSanityCheckFails(); perror(gettext("Cannot seek to start of disk partition")); (void) close(fd); exit(7); } if (Verbose) (void) fprintf(stderr, gettext("Reading BIOS parameter block\n")); if (read(fd, ubpb.buf, bpsec) < bpsec) { mountSanityCheckFails(); perror(gettext("Read BIOS parameter block")); (void) close(fd); exit(2); } if (ltohs(ubpb.mb.signature) != BOOTSECSIG) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bad signature on BPB. Giving up.\n")); exit(2); } #ifdef _BIG_ENDIAN swap_pack_grabbpb(&TheBIOSParameterBlock, &(ubpb.bs)); #else (void) memcpy(&(TheBIOSParameterBlock.bpb), &(ubpb.bs.bs_front.bs_bpb), sizeof (TheBIOSParameterBlock.bpb)); (void) memcpy(&(TheBIOSParameterBlock.ebpb), &(ubpb.bs.bs_ebpb), sizeof (TheBIOSParameterBlock.ebpb)); #endif if (TheBIOSParameterBlock.bpb.bytes_per_sector != 512 && TheBIOSParameterBlock.bpb.bytes_per_sector != 1024 && TheBIOSParameterBlock.bpb.bytes_per_sector != 2048 && TheBIOSParameterBlock.bpb.bytes_per_sector != 4096) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus bytes per sector value. Giving up.\n")); exit(2); } if (!(ISP2(TheBIOSParameterBlock.bpb.sectors_per_cluster) && IN_RANGE(TheBIOSParameterBlock.bpb.sectors_per_cluster, 1, 128))) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus sectors per cluster value. Giving up.\n")); (void) close(fd); exit(6); } if (TheBIOSParameterBlock.bpb.sectors_per_fat == 0) { #ifdef _BIG_ENDIAN swap_pack_grab32bpb(&TheBIOSParameterBlock, &(ubpb.bs)); #else (void) memcpy(&(TheBIOSParameterBlock.bpb32), &(ubpb.bs32.bs_bpb32), sizeof (TheBIOSParameterBlock.bpb32)); #endif IsFAT32 = 1; } if (!IsFAT32) { if ((TheBIOSParameterBlock.bpb.num_root_entries == 0) || ((TheBIOSParameterBlock.bpb.num_root_entries * sizeof (struct pcdir)) % TheBIOSParameterBlock.bpb.bytes_per_sector) != 0) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus number of root entries. " "Giving up.\n")); exit(2); } } else { if (TheBIOSParameterBlock.bpb.num_root_entries != 0) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus number of root entries. " "Giving up.\n")); exit(2); } } /* * In general, we would expect the number of FATs field to * equal 2. Our mkfs and Windows have this as a default * value. I suppose someone could override the default, * though, so we'll sort of arbitrarily accept any number * between 1 and 4 inclusive as reasonable values. * * XXX: Warn, but continue, if value is suspicious? (>2?) */ if (TheBIOSParameterBlock.bpb.num_fats > 4 || TheBIOSParameterBlock.bpb.num_fats < 1) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Bogus number of FATs. Giving up.\n")); exit(2); } computeFileAreaSize(); } /* * 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. * Copyright 2024 MNX Cloud, Inc. */ /* * fsck_pcfs -- routines for manipulating clusters. */ #include #include #include #include #include #include #include #include #include #include #include #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; /* * 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 bool 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) ")); if (AlwaysYes) return (true); if (AlwaysNo) return (false); return (yes()); } static bool 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) ")); if (AlwaysYes) return (true); if (AlwaysNo) return (false); 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); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * fsck_pcfs -- routines for manipulating directories. */ #include #include #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 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); } /* * Convert current UNIX time into a PCFS timestamp (which is in local time). * * Since the "seconds" field of that is only accurate to 2sec precision, * we allow for the optional (used only for creation times on FAT) "msec" * parameter that takes the fractional part. */ static void getNow(struct pctime *pctp, uchar_t *msec) { time_t now; struct tm tm; ushort_t tim, dat; /* * Disable daylight savings corrections - Solaris PCFS doesn't * support such conversions yet. Save timestamps in local time. */ 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; /* * Sanity check. If we overflow the PCFS timestamp range * we set the time to 01/01/1980, 00:00:00 */ 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; } /* * FAT file systems store the following time information in a directory * entry: * timestamp member of "struct pcdir" * ====================================================================== * creation time pcd_crtime.pct_time * creation date pcd_crtime.pct_date * last access date pcd_ladate * last modify time pcd_mtime.pct_time * last modify date pcd_mtime.pct_date * * No access time is kept. */ 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); } /* * 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)); } /* * 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 by Sun Microsystems, Inc. * All rights reserved. */ /* * fsck_pcfs -- routines for manipulating the FAT. */ #include #include #include #include #include #include #include #include #include "pcfs_common.h" #include "fsck_pcfs.h" extern int32_t BytesPerCluster; extern int32_t TotalClusters; extern int32_t LastCluster; extern off64_t FirstClusterOffset; extern off64_t PartitionOffset; extern bpb_t TheBIOSParameterBlock; extern int ReadOnly; extern int IsFAT32; extern int Verbose; static uchar_t *TheFAT; static int FATRewriteNeeded = 0; int32_t FATSize; short FATEntrySize; static off64_t seekFAT(int fd) { off64_t seekto; /* * The FAT(s) immediately follows the reserved sectors. */ seekto = TheBIOSParameterBlock.bpb.resv_sectors * TheBIOSParameterBlock.bpb.bytes_per_sector + PartitionOffset; return (lseek64(fd, seekto, SEEK_SET)); } void getFAT(int fd) { ssize_t bytesRead; if (TheFAT != NULL) { return; } else if ((TheFAT = (uchar_t *)malloc(FATSize)) == NULL) { mountSanityCheckFails(); perror(gettext("No memory for a copy of the FAT")); (void) close(fd); exit(7); } if (seekFAT(fd) < 0) { mountSanityCheckFails(); perror(gettext("Cannot seek to FAT")); (void) close(fd); exit(7); } if (Verbose) (void) fprintf(stderr, gettext("Reading FAT\n")); if ((bytesRead = read(fd, TheFAT, FATSize)) != FATSize) { mountSanityCheckFails(); if (bytesRead < 0) { perror(gettext("Cannot read a FAT")); } else { (void) fprintf(stderr, gettext("Short read of FAT.")); } (void) close(fd); exit(7); } /* * XXX - might want to read the other copies of the FAT * for comparison and/or to use if the first one seems hosed. */ if (Verbose) { (void) fprintf(stderr, gettext("Dump of FAT's first 32 bytes.\n")); header_for_dump(); dump_bytes(TheFAT, 32); } } void writeFATMods(int fd) { ssize_t bytesWritten; if (TheFAT == NULL) { (void) fprintf(stderr, gettext("Internal error: No FAT to write\n")); (void) close(fd); exit(11); } if (!FATRewriteNeeded) { if (Verbose) { (void) fprintf(stderr, gettext("No FAT changes need to be written.\n")); } return; } if (ReadOnly) return; if (Verbose) (void) fprintf(stderr, gettext("Writing FAT\n")); if (seekFAT(fd) < 0) { perror(gettext("Cannot seek to FAT")); (void) close(fd); exit(11); } if ((bytesWritten = write(fd, TheFAT, FATSize)) != FATSize) { if (bytesWritten < 0) { perror(gettext("Cannot write FAT")); } else { (void) fprintf(stderr, gettext("Short write of FAT.")); } (void) close(fd); exit(11); } FATRewriteNeeded = 0; } /* * checkFAT32CleanBit() * Return non-zero if the bit indicating proper Windows shutdown has * been set. */ int checkFAT32CleanBit(int fd) { getFAT(fd); return (TheFAT[WIN_SHUTDOWN_STATUS_BYTE] & WIN_SHUTDOWN_BIT_MASK); } static uchar_t * findClusterEntryInFAT(int32_t currentCluster) { int32_t idx; if (FATEntrySize == 32) { idx = currentCluster * 4; } else if (FATEntrySize == 16) { idx = currentCluster * 2; } else { idx = currentCluster + currentCluster/2; } return (TheFAT + idx); } /* * {read,write}FATentry * For the 16 and 32 bit FATs these routines are relatively easy * to follow. * * 12 bit FATs are kind of strange, though. The magic index for * 12 bit FATS computed below, 1.5 * clusterNum, is a * simplification that there are 8 bits in a byte, so you need * 1.5 bytes per entry. * * It's easiest to think about FAT12 entries in pairs: * * --------------------------------------------- * | mid1 | low1 | low2 | high1 | high2 | mid2 | * --------------------------------------------- * * Each box in the diagram represents a nibble (4 bits) of a FAT * entry. A FAT entry is made up of three nibbles. So if you * look closely, you'll see that first byte of the pair of * entries contains the low and middle nibbles of the first * entry. The second byte has the low nibble of the second entry * and the high nibble of the first entry. Those two bytes alone * are enough to read the first entry. The second FAT entry is * finished out by the last nibble pair. */ int32_t readFATEntry(int32_t currentCluster) { int32_t value; uchar_t *ep; ep = findClusterEntryInFAT(currentCluster); if (FATEntrySize == 32) { read_32_bits(ep, (uint32_t *)&value); } else if (FATEntrySize == 16) { read_16_bits(ep, (uint32_t *)&value); /* * Convert 16 bit entry to 32 bit if we are * into the reserved or higher values. */ if (value >= PCF_RESCLUSTER) value |= 0xFFF0000; } else { value = 0; if (currentCluster & 1) { /* * Odd numbered cluster */ value = (((unsigned int)*ep++ & 0xf0) >> 4); value += (*ep << 4); } else { value = *ep++; value += ((*ep & 0x0f) << 8); } /* * Convert 12 bit entry to 32 bit if we are * into the reserved or higher values. */ if (value >= PCF_12BCLUSTER) value |= 0xFFFF000; } return (value); } void writeFATEntry(int32_t currentCluster, int32_t value) { uchar_t *ep; FATRewriteNeeded = 1; ep = findClusterEntryInFAT(currentCluster); if (FATEntrySize == 32) { store_32_bits(&ep, value); } else if (FATEntrySize == 16) { store_16_bits(&ep, value); } else { if (currentCluster & 1) { /* * Odd numbered cluster */ *ep = (*ep & 0x0f) | ((value << 4) & 0xf0); ep++; *ep = (value >> 4) & 0xff; } else { *ep++ = value & 0xff; *ep = (*ep & 0xf0) | ((value >> 8) & 0x0f); } } } /* * reservedInFAT - Is this cluster marked in the reserved range? * The range from PCF_RESCLUSTER32 to PCF_BADCLUSTER32 - 1, * have been reserved by Microsoft. No cluster should be * marked with these; they are effectively invalid cluster values. */ int reservedInFAT(int32_t clusterNum) { int32_t e; e = readFATEntry(clusterNum); return (e >= PCF_RESCLUSTER32 && e < PCF_BADCLUSTER32); } /* * badInFAT - Is this cluster marked as bad? I.e., is it inaccessible? */ int badInFAT(int32_t clusterNum) { return (readFATEntry(clusterNum) == PCF_BADCLUSTER32); } /* * lastInFAT - Is this cluster marked as free? I.e., is it available * for use? */ int freeInFAT(int32_t clusterNum) { return (readFATEntry(clusterNum) == PCF_FREECLUSTER); } /* * lastInFAT - Is this cluster the last in its cluster chain? */ int lastInFAT(int32_t clusterNum) { return (readFATEntry(clusterNum) == PCF_LASTCLUSTER32); } /* * markLastInFAT - Mark this cluster as the last in its cluster chain. */ void markLastInFAT(int32_t clusterNum) { writeFATEntry(clusterNum, PCF_LASTCLUSTER32); } void markFreeInFAT(int32_t clusterNum) { writeFATEntry(clusterNum, PCF_FREECLUSTER); } void markBadInFAT(int32_t clusterNum) { writeFATEntry(clusterNum, PCF_BADCLUSTER32); } /* * 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,2001 by Sun Microsystems, Inc. * All rights reserved. * Copyright 2024 MNX Cloud, Inc. */ /* * fsck_pcfs -- main routines. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "getresponse.h" #include "pcfs_common.h" #include "fsck_pcfs.h" #include "pcfs_bpb.h" size_t bpsec = MINBPS; int32_t BytesPerCluster; int32_t TotalClusters; int32_t LastCluster; off64_t FirstClusterOffset; off64_t PartitionOffset; bpb_t TheBIOSParameterBlock; /* * {Output,Input}Image are the file names where we should write the * checked fs image and from which we should read the initial fs. * The image capability is designed for debugging purposes. */ static char *OutputImage = NULL; static char *InputImage = NULL; static int WritableOnly = 0; /* -o w, check writable fs' only */ static int Mflag = 0; /* -m, sanity check if fs is mountable */ static int Preen = 0; /* -o p, preen; non-interactive */ /* * By default be quick; skip verify reads. * If the user wants more exhaustive checking, * they should run with the -o v option. */ static int Quick = 1; int ReadOnly = 0; int IsFAT32 = 0; int Verbose = 0; bool AlwaysYes = false; /* -y or -Y, assume a yes answer to all questions */ bool AlwaysNo = false; /* -n or -N, assume a no answer to all questions */ extern ClusterContents TheRootDir; /* * Function definitions */ static void passOne(int fd) { if (!Quick) findBadClusters(fd); scanAndFixMetadata(fd); } static void writeBackChanges(int fd) { writeFATMods(fd); if (!IsFAT32) writeRootDirMods(fd); writeClusterMods(fd); } static void tryOpen(int *fd, char *openMe, int oflag, int exitOnFailure) { int saveError; if ((*fd = open(openMe, oflag)) < 0) { if (exitOnFailure == RETURN_ON_OPEN_FAILURE) return; saveError = errno; mountSanityCheckFails(); (void) fprintf(stderr, "%s: ", openMe); (void) fprintf(stderr, strerror(saveError)); (void) fprintf(stderr, "\n"); exit(1); } } static void doOpen(int *inFD, int *outFD, char *name, char *outName) { if (ReadOnly) { tryOpen(inFD, name, O_RDONLY, EXIT_ON_OPEN_FAILURE); *outFD = -1; } else { tryOpen(inFD, name, O_RDWR, RETURN_ON_OPEN_FAILURE); if (*inFD < 0) { if (errno != EACCES || WritableOnly) { int saveError = errno; mountSanityCheckFails(); (void) fprintf(stderr, gettext("%s: "), name); (void) fprintf(stderr, strerror(saveError)); (void) fprintf(stderr, "\n"); exit(2); } else { tryOpen(inFD, name, O_RDONLY, EXIT_ON_OPEN_FAILURE); AlwaysYes = false; AlwaysNo = true; ReadOnly = 1; *outFD = -1; } } else { *outFD = *inFD; } } if (outName != NULL) { tryOpen(outFD, outName, (O_RDWR | O_CREAT), EXIT_ON_OPEN_FAILURE); } (void) printf("** %s %s\n", name, ReadOnly ? gettext("(NO WRITE)") : ""); } static void openFS(char *special, int *inFD, int *outFD) { struct stat dinfo; char *actualDisk = NULL; char *suffix = NULL; int rv; if (Verbose) (void) fprintf(stderr, gettext("Opening file system.\n")); if (InputImage == NULL) { actualDisk = stat_actual_disk(special, &dinfo, &suffix); /* * Destination exists, now find more about it. */ if (!(S_ISCHR(dinfo.st_mode))) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("\n%s: device name must be a " "character special device.\n"), actualDisk); exit(2); } } else { actualDisk = InputImage; } doOpen(inFD, outFD, actualDisk, OutputImage); rv = get_media_sector_size(*inFD, &bpsec); if (rv != 0) { (void) fprintf(stderr, gettext("error detecting device sector size: %s\n"), strerror(rv)); exit(2); } if (!is_sector_size_valid(bpsec)) { (void) fprintf(stderr, gettext("unsupported sector size: %zu\n"), bpsec); exit(2); } if (suffix) { if ((PartitionOffset = findPartitionOffset(*inFD, bpsec, suffix)) < 0) { mountSanityCheckFails(); (void) fprintf(stderr, gettext("Unable to find logical drive %s\n"), suffix); exit(2); } else if (Verbose) { (void) fprintf(stderr, gettext("Partition starts at offset %lld\n"), PartitionOffset); } } else { PartitionOffset = 0; } } void usage(void) { (void) fprintf(stderr, gettext("pcfs Usage: fsck -F pcfs [-o v|p|w] special-file\n")); exit(1); } static char *LegalOpts[] = { #define VFLAG 0 "v", #define PFLAG 1 "p", #define WFLAG 2 "w", #define DFLAG 3 "d", #define IFLAG 4 "i", #define OFLAG 5 "o", NULL }; static void parseSubOptions(char *optsstr) { char *value; int c; while (*optsstr != '\0') { switch (c = getsubopt(&optsstr, LegalOpts, &value)) { case VFLAG: Quick = 0; break; case PFLAG: Preen++; break; case WFLAG: WritableOnly++; break; case DFLAG: Verbose++; break; case IFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { InputImage = value; } break; case OFLAG: if (value == NULL) { missing_arg(LegalOpts[c]); } else { OutputImage = value; } break; default: bad_arg(value); break; } } } static void sanityCheckOpts(void) { if (WritableOnly && ReadOnly) { (void) fprintf(stderr, gettext("-w option may not be used with the -n " "or -m options\n")); exit(4); } } static void confirmMountable(char *special, int fd) { char *printName; int okayToMount = 1; printName = InputImage ? InputImage : special; if (!IsFAT32) { /* make sure we can at least read the root directory */ getRootDirectory(fd); if (TheRootDir.bytes == NULL) okayToMount = 0; } else { /* check the bit designed into FAT32 for this purpose */ okayToMount = checkFAT32CleanBit(fd); } if (okayToMount) { (void) fprintf(stderr, gettext("pcfs fsck: sanity check: %s okay\n"), printName); exit(0); } else { (void) fprintf(stderr, gettext("pcfs fsck: sanity check: %s needs checking\n"), printName); exit(32); } } void mountSanityCheckFails(void) { if (Mflag) { (void) fprintf(stderr, gettext("pcfs fsck: sanity check failed: ")); } } /* * preenBail * Routine that other routines can call if they would go into a * state where they need user input. They can send an optional * message string to be printed before the exit. Caller should * send a NULL string if they don't have an exit message. */ void preenBail(char *outString) { /* * If we are running in the 'preen' mode, we got here because * we reached a situation that would require user intervention. * We have no choice but to bail at this point. */ if (Preen) { if (outString) (void) printf("%s", outString); (void) printf(gettext("FILE SYSTEM FIX REQUIRES USER " "INTERVENTION; RUN fsck MANUALLY.\n")); exit(36); } } int main(int argc, char *argv[]) { char *string; int ifd, ofd; int c; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (init_yes() < 0) errx(2, gettext(ERR_MSG_INIT_YES), strerror(errno)); if (argc < 2) usage(); while ((c = getopt(argc, argv, "F:VYNynmo:")) != EOF) { switch (c) { case 'F': string = optarg; if (strcmp(string, "pcfs") != 0) usage(); break; case 'V': { char *opt_text; int opt_count; (void) printf(gettext("fsck -F pcfs ")); for (opt_count = 1; opt_count < argc; opt_count++) { opt_text = argv[opt_count]; if (opt_text) (void) printf(" %s ", opt_text); } (void) printf("\n"); fini_yes(); exit(0); } break; case 'N': case 'n': AlwaysYes = false; AlwaysNo = true; ReadOnly = 1; break; case 'Y': case 'y': AlwaysYes = true; AlwaysNo = false; break; case 'm': Mflag++; ReadOnly = 1; break; case 'o': string = optarg; parseSubOptions(string); break; } } sanityCheckOpts(); if (InputImage == NULL && (optind < 0 || optind >= argc)) usage(); openFS(argv[optind], &ifd, &ofd); readBPB(ifd); /* * -m mountable fs check. This call will not return. */ if (Mflag) confirmMountable(argv[optind], ifd); /* * Pass 1: Find any bad clusters and adjust the FAT and directory * entries accordingly */ passOne(ifd); /* * XXX - future passes? * Ideas: * Data relocation for bad clusters with partial read success? * Syncing backup FAT copies with main copy? * Syncing backup root sector for FAT32? */ /* * No problems if we made it this far. */ printSummary(stdout); writeBackChanges(ofd); fini_yes(); return (0); } /* * 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 2024 MNX Cloud, Inc. */ #ifndef _FSCK_PCFS_H #define _FSCK_PCFS_H /* * Structures used by the pcfs file system checker. */ #ifdef __cplusplus extern "C" { #endif #include /* * The root directory of FAT12/16 file systems doesn't sit in * a cluster. */ #define FAKE_ROOTDIR_CLUST -1 /* * The first available cluster number for a FAT fs is always the same, 2. */ #define FIRST_CLUSTER 2 #define RETURN_ON_OPEN_FAILURE 0 #define EXIT_ON_OPEN_FAILURE 1 #define NO_FAT_IN_SUMMARY 0 #define INCLUDE_FAT_IN_SUMMARY 1 #define RDCLUST_DONT_CACHE 0 #define RDCLUST_DO_CACHE 1 /* * Return values for sanityCheckSize() */ #define SIZE_MATCHED 0 #define TRUNCATED 1 #define RDCLUST_MAX_RETRY 3 #define RDCLUST_GOOD 0 #define RDCLUST_FAIL -1 #define RDCLUST_MEMERR -2 #define RDCLUST_BADINPUT -3 typedef union clustDataTypes { struct pcdir *dirp; uchar_t *bytes; } ClusterContents; struct cached { int32_t clusterNum; ClusterContents clusterData; short modified; struct cached *next; }; typedef struct cached CachedCluster; struct nameinfo { char *fullName; int references; }; /* * This structure is shared between all structures belonging to * a single file. The refcnt is a 24 bit integer, that should be * sufficient for 4GB files, even when someone uses 256 byte clusters * (4K is the typical cluster size, 512 bytes is probably the minimum) * The inefficiency of using a bit field is compensated by the memory * savings and prevented paging on large filesystems. */ struct clinfo { struct pcdir *dirent; union { struct clinfo *_nextfree; struct pcdir *_longent; } _unionelem; int32_t longEntStartClust; int refcnt:24; uint_t flags:8; uchar_t *saved; struct nameinfo *path; }; /* * #define dirent conflicts with other dirent uses, so we used the * second element instead of the first one one as union for the free * list */ #define longent _unionelem._longent #define nextfree _unionelem._nextfree typedef struct clinfo ClusterInfo; /* * Return values for allocInUse */ #define CLINFO_PREVIOUSLY_ALLOCED 1 #define CLINFO_NEWLY_ALLOCED 0 #define CLINFO_BAD 0x1 #define CLINFO_ORPHAN 0x2 #define CLINFO_HIDDEN 0x4 /* * Traversal operations for wandering the file system metadata */ #define PCFS_NO_SUBDIRS 0 #define PCFS_VISIT_SUBDIRS 1 #define PCFS_TRAVERSE_ALL 1 /* visit all nodes */ #define PCFS_FIND_ATTR 2 /* search for matching attribute */ #define PCFS_FIND_STATUS 3 /* search for same status */ #define PCFS_FIND_CHKS 4 /* find FILENNNN.CHK files */ /* * Booleans for markInUse, whether or not file is marked hidden. */ #define VISIBLE 0 #define HIDDEN 1 /* * Indices for various parts of the FILEnnnn.CHK name */ #define CHKNAME_F 0 #define CHKNAME_I 1 #define CHKNAME_L 2 #define CHKNAME_E 3 #define CHKNAME_THOUSANDS 4 #define CHKNAME_HUNDREDS 5 #define CHKNAME_TENS 6 #define CHKNAME_ONES 7 #define CHKNAME_C 0 #define CHKNAME_H 1 #define CHKNAME_K 2 /* * Largest value that will fit into our lost+found naming scheme of * FILEnnnn.CHK. */ #define MAXCHKVAL 9999 extern size_t bpsec; extern bool AlwaysYes; /* assume a yes answer to all questions */ extern bool AlwaysNo; /* assume a no answer to all questions */ /* * Function prototypes */ extern struct pcdir *addRootDirEnt(int fd, struct pcdir *copyme); extern struct pcdir *newDirEnt(struct pcdir *copyme); extern int32_t extractStartCluster(struct pcdir *dp); extern int32_t findImpactedCluster(struct pcdir *modified); extern int32_t readFATEntry(int32_t currentCluster); extern uint32_t extractSize(struct pcdir *dp); extern int32_t nextInChain(int32_t currentCluster); extern char *nextAvailableCHKName(int *chosen); extern void truncChainWithBadCluster(int fd, struct pcdir *dp, int32_t startCluster); extern void mountSanityCheckFails(void); extern void markClusterModified(int32_t clusterNum); extern void scanAndFixMetadata(int fd); extern void updateDirEnt_Start(struct pcdir *dp, int32_t newStart); extern void addEntryToCHKList(int chkNumber); extern void createCHKNameList(int fd); extern void updateDirEnt_Name(struct pcdir *dp, char *newName); extern void updateDirEnt_Size(struct pcdir *dp, uint32_t newSize); extern void getRootDirectory(int fd); extern void writeClusterMods(int fd); extern void writeRootDirMods(int fd); extern 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); extern void findBadClusters(int fd); extern void markFreeInFAT(int32_t clusterNum); extern void markLastInFAT(int32_t clusterNum); extern void writeFATEntry(int32_t currentCluster, int32_t value); extern void markBadInFAT(int32_t clusterNum); extern void printSummary(FILE *outDest); extern void squirrelPath(struct nameinfo *pathInfo, int32_t clusterNum); extern void usingCHKName(void *nameCookie); extern void writeFATMods(int fd); extern 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); extern void splitChain(int fd, struct pcdir *dp, int32_t problemCluster, struct pcdir **newdp, int32_t *orphanStart); extern void preenBail(char *outString); extern void readBPB(int fd); extern void getFAT(int fd); extern int checkFAT32CleanBit(int fd); extern int reservedInFAT(int32_t clusterNum); extern int isMarkedBad(int32_t clusterNum); extern int readCluster(int fd, int32_t clusterNum, uchar_t **data, int32_t *datasize, int shouldCache); extern int freeInFAT(int32_t clusterNum); extern int lastInFAT(int32_t clusterNum); extern int markInUse(int fd, int32_t clusterNum, struct pcdir *referencer, struct pcdir *longRef, int32_t longStartCluster, int isHidden, ClusterInfo **template); extern int badInFAT(int32_t clusterNum); #ifdef __cplusplus } #endif #endif /* _FSCK_PCFS_H */ /* * 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 by Sun Microsystems, Inc. * All rights reserved. */ /* * fsck_pcfs -- inject.c * Debugging routine that will insert random errors into the reads, * so that you'll actually end up with some bad clusters. */ #include #include #include extern ssize_t _read(int fildes, void *buf, size_t nbyte); ssize_t read(int fildes, void *buf, size_t nbyte) { static int count = 0; if ((count++ >= 263 && count <= 267) || (count >= 381 && count <= 385) || (count >= 1014 && count <= 1019) || (count >= 1119 && count <= 1123) || (count >= 1888 && count <= 1892)) { errno = EIO; return (-1); } return (_read(fildes, buf, nbyte)); }