Index: head/sbin/fsck_msdosfs/boot.c =================================================================== --- head/sbin/fsck_msdosfs/boot.c (revision 356312) +++ head/sbin/fsck_msdosfs/boot.c (revision 356313) @@ -1,350 +1,364 @@ /*- * 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->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; + } boot->ClustMask = CLUST32_MASK; - else if (boot->NumClusters < (CLUST_RSRVD&CLUST12_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)) { 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 356312) +++ head/sbin/fsck_msdosfs/check.c (revision 356313) @@ -1,196 +1,169 @@ /*- * 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 fatEntry *fat = NULL; + struct fat_descriptor *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) { - if (boot.ValidFat < 0) - printf("** Phase 1 - Read and Compare FATs\n"); - else - printf("** Phase 1 - Read FAT\n"); + printf("** Phase 1 - Read FAT and checking connectivity\n"); } - mod |= readfat(dosfs, &boot, boot.ValidFat >= 0 ? boot.ValidFat : 0, &fat); + mod |= readfat(dosfs, &boot, &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 - Check Cluster Chains\n"); + printf("** Phase 2 - Checking Directories\n"); - mod |= checkfat(&boot, fat); - if (mod & FSFATAL) - goto out; - /* delay writing FATs */ - - if (!preen) - printf("** Phase 3 - Checking Directories\n"); - - mod |= resetDosDirSection(&boot, fat); + mod |= resetDosDirSection(fat); finish_dosdirsection = 1; if (mod & FSFATAL) goto out; /* delay writing FATs */ - mod |= handleDirTree(dosfs, &boot, fat); + mod |= handleDirTree(fat); if (mod & FSFATAL) goto out; if (!preen) - printf("** Phase 4 - Checking for Lost Files\n"); + printf("** Phase 3 - Checking for Lost Files\n"); - mod |= checklost(dosfs, &boot, fat); + mod |= checklost(fat); if (mod & FSFATAL) goto out; /* now write the FATs */ - if (mod & (FSFATMOD|FSFIXFAT)) { + if (mod & FSFATMOD) { if (ask(1, "Update FATs")) { - mod |= writefat(dosfs, &boot, fat, mod & FSFIXFAT); + mod |= writefat(fat); 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(dosfs, &boot, fat, 1); + mod |= writefat(fat); } 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 356312) +++ head/sbin/fsck_msdosfs/dir.c (revision 356313) @@ -1,1121 +1,1161 @@ /*- * 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(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 *); +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 *); /* * 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 0; + return (NULL); } 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 u_char +static inline 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 bootblock *boot, struct fatEntry *fat) +resetDosDirSection(struct fat_descriptor *fat) { - int b1, b2; + int rootdir_size, cluster_size; int ret = FSOK; size_t len; + struct bootblock *boot; - b1 = boot->bpbRootDirEnts * 32; - b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec; + boot = fat_get_boot(fat); - if ((buffer = malloc(len = MAX(b1, b2))) == NULL) { + rootdir_size = boot->bpbRootDirEnts * 32; + cluster_size = boot->bpbSecPerClust * boot->bpbBytesPerSec; + + if ((buffer = malloc(len = MAX(rootdir_size, cluster_size))) == NULL) { perr("No space for directory buffer (%zu)", len); return FSFATAL; } - if ((delbuf = malloc(len = b2)) == NULL) { + if ((delbuf = malloc(len = cluster_size)) == 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 (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) { + if (!fat_is_cl_head(fat, 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(int f, struct bootblock *boot, struct fatEntry *fat, cl_t startcl, +delete(struct fat_descriptor *fat, cl_t startcl, int startoff, cl_t endcl, int endoff, int notlast) { u_char *s, *e; off_t off; - int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec; + int clsz, fd; + struct bootblock *boot; + boot = fat_get_boot(fat); + fd = fat_get_fd(fat); + clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec; + s = delbuf + startoff; e = delbuf + clsz; - while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) { + while (fat_is_valid_cl(fat, startcl)) { if (startcl == endcl) { if (notlast) break; e = delbuf + endoff; } off = (startcl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; off *= boot->bpbBytesPerSec; - if (lseek(f, off, SEEK_SET) != off) { + if (lseek(fd, off, SEEK_SET) != off) { perr("Unable to lseek to %" PRId64, off); return FSFATAL; } - if (read(f, delbuf, clsz) != clsz) { + if (read(fd, delbuf, clsz) != clsz) { perr("Unable to read directory"); return FSFATAL; } while (s < e) { *s = SLOT_DELETED; s += 32; } - if (lseek(f, off, SEEK_SET) != off) { + if (lseek(fd, off, SEEK_SET) != off) { perr("Unable to lseek to %" PRId64, off); return FSFATAL; } - if (write(f, delbuf, clsz) != clsz) { + if (write(fd, delbuf, clsz) != clsz) { perr("Unable to write directory"); return FSFATAL; } if (startcl == endcl) break; - startcl = fat[startcl].next; + startcl = fat_get_cl_next(fat, startcl); s = delbuf; } return FSOK; } static int -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) +removede(struct fat_descriptor *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(f, boot, fat, + if (delete(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 bootblock *boot, struct fatEntry *fat, u_char *p, - struct dosDirEntry *dir) +checksize(struct fat_descriptor *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 */ - u_int32_t physicalSize; - - if (dir->head == CLUST_FREE) + if (dir->head == CLUST_FREE) { physicalSize = 0; - else { - if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters) + } else { + if (!fat_is_valid_cl(fat, dir->head)) return FSERROR; - physicalSize = fat[dir->head].length * boot->ClusterSize; + 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; } if (physicalSize < dir->size) { - pwarn("size of %s is %u, should at most be %u\n", + pwarn("size of %s is %u, should at most be %zu\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[cl].next; - clearchain(boot, fat, fat[cl].next); - fat[cl].next = CLUST_EOF; - fat[dir->head].length = len; - return FSFATMOD; + 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); } 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(int f, struct bootblock *boot, struct dosDirEntry *dir) +check_subdirectory(struct fat_descriptor *fat, 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 && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { + if (dir->parent && !fat_is_valid_cl(fat, cl)) { 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(f, off, SEEK_SET) != off || - read(f, buf, boot->bpbBytesPerSec) != (ssize_t)boot->bpbBytesPerSec) { + if (lseek(fd, off, SEEK_SET) != off || + read(fd, 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(int f, struct bootblock *boot, struct fatEntry *fat, - struct dosDirEntry *dir) +readDosDirSection(struct fat_descriptor *fat, struct dosDirEntry *dir) { + struct bootblock *boot; struct dosDirEntry dirent, *d; u_char *p, *vallfn, *invlfn, *empty; off_t off; - int i, j, k, last; + int fd, i, j, k, iosize, entries; + bool is_legacyroot; 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 && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { + if (dir->parent && (!fat_is_valid_cl(fat, cl))) { /* * 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 (!(boot->flags & FAT32) && !dir->parent) { - last = boot->bpbRootDirEnts * 32; + if (is_legacyroot) { + /* + * Special case for FAT12/FAT16 root -- read + * in the whole root directory. + */ off = boot->bpbResSectors + boot->bpbFATs * boot->FATsecs; } else { - last = boot->bpbSecPerClust * boot->bpbBytesPerSec; + /* + * Otherwise, read in a cluster of the + * directory. + */ off = (cl - CLUST_FIRST) * boot->bpbSecPerClust + boot->FirstCluster; } off *= boot->bpbBytesPerSec; - if (lseek(f, off, SEEK_SET) != off - || read(f, buffer, last) != last) { + if (lseek(fd, off, SEEK_SET) != off || + read(fd, buffer, iosize) != iosize) { perr("Unable to read directory"); return FSFATAL; } - last /= 32; - for (p = buffer, i = 0; i < last; i++, p += 32) { + + for (p = buffer, i = 0; i < entries; 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(f, boot, fat, + if (delete(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(f, boot, fat, + mod |= removede(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(f, boot, fat, + mod |= k = removede(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; - 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; + /* + * 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; + } } - } 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; - } - if (strcmp(dirent.name, "..") == 0) { + } else 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(f, boot, + } else if ((check_subdirectory(fat, &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(boot, fat, p, &dirent); + mod |= k = checksize(fat, p, &dirent); if (k & FSDIRMOD) mod |= THISMOD; } boot->NumFiles++; } - if (!(boot->flags & FAT32) && !dir->parent) + 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. + */ break; - - if (mod & THISMOD) { - last *= 32; - if (lseek(f, off, SEEK_SET) != off - || write(f, buffer, last) != last) { + } else if (mod & THISMOD) { + if (lseek(fd, off, SEEK_SET) != off + || write(fd, buffer, iosize) != iosize) { perr("Unable to write directory"); return FSFATAL; } mod &= ~THISMOD; } - } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); + } while (fat_is_valid_cl(fat, (cl = fat_get_cl_next(fat, cl)))); if (invlfn || vallfn) - mod |= removede(f, boot, fat, + mod |= removede(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 without being written out. + /* + * The root directory of non-FAT32 filesystems is in a special + * area and may have been modified above removede() without + * being written out. */ - if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) { - last *= 32; - if (lseek(f, off, SEEK_SET) != off - || write(f, buffer, last) != last) { + if ((mod & FSDIRMOD) && is_legacyroot) { + if (lseek(fd, off, SEEK_SET) != off + || write(fd, buffer, iosize) != iosize) { perr("Unable to write directory"); return FSFATAL; } mod &= ~THISMOD; } return mod & ~THISMOD; } int -handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat) +handleDirTree(struct fat_descriptor *fat) { int mod; - mod = readDosDirSection(dosfs, boot, fat, rootDir); + mod = readDosDirSection(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(dosfs, boot, fat, dir); + mod |= readDosDirSection(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(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head) +reconnect(struct fat_descriptor *fat, cl_t head, size_t length) { + struct bootblock *boot = fat_get_boot(fat); struct dosDirEntry d; - int len; + int len, dosfs; 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[lfcl].next : lostDir->head; + lfcl = p ? fat_get_cl_next(fat, lfcl) : 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 = fat[head].length * boot->ClusterSize; + d.size = 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 356312) +++ head/sbin/fsck_msdosfs/dosfs.h (revision 356313) @@ -1,141 +1,133 @@ /*- * 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 356312) +++ head/sbin/fsck_msdosfs/ext.h (revision 356313) @@ -1,143 +1,152 @@ /*- * 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 *); -/* - * 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 **); +/* Opaque type */ +struct fat_descriptor; -/* - * Check two FAT copies for consistency and merge changes into the - * first if necessary. - */ -int comparefat(struct bootblock *, struct fatEntry *, struct fatEntry *, u_int); +void fat_clear_cl_head(struct fat_descriptor *, cl_t); +bool fat_is_cl_head(struct fat_descriptor *, cl_t); +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); + /* - * Check a FAT + * Read the FAT 0 and return a pointer to the newly allocated + * descriptor of it. */ -int checkfat(struct bootblock *, struct fatEntry *); +int readfat(int, struct bootblock *, struct fat_descriptor **); /* * Write back FAT entries */ -int writefat(int, struct bootblock *, struct fatEntry *, int); +int writefat(struct fat_descriptor *); /* * Read a directory */ -int resetDosDirSection(struct bootblock *, struct fatEntry *); +int resetDosDirSection(struct fat_descriptor *); void finishDosDirSection(void); -int handleDirTree(int, struct bootblock *, struct fatEntry *); +int handleDirTree(struct fat_descriptor *); /* * 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(int, struct bootblock *, struct fatEntry *); +int checklost(struct fat_descriptor *); /* * Try to reconnect a lost cluster chain */ -int reconnect(int, struct bootblock *, struct fatEntry *, cl_t); +int reconnect(struct fat_descriptor *, cl_t, size_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 bootblock *, struct fatEntry *, cl_t); +void clearchain(struct fat_descriptor *, cl_t); #endif Index: head/sbin/fsck_msdosfs/fat.c =================================================================== --- head/sbin/fsck_msdosfs/fat.c (revision 356312) +++ head/sbin/fsck_msdosfs/fat.c (revision 356313) @@ -1,726 +1,1271 @@ /*- * 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 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 **); +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); -/*- + +/* + * 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 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 = 131072; /* MAXPHYS */ +static const size_t fat32_cache_size = 4194304; +static const size_t fat32_cache_entries = 32; /* XXXgcc: cache_size / 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)fat32_cache_chunk_size - 1)); + } else { + return (fat32_cache_chunk_size); + } +} + +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; } /* - * Check a cluster number for valid value + * Read a FAT from disk. Returns 1 if successful, 0 otherwise. */ static int -checkclnum(struct bootblock *boot, u_int fat, cl_t cl, cl_t *next) +_readfat(struct fat_descriptor *fat) { - 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; + 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; } - return FSERROR; } - return FSOK; -} -/* - * 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; + /* + * 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; - *buffer = calloc(boot->FATsecs, boot->bpbBytesPerSec); - if (*buffer == NULL) { - perr("No space for FAT sectors (%zu)", - (size_t)boot->FATsecs); + 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); return 0; } - off = boot->bpbResSectors + no * boot->FATsecs; - off *= boot->bpbBytesPerSec; - - if (lseek(fs, off, SEEK_SET) != off) { + if (lseek(fd, off, SEEK_SET) != off) { perr("Unable to read FAT"); goto err; } - - if ((size_t)read(fs, *buffer, boot->FATsecs * boot->bpbBytesPerSec) - != boot->FATsecs * boot->bpbBytesPerSec) { + if ((size_t)read(fd, fat->fatbuf, readsize) != readsize) { 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(*buffer); +err: + free(fat->fatbuf); + fat->fatbuf = NULL; 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 a FAT and decode it into internal format + * Read or map a FAT and populate head bitmap */ int -readfat(int fs, struct bootblock *boot, u_int no, struct fatEntry **fp) +readfat(int fs, struct bootblock *boot, struct fat_descriptor **fp) { - struct fatEntry *fat; + struct fat_descriptor *fat; u_char *buffer, *p; - cl_t cl; + cl_t cl, nextcl; int ret = FSOK; boot->NumFree = boot->NumBad = 0; - if (!_readfat(fs, boot, no, &buffer)) + fat = calloc(1, sizeof(struct fat_descriptor)); + if (fat == NULL) { + perr("No space for FAT descriptor"); return FSFATAL; + } - fat = calloc(boot->NumClusters, sizeof(struct fatEntry)); - if (fat == NULL) { - perr("No space for FAT clusters (%zu)", + 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)", (size_t)boot->NumClusters); - free(buffer); + releasefat(fat); + free(fat); 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; - if (ask(1, "Correct")) - ret |= FSFIXFAT; + *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; + } + } } } - 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; + + /* + * 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; + } + } } + } - 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"; } -static int -clustdiffer(cl_t cl, cl_t *cp1, cl_t *cp2, u_int fatnum) +/* + * 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) { - 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; - } + int ret = 0; + + if (ask(0, "Truncate")) { + ret = fat_set_cl_next(fat, current_cl, CLUST_EOF); + (*chainsize)++; + return (ret | FSFATMOD); + } else { 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; } /* - * Compare two FAT copies in memory. Resolve any conflicts and merge them - * into the first one. + * Examine a cluster chain for errors and count its size. */ int -comparefat(struct bootblock *boot, struct fatEntry *first, - struct fatEntry *second, u_int fatnum) +checkchain(struct fat_descriptor *fat, cl_t head, size_t *chainsize) { - cl_t cl; - int ret = FSOK; + cl_t current_cl, next_cl; - 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; -} + /* + * 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)); -void -clearchain(struct bootblock *boot, struct fatEntry *fat, cl_t head) -{ - cl_t p, q; + /* + * Immediately mark the 'head' cluster that we are about to visit. + */ + fat_clear_cl_head(fat, head); - 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; + /* + * 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)); } -int -tryclear(struct bootblock *boot, struct fatEntry *fat, cl_t head, cl_t *truncp) +/* + * Clear cluster chain from head. + */ +void +clearchain(struct fat_descriptor *fat, cl_t head) { - 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; + cl_t current_cl, next_cl; + struct bootblock *boot = boot_of_(fat); - 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; + 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; + } + } /* - * Check a complete FAT in-memory for crosslinks + * Overwrite the n-th FAT with FAT0 */ -int -checkfat(struct bootblock *boot, struct fatEntry *fat) +static int +copyfat(struct fat_descriptor *fat, int n) { - cl_t head, p, h, n; - u_int len; - int ret = 0; - int conf; + size_t rwsize, tailsize, blobs, i; + off_t dst_off, src_off; + struct bootblock *boot; + int ret, fd; - /* - * 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. */ + ret = FSOK; + fd = fd_of_(fat); + boot = boot_of_(fat); - /* 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; + blobs = howmany(fat->fatsize, fat32_cache_size); + tailsize = fat->fatsize % fat32_cache_size; + if (tailsize == 0) { + tailsize = fat32_cache_size; } + rwsize = fat32_cache_size; - /* - * 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; + src_off = fat->fat32_offset; + dst_off = boot->bpbResSectors + n * boot->FATsecs; + dst_off *= boot->bpbBytesPerSec; - /* 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) + 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; 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 (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 ((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 (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 FATs encoding them from the internal format + * Write out FAT */ int -writefat(int fs, struct bootblock *boot, struct fatEntry *fat, int correct_fat) +writefat(struct fat_descriptor *fat) { - u_char *buffer, *p; - cl_t cl; u_int i; - size_t fatsz; - off_t off; - int ret = FSOK; + size_t writesz; + off_t dst_base; + int ret = FSOK, fd; + struct bootblock *boot; + struct fat32_cache_entry *entry; - 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; - } - } else { - /* use same FAT signature as the old FAT has */ - int count; - u_char *old_fat; + boot = boot_of_(fat); + fd = fd_of_(fat); - switch (boot->ClustMask) { - case CLUST32_MASK: - count = 8; - break; - case CLUST16_MASK: - count = 4; - break; - default: - count = 3; - break; + 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; + } + } } + if (ret != FSOK) + return (ret); - if (!_readfat(fs, boot, boot->ValidFat >= 0 ? boot->ValidFat :0, - &old_fat)) { - free(buffer); - return FSFATAL; + /* Update backup copies of FAT, error is not fatal */ + for (i = 1; i < boot->bpbFATs; i++) { + if (copyfat(fat, i) != FSOK) + ret = FSERROR; } + } else { + writesz = fat->fatsize; - 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 = 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); + } } } - 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(int dosfs, struct bootblock *boot, struct fatEntry *fat) +checklost(struct fat_descriptor *fat) { cl_t head; int mod = FSOK; - int ret; + int dosfs, ret; + size_t chains, chainlength; + struct bootblock *boot; - 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; + dosfs = fd_of_(fat); + boot = boot_of_(fat); - 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; + /* + * 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; + continue; } + 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[boot->FSNext].next != CLUST_FREE))) { + (boot->NumFree && fat_get_cl_next(fat, boot->FSNext) != 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[head].next == CLUST_FREE) { + if (fat_get_cl_next(fat, head) == 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 356312) +++ head/sbin/fsck_msdosfs/main.c (revision 356313) @@ -1,157 +1,163 @@ /*- * 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; - while ((ch = getopt(argc, argv, "CfFnpy")) != -1) { + allow_mmap = 1; + while ((ch = getopt(argc, argv, "CfFnpyM")) != -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'; }