Index: head/sbin/fsck_msdosfs/boot.c =================================================================== --- head/sbin/fsck_msdosfs/boot.c (revision 356249) +++ head/sbin/fsck_msdosfs/boot.c (revision 356250) @@ -1,364 +1,350 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 1995, 1997 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: boot.c,v 1.11 2006/06/05 16:51:18 christos Exp "); static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include "ext.h" #include "fsutil.h" int readboot(int dosfs, struct bootblock *boot) { u_char block[DOSBOOTBLOCKSIZE]; u_char fsinfo[2 * DOSBOOTBLOCKSIZE]; int ret = FSOK; if ((size_t)read(dosfs, block, sizeof block) != sizeof block) { perr("could not read boot block"); return FSFATAL; } if (block[510] != 0x55 || block[511] != 0xaa) { pfatal("Invalid signature in boot block: %02x%02x", block[511], block[510]); return FSFATAL; } memset(boot, 0, sizeof *boot); boot->ValidFat = -1; /* Decode BIOS Parameter Block */ /* Bytes per sector: can only be 512, 1024, 2048 and 4096. */ boot->bpbBytesPerSec = block[11] + (block[12] << 8); if (boot->bpbBytesPerSec < DOSBOOTBLOCKSIZE_REAL || boot->bpbBytesPerSec > DOSBOOTBLOCKSIZE || !powerof2(boot->bpbBytesPerSec)) { pfatal("Invalid sector size: %u", boot->bpbBytesPerSec); return FSFATAL; } /* Sectors per cluster: can only be: 1, 2, 4, 8, 16, 32, 64, 128. */ boot->bpbSecPerClust = block[13]; if (boot->bpbSecPerClust == 0 || !powerof2(boot->bpbSecPerClust)) { pfatal("Invalid cluster size: %u", boot->bpbSecPerClust); return FSFATAL; } /* Reserved sectors: must be non-zero */ boot->bpbResSectors = block[14] + (block[15] << 8); if (boot->bpbResSectors < 1) { pfatal("Invalid reserved sectors: %u", boot->bpbResSectors); return FSFATAL; } /* Number of FATs */ boot->bpbFATs = block[16]; if (boot->bpbFATs == 0) { pfatal("Invalid number of FATs: %u", boot->bpbFATs); return FSFATAL; } /* Root directory entries for FAT12 and FAT16 */ boot->bpbRootDirEnts = block[17] + (block[18] << 8); if (!boot->bpbRootDirEnts) { /* bpbRootDirEnts = 0 suggests that we are FAT32 */ boot->flags |= FAT32; } /* Total sectors (16 bits) */ boot->bpbSectors = block[19] + (block[20] << 8); if (boot->bpbSectors != 0 && (boot->flags & FAT32)) { pfatal("Invalid 16-bit total sector count on FAT32: %u", boot->bpbSectors); return FSFATAL; } /* Media type: ignored */ boot->bpbMedia = block[21]; /* FAT12/FAT16: 16-bit count of sectors per FAT */ boot->bpbFATsmall = block[22] + (block[23] << 8); if (boot->bpbFATsmall != 0 && (boot->flags & FAT32)) { pfatal("Invalid 16-bit FAT sector count on FAT32: %u", boot->bpbFATsmall); return FSFATAL; } /* Legacy CHS geometry numbers: ignored */ boot->SecPerTrack = block[24] + (block[25] << 8); boot->bpbHeads = block[26] + (block[27] << 8); /* Hidden sectors: ignored */ boot->bpbHiddenSecs = block[28] + (block[29] << 8) + (block[30] << 16) + (block[31] << 24); /* Total sectors (32 bits) */ boot->bpbHugeSectors = block[32] + (block[33] << 8) + (block[34] << 16) + (block[35] << 24); if (boot->bpbHugeSectors == 0) { if (boot->flags & FAT32) { pfatal("FAT32 with sector count of zero"); return FSFATAL; } else if (boot->bpbSectors == 0) { pfatal("FAT with sector count of zero"); return FSFATAL; } boot->NumSectors = boot->bpbSectors; } else { if (boot->bpbSectors != 0) { pfatal("Invalid FAT sector count"); return FSFATAL; } boot->NumSectors = boot->bpbHugeSectors; } + + + if (boot->flags & FAT32) { /* If the OEM Name field is EXFAT, it's not FAT32, so bail */ if (!memcmp(&block[3], "EXFAT ", 8)) { pfatal("exFAT filesystem is not supported."); return FSFATAL; } /* 32-bit count of sectors per FAT */ boot->FATsecs = block[36] + (block[37] << 8) + (block[38] << 16) + (block[39] << 24); if (block[40] & 0x80) boot->ValidFat = block[40] & 0x0f; /* FAT32 version, bail out if not 0.0 */ if (block[42] || block[43]) { pfatal("Unknown file system version: %x.%x", block[43], block[42]); return FSFATAL; } /* * Cluster number of the first cluster of root directory. * * Should be 2 but do not require it. */ boot->bpbRootClust = block[44] + (block[45] << 8) + (block[46] << 16) + (block[47] << 24); /* Sector number of the FSInfo structure, usually 1 */ boot->bpbFSInfo = block[48] + (block[49] << 8); /* Sector number of the backup boot block, ignored */ boot->bpbBackup = block[50] + (block[51] << 8); /* Check basic parameters */ if (boot->bpbFSInfo == 0) { /* * Either the BIOS Parameter Block has been corrupted, * or this is not a FAT32 filesystem, most likely an * exFAT filesystem. */ pfatal("Invalid FAT32 Extended BIOS Parameter Block"); return FSFATAL; } /* Read in and verify the FSInfo block */ if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec, SEEK_SET) != boot->bpbFSInfo * boot->bpbBytesPerSec || read(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) { perr("could not read fsinfo block"); return FSFATAL; } if (memcmp(fsinfo, "RRaA", 4) || memcmp(fsinfo + 0x1e4, "rrAa", 4) || fsinfo[0x1fc] || fsinfo[0x1fd] || fsinfo[0x1fe] != 0x55 || fsinfo[0x1ff] != 0xaa || fsinfo[0x3fc] || fsinfo[0x3fd] || fsinfo[0x3fe] != 0x55 || fsinfo[0x3ff] != 0xaa) { pwarn("Invalid signature in fsinfo block\n"); if (ask(0, "Fix")) { memcpy(fsinfo, "RRaA", 4); memcpy(fsinfo + 0x1e4, "rrAa", 4); fsinfo[0x1fc] = fsinfo[0x1fd] = 0; fsinfo[0x1fe] = 0x55; fsinfo[0x1ff] = 0xaa; fsinfo[0x3fc] = fsinfo[0x3fd] = 0; fsinfo[0x3fe] = 0x55; fsinfo[0x3ff] = 0xaa; if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec, SEEK_SET) != boot->bpbFSInfo * boot->bpbBytesPerSec || write(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) { perr("Unable to write bpbFSInfo"); return FSFATAL; } ret = FSBOOTMOD; } else boot->bpbFSInfo = 0; } else { /* We appear to have a valid FSInfo block, decode */ boot->FSFree = fsinfo[0x1e8] + (fsinfo[0x1e9] << 8) + (fsinfo[0x1ea] << 16) + (fsinfo[0x1eb] << 24); boot->FSNext = fsinfo[0x1ec] + (fsinfo[0x1ed] << 8) + (fsinfo[0x1ee] << 16) + (fsinfo[0x1ef] << 24); } } else { /* !FAT32: FAT12/FAT16 */ boot->FATsecs = boot->bpbFATsmall; } if (boot->FATsecs > UINT32_MAX / boot->bpbFATs) { pfatal("Invalid FATs(%u) with FATsecs(%zu)", boot->bpbFATs, (size_t)boot->FATsecs); return FSFATAL; } boot->FirstCluster = (boot->bpbRootDirEnts * 32 + boot->bpbBytesPerSec - 1) / boot->bpbBytesPerSec + boot->bpbResSectors + boot->bpbFATs * boot->FATsecs; if (boot->FirstCluster + boot->bpbSecPerClust > boot->NumSectors) { pfatal("Cluster offset too large (%u clusters)\n", boot->FirstCluster); return FSFATAL; } boot->NumClusters = (boot->NumSectors - boot->FirstCluster) / boot->bpbSecPerClust + CLUST_FIRST; - if (boot->flags & FAT32) { - if (boot->NumClusters > (CLUST_RSRVD & CLUST32_MASK)) { - pfatal("Filesystem too big (%u clusters) for FAT32 partition", - boot->NumClusters); - return FSFATAL; - } - if (boot->NumClusters < (CLUST_RSRVD & CLUST16_MASK)) { - pfatal("Filesystem too small (%u clusters) for FAT32 partition", - boot->NumClusters); - return FSFATAL; - } + if (boot->flags & FAT32) boot->ClustMask = CLUST32_MASK; - - if (boot->bpbRootClust < CLUST_FIRST || - boot->bpbRootClust >= boot->NumClusters) { - pfatal("Root directory starts with cluster out of range(%u)", - boot->bpbRootClust); - return FSFATAL; - } - } else if (boot->NumClusters < (CLUST_RSRVD&CLUST12_MASK)) { + else if (boot->NumClusters < (CLUST_RSRVD&CLUST12_MASK)) boot->ClustMask = CLUST12_MASK; - } else if (boot->NumClusters < (CLUST_RSRVD&CLUST16_MASK)) { + else if (boot->NumClusters < (CLUST_RSRVD&CLUST16_MASK)) boot->ClustMask = CLUST16_MASK; - } else { + else { pfatal("Filesystem too big (%u clusters) for non-FAT32 partition", boot->NumClusters); return FSFATAL; } switch (boot->ClustMask) { case CLUST32_MASK: boot->NumFatEntries = (boot->FATsecs * boot->bpbBytesPerSec) / 4; break; case CLUST16_MASK: boot->NumFatEntries = (boot->FATsecs * boot->bpbBytesPerSec) / 2; break; default: boot->NumFatEntries = (boot->FATsecs * boot->bpbBytesPerSec * 2) / 3; break; } if (boot->NumFatEntries < boot->NumClusters - CLUST_FIRST) { pfatal("FAT size too small, %u entries won't fit into %u sectors\n", boot->NumClusters, boot->FATsecs); return FSFATAL; } boot->ClusterSize = boot->bpbBytesPerSec * boot->bpbSecPerClust; boot->NumFiles = 1; boot->NumFree = 0; return ret; } int writefsinfo(int dosfs, struct bootblock *boot) { u_char fsinfo[2 * DOSBOOTBLOCKSIZE]; if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec, SEEK_SET) != boot->bpbFSInfo * boot->bpbBytesPerSec || read(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) { perr("could not read fsinfo block"); return FSFATAL; } fsinfo[0x1e8] = (u_char)boot->FSFree; fsinfo[0x1e9] = (u_char)(boot->FSFree >> 8); fsinfo[0x1ea] = (u_char)(boot->FSFree >> 16); fsinfo[0x1eb] = (u_char)(boot->FSFree >> 24); fsinfo[0x1ec] = (u_char)boot->FSNext; fsinfo[0x1ed] = (u_char)(boot->FSNext >> 8); fsinfo[0x1ee] = (u_char)(boot->FSNext >> 16); fsinfo[0x1ef] = (u_char)(boot->FSNext >> 24); if (lseek(dosfs, boot->bpbFSInfo * boot->bpbBytesPerSec, SEEK_SET) != boot->bpbFSInfo * boot->bpbBytesPerSec || write(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) { perr("Unable to write bpbFSInfo"); return FSFATAL; } /* * Technically, we should return FSBOOTMOD here. * * However, since Win95 OSR2 (the first M$ OS that has * support for FAT32) doesn't maintain the FSINFO block * correctly, it has to be fixed pretty often. * * Therefor, we handle the FSINFO block only informally, * fixing it if necessary, but otherwise ignoring the * fact that it was incorrect. */ return 0; } Index: head/sbin/fsck_msdosfs/check.c =================================================================== --- head/sbin/fsck_msdosfs/check.c (revision 356249) +++ head/sbin/fsck_msdosfs/check.c (revision 356250) @@ -1,169 +1,196 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: check.c,v 1.14 2006/06/05 16:51:18 christos Exp $"); static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include "ext.h" #include "fsutil.h" int checkfilesys(const char *fname) { int dosfs; struct bootblock boot; - struct fat_descriptor *fat = NULL; + struct fatEntry *fat = NULL; int finish_dosdirsection=0; + u_int i; int mod = 0; int ret = 8; rdonly = alwaysno; if (!preen) printf("** %s", fname); dosfs = open(fname, rdonly ? O_RDONLY : O_RDWR, 0); if (dosfs < 0 && !rdonly) { dosfs = open(fname, O_RDONLY, 0); if (dosfs >= 0) pwarn(" (NO WRITE)\n"); else if (!preen) printf("\n"); rdonly = 1; } else if (!preen) printf("\n"); if (dosfs < 0) { perr("Can't open `%s'", fname); printf("\n"); return 8; } if (readboot(dosfs, &boot) == FSFATAL) { close(dosfs); printf("\n"); return 8; } if (skipclean && preen && checkdirty(dosfs, &boot)) { printf("%s: ", fname); printf("FILESYSTEM CLEAN; SKIPPING CHECKS\n"); ret = 0; goto out; } if (!preen) { - printf("** Phase 1 - Read FAT and checking connectivity\n"); + if (boot.ValidFat < 0) + printf("** Phase 1 - Read and Compare FATs\n"); + else + printf("** Phase 1 - Read FAT\n"); } - mod |= readfat(dosfs, &boot, &fat); + mod |= readfat(dosfs, &boot, boot.ValidFat >= 0 ? boot.ValidFat : 0, &fat); if (mod & FSFATAL) { close(dosfs); return 8; } + if (boot.ValidFat < 0) + for (i = 1; i < boot.bpbFATs; i++) { + struct fatEntry *currentFat; + + mod |= readfat(dosfs, &boot, i, ¤tFat); + + if (mod & FSFATAL) + goto out; + + mod |= comparefat(&boot, fat, currentFat, i); + free(currentFat); + if (mod & FSFATAL) + goto out; + } + if (!preen) - printf("** Phase 2 - Checking Directories\n"); + printf("** Phase 2 - Check Cluster Chains\n"); - mod |= resetDosDirSection(fat); + mod |= checkfat(&boot, fat); + if (mod & FSFATAL) + goto out; + /* delay writing FATs */ + + if (!preen) + printf("** Phase 3 - Checking Directories\n"); + + mod |= resetDosDirSection(&boot, fat); finish_dosdirsection = 1; if (mod & FSFATAL) goto out; /* delay writing FATs */ - mod |= handleDirTree(fat); + mod |= handleDirTree(dosfs, &boot, fat); if (mod & FSFATAL) goto out; if (!preen) - printf("** Phase 3 - Checking for Lost Files\n"); + printf("** Phase 4 - Checking for Lost Files\n"); - mod |= checklost(fat); + mod |= checklost(dosfs, &boot, fat); if (mod & FSFATAL) goto out; /* now write the FATs */ - if (mod & FSFATMOD) { + if (mod & (FSFATMOD|FSFIXFAT)) { if (ask(1, "Update FATs")) { - mod |= writefat(fat); + mod |= writefat(dosfs, &boot, fat, mod & FSFIXFAT); if (mod & FSFATAL) goto out; } else mod |= FSERROR; } if (boot.NumBad) pwarn("%d files, %d free (%d clusters), %d bad (%d clusters)\n", boot.NumFiles, boot.NumFree * boot.ClusterSize / 1024, boot.NumFree, boot.NumBad * boot.ClusterSize / 1024, boot.NumBad); else pwarn("%d files, %d free (%d clusters)\n", boot.NumFiles, boot.NumFree * boot.ClusterSize / 1024, boot.NumFree); if (mod && (mod & FSERROR) == 0) { if (mod & FSDIRTY) { if (ask(1, "MARK FILE SYSTEM CLEAN") == 0) mod &= ~FSDIRTY; if (mod & FSDIRTY) { pwarn("MARKING FILE SYSTEM CLEAN\n"); - mod |= writefat(fat); + mod |= writefat(dosfs, &boot, fat, 1); } else { pwarn("\n***** FILE SYSTEM IS LEFT MARKED AS DIRTY *****\n"); mod |= FSERROR; /* file system not clean */ } } } if (mod & (FSFATAL | FSERROR)) goto out; ret = 0; out: if (finish_dosdirsection) finishDosDirSection(); free(fat); close(dosfs); if (mod & (FSFATMOD|FSDIRMOD)) pwarn("\n***** FILE SYSTEM WAS MODIFIED *****\n"); return ret; } Index: head/sbin/fsck_msdosfs/dir.c =================================================================== --- head/sbin/fsck_msdosfs/dir.c (revision 356249) +++ head/sbin/fsck_msdosfs/dir.c (revision 356250) @@ -1,1161 +1,1121 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2019 Google LLC * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * Some structure declaration borrowed from Paul Popelka * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: dir.c,v 1.20 2006/06/05 16:51:18 christos Exp $"); static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include "ext.h" #include "fsutil.h" #define SLOT_EMPTY 0x00 /* slot has never been used */ #define SLOT_E5 0x05 /* the real value is 0xe5 */ #define SLOT_DELETED 0xe5 /* file in this slot deleted */ #define ATTR_NORMAL 0x00 /* normal file */ #define ATTR_READONLY 0x01 /* file is readonly */ #define ATTR_HIDDEN 0x02 /* file is hidden */ #define ATTR_SYSTEM 0x04 /* file is a system file */ #define ATTR_VOLUME 0x08 /* entry is a volume label */ #define ATTR_DIRECTORY 0x10 /* entry is a directory name */ #define ATTR_ARCHIVE 0x20 /* file is new or modified */ #define ATTR_WIN95 0x0f /* long name record */ /* * This is the format of the contents of the deTime field in the direntry * structure. * We don't use bitfields because we don't know how compilers for * arbitrary machines will lay them out. */ #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */ #define DT_2SECONDS_SHIFT 0 #define DT_MINUTES_MASK 0x7E0 /* minutes */ #define DT_MINUTES_SHIFT 5 #define DT_HOURS_MASK 0xF800 /* hours */ #define DT_HOURS_SHIFT 11 /* * This is the format of the contents of the deDate field in the direntry * structure. */ #define DD_DAY_MASK 0x1F /* day of month */ #define DD_DAY_SHIFT 0 #define DD_MONTH_MASK 0x1E0 /* month */ #define DD_MONTH_SHIFT 5 #define DD_YEAR_MASK 0xFE00 /* year - 1980 */ #define DD_YEAR_SHIFT 9 /* dir.c */ static struct dosDirEntry *newDosDirEntry(void); static void freeDosDirEntry(struct dosDirEntry *); static struct dirTodoNode *newDirTodo(void); static void freeDirTodo(struct dirTodoNode *); static char *fullpath(struct dosDirEntry *); static u_char calcShortSum(u_char *); -static int delete(struct fat_descriptor *, cl_t, int, cl_t, int, int); -static int removede(struct fat_descriptor *, u_char *, u_char *, - cl_t, cl_t, cl_t, char *, int); -static int checksize(struct fat_descriptor *, u_char *, struct dosDirEntry *); -static int readDosDirSection(struct fat_descriptor *, struct dosDirEntry *); +static int delete(int, struct bootblock *, struct fatEntry *, cl_t, int, + cl_t, int, int); +static int removede(int, struct bootblock *, struct fatEntry *, u_char *, + u_char *, cl_t, cl_t, cl_t, char *, int); +static int checksize(struct bootblock *, struct fatEntry *, u_char *, + struct dosDirEntry *); +static int readDosDirSection(int, struct bootblock *, struct fatEntry *, + struct dosDirEntry *); /* * Manage free dosDirEntry structures. */ static struct dosDirEntry *freede; static struct dosDirEntry * newDosDirEntry(void) { struct dosDirEntry *de; if (!(de = freede)) { if (!(de = malloc(sizeof *de))) - return (NULL); + return 0; } else freede = de->next; return de; } static void freeDosDirEntry(struct dosDirEntry *de) { de->next = freede; freede = de; } /* * The same for dirTodoNode structures. */ static struct dirTodoNode *freedt; static struct dirTodoNode * newDirTodo(void) { struct dirTodoNode *dt; if (!(dt = freedt)) { if (!(dt = malloc(sizeof *dt))) return 0; } else freedt = dt->next; return dt; } static void freeDirTodo(struct dirTodoNode *dt) { dt->next = freedt; freedt = dt; } /* * The stack of unread directories */ static struct dirTodoNode *pendingDirectories = NULL; /* * Return the full pathname for a directory entry. */ static char * fullpath(struct dosDirEntry *dir) { static char namebuf[MAXPATHLEN + 1]; char *cp, *np; int nl; cp = namebuf + sizeof namebuf; *--cp = '\0'; for(;;) { np = dir->lname[0] ? dir->lname : dir->name; nl = strlen(np); if (cp <= namebuf + 1 + nl) { *--cp = '?'; break; } cp -= nl; memcpy(cp, np, nl); dir = dir->parent; if (!dir) break; *--cp = '/'; } return cp; } /* * Calculate a checksum over an 8.3 alias name */ -static inline u_char +static u_char calcShortSum(u_char *p) { u_char sum = 0; int i; for (i = 0; i < 11; i++) { sum = (sum << 7)|(sum >> 1); /* rotate right */ sum += p[i]; } return sum; } /* * Global variables temporarily used during a directory scan */ static char longName[DOSLONGNAMELEN] = ""; static u_char *buffer = NULL; static u_char *delbuf = NULL; static struct dosDirEntry *rootDir; static struct dosDirEntry *lostDir; /* * Init internal state for a new directory scan. */ int -resetDosDirSection(struct fat_descriptor *fat) +resetDosDirSection(struct bootblock *boot, struct fatEntry *fat) { - int rootdir_size, cluster_size; + int b1, b2; int ret = FSOK; size_t len; - struct bootblock *boot; - boot = fat_get_boot(fat); + b1 = boot->bpbRootDirEnts * 32; + b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec; - rootdir_size = boot->bpbRootDirEnts * 32; - cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec; - - if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) { + if ((buffer = malloc(len = MAX(b1, b2))) == NULL) { perr("No space for directory buffer (%zu)", len); return FSFATAL; } - if ((delbuf = malloc(len = cluster_size)) == NULL) { + if ((delbuf = malloc(len = b2)) == NULL) { free(buffer); perr("No space for directory delbuf (%zu)", len); return FSFATAL; } if ((rootDir = newDosDirEntry()) == NULL) { free(buffer); free(delbuf); perr("No space for directory entry"); return FSFATAL; } memset(rootDir, 0, sizeof *rootDir); if (boot->flags & FAT32) { - if (!fat_is_cl_head(fat, boot->bpbRootClust)) { + if (boot->bpbRootClust < CLUST_FIRST || + boot->bpbRootClust >= boot->NumClusters) { + pfatal("Root directory starts with cluster out of range(%u)", + boot->bpbRootClust); + return FSFATAL; + } + if (fat[boot->bpbRootClust].head != boot->bpbRootClust) { pfatal("Root directory doesn't start a cluster chain"); return FSFATAL; } + + fat[boot->bpbRootClust].flags |= FAT_USED; rootDir->head = boot->bpbRootClust; } return ret; } /* * Cleanup after a directory scan */ void finishDosDirSection(void) { struct dirTodoNode *p, *np; struct dosDirEntry *d, *nd; for (p = pendingDirectories; p; p = np) { np = p->next; freeDirTodo(p); } pendingDirectories = NULL; for (d = rootDir; d; d = nd) { if ((nd = d->child) != NULL) { d->child = 0; continue; } if (!(nd = d->next)) nd = d->parent; freeDosDirEntry(d); } rootDir = lostDir = NULL; free(buffer); free(delbuf); buffer = NULL; delbuf = NULL; } /* * Delete directory entries between startcl, startoff and endcl, endoff. */ static int -delete(struct fat_descriptor *fat, cl_t startcl, +delete(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl, int startoff, cl_t endcl, int endoff, int notlast) { u_char *s, *e; off_t off; - int clsz, fd; - struct bootblock *boot; + int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec; - boot = fat_get_boot(fat); - fd = fat_get_fd(fat); - clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec; - s = delbuf + startoff; e = delbuf + clsz; - while (fat_is_valid_cl(fat, startcl)) { + while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) { if (startcl == endcl) { if (notlast) break; e = delbuf + endoff; } off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; off *= boot->bpbBytesPerSec; - if (lseek(fd, off, SEEK_SET) != off) { + if (lseek(f, off, SEEK_SET) != off) { perr("Unable to lseek to %" PRId64, off); return FSFATAL; } - if (read(fd, delbuf, clsz) != clsz) { + if (read(f, delbuf, clsz) != clsz) { perr("Unable to read directory"); return FSFATAL; } while (s < e) { *s = SLOT_DELETED; s += 32; } - if (lseek(fd, off, SEEK_SET) != off) { + if (lseek(f, off, SEEK_SET) != off) { perr("Unable to lseek to %" PRId64, off); return FSFATAL; } - if (write(fd, delbuf, clsz) != clsz) { + if (write(f, delbuf, clsz) != clsz) { perr("Unable to write directory"); return FSFATAL; } if (startcl == endcl) break; - startcl = fat_get_cl_next(fat, startcl); + startcl = fat[startcl].next; s = delbuf; } return FSOK; } static int -removede(struct fat_descriptor *fat, u_char *start, - u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, - char *path, int type) +removede(int f, struct bootblock *boot, struct fatEntry *fat, u_char *start, + u_char *end, cl_t startcl, cl_t endcl, cl_t curcl, char *path, int type) { switch (type) { case 0: pwarn("Invalid long filename entry for %s\n", path); break; case 1: pwarn("Invalid long filename entry at end of directory %s\n", path); break; case 2: pwarn("Invalid long filename entry for volume label\n"); break; } if (ask(0, "Remove")) { if (startcl != curcl) { - if (delete(fat, + if (delete(f, boot, fat, startcl, start - buffer, endcl, end - buffer, endcl == curcl) == FSFATAL) return FSFATAL; start = buffer; } - /* startcl is < CLUST_FIRST for !FAT32 root */ + /* startcl is < CLUST_FIRST for !fat32 root */ if ((endcl == curcl) || (startcl < CLUST_FIRST)) for (; start < end; start += 32) *start = SLOT_DELETED; return FSDIRMOD; } return FSERROR; } /* * Check an in-memory file entry */ static int -checksize(struct fat_descriptor *fat, u_char *p, struct dosDirEntry *dir) +checksize(struct bootblock *boot, struct fatEntry *fat, u_char *p, + struct dosDirEntry *dir) { - int ret = FSOK; - size_t physicalSize; - struct bootblock *boot; - - boot = fat_get_boot(fat); - /* * Check size on ordinary files */ - if (dir->head == CLUST_FREE) { + u_int32_t physicalSize; + + if (dir->head == CLUST_FREE) physicalSize = 0; - } else { - if (!fat_is_valid_cl(fat, dir->head)) + else { + if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters) return FSERROR; - ret = checkchain(fat, dir->head, &physicalSize); - /* - * Upon return, physicalSize would hold the chain length - * that checkchain() was able to validate, but if the user - * refused the proposed repair, it would be unsafe to - * proceed with directory entry fix, so bail out in that - * case. - */ - if (ret == FSERROR) { - return (FSERROR); - } - physicalSize *= boot->ClusterSize; + physicalSize = fat[dir->head].length * boot->ClusterSize; } if (physicalSize < dir->size) { - pwarn("size of %s is %u, should at most be %zu\n", + pwarn("size of %s is %u, should at most be %u\n", fullpath(dir), dir->size, physicalSize); if (ask(1, "Truncate")) { dir->size = physicalSize; p[28] = (u_char)physicalSize; p[29] = (u_char)(physicalSize >> 8); p[30] = (u_char)(physicalSize >> 16); p[31] = (u_char)(physicalSize >> 24); return FSDIRMOD; } else return FSERROR; } else if (physicalSize - dir->size >= boot->ClusterSize) { pwarn("%s has too many clusters allocated\n", fullpath(dir)); if (ask(1, "Drop superfluous clusters")) { cl_t cl; u_int32_t sz, len; for (cl = dir->head, len = sz = 0; (sz += boot->ClusterSize) < dir->size; len++) - cl = fat_get_cl_next(fat, cl); - clearchain(fat, fat_get_cl_next(fat, cl)); - ret = fat_set_cl_next(fat, cl, CLUST_EOF); - return (FSFATMOD | ret); + cl = fat[cl].next; + clearchain(boot, fat, fat[cl].next); + fat[cl].next = CLUST_EOF; + fat[dir->head].length = len; + return FSFATMOD; } else return FSERROR; } return FSOK; } static const u_char dot_name[11] = ". "; static const u_char dotdot_name[11] = ".. "; /* * Basic sanity check if the subdirectory have good '.' and '..' entries, * and they are directory entries. Further sanity checks are performed * when we traverse into it. */ static int -check_subdirectory(struct fat_descriptor *fat, struct dosDirEntry *dir) +check_subdirectory(int f, struct bootblock *boot, struct dosDirEntry *dir) { u_char *buf, *cp; off_t off; cl_t cl; int retval = FSOK; - int fd; - struct bootblock *boot; - boot = fat_get_boot(fat); - fd = fat_get_fd(fat); - cl = dir->head; - if (dir->parent && !fat_is_valid_cl(fat, cl)) { + if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { return FSERROR; } if (!(boot->flags & FAT32) && !dir->parent) { off = boot->bpbResSectors + boot->bpbFATs * boot->FATsecs; } else { off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; } /* * We only need to check the first two entries of the directory, * which is found in the first sector of the directory entry, * so read in only the first sector. */ buf = malloc(boot->bpbBytesPerSec); if (buf == NULL) { perr("No space for directory buffer (%u)", boot->bpbBytesPerSec); return FSFATAL; } off *= boot->bpbBytesPerSec; - if (lseek(fd, off, SEEK_SET) != off || - read(fd, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) { + if (lseek(f, off, SEEK_SET) != off || + read(f, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) { perr("Unable to read directory"); free(buf); return FSFATAL; } /* * Both `.' and `..' must be present and be the first two entries * and be ATTR_DIRECTORY of a valid subdirectory. */ cp = buf; if (memcmp(cp, dot_name, sizeof(dot_name)) != 0 || (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { pwarn("%s: Incorrect `.' for %s.\n", __func__, dir->name); retval |= FSERROR; } cp += 32; if (memcmp(cp, dotdot_name, sizeof(dotdot_name)) != 0 || (cp[11] & ATTR_DIRECTORY) != ATTR_DIRECTORY) { pwarn("%s: Incorrect `..' for %s. \n", __func__, dir->name); retval |= FSERROR; } free(buf); return retval; } /* * Read a directory and * - resolve long name records * - enter file and directory records into the parent's list * - push directories onto the todo-stack */ static int -readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir) +readDosDirSection(int f, struct bootblock *boot, struct fatEntry *fat, + struct dosDirEntry *dir) { - struct bootblock *boot; struct dosDirEntry dirent, *d; u_char *p, *vallfn, *invlfn, *empty; off_t off; - int fd, i, j, k, iosize, entries; - bool is_legacyroot; + int i, j, k, last; cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; char *t; u_int lidx = 0; int shortSum; int mod = FSOK; - size_t dirclusters; #define THISMOD 0x8000 /* Only used within this routine */ - boot = fat_get_boot(fat); - fd = fat_get_fd(fat); - cl = dir->head; - if (dir->parent && (!fat_is_valid_cl(fat, cl))) { + if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { /* * Already handled somewhere else. */ return FSOK; } shortSum = -1; vallfn = invlfn = empty = NULL; - - /* - * If we are checking the legacy root (for FAT12/FAT16), - * we will operate on the whole directory; otherwise, we - * will operate on one cluster at a time, and also take - * this opportunity to examine the chain. - * - * Derive how many entries we are going to encounter from - * the I/O size. - */ - is_legacyroot = (dir->parent == NULL && !(boot->flags & FAT32)); - if (is_legacyroot) { - iosize = boot->bpbRootDirEnts * 32; - entries = boot->bpbRootDirEnts; - } else { - iosize = boot->bpbSecPerClust * boot->bpbBytesPerSec; - entries = iosize / 32; - mod |= checkchain(fat, dir->head, &dirclusters); - } - do { - if (is_legacyroot) { - /* - * Special case for FAT12/FAT16 root -- read - * in the whole root directory. - */ + if (!(boot->flags & FAT32) && !dir->parent) { + last = boot->bpbRootDirEnts * 32; off = boot->bpbResSectors + boot->bpbFATs * boot->FATsecs; } else { - /* - * Otherwise, read in a cluster of the - * directory. - */ + last = boot->bpbSecPerClust * boot->bpbBytesPerSec; off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; } off *= boot->bpbBytesPerSec; - if (lseek(fd, off, SEEK_SET) != off || - read(fd, buffer, iosize) != iosize) { + if (lseek(f, off, SEEK_SET) != off + || read(f, buffer, last) != last) { perr("Unable to read directory"); return FSFATAL; } - - for (p = buffer, i = 0; i < entries; i++, p += 32) { + last /= 32; + for (p = buffer, i = 0; i < last; i++, p += 32) { if (dir->fsckflags & DIREMPWARN) { *p = SLOT_EMPTY; continue; } if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { if (*p == SLOT_EMPTY) { dir->fsckflags |= DIREMPTY; empty = p; empcl = cl; } continue; } if (dir->fsckflags & DIREMPTY) { if (!(dir->fsckflags & DIREMPWARN)) { pwarn("%s has entries after end of directory\n", fullpath(dir)); if (ask(1, "Extend")) { u_char *q; dir->fsckflags &= ~DIREMPTY; - if (delete(fat, + if (delete(f, boot, fat, empcl, empty - buffer, cl, p - buffer, 1) == FSFATAL) return FSFATAL; q = ((empcl == cl) ? empty : buffer); assert(q != NULL); for (; q < p; q += 32) *q = SLOT_DELETED; mod |= THISMOD|FSDIRMOD; } else if (ask(0, "Truncate")) dir->fsckflags |= DIREMPWARN; } if (dir->fsckflags & DIREMPWARN) { *p = SLOT_DELETED; mod |= THISMOD|FSDIRMOD; continue; } else if (dir->fsckflags & DIREMPTY) mod |= FSERROR; empty = NULL; } if (p[11] == ATTR_WIN95) { if (*p & LRFIRST) { if (shortSum != -1) { if (!invlfn) { invlfn = vallfn; invcl = valcl; } } memset(longName, 0, sizeof longName); shortSum = p[13]; vallfn = p; valcl = cl; } else if (shortSum != p[13] || lidx != (*p & LRNOMASK)) { if (!invlfn) { invlfn = vallfn; invcl = valcl; } if (!invlfn) { invlfn = p; invcl = cl; } vallfn = NULL; } lidx = *p & LRNOMASK; if (lidx == 0) { pwarn("invalid long name\n"); if (!invlfn) { invlfn = vallfn; invcl = valcl; } vallfn = NULL; continue; } t = longName + --lidx * 13; for (k = 1; k < 11 && t < longName + sizeof(longName); k += 2) { if (!p[k] && !p[k + 1]) break; *t++ = p[k]; /* * Warn about those unusable chars in msdosfs here? XXX */ if (p[k + 1]) t[-1] = '?'; } if (k >= 11) for (k = 14; k < 26 && t < longName + sizeof(longName); k += 2) { if (!p[k] && !p[k + 1]) break; *t++ = p[k]; if (p[k + 1]) t[-1] = '?'; } if (k >= 26) for (k = 28; k < 32 && t < longName + sizeof(longName); k += 2) { if (!p[k] && !p[k + 1]) break; *t++ = p[k]; if (p[k + 1]) t[-1] = '?'; } if (t >= longName + sizeof(longName)) { pwarn("long filename too long\n"); if (!invlfn) { invlfn = vallfn; invcl = valcl; } vallfn = NULL; } if (p[26] | (p[27] << 8)) { pwarn("long filename record cluster start != 0\n"); if (!invlfn) { invlfn = vallfn; invcl = cl; } vallfn = NULL; } continue; /* long records don't carry further * information */ } /* * This is a standard msdosfs directory entry. */ memset(&dirent, 0, sizeof dirent); /* * it's a short name record, but we need to know * more, so get the flags first. */ dirent.flags = p[11]; /* * Translate from 850 to ISO here XXX */ for (j = 0; j < 8; j++) dirent.name[j] = p[j]; dirent.name[8] = '\0'; for (k = 7; k >= 0 && dirent.name[k] == ' '; k--) dirent.name[k] = '\0'; if (k < 0 || dirent.name[k] != '\0') k++; if (dirent.name[0] == SLOT_E5) dirent.name[0] = 0xe5; if (dirent.flags & ATTR_VOLUME) { if (vallfn || invlfn) { - mod |= removede(fat, + mod |= removede(f, boot, fat, invlfn ? invlfn : vallfn, p, invlfn ? invcl : valcl, -1, 0, fullpath(dir), 2); vallfn = NULL; invlfn = NULL; } continue; } if (p[8] != ' ') dirent.name[k++] = '.'; for (j = 0; j < 3; j++) dirent.name[k++] = p[j+8]; dirent.name[k] = '\0'; for (k--; k >= 0 && dirent.name[k] == ' '; k--) dirent.name[k] = '\0'; if (vallfn && shortSum != calcShortSum(p)) { if (!invlfn) { invlfn = vallfn; invcl = valcl; } vallfn = NULL; } dirent.head = p[26] | (p[27] << 8); if (boot->ClustMask == CLUST32_MASK) dirent.head |= (p[20] << 16) | (p[21] << 24); dirent.size = p[28] | (p[29] << 8) | (p[30] << 16) | (p[31] << 24); if (vallfn) { strlcpy(dirent.lname, longName, sizeof(dirent.lname)); longName[0] = '\0'; shortSum = -1; } dirent.parent = dir; dirent.next = dir->child; if (invlfn) { - mod |= k = removede(fat, + mod |= k = removede(f, boot, fat, invlfn, vallfn ? vallfn : p, invcl, vallfn ? valcl : cl, cl, fullpath(&dirent), 0); if (mod & FSFATAL) return FSFATAL; if (vallfn ? (valcl == cl && vallfn != buffer) : p != buffer) if (k & FSDIRMOD) mod |= THISMOD; } vallfn = NULL; /* not used any longer */ invlfn = NULL; - /* - * Check if the directory entry is sane. - * - * '.' and '..' are skipped, their sanity is - * checked somewhere else. - * - * For everything else, check if we have a new, - * valid cluster chain (beginning of a file or - * directory that was never previously claimed - * by another file) when it's a non-empty file - * or a directory. The sanity of the cluster - * chain is checked at a later time when we - * traverse into the directory, or examine the - * file's directory entry. - * - * The only possible fix is to delete the entry - * if it's a directory; for file, we have to - * truncate the size to 0. - */ - if (!(dirent.flags & ATTR_DIRECTORY) || - (strcmp(dirent.name, ".") != 0 && - strcmp(dirent.name, "..") != 0)) { - if ((dirent.size != 0 || (dirent.flags & ATTR_DIRECTORY)) && - ((!fat_is_valid_cl(fat, dirent.head) || - !fat_is_cl_head(fat, dirent.head)))) { - if (!fat_is_valid_cl(fat, dirent.head)) { - pwarn("%s starts with cluster out of range(%u)\n", - fullpath(&dirent), - dirent.head); - } else { - pwarn("%s doesn't start a new cluster chain\n", - fullpath(&dirent)); - } - - if (dirent.flags & ATTR_DIRECTORY) { - if (ask(0, "Remove")) { - *p = SLOT_DELETED; - mod |= THISMOD|FSDIRMOD; - } else - mod |= FSERROR; - continue; - } else { - if (ask(1, "Truncate")) { - p[28] = p[29] = p[30] = p[31] = 0; - p[26] = p[27] = 0; - if (boot->ClustMask == CLUST32_MASK) - p[20] = p[21] = 0; - dirent.size = 0; - dirent.head = 0; - mod |= THISMOD|FSDIRMOD; - } else - mod |= FSERROR; - } + if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) { + if (dirent.head != 0) { + pwarn("%s has clusters, but size 0\n", + fullpath(&dirent)); + if (ask(1, "Drop allocated clusters")) { + p[26] = p[27] = 0; + if (boot->ClustMask == CLUST32_MASK) + p[20] = p[21] = 0; + clearchain(boot, fat, dirent.head); + dirent.head = 0; + mod |= THISMOD|FSDIRMOD|FSFATMOD; + } else + mod |= FSERROR; } + } else if (dirent.head == 0 + && !strcmp(dirent.name, "..") + && dir->parent /* XXX */ + && !dir->parent->parent) { + /* + * Do nothing, the parent is the root + */ + } else if (dirent.head < CLUST_FIRST + || dirent.head >= boot->NumClusters + || fat[dirent.head].next == CLUST_FREE + || (fat[dirent.head].next >= CLUST_RSRVD + && fat[dirent.head].next < CLUST_EOFS) + || fat[dirent.head].head != dirent.head) { + if (dirent.head == 0) + pwarn("%s has no clusters\n", + fullpath(&dirent)); + else if (dirent.head < CLUST_FIRST + || dirent.head >= boot->NumClusters) + pwarn("%s starts with cluster out of range(%u)\n", + fullpath(&dirent), + dirent.head); + else if (fat[dirent.head].next == CLUST_FREE) + pwarn("%s starts with free cluster\n", + fullpath(&dirent)); + else if (fat[dirent.head].next >= CLUST_RSRVD) + pwarn("%s starts with cluster marked %s\n", + fullpath(&dirent), + rsrvdcltype(fat[dirent.head].next)); + else + pwarn("%s doesn't start a cluster chain\n", + fullpath(&dirent)); + if (dirent.flags & ATTR_DIRECTORY) { + if (ask(0, "Remove")) { + *p = SLOT_DELETED; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + continue; + } else { + if (ask(1, "Truncate")) { + p[28] = p[29] = p[30] = p[31] = 0; + p[26] = p[27] = 0; + if (boot->ClustMask == CLUST32_MASK) + p[20] = p[21] = 0; + dirent.size = 0; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + } } + + if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters) + fat[dirent.head].flags |= FAT_USED; + if (dirent.flags & ATTR_DIRECTORY) { /* * gather more info for directories */ struct dirTodoNode *n; if (dirent.size) { pwarn("Directory %s has size != 0\n", fullpath(&dirent)); if (ask(1, "Correct")) { p[28] = p[29] = p[30] = p[31] = 0; dirent.size = 0; mod |= THISMOD|FSDIRMOD; } else mod |= FSERROR; } /* * handle `.' and `..' specially */ if (strcmp(dirent.name, ".") == 0) { if (dirent.head != dir->head) { pwarn("`.' entry in %s has incorrect start cluster\n", fullpath(dir)); if (ask(1, "Correct")) { dirent.head = dir->head; p[26] = (u_char)dirent.head; p[27] = (u_char)(dirent.head >> 8); if (boot->ClustMask == CLUST32_MASK) { p[20] = (u_char)(dirent.head >> 16); p[21] = (u_char)(dirent.head >> 24); } mod |= THISMOD|FSDIRMOD; } else mod |= FSERROR; } continue; - } else if (strcmp(dirent.name, "..") == 0) { + } + if (strcmp(dirent.name, "..") == 0) { if (dir->parent) { /* XXX */ if (!dir->parent->parent) { if (dirent.head) { pwarn("`..' entry in %s has non-zero start cluster\n", fullpath(dir)); if (ask(1, "Correct")) { dirent.head = 0; p[26] = p[27] = 0; if (boot->ClustMask == CLUST32_MASK) p[20] = p[21] = 0; mod |= THISMOD|FSDIRMOD; } else mod |= FSERROR; } } else if (dirent.head != dir->parent->head) { pwarn("`..' entry in %s has incorrect start cluster\n", fullpath(dir)); if (ask(1, "Correct")) { dirent.head = dir->parent->head; p[26] = (u_char)dirent.head; p[27] = (u_char)(dirent.head >> 8); if (boot->ClustMask == CLUST32_MASK) { p[20] = (u_char)(dirent.head >> 16); p[21] = (u_char)(dirent.head >> 24); } mod |= THISMOD|FSDIRMOD; } else mod |= FSERROR; } } continue; } else { /* * Only one directory entry can point * to dir->head, it's '.'. */ if (dirent.head == dir->head) { pwarn("%s entry in %s has incorrect start cluster\n", dirent.name, fullpath(dir)); if (ask(1, "Remove")) { *p = SLOT_DELETED; mod |= THISMOD|FSDIRMOD; } else mod |= FSERROR; continue; - } else if ((check_subdirectory(fat, + } else if ((check_subdirectory(f, boot, &dirent) & FSERROR) == FSERROR) { /* * A subdirectory should have * a dot (.) entry and a dot-dot * (..) entry of ATTR_DIRECTORY, * we will inspect further when * traversing into it. */ if (ask(1, "Remove")) { *p = SLOT_DELETED; mod |= THISMOD|FSDIRMOD; } else mod |= FSERROR; continue; } } /* create directory tree node */ if (!(d = newDosDirEntry())) { perr("No space for directory"); return FSFATAL; } memcpy(d, &dirent, sizeof(struct dosDirEntry)); /* link it into the tree */ dir->child = d; /* Enter this directory into the todo list */ if (!(n = newDirTodo())) { perr("No space for todo list"); return FSFATAL; } n->next = pendingDirectories; n->dir = d; pendingDirectories = n; } else { - mod |= k = checksize(fat, p, &dirent); + mod |= k = checksize(boot, fat, p, &dirent); if (k & FSDIRMOD) mod |= THISMOD; } boot->NumFiles++; } - if (is_legacyroot) { - /* - * Don't bother to write back right now because - * we may continue to make modification to the - * non-FAT32 root directory below. - */ + if (!(boot->flags & FAT32) && !dir->parent) break; - } else if (mod & THISMOD) { - if (lseek(fd, off, SEEK_SET) != off - || write(fd, buffer, iosize) != iosize) { + + if (mod & THISMOD) { + last *= 32; + if (lseek(f, off, SEEK_SET) != off + || write(f, buffer, last) != last) { perr("Unable to write directory"); return FSFATAL; } mod &= ~THISMOD; } - } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl)))); + } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); if (invlfn || vallfn) - mod |= removede(fat, + mod |= removede(f, boot, fat, invlfn ? invlfn : vallfn, p, invlfn ? invcl : valcl, -1, 0, fullpath(dir), 1); - /* - * The root directory of non-FAT32 filesystems is in a special - * area and may have been modified above removede() without - * being written out. + /* The root directory of non fat32 filesystems is in a special + * area and may have been modified above without being written out. */ - if ((mod & FSDIRMOD) && is_legacyroot) { - if (lseek(fd, off, SEEK_SET) != off - || write(fd, buffer, iosize) != iosize) { + if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) { + last *= 32; + if (lseek(f, off, SEEK_SET) != off + || write(f, buffer, last) != last) { perr("Unable to write directory"); return FSFATAL; } mod &= ~THISMOD; } return mod & ~THISMOD; } int -handleDirTree(struct fat_descriptor *fat) +handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat) { int mod; - mod = readDosDirSection(fat, rootDir); + mod = readDosDirSection(dosfs, boot, fat, rootDir); if (mod & FSFATAL) return FSFATAL; /* * process the directory todo list */ while (pendingDirectories) { struct dosDirEntry *dir = pendingDirectories->dir; struct dirTodoNode *n = pendingDirectories->next; /* * remove TODO entry now, the list might change during * directory reads */ freeDirTodo(pendingDirectories); pendingDirectories = n; /* * handle subdirectory */ - mod |= readDosDirSection(fat, dir); + mod |= readDosDirSection(dosfs, boot, fat, dir); if (mod & FSFATAL) return FSFATAL; } return mod; } /* * Try to reconnect a FAT chain into dir */ static u_char *lfbuf; static cl_t lfcl; static off_t lfoff; int -reconnect(struct fat_descriptor *fat, cl_t head, size_t length) +reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head) { - struct bootblock *boot = fat_get_boot(fat); struct dosDirEntry d; - int len, dosfs; + int len; u_char *p; - dosfs = fat_get_fd(fat); - if (!ask(1, "Reconnect")) return FSERROR; if (!lostDir) { for (lostDir = rootDir->child; lostDir; lostDir = lostDir->next) { if (!strcmp(lostDir->name, LOSTDIR)) break; } if (!lostDir) { /* Create LOSTDIR? XXX */ pwarn("No %s directory\n", LOSTDIR); return FSERROR; } } if (!lfbuf) { lfbuf = malloc(boot->ClusterSize); if (!lfbuf) { perr("No space for buffer"); return FSFATAL; } p = NULL; } else p = lfbuf; while (1) { if (p) for (; p < lfbuf + boot->ClusterSize; p += 32) if (*p == SLOT_EMPTY || *p == SLOT_DELETED) break; if (p && p < lfbuf + boot->ClusterSize) break; - lfcl = p ? fat_get_cl_next(fat, lfcl) : lostDir->head; + lfcl = p ? fat[lfcl].next : lostDir->head; if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) { /* Extend LOSTDIR? XXX */ pwarn("No space in %s\n", LOSTDIR); lfcl = (lostDir->head < boot->NumClusters) ? lostDir->head : 0; return FSERROR; } lfoff = (lfcl - CLUST_FIRST) * boot->ClusterSize + boot->FirstCluster * boot->bpbBytesPerSec; if (lseek(dosfs, lfoff, SEEK_SET) != lfoff || (size_t)read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { perr("could not read LOST.DIR"); return FSFATAL; } p = lfbuf; } boot->NumFiles++; /* Ensure uniqueness of entry here! XXX */ memset(&d, 0, sizeof d); /* worst case -1 = 4294967295, 10 digits */ len = snprintf(d.name, sizeof(d.name), "%u", head); d.flags = 0; d.head = head; - d.size = length * boot->ClusterSize; + d.size = fat[head].length * boot->ClusterSize; memcpy(p, d.name, len); memset(p + len, ' ', 11 - len); memset(p + 11, 0, 32 - 11); p[26] = (u_char)d.head; p[27] = (u_char)(d.head >> 8); if (boot->ClustMask == CLUST32_MASK) { p[20] = (u_char)(d.head >> 16); p[21] = (u_char)(d.head >> 24); } p[28] = (u_char)d.size; p[29] = (u_char)(d.size >> 8); p[30] = (u_char)(d.size >> 16); p[31] = (u_char)(d.size >> 24); + fat[head].flags |= FAT_USED; if (lseek(dosfs, lfoff, SEEK_SET) != lfoff || (size_t)write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { perr("could not write LOST.DIR"); return FSFATAL; } return FSDIRMOD; } void finishlf(void) { if (lfbuf) free(lfbuf); lfbuf = NULL; } Index: head/sbin/fsck_msdosfs/dosfs.h =================================================================== --- head/sbin/fsck_msdosfs/dosfs.h (revision 356249) +++ head/sbin/fsck_msdosfs/dosfs.h (revision 356250) @@ -1,133 +1,141 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * Some structure declaration borrowed from Paul Popelka * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * $NetBSD: dosfs.h,v 1.4 1997/01/03 14:32:48 ws Exp $ * $FreeBSD$ */ #ifndef DOSFS_H #define DOSFS_H /* support 4Kn disk reads */ #define DOSBOOTBLOCKSIZE_REAL 512 #define DOSBOOTBLOCKSIZE 4096 typedef u_int32_t cl_t; /* type holding a cluster number */ /* * architecture independent description of all the info stored in a * FAT boot block. */ struct bootblock { u_int bpbBytesPerSec; /* bytes per sector */ u_int bpbSecPerClust; /* sectors per cluster */ u_int bpbResSectors; /* number of reserved sectors */ u_int bpbFATs; /* number of bpbFATs */ u_int bpbRootDirEnts; /* number of root directory entries */ u_int32_t bpbSectors; /* total number of sectors */ u_int bpbMedia; /* media descriptor */ u_int bpbFATsmall; /* number of sectors per FAT */ u_int SecPerTrack; /* sectors per track */ u_int bpbHeads; /* number of heads */ u_int32_t bpbHiddenSecs; /* # of hidden sectors */ u_int32_t bpbHugeSectors; /* # of sectors if bpbbpbSectors == 0 */ cl_t bpbRootClust; /* Start of Root Directory */ u_int bpbFSInfo; /* FSInfo sector */ u_int bpbBackup; /* Backup of Bootblocks */ cl_t FSFree; /* Number of free clusters acc. FSInfo */ cl_t FSNext; /* Next free cluster acc. FSInfo */ /* and some more calculated values */ u_int flags; /* some flags: */ #define FAT32 1 /* this is a FAT32 file system */ /* * Maybe, we should separate out * various parts of FAT32? XXX */ int ValidFat; /* valid fat if FAT32 non-mirrored */ cl_t ClustMask; /* mask for entries in FAT */ cl_t NumClusters; /* # of entries in a FAT */ u_int32_t NumSectors; /* how many sectors are there */ u_int32_t FATsecs; /* how many sectors are in FAT */ u_int32_t NumFatEntries; /* how many entries really are there */ u_int FirstCluster; /* at what sector is Cluster CLUST_FIRST */ u_int ClusterSize; /* Cluster size in bytes */ /* Now some statistics: */ u_int NumFiles; /* # of plain files */ u_int NumFree; /* # of free clusters */ u_int NumBad; /* # of bad clusters */ }; +struct fatEntry { + cl_t next; /* pointer to next cluster */ + cl_t head; /* pointer to start of chain */ + u_int32_t length; /* number of clusters on chain */ + int flags; /* see below */ +}; + #define CLUST_FREE 0 /* 0 means cluster is free */ #define CLUST_FIRST 2 /* 2 is the minimum valid cluster number */ #define CLUST_RSRVD 0xfffffff6 /* start of reserved clusters */ #define CLUST_BAD 0xfffffff7 /* a cluster with a defect */ #define CLUST_EOFS 0xfffffff8 /* start of EOF indicators */ #define CLUST_EOF 0xffffffff /* standard value for last cluster */ -#define CLUST_DEAD 0xfdeadc0d /* error encountered */ /* * Masks for cluster values */ #define CLUST12_MASK 0xfff #define CLUST16_MASK 0xffff #define CLUST32_MASK 0xfffffff + +#define FAT_USED 1 /* This fat chain is used in a file */ #define DOSLONGNAMELEN 256 /* long name maximal length */ #define LRFIRST 0x40 /* first long name record */ #define LRNOMASK 0x1f /* mask to extract long record * sequence number */ /* * Architecture independent description of a directory entry */ struct dosDirEntry { struct dosDirEntry *parent, /* previous tree level */ *next, /* next brother */ *child; /* if this is a directory */ char name[8+1+3+1]; /* alias name first part */ char lname[DOSLONGNAMELEN]; /* real name */ uint flags; /* attributes */ cl_t head; /* cluster no */ u_int32_t size; /* filesize in bytes */ uint fsckflags; /* flags during fsck */ }; /* Flags in fsckflags: */ #define DIREMPTY 1 #define DIREMPWARN 2 /* * TODO-list of unread directories */ struct dirTodoNode { struct dosDirEntry *dir; struct dirTodoNode *next; }; #endif Index: head/sbin/fsck_msdosfs/ext.h =================================================================== --- head/sbin/fsck_msdosfs/ext.h (revision 356249) +++ head/sbin/fsck_msdosfs/ext.h (revision 356250) @@ -1,152 +1,143 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * $NetBSD: ext.h,v 1.6 2000/04/25 23:02:51 jdolecek Exp $ * $FreeBSD$ */ #ifndef EXT_H #define EXT_H #include -#include - #include "dosfs.h" #define LOSTDIR "LOST.DIR" /* * Options: */ extern int alwaysno; /* assume "no" for all questions */ extern int alwaysyes; /* assume "yes" for all questions */ extern int preen; /* we are preening */ extern int rdonly; /* device is opened read only (supersedes above) */ extern int skipclean; /* skip clean file systems if preening */ -extern int allow_mmap; /* allow the use of mmap() */ /* * function declarations */ int ask(int, const char *, ...) __printflike(2, 3); /* * Check the dirty flag. If the file system is clean, then return 1. * Otherwise, return 0 (this includes the case of FAT12 file systems -- * they have no dirty flag, so they must be assumed to be unclean). */ int checkdirty(int, struct bootblock *); /* * Check file system given as arg */ int checkfilesys(const char *); /* * Return values of various functions */ #define FSOK 0 /* Check was OK */ #define FSBOOTMOD 1 /* Boot block was modified */ #define FSDIRMOD 2 /* Some directory was modified */ #define FSFATMOD 4 /* The FAT was modified */ #define FSERROR 8 /* Some unrecovered error remains */ #define FSFATAL 16 /* Some unrecoverable error occurred */ #define FSDIRTY 32 /* File system is dirty */ +#define FSFIXFAT 64 /* Fix file system FAT */ /* * read a boot block in a machine independent fashion and translate * it into our struct bootblock. */ int readboot(int, struct bootblock *); /* * Correct the FSInfo block. */ int writefsinfo(int, struct bootblock *); -/* Opaque type */ -struct fat_descriptor; +/* + * Read one of the FAT copies and return a pointer to the new + * allocated array holding our description of it. + */ +int readfat(int, struct bootblock *, u_int, struct fatEntry **); -void fat_clear_cl_head(struct fat_descriptor *, cl_t); -bool fat_is_cl_head(struct fat_descriptor *, cl_t); +/* + * Check two FAT copies for consistency and merge changes into the + * first if necessary. + */ +int comparefat(struct bootblock *, struct fatEntry *, struct fatEntry *, u_int); -cl_t fat_get_cl_next(struct fat_descriptor *, cl_t); - -int fat_set_cl_next(struct fat_descriptor *, cl_t, cl_t); - -cl_t fat_allocate_cluster(struct fat_descriptor *fat); - -struct bootblock* fat_get_boot(struct fat_descriptor *); -int fat_get_fd(struct fat_descriptor *); -bool fat_is_valid_cl(struct fat_descriptor *, cl_t); - /* - * Read the FAT 0 and return a pointer to the newly allocated - * descriptor of it. + * Check a FAT */ -int readfat(int, struct bootblock *, struct fat_descriptor **); +int checkfat(struct bootblock *, struct fatEntry *); /* * Write back FAT entries */ -int writefat(struct fat_descriptor *); +int writefat(int, struct bootblock *, struct fatEntry *, int); /* * Read a directory */ -int resetDosDirSection(struct fat_descriptor *); +int resetDosDirSection(struct bootblock *, struct fatEntry *); void finishDosDirSection(void); -int handleDirTree(struct fat_descriptor *); +int handleDirTree(int, struct bootblock *, struct fatEntry *); /* * Cross-check routines run after everything is completely in memory */ -int checkchain(struct fat_descriptor *, cl_t, size_t *); - /* * Check for lost cluster chains */ -int checklost(struct fat_descriptor *); +int checklost(int, struct bootblock *, struct fatEntry *); /* * Try to reconnect a lost cluster chain */ -int reconnect(struct fat_descriptor *, cl_t, size_t); +int reconnect(int, struct bootblock *, struct fatEntry *, cl_t); void finishlf(void); /* * Small helper functions */ /* * Return the type of a reserved cluster as text */ const char *rsrvdcltype(cl_t); /* * Clear a cluster chain in a FAT */ -void clearchain(struct fat_descriptor *, cl_t); +void clearchain(struct bootblock *, struct fatEntry *, cl_t); #endif Index: head/sbin/fsck_msdosfs/fat.c =================================================================== --- head/sbin/fsck_msdosfs/fat.c (revision 356249) +++ head/sbin/fsck_msdosfs/fat.c (revision 356250) @@ -1,1271 +1,726 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * - * Copyright (c) 2019 Google LLC * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: fat.c,v 1.18 2006/06/05 16:51:18 christos Exp $"); static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ -#include -#include -#include -#include -#include - -#include -#include #include #include #include #include #include #include "ext.h" #include "fsutil.h" -static int _readfat(struct fat_descriptor *); -static inline struct bootblock* boot_of_(struct fat_descriptor *); -static inline int fd_of_(struct fat_descriptor *); -static inline bool valid_cl(struct fat_descriptor *, cl_t); +static int checkclnum(struct bootblock *, u_int, cl_t, cl_t *); +static int clustdiffer(cl_t, cl_t *, cl_t *, u_int); +static int tryclear(struct bootblock *, struct fatEntry *, cl_t, cl_t *); +static int _readfat(int, struct bootblock *, u_int, u_char **); - -/* - * Head bitmap for FAT scanning. - * - * FAT32 have up to 2^28 = 256M entries, and FAT16/12 have much less. - * For each cluster, we use 1 bit to represent if it's a head cluster - * (the first cluster of a cluster chain). - * - * Head bitmap - * =========== - * Initially, we set all bits to 1. In readfat(), we traverse the - * whole FAT and mark each cluster identified as "next" cluster as - * 0. After the scan, we have a bitmap with 1's to indicate the - * corresponding cluster was a "head" cluster. - * - * We use head bitmap to identify lost chains: a head cluster that was - * not being claimed by any file or directories is the head cluster of - * a lost chain. - * - * Handle of lost chains - * ===================== - * At the end of scanning, we can easily find all lost chain's heads - * by finding out the 1's in the head bitmap. - */ - -typedef struct long_bitmap { - unsigned long *map; - size_t count; /* Total set bits in the map */ -} long_bitmap_t; - -static inline void -bitmap_clear(long_bitmap_t *lbp, cl_t cl) -{ - cl_t i = cl / LONG_BIT; - unsigned long clearmask = ~(1UL << (cl % LONG_BIT)); - - assert((lbp->map[i] & ~clearmask) != 0); - lbp->map[i] &= clearmask; - lbp->count--; -} - -static inline bool -bitmap_get(long_bitmap_t *lbp, cl_t cl) -{ - cl_t i = cl / LONG_BIT; - unsigned long usedbit = 1UL << (cl % LONG_BIT); - - return ((lbp->map[i] & usedbit) == usedbit); -} - -static inline bool -bitmap_none_in_range(long_bitmap_t *lbp, cl_t cl) -{ - cl_t i = cl / LONG_BIT; - - return (lbp->map[i] == 0); -} - -static inline size_t -bitmap_count(long_bitmap_t *lbp) -{ - return (lbp->count); -} - -static int -bitmap_ctor(long_bitmap_t *lbp, size_t bits, bool allone) -{ - size_t bitmap_size = roundup2(bits, LONG_BIT) / (LONG_BIT / 8); - - free(lbp->map); - lbp->map = calloc(1, bitmap_size); - if (lbp->map == NULL) - return FSFATAL; - - if (allone) { - memset(lbp->map, 0xff, bitmap_size); - lbp->count = bits; - } else { - lbp->count = 0; - } - return FSOK; -} - -static void -bitmap_dtor(long_bitmap_t *lbp) -{ - free(lbp->map); - lbp->map = NULL; -} - -/* - * FAT32 can be as big as 256MiB (2^26 entries * 4 bytes), when we - * can not ask the kernel to manage the access, use a simple LRU - * cache with chunk size of MAXPHYS (128 KiB) to manage it. - */ -struct fat32_cache_entry { - TAILQ_ENTRY(fat32_cache_entry) entries; - uint8_t *chunk; /* pointer to chunk */ - off_t addr; /* offset */ - bool dirty; /* dirty bit */ -}; - -static const size_t fat32_cache_chunk_size = MAXPHYS; -static const size_t fat32_cache_size = 4 * 1024 * 1024; /* 4MiB */ -static const size_t fat32_cache_entries = howmany(fat32_cache_size, fat32_cache_chunk_size); - -/* - * FAT table descriptor, represents a FAT table that is already loaded - * into memory. - */ -struct fat_descriptor { - struct bootblock *boot; - uint8_t *fatbuf; - cl_t (*get)(struct fat_descriptor *, cl_t); - int (*set)(struct fat_descriptor *, cl_t, cl_t); - long_bitmap_t headbitmap; - int fd; - bool is_mmapped; - bool use_cache; - size_t fatsize; - - size_t fat32_cached_chunks; - TAILQ_HEAD(cachehead, fat32_cache_entry) fat32_cache_head; - struct fat32_cache_entry *fat32_cache_allentries; - off_t fat32_offset; - off_t fat32_lastaddr; -}; - -void -fat_clear_cl_head(struct fat_descriptor *fat, cl_t cl) -{ - bitmap_clear(&fat->headbitmap, cl); -} - -bool -fat_is_cl_head(struct fat_descriptor *fat, cl_t cl) -{ - return (bitmap_get(&fat->headbitmap, cl)); -} - -static inline bool -fat_is_cl_head_in_range(struct fat_descriptor *fat, cl_t cl) -{ - return (!(bitmap_none_in_range(&fat->headbitmap, cl))); -} - -static size_t -fat_get_head_count(struct fat_descriptor *fat) -{ - return (bitmap_count(&fat->headbitmap)); -} - -/* - * FAT12 accessors. - * - * FAT12s are sufficiently small, expect it to always fit in the RAM. - */ -static inline uint8_t * -fat_get_fat12_ptr(struct fat_descriptor *fat, cl_t cl) -{ - return (fat->fatbuf + ((cl + (cl >> 1)))); -} - -static cl_t -fat_get_fat12_next(struct fat_descriptor *fat, cl_t cl) -{ - const uint8_t *p; - cl_t retval; - - p = fat_get_fat12_ptr(fat, cl); - retval = le16dec(p); - /* Odd cluster: lower 4 bits belongs to the subsequent cluster */ - if ((cl & 1) == 1) - retval >>= 4; - retval &= CLUST12_MASK; - - if (retval >= (CLUST_BAD & CLUST12_MASK)) - retval |= ~CLUST12_MASK; - - return (retval); -} - -static int -fat_set_fat12_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl) -{ - uint8_t *p; - - /* Truncate 'nextcl' value, if needed */ - nextcl &= CLUST12_MASK; - - p = fat_get_fat12_ptr(fat, cl); - - /* - * Read in the 4 bits from the subsequent (for even clusters) - * or the preceding (for odd clusters) cluster and combine - * it to the nextcl value for encoding - */ - if ((cl & 1) == 0) { - nextcl |= ((p[1] & 0xf0) << 8); - } else { - nextcl <<= 4; - nextcl |= (p[0] & 0x0f); - } - - le16enc(p, (uint16_t)nextcl); - - return (0); -} - -/* - * FAT16 accessors. - * - * FAT16s are sufficiently small, expect it to always fit in the RAM. - */ -static inline uint8_t * -fat_get_fat16_ptr(struct fat_descriptor *fat, cl_t cl) -{ - return (fat->fatbuf + (cl << 1)); -} - -static cl_t -fat_get_fat16_next(struct fat_descriptor *fat, cl_t cl) -{ - const uint8_t *p; - cl_t retval; - - p = fat_get_fat16_ptr(fat, cl); - retval = le16dec(p) & CLUST16_MASK; - - if (retval >= (CLUST_BAD & CLUST16_MASK)) - retval |= ~CLUST16_MASK; - - return (retval); -} - -static int -fat_set_fat16_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl) -{ - uint8_t *p; - - /* Truncate 'nextcl' value, if needed */ - nextcl &= CLUST16_MASK; - - p = fat_get_fat16_ptr(fat, cl); - - le16enc(p, (uint16_t)nextcl); - - return (0); -} - -/* - * FAT32 accessors. - */ -static inline uint8_t * -fat_get_fat32_ptr(struct fat_descriptor *fat, cl_t cl) -{ - return (fat->fatbuf + (cl << 2)); -} - -static cl_t -fat_get_fat32_next(struct fat_descriptor *fat, cl_t cl) -{ - const uint8_t *p; - cl_t retval; - - p = fat_get_fat32_ptr(fat, cl); - retval = le32dec(p) & CLUST32_MASK; - - if (retval >= (CLUST_BAD & CLUST32_MASK)) - retval |= ~CLUST32_MASK; - - return (retval); -} - -static int -fat_set_fat32_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl) -{ - uint8_t *p; - - /* Truncate 'nextcl' value, if needed */ - nextcl &= CLUST32_MASK; - - p = fat_get_fat32_ptr(fat, cl); - - le32enc(p, (uint32_t)nextcl); - - return (0); -} - -static inline size_t -fat_get_iosize(struct fat_descriptor *fat, off_t address) -{ - - if (address == fat->fat32_lastaddr) { - return (fat->fatsize & ((off_t)MAXPHYS - 1)); - } else { - return (MAXPHYS); - } -} - -static int -fat_flush_fat32_cache_entry(struct fat_descriptor *fat, - struct fat32_cache_entry *entry) -{ - int fd; - off_t fat_addr; - size_t writesize; - - fd = fd_of_(fat); - - if (!entry->dirty) - return (FSOK); - - writesize = fat_get_iosize(fat, entry->addr); - - fat_addr = fat->fat32_offset + entry->addr; - if (lseek(fd, fat_addr, SEEK_SET) != fat_addr || - (size_t)write(fd, entry->chunk, writesize) != writesize) { - pfatal("Unable to write FAT"); - return (FSFATAL); - } - - entry->dirty = false; - return (FSOK); -} - -static struct fat32_cache_entry * -fat_get_fat32_cache_entry(struct fat_descriptor *fat, off_t addr, - bool writing) -{ - int fd; - struct fat32_cache_entry *entry, *first; - off_t fat_addr; - size_t rwsize; - - addr &= ~(fat32_cache_chunk_size - 1); - - first = TAILQ_FIRST(&fat->fat32_cache_head); - - /* - * Cache hit: if we already have the chunk, move it to list head - */ - TAILQ_FOREACH(entry, &fat->fat32_cache_head, entries) { - if (entry->addr == addr) { - if (writing) { - entry->dirty = true; - } - if (entry != first) { - - TAILQ_REMOVE(&fat->fat32_cache_head, entry, entries); - TAILQ_INSERT_HEAD(&fat->fat32_cache_head, entry, entries); - } - return (entry); - } - } - - /* - * Cache miss: detach the chunk at tail of list, overwrite with - * the located chunk, and populate with data from disk. - */ - entry = TAILQ_LAST(&fat->fat32_cache_head, cachehead); - TAILQ_REMOVE(&fat->fat32_cache_head, entry, entries); - if (fat_flush_fat32_cache_entry(fat, entry) != FSOK) { - return (NULL); - } - - rwsize = fat_get_iosize(fat, addr); - fat_addr = fat->fat32_offset + addr; - entry->addr = addr; - fd = fd_of_(fat); - if (lseek(fd, fat_addr, SEEK_SET) != fat_addr || - (size_t)read(fd, entry->chunk, rwsize) != rwsize) { - pfatal("Unable to read FAT"); - return (NULL); - } - if (writing) { - entry->dirty = true; - } - TAILQ_INSERT_HEAD(&fat->fat32_cache_head, entry, entries); - - return (entry); -} - -static inline uint8_t * -fat_get_fat32_cached_ptr(struct fat_descriptor *fat, cl_t cl, bool writing) -{ - off_t addr, off; - struct fat32_cache_entry *entry; - - addr = cl << 2; - entry = fat_get_fat32_cache_entry(fat, addr, writing); - - if (entry != NULL) { - off = addr & (fat32_cache_chunk_size - 1); - return (entry->chunk + off); - } else { - return (NULL); - } -} - - -static cl_t -fat_get_fat32_cached_next(struct fat_descriptor *fat, cl_t cl) -{ - const uint8_t *p; - cl_t retval; - - p = fat_get_fat32_cached_ptr(fat, cl, false); - if (p != NULL) { - retval = le32dec(p) & CLUST32_MASK; - if (retval >= (CLUST_BAD & CLUST32_MASK)) - retval |= ~CLUST32_MASK; - } else { - retval = CLUST_DEAD; - } - - return (retval); -} - -static int -fat_set_fat32_cached_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl) -{ - uint8_t *p; - - /* Truncate 'nextcl' value, if needed */ - nextcl &= CLUST32_MASK; - - p = fat_get_fat32_cached_ptr(fat, cl, true); - if (p != NULL) { - le32enc(p, (uint32_t)nextcl); - return FSOK; - } else { - return FSFATAL; - } -} - -cl_t fat_get_cl_next(struct fat_descriptor *fat, cl_t cl) -{ - - if (!valid_cl(fat, cl)) { - pfatal("Invalid cluster: %ud", cl); - return CLUST_DEAD; - } - - return (fat->get(fat, cl)); -} - -int fat_set_cl_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl) -{ - - if (rdonly) { - pwarn(" (NO WRITE)\n"); - return FSFATAL; - } - - if (!valid_cl(fat, cl)) { - pfatal("Invalid cluster: %ud", cl); - return FSFATAL; - } - - return (fat->set(fat, cl, nextcl)); -} - -static inline struct bootblock* -boot_of_(struct fat_descriptor *fat) { - - return (fat->boot); -} - -struct bootblock* -fat_get_boot(struct fat_descriptor *fat) { - - return (boot_of_(fat)); -} - -static inline int -fd_of_(struct fat_descriptor *fat) -{ - return (fat->fd); -} - -int -fat_get_fd(struct fat_descriptor * fat) -{ - return (fd_of_(fat)); -} - -/* - * Whether a cl is in valid data range. - */ -bool -fat_is_valid_cl(struct fat_descriptor *fat, cl_t cl) -{ - - return (valid_cl(fat, cl)); -} - -static inline bool -valid_cl(struct fat_descriptor *fat, cl_t cl) -{ - const struct bootblock *boot = boot_of_(fat); - - return (cl >= CLUST_FIRST && cl < boot->NumClusters); -} - -/* +/*- * The first 2 FAT entries contain pseudo-cluster numbers with the following * layout: * * 31...... ........ ........ .......0 * rrrr1111 11111111 11111111 mmmmmmmm FAT32 entry 0 * rrrrsh11 11111111 11111111 11111xxx FAT32 entry 1 * * 11111111 mmmmmmmm FAT16 entry 0 * sh111111 11111xxx FAT16 entry 1 * * r = reserved * m = BPB media ID byte * s = clean flag (1 = dismounted; 0 = still mounted) * h = hard error flag (1 = ok; 0 = I/O error) * x = any value ok */ int checkdirty(int fs, struct bootblock *boot) { off_t off; u_char *buffer; int ret = 0; size_t len; if (boot->ClustMask != CLUST16_MASK && boot->ClustMask != CLUST32_MASK) return 0; off = boot->bpbResSectors; off *= boot->bpbBytesPerSec; buffer = malloc(len = boot->bpbBytesPerSec); if (buffer == NULL) { perr("No space for FAT sectors (%zu)", len); return 1; } if (lseek(fs, off, SEEK_SET) != off) { perr("Unable to read FAT"); goto err; } if ((size_t)read(fs, buffer, boot->bpbBytesPerSec) != boot->bpbBytesPerSec) { perr("Unable to read FAT"); goto err; } /* * If we don't understand the FAT, then the file system must be * assumed to be unclean. */ if (buffer[0] != boot->bpbMedia || buffer[1] != 0xff) goto err; if (boot->ClustMask == CLUST16_MASK) { if ((buffer[2] & 0xf8) != 0xf8 || (buffer[3] & 0x3f) != 0x3f) goto err; } else { if (buffer[2] != 0xff || (buffer[3] & 0x0f) != 0x0f || (buffer[4] & 0xf8) != 0xf8 || buffer[5] != 0xff || buffer[6] != 0xff || (buffer[7] & 0x03) != 0x03) goto err; } /* * Now check the actual clean flag (and the no-error flag). */ if (boot->ClustMask == CLUST16_MASK) { if ((buffer[3] & 0xc0) == 0xc0) ret = 1; } else { if ((buffer[7] & 0x0c) == 0x0c) ret = 1; } err: free(buffer); return ret; } /* - * Read a FAT from disk. Returns 1 if successful, 0 otherwise. + * Check a cluster number for valid value */ static int -_readfat(struct fat_descriptor *fat) +checkclnum(struct bootblock *boot, u_int fat, cl_t cl, cl_t *next) { - int fd; - size_t i; - off_t off; - size_t readsize; - struct bootblock *boot; - struct fat32_cache_entry *entry; - - boot = boot_of_(fat); - fd = fd_of_(fat); - fat->fatsize = boot->FATsecs * boot->bpbBytesPerSec; - - off = boot->bpbResSectors; - off *= boot->bpbBytesPerSec; - - fat->is_mmapped = false; - fat->use_cache = false; - - /* Attempt to mmap() first */ - if (allow_mmap) { - fat->fatbuf = mmap(NULL, fat->fatsize, - PROT_READ | (rdonly ? 0 : PROT_WRITE), - MAP_SHARED, fd_of_(fat), off); - if (fat->fatbuf != MAP_FAILED) { - fat->is_mmapped = true; - return 1; + if (*next >= (CLUST_RSRVD&boot->ClustMask)) + *next |= ~boot->ClustMask; + if (*next == CLUST_FREE) { + boot->NumFree++; + return FSOK; + } + if (*next == CLUST_BAD) { + boot->NumBad++; + return FSOK; + } + if (*next < CLUST_FIRST + || (*next >= boot->NumClusters && *next < CLUST_EOFS)) { + pwarn("Cluster %u in FAT %d continues with %s cluster number %u\n", + cl, fat, + *next < CLUST_RSRVD ? "out of range" : "reserved", + *next&boot->ClustMask); + if (ask(0, "Truncate")) { + *next = CLUST_EOF; + return FSFATMOD; } + return FSERROR; } + return FSOK; +} - /* - * Unfortunately, we were unable to mmap(). - * - * Only use the cache manager when it's necessary, that is, - * when the FAT is sufficiently large; in that case, only - * read in the first 4 MiB of FAT into memory, and split the - * buffer into chunks and insert to the LRU queue to populate - * the cache with data. - */ - if (boot->ClustMask == CLUST32_MASK && - fat->fatsize >= fat32_cache_size) { - readsize = fat32_cache_size; - fat->use_cache = true; +/* + * Read a FAT from disk. Returns 1 if successful, 0 otherwise. + */ +static int +_readfat(int fs, struct bootblock *boot, u_int no, u_char **buffer) +{ + off_t off; - fat->fat32_offset = boot->bpbResSectors * boot->bpbBytesPerSec; - fat->fat32_lastaddr = fat->fatsize & ~(fat32_cache_chunk_size); - } else { - readsize = fat->fatsize; - } - fat->fatbuf = malloc(readsize); - if (fat->fatbuf == NULL) { - perr("No space for FAT (%zu)", readsize); + *buffer = calloc(boot->FATsecs, boot->bpbBytesPerSec); + if (*buffer == NULL) { + perr("No space for FAT sectors (%zu)", + (size_t)boot->FATsecs); return 0; } - if (lseek(fd, off, SEEK_SET) != off) { + off = boot->bpbResSectors + no * boot->FATsecs; + off *= boot->bpbBytesPerSec; + + if (lseek(fs, off, SEEK_SET) != off) { perr("Unable to read FAT"); goto err; } - if ((size_t)read(fd, fat->fatbuf, readsize) != readsize) { + + if ((size_t)read(fs, *buffer, boot->FATsecs * boot->bpbBytesPerSec) + != boot->FATsecs * boot->bpbBytesPerSec) { perr("Unable to read FAT"); goto err; } - /* - * When cache is used, split the buffer into chunks, and - * connect the buffer into the cache. - */ - if (fat->use_cache) { - TAILQ_INIT(&fat->fat32_cache_head); - entry = calloc(fat32_cache_entries, sizeof(*entry)); - if (entry == NULL) { - perr("No space for FAT cache (%zu of %zu)", - fat32_cache_entries, sizeof(entry)); - goto err; - } - for (i = 0; i < fat32_cache_entries; i++) { - entry[i].addr = fat32_cache_chunk_size * i; - entry[i].chunk = &fat->fatbuf[entry[i].addr]; - TAILQ_INSERT_TAIL(&fat->fat32_cache_head, - &entry[i], entries); - } - fat->fat32_cache_allentries = entry; - } - return 1; -err: - free(fat->fatbuf); - fat->fatbuf = NULL; + err: + free(*buffer); return 0; } -static void -releasefat(struct fat_descriptor *fat) -{ - if (fat->is_mmapped) { - munmap(fat->fatbuf, fat->fatsize); - } else { - if (fat->use_cache) { - free(fat->fat32_cache_allentries); - fat->fat32_cache_allentries = NULL; - } - free(fat->fatbuf); - } - fat->fatbuf = NULL; - bitmap_dtor(&fat->headbitmap); -} - /* - * Read or map a FAT and populate head bitmap + * Read a FAT and decode it into internal format */ int -readfat(int fs, struct bootblock *boot, struct fat_descriptor **fp) +readfat(int fs, struct bootblock *boot, u_int no, struct fatEntry **fp) { - struct fat_descriptor *fat; + struct fatEntry *fat; u_char *buffer, *p; - cl_t cl, nextcl; + cl_t cl; int ret = FSOK; boot->NumFree = boot->NumBad = 0; - fat = calloc(1, sizeof(struct fat_descriptor)); - if (fat == NULL) { - perr("No space for FAT descriptor"); + if (!_readfat(fs, boot, no, &buffer)) return FSFATAL; - } - fat->fd = fs; - fat->boot = boot; - - if (!_readfat(fat)) { - free(fat); - return FSFATAL; - } - buffer = fat->fatbuf; - - /* Populate accessors */ - switch(boot->ClustMask) { - case CLUST12_MASK: - fat->get = fat_get_fat12_next; - fat->set = fat_set_fat12_next; - break; - case CLUST16_MASK: - fat->get = fat_get_fat16_next; - fat->set = fat_set_fat16_next; - break; - case CLUST32_MASK: - if (fat->is_mmapped || !fat->use_cache) { - fat->get = fat_get_fat32_next; - fat->set = fat_set_fat32_next; - } else { - fat->get = fat_get_fat32_cached_next; - fat->set = fat_set_fat32_cached_next; - } - break; - default: - pfatal("Invalid ClustMask: %d", boot->ClustMask); - releasefat(fat); - free(fat); - return FSFATAL; - } - - if (bitmap_ctor(&fat->headbitmap, boot->NumClusters, - true) != FSOK) { - perr("No space for head bitmap for FAT clusters (%zu)", + fat = calloc(boot->NumClusters, sizeof(struct fatEntry)); + if (fat == NULL) { + perr("No space for FAT clusters (%zu)", (size_t)boot->NumClusters); - releasefat(fat); - free(fat); + free(buffer); return FSFATAL; } if (buffer[0] != boot->bpbMedia || buffer[1] != 0xff || buffer[2] != 0xff || (boot->ClustMask == CLUST16_MASK && buffer[3] != 0xff) || (boot->ClustMask == CLUST32_MASK && ((buffer[3]&0x0f) != 0x0f || buffer[4] != 0xff || buffer[5] != 0xff || buffer[6] != 0xff || (buffer[7]&0x0f) != 0x0f))) { /* Windows 95 OSR2 (and possibly any later) changes * the FAT signature to 0xXXffff7f for FAT16 and to * 0xXXffff0fffffff07 for FAT32 upon boot, to know that the * file system is dirty if it doesn't reboot cleanly. * Check this special condition before errorring out. */ if (buffer[0] == boot->bpbMedia && buffer[1] == 0xff && buffer[2] == 0xff && ((boot->ClustMask == CLUST16_MASK && buffer[3] == 0x7f) || (boot->ClustMask == CLUST32_MASK && buffer[3] == 0x0f && buffer[4] == 0xff && buffer[5] == 0xff && buffer[6] == 0xff && buffer[7] == 0x07))) ret |= FSDIRTY; else { /* just some odd byte sequence in FAT */ switch (boot->ClustMask) { case CLUST32_MASK: pwarn("%s (%02x%02x%02x%02x%02x%02x%02x%02x)\n", "FAT starts with odd byte sequence", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]); break; case CLUST16_MASK: pwarn("%s (%02x%02x%02x%02x)\n", "FAT starts with odd byte sequence", buffer[0], buffer[1], buffer[2], buffer[3]); break; default: pwarn("%s (%02x%02x%02x)\n", "FAT starts with odd byte sequence", buffer[0], buffer[1], buffer[2]); break; } - if (ask(1, "Correct")) { - ret |= FSFATMOD; - p = buffer; - *p++ = (u_char)boot->bpbMedia; - *p++ = 0xff; - *p++ = 0xff; - switch (boot->ClustMask) { - case CLUST16_MASK: - *p++ = 0xff; - break; - case CLUST32_MASK: - *p++ = 0x0f; - *p++ = 0xff; - *p++ = 0xff; - *p++ = 0xff; - *p++ = 0x0f; - break; - default: - break; - } - } + if (ask(1, "Correct")) + ret |= FSFIXFAT; } } - - /* - * Traverse the FAT table and populate head map. Initially, we - * consider all clusters as possible head cluster (beginning of - * a file or directory), and traverse the whole allocation table - * by marking every non-head nodes as such (detailed below) and - * fix obvious issues while we walk. - * - * For each "next" cluster, the possible values are: - * - * a) CLUST_FREE or CLUST_BAD. The *current* cluster can't be a - * head node. - * b) An out-of-range value. The only fix would be to truncate at - * the cluster. - * c) A valid cluster. It means that cluster (nextcl) is not a - * head cluster. Note that during the scan, every cluster is - * expected to be seen for at most once, and when we saw them - * twice, it means a cross-linked chain which should be - * truncated at the current cluster. - * - * After scan, the remaining set bits indicates all possible - * head nodes, because they were never claimed by any other - * node as the next node, but we do not know if these chains - * would end with a valid EOF marker. We will check that in - * checkchain() at a later time when checking directories, - * where these head nodes would be marked as non-head. - * - * In the final pass, all head nodes should be cleared, and if - * there is still head nodes, these would be leaders of lost - * chain. - */ - for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) { - nextcl = fat_get_cl_next(fat, cl); - - /* Check if the next cluster number is valid */ - if (nextcl == CLUST_FREE) { - /* Save a hint for next free cluster */ - if (boot->FSNext == 0) { - boot->FSNext = cl; - } - if (fat_is_cl_head(fat, cl)) { - fat_clear_cl_head(fat, cl); - } - boot->NumFree++; - } else if (nextcl == CLUST_BAD) { - if (fat_is_cl_head(fat, cl)) { - fat_clear_cl_head(fat, cl); - } - boot->NumBad++; - } else if (!valid_cl(fat, nextcl) && nextcl < CLUST_EOFS) { - pwarn("Cluster %u continues with %s " - "cluster number %u\n", - cl, (nextcl < CLUST_RSRVD) ? - "out of range" : "reserved", - nextcl & boot->ClustMask); - if (ask(0, "Truncate")) { - ret |= fat_set_cl_next(fat, cl, CLUST_EOF); - ret |= FSFATMOD; - } - } else if (nextcl < boot->NumClusters) { - if (fat_is_cl_head(fat, nextcl)) { - fat_clear_cl_head(fat, nextcl); - } else { - pwarn("Cluster %u crossed another chain at %u\n", - cl, nextcl); - if (ask(0, "Truncate")) { - ret |= fat_set_cl_next(fat, cl, CLUST_EOF); - ret |= FSFATMOD; - } - } + switch (boot->ClustMask) { + case CLUST32_MASK: + p = buffer + 8; + break; + case CLUST16_MASK: + p = buffer + 4; + break; + default: + p = buffer + 3; + break; + } + for (cl = CLUST_FIRST; cl < boot->NumClusters;) { + switch (boot->ClustMask) { + case CLUST32_MASK: + fat[cl].next = p[0] + (p[1] << 8) + + (p[2] << 16) + (p[3] << 24); + fat[cl].next &= boot->ClustMask; + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + p += 4; + break; + case CLUST16_MASK: + fat[cl].next = p[0] + (p[1] << 8); + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + p += 2; + break; + default: + fat[cl].next = (p[0] + (p[1] << 8)) & 0x0fff; + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + if (cl >= boot->NumClusters) + break; + fat[cl].next = ((p[1] >> 4) + (p[2] << 4)) & 0x0fff; + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + p += 3; + break; } - } + free(buffer); if (ret & FSFATAL) { - releasefat(fat); free(fat); *fp = NULL; } else *fp = fat; return ret; } /* * Get type of reserved cluster */ const char * rsrvdcltype(cl_t cl) { if (cl == CLUST_FREE) return "free"; if (cl < CLUST_BAD) return "reserved"; if (cl > CLUST_BAD) return "as EOF"; return "bad"; } -/* - * Offer to truncate a chain at the specified CL, called by checkchain(). - */ -static inline int -truncate_at(struct fat_descriptor *fat, cl_t current_cl, size_t *chainsize) +static int +clustdiffer(cl_t cl, cl_t *cp1, cl_t *cp2, u_int fatnum) { - int ret = 0; - - if (ask(0, "Truncate")) { - ret = fat_set_cl_next(fat, current_cl, CLUST_EOF); - (*chainsize)++; - return (ret | FSFATMOD); - } else { + if (*cp1 == CLUST_FREE || *cp1 >= CLUST_RSRVD) { + if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) { + if ((*cp1 != CLUST_FREE && *cp1 < CLUST_BAD + && *cp2 != CLUST_FREE && *cp2 < CLUST_BAD) + || (*cp1 > CLUST_BAD && *cp2 > CLUST_BAD)) { + pwarn("Cluster %u is marked %s with different indicators\n", + cl, rsrvdcltype(*cp1)); + if (ask(1, "Fix")) { + *cp2 = *cp1; + return FSFATMOD; + } + return FSFATAL; + } + pwarn("Cluster %u is marked %s in FAT 0, %s in FAT %u\n", + cl, rsrvdcltype(*cp1), rsrvdcltype(*cp2), fatnum); + if (ask(0, "Use FAT 0's entry")) { + *cp2 = *cp1; + return FSFATMOD; + } + if (ask(0, "Use FAT %u's entry", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } + return FSFATAL; + } + pwarn("Cluster %u is marked %s in FAT 0, but continues with cluster %u in FAT %d\n", + cl, rsrvdcltype(*cp1), *cp2, fatnum); + if (ask(0, "Use continuation from FAT %u", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } + if (ask(0, "Use mark from FAT 0")) { + *cp2 = *cp1; + return FSFATMOD; + } + return FSFATAL; + } + if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) { + pwarn("Cluster %u continues with cluster %u in FAT 0, but is marked %s in FAT %u\n", + cl, *cp1, rsrvdcltype(*cp2), fatnum); + if (ask(0, "Use continuation from FAT 0")) { + *cp2 = *cp1; + return FSFATMOD; + } + if (ask(0, "Use mark from FAT %d", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } return FSERROR; } + pwarn("Cluster %u continues with cluster %u in FAT 0, but with cluster %u in FAT %u\n", + cl, *cp1, *cp2, fatnum); + if (ask(0, "Use continuation from FAT 0")) { + *cp2 = *cp1; + return FSFATMOD; + } + if (ask(0, "Use continuation from FAT %u", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } + return FSERROR; } /* - * Examine a cluster chain for errors and count its size. + * Compare two FAT copies in memory. Resolve any conflicts and merge them + * into the first one. */ int -checkchain(struct fat_descriptor *fat, cl_t head, size_t *chainsize) +comparefat(struct bootblock *boot, struct fatEntry *first, + struct fatEntry *second, u_int fatnum) { - cl_t current_cl, next_cl; + cl_t cl; + int ret = FSOK; - /* - * We expect that the caller to give us a real, unvisited 'head' - * cluster, and it must be a valid cluster. While scanning the - * FAT table, we already excluded all clusters that was claimed - * as a "next" cluster. Assert all the three conditions. - */ - assert(valid_cl(fat, head)); - assert(fat_is_cl_head(fat, head)); - - /* - * Immediately mark the 'head' cluster that we are about to visit. - */ - fat_clear_cl_head(fat, head); - - /* - * The allocation of a non-zero sized file or directory is - * represented as a singly linked list, and the tail node - * would be the EOF marker (>=CLUST_EOFS). - * - * With a valid head node at hand, we expect all subsequent - * cluster to be either a not yet seen and valid cluster (we - * would continue counting), or the EOF marker (we conclude - * the scan of this chain). - * - * For all other cases, the chain is invalid, and the only - * viable fix would be to truncate at the current node (mark - * it as EOF) when the next node violates that. - */ - *chainsize = 0; - current_cl = head; - for (next_cl = fat_get_cl_next(fat, current_cl); - valid_cl(fat, next_cl); - current_cl = next_cl, next_cl = fat_get_cl_next(fat, current_cl)) - (*chainsize)++; - - /* A natural end */ - if (next_cl >= CLUST_EOFS) { - (*chainsize)++; - return FSOK; - } - - /* The chain ended with an out-of-range cluster number. */ - pwarn("Cluster %u continues with %s cluster number %u\n", - current_cl, - next_cl < CLUST_RSRVD ? "out of range" : "reserved", - next_cl & boot_of_(fat)->ClustMask); - return (truncate_at(fat, current_cl, chainsize)); + for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) + if (first[cl].next != second[cl].next) + ret |= clustdiffer(cl, &first[cl].next, &second[cl].next, fatnum); + return ret; } -/* - * Clear cluster chain from head. - */ void -clearchain(struct fat_descriptor *fat, cl_t head) +clearchain(struct bootblock *boot, struct fatEntry *fat, cl_t head) { - cl_t current_cl, next_cl; - struct bootblock *boot = boot_of_(fat); + cl_t p, q; - current_cl = head; - - while (valid_cl(fat, current_cl)) { - next_cl = fat_get_cl_next(fat, head); - (void)fat_set_cl_next(fat, current_cl, CLUST_FREE); - boot->NumFree++; - current_cl = next_cl; + for (p = head; p >= CLUST_FIRST && p < boot->NumClusters; p = q) { + if (fat[p].head != head) + break; + q = fat[p].next; + fat[p].next = fat[p].head = CLUST_FREE; + fat[p].length = 0; } +} +int +tryclear(struct bootblock *boot, struct fatEntry *fat, cl_t head, cl_t *truncp) +{ + if (ask(0, "Clear chain starting at %u", head)) { + clearchain(boot, fat, head); + return FSFATMOD; + } else if (ask(0, "Truncate")) { + uint32_t len; + cl_t p; + + for (p = head, len = 0; + p >= CLUST_FIRST && p < boot->NumClusters; + p = fat[p].next, len++) + continue; + *truncp = CLUST_EOF; + fat[head].length = len; + return FSFATMOD; + } else + return FSERROR; } /* - * Overwrite the n-th FAT with FAT0 + * Check a complete FAT in-memory for crosslinks */ -static int -copyfat(struct fat_descriptor *fat, int n) +int +checkfat(struct bootblock *boot, struct fatEntry *fat) { - size_t rwsize, tailsize, blobs, i; - off_t dst_off, src_off; - struct bootblock *boot; - int ret, fd; + cl_t head, p, h, n; + u_int len; + int ret = 0; + int conf; - ret = FSOK; - fd = fd_of_(fat); - boot = boot_of_(fat); + /* + * pass 1: figure out the cluster chains. + */ + for (head = CLUST_FIRST; head < boot->NumClusters; head++) { + /* find next untravelled chain */ + if (fat[head].head != 0 /* cluster already belongs to some chain */ + || fat[head].next == CLUST_FREE + || fat[head].next == CLUST_BAD) + continue; /* skip it. */ - blobs = howmany(fat->fatsize, fat32_cache_size); - tailsize = fat->fatsize % fat32_cache_size; - if (tailsize == 0) { - tailsize = fat32_cache_size; + /* follow the chain and mark all clusters on the way */ + for (len = 0, p = head; + p >= CLUST_FIRST && p < boot->NumClusters && + fat[p].head != head; + p = fat[p].next) { + fat[p].head = head; + len++; + } + + /* the head record gets the length */ + fat[head].length = fat[head].next == CLUST_FREE ? 0 : len; } - rwsize = fat32_cache_size; - src_off = fat->fat32_offset; - dst_off = boot->bpbResSectors + n * boot->FATsecs; - dst_off *= boot->bpbBytesPerSec; + /* + * pass 2: check for crosslinked chains (we couldn't do this in pass 1 because + * we didn't know the real start of the chain then - would have treated partial + * chains as interlinked with their main chain) + */ + for (head = CLUST_FIRST; head < boot->NumClusters; head++) { + /* find next untravelled chain */ + if (fat[head].head != head) + continue; - for (i = 0; i < blobs; - i++, src_off += fat32_cache_size, dst_off += fat32_cache_size) { - if (i == blobs - 1) { - rwsize = tailsize; - } - if ((lseek(fd, src_off, SEEK_SET) != src_off || - (size_t)read(fd, fat->fatbuf, rwsize) != rwsize) && - ret == FSOK) { - perr("Unable to read FAT0"); - ret = FSFATAL; + /* follow the chain to its end (hopefully) */ + for (len = fat[head].length, p = head; + (n = fat[p].next) >= CLUST_FIRST && n < boot->NumClusters; + p = n) + if (fat[n].head != head || len-- < 2) + break; + if (n >= CLUST_EOFS) continue; + + if (n == CLUST_FREE || n >= CLUST_RSRVD) { + pwarn("Cluster chain starting at %u ends with cluster marked %s\n", + head, rsrvdcltype(n)); +clear: + ret |= tryclear(boot, fat, head, &fat[p].next); + continue; } - if ((lseek(fd, dst_off, SEEK_SET) != dst_off || - (size_t)write(fd, fat->fatbuf, rwsize) != rwsize) && - ret == FSOK) { - perr("Unable to write FAT %d", n); - ret = FSERROR; + if (n < CLUST_FIRST || n >= boot->NumClusters) { + pwarn("Cluster chain starting at %u ends with cluster out of range (%u)\n", + head, n); + goto clear; } + if (head == fat[n].head) { + pwarn("Cluster chain starting at %u loops at cluster %u\n", + head, p); + goto clear; + } + pwarn("Cluster chains starting at %u and %u are linked at cluster %u\n", + head, fat[n].head, n); + conf = tryclear(boot, fat, head, &fat[p].next); + if (ask(0, "Clear chain starting at %u", h = fat[n].head)) { + if (conf == FSERROR) { + /* + * Transfer the common chain to the one not cleared above. + */ + for (p = n; + p >= CLUST_FIRST && p < boot->NumClusters; + p = fat[p].next) { + if (h != fat[p].head) { + /* + * Have to reexamine this chain. + */ + head--; + break; + } + fat[p].head = head; + } + } + clearchain(boot, fat, h); + conf |= FSFATMOD; + } + ret |= conf; } - return (ret); + + return ret; } /* - * Write out FAT + * Write out FATs encoding them from the internal format */ int -writefat(struct fat_descriptor *fat) +writefat(int fs, struct bootblock *boot, struct fatEntry *fat, int correct_fat) { + u_char *buffer, *p; + cl_t cl; u_int i; - size_t writesz; - off_t dst_base; - int ret = FSOK, fd; - struct bootblock *boot; - struct fat32_cache_entry *entry; + size_t fatsz; + off_t off; + int ret = FSOK; - boot = boot_of_(fat); - fd = fd_of_(fat); - - if (fat->use_cache) { - /* - * Attempt to flush all in-flight cache, and bail out - * if we encountered an error (but only emit error - * message once). Stop proceeding with copyfat() - * if any flush failed. - */ - TAILQ_FOREACH(entry, &fat->fat32_cache_head, entries) { - if (fat_flush_fat32_cache_entry(fat, entry) != FSOK) { - if (ret == FSOK) { - perr("Unable to write FAT"); - ret = FSFATAL; - } - } + fatsz = boot->FATsecs * boot->bpbBytesPerSec; + buffer = calloc(boot->FATsecs, boot->bpbBytesPerSec); + if (buffer == NULL) { + perr("No space for FAT sectors (%zu)", + (size_t)boot->FATsecs); + return FSFATAL; + } + boot->NumFree = 0; + p = buffer; + if (correct_fat) { + *p++ = (u_char)boot->bpbMedia; + *p++ = 0xff; + *p++ = 0xff; + switch (boot->ClustMask) { + case CLUST16_MASK: + *p++ = 0xff; + break; + case CLUST32_MASK: + *p++ = 0x0f; + *p++ = 0xff; + *p++ = 0xff; + *p++ = 0xff; + *p++ = 0x0f; + break; } - if (ret != FSOK) - return (ret); + } else { + /* use same FAT signature as the old FAT has */ + int count; + u_char *old_fat; - /* Update backup copies of FAT, error is not fatal */ - for (i = 1; i < boot->bpbFATs; i++) { - if (copyfat(fat, i) != FSOK) - ret = FSERROR; + switch (boot->ClustMask) { + case CLUST32_MASK: + count = 8; + break; + case CLUST16_MASK: + count = 4; + break; + default: + count = 3; + break; } - } else { - writesz = fat->fatsize; - for (i = fat->is_mmapped ? 1 : 0; i < boot->bpbFATs; i++) { - dst_base = boot->bpbResSectors + i * boot->FATsecs; - dst_base *= boot->bpbBytesPerSec; - if ((lseek(fd, dst_base, SEEK_SET) != dst_base || - (size_t)write(fd, fat->fatbuf, writesz) != writesz) && - ret == FSOK) { - perr("Unable to write FAT %d", i); - ret = ((i == 0) ? FSFATAL : FSERROR); - } + if (!_readfat(fs, boot, boot->ValidFat >= 0 ? boot->ValidFat :0, + &old_fat)) { + free(buffer); + return FSFATAL; } + + memcpy(p, old_fat, count); + free(old_fat); + p += count; } + for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) { + switch (boot->ClustMask) { + case CLUST32_MASK: + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + *p++ = (u_char)fat[cl].next; + *p++ = (u_char)(fat[cl].next >> 8); + *p++ = (u_char)(fat[cl].next >> 16); + *p &= 0xf0; + *p++ |= (fat[cl].next >> 24)&0x0f; + break; + case CLUST16_MASK: + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + *p++ = (u_char)fat[cl].next; + *p++ = (u_char)(fat[cl].next >> 8); + break; + default: + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + *p++ = (u_char)fat[cl].next; + *p = (u_char)((fat[cl].next >> 8) & 0xf); + cl++; + if (cl >= boot->NumClusters) + break; + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + *p++ |= (u_char)(fat[cl].next << 4); + *p++ = (u_char)(fat[cl].next >> 4); + break; + } + } + for (i = 0; i < boot->bpbFATs; i++) { + off = boot->bpbResSectors + i * boot->FATsecs; + off *= boot->bpbBytesPerSec; + if (lseek(fs, off, SEEK_SET) != off + || (size_t)write(fs, buffer, fatsz) != fatsz) { + perr("Unable to write FAT"); + ret = FSFATAL; /* Return immediately? XXX */ + } + } + free(buffer); return ret; } /* * Check a complete in-memory FAT for lost cluster chains */ int -checklost(struct fat_descriptor *fat) +checklost(int dosfs, struct bootblock *boot, struct fatEntry *fat) { cl_t head; int mod = FSOK; - int dosfs, ret; - size_t chains, chainlength; - struct bootblock *boot; + int ret; - dosfs = fd_of_(fat); - boot = boot_of_(fat); - - /* - * At this point, we have already traversed all directories. - * All remaining chain heads in the bitmap are heads of lost - * chains. - */ - chains = fat_get_head_count(fat); - for (head = CLUST_FIRST; - chains > 0 && head < boot->NumClusters; - ) { - /* - * We expect the bitmap to be very sparse, so skip if - * the range is full of 0's - */ - if (head % LONG_BIT == 0 && - !fat_is_cl_head_in_range(fat, head)) { - head += LONG_BIT; + for (head = CLUST_FIRST; head < boot->NumClusters; head++) { + /* find next untravelled chain */ + if (fat[head].head != head + || fat[head].next == CLUST_FREE + || (fat[head].next >= CLUST_RSRVD + && fat[head].next < CLUST_EOFS) + || (fat[head].flags & FAT_USED)) continue; + + pwarn("Lost cluster chain at cluster %u\n%d Cluster(s) lost\n", + head, fat[head].length); + mod |= ret = reconnect(dosfs, boot, fat, head); + if (mod & FSFATAL) + break; + if (ret == FSERROR && ask(0, "Clear")) { + clearchain(boot, fat, head); + mod |= FSFATMOD; } - if (fat_is_cl_head(fat, head)) { - ret = checkchain(fat, head, &chainlength); - if (ret != FSERROR) { - pwarn("Lost cluster chain at cluster %u\n" - "%zd Cluster(s) lost\n", - head, chainlength); - mod |= ret = reconnect(fat, head, - chainlength); - } - if (mod & FSFATAL) - break; - if (ret == FSERROR && ask(0, "Clear")) { - clearchain(fat, head); - mod |= FSFATMOD; - } - chains--; - } - head++; } - finishlf(); if (boot->bpbFSInfo) { ret = 0; if (boot->FSFree != 0xffffffffU && boot->FSFree != boot->NumFree) { pwarn("Free space in FSInfo block (%u) not correct (%u)\n", boot->FSFree, boot->NumFree); if (ask(1, "Fix")) { boot->FSFree = boot->NumFree; ret = 1; } } if (boot->FSNext != 0xffffffffU && (boot->FSNext >= boot->NumClusters || - (boot->NumFree && fat_get_cl_next(fat, boot->FSNext) != CLUST_FREE))) { + (boot->NumFree && fat[boot->FSNext].next != CLUST_FREE))) { pwarn("Next free cluster in FSInfo block (%u) %s\n", boot->FSNext, (boot->FSNext >= boot->NumClusters) ? "invalid" : "not free"); - if (ask(1, "Fix")) + if (ask(1, "fix")) for (head = CLUST_FIRST; head < boot->NumClusters; head++) - if (fat_get_cl_next(fat, head) == CLUST_FREE) { + if (fat[head].next == CLUST_FREE) { boot->FSNext = head; ret = 1; break; } } if (ret) mod |= writefsinfo(dosfs, boot); } return mod; } Index: head/sbin/fsck_msdosfs/main.c =================================================================== --- head/sbin/fsck_msdosfs/main.c (revision 356249) +++ head/sbin/fsck_msdosfs/main.c (revision 356250) @@ -1,163 +1,157 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (C) 1995 Wolfgang Solfrank * Copyright (c) 1995 Martin Husemann * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #ifndef lint __RCSID("$NetBSD: main.c,v 1.10 1997/10/01 02:18:14 enami Exp $"); static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include "fsutil.h" #include "ext.h" int alwaysno; /* assume "no" for all questions */ int alwaysyes; /* assume "yes" for all questions */ int preen; /* set when preening */ int rdonly; /* device is opened read only (supersedes above) */ int skipclean; /* skip clean file systems if preening */ -int allow_mmap; /* Allow the use of mmap(), if possible */ static void usage(void) __dead2; static void usage(void) { fprintf(stderr, "%s\n%s\n", "usage: fsck_msdosfs -p [-f] filesystem ...", " fsck_msdosfs [-ny] filesystem ..."); exit(1); } int main(int argc, char **argv) { int ret = 0, erg; int ch; skipclean = 1; - allow_mmap = 1; - while ((ch = getopt(argc, argv, "CfFnpyM")) != -1) { + while ((ch = getopt(argc, argv, "CfFnpy")) != -1) { switch (ch) { case 'C': /* for fsck_ffs compatibility */ break; case 'f': skipclean = 0; break; case 'F': /* * We can never run in the background. We must exit * silently with a nonzero exit code so that fsck(8) * can probe our support for -F. The exit code * doesn't really matter, but we use an unusual one * in case someone tries -F directly. The -F flag * is intentionally left out of the usage message. */ exit(5); case 'n': alwaysno = 1; alwaysyes = 0; break; case 'y': alwaysyes = 1; alwaysno = 0; break; case 'p': preen = 1; - break; - - case 'M': - allow_mmap = 0; break; default: usage(); break; } } argc -= optind; argv += optind; if (!argc) usage(); while (--argc >= 0) { setcdevname(*argv, preen); erg = checkfilesys(*argv++); if (erg > ret) ret = erg; } return ret; } /*VARARGS*/ int ask(int def, const char *fmt, ...) { va_list ap; char prompt[256]; int c; if (alwaysyes || alwaysno || rdonly) def = (alwaysyes && !rdonly && !alwaysno); if (preen) { if (def) printf("FIXED\n"); return def; } va_start(ap, fmt); vsnprintf(prompt, sizeof(prompt), fmt, ap); va_end(ap); if (alwaysyes || alwaysno || rdonly) { printf("%s? %s\n", prompt, def ? "yes" : "no"); return def; } do { printf("%s? [yn] ", prompt); fflush(stdout); c = getchar(); while (c != '\n' && getchar() != '\n') if (feof(stdin)) return 0; } while (c != 'y' && c != 'Y' && c != 'n' && c != 'N'); return c == 'y' || c == 'Y'; }