From e2ff8d0c412f9d22fcd772808914cce40a3c6761 Mon Sep 17 00:00:00 2001 From: silas jeon Date: Tue, 16 Aug 2016 12:57:04 +0900 Subject: [PATCH] Imported Upstream version 1.0.0 Change-Id: Iab341b67a15248d42d05c387b858ab271b4f2996 --- CMakeLists.txt | 29 ++ LICENSE | 29 ++ src/boot.c | 298 +++++++++++++++ src/check.c | 222 ++++++++++++ src/dir.c | 1100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/dosfs.h | 144 ++++++++ src/ext.h | 150 ++++++++ src/fat.c | 735 +++++++++++++++++++++++++++++++++++++ src/fsutil.h | 7 + src/main.c | 154 ++++++++ 10 files changed, 2868 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 src/boot.c create mode 100644 src/check.c create mode 100644 src/dir.c create mode 100644 src/dosfs.h create mode 100644 src/ext.h create mode 100644 src/fat.c create mode 100644 src/fsutil.h create mode 100644 src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..048527d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +PROJECT(fsck_msdosfs C) + +SET(SRCS + src/boot.c + src/check.c + src/dir.c + src/fat.c + src/main.c +) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +INCLUDE(FindPkgConfig) + +FOREACH(flag ${pkgs_CFLAGS}) + SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}") +ENDFOREACH(flag) + +SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} -g -fno-omit-frame-pointer -finstrument-functions") + +SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS}") + +ADD_DEFINITIONS("-D_GNU_SOURCE") + +ADD_EXECUTABLE(${PROJECT_NAME} ${SRCS}) +TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${pkgs_LDFLAGS}) + +INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e3c375e --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +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. +3. All advertising materials mentioning features or use of this software + must display the following acknowledgement: + This product includes software developed by Martin Husemann + and Wolfgang Solfrank. +4. Neither the name of the University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +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. diff --git a/src/boot.c b/src/boot.c new file mode 100644 index 0000000..f51fc6f --- /dev/null +++ b/src/boot.c @@ -0,0 +1,298 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 + +#include +#include +#include +#include +#include + +#include "ext.h" +#include "fsutil.h" + +int +readboot(dosfs, boot) + int dosfs; + struct bootblock *boot; +{ + u_char block[DOSBOOTBLOCKSIZE]; + u_char fsinfo[2 * DOSBOOTBLOCKSIZE]; + u_char backup[DOSBOOTBLOCKSIZE]; + int ret = FSOK; + + if (read(dosfs, block, sizeof block) < sizeof block) { + perror("could not read boot block"); + exit(2); + } + + if (block[510] != 0x55 || block[511] != 0xaa) { + pfatal("Invalid signature in boot block: %02x%02x", block[511], block[510]); + exit(2); + } + + memset(boot, 0, sizeof *boot); + boot->ValidFat = -1; + + /* decode bios parameter block */ + boot->BytesPerSec = block[11] + (block[12] << 8); + boot->SecPerClust = block[13]; + boot->ResSectors = block[14] + (block[15] << 8); + boot->FATs = block[16]; + boot->RootDirEnts = block[17] + (block[18] << 8); + boot->Sectors = block[19] + (block[20] << 8); + boot->Media = block[21]; + boot->FATsmall = block[22] + (block[23] << 8); + boot->SecPerTrack = block[24] + (block[25] << 8); + boot->Heads = block[26] + (block[27] << 8); + boot->HiddenSecs = block[28] + (block[29] << 8) + (block[30] << 16) + (block[31] << 24); + boot->HugeSectors = block[32] + (block[33] << 8) + (block[34] << 16) + (block[35] << 24); + + boot->FATsecs = boot->FATsmall; + + if (!boot->RootDirEnts) + boot->flags |= FAT32; + if (boot->flags & FAT32) { + boot->FATsecs = block[36] + (block[37] << 8) + + (block[38] << 16) + (block[39] << 24); + if (block[40] & 0x80) + boot->ValidFat = block[40] & 0x0f; + + /* check version number: */ + if (block[42] || block[43]) { + /* Correct? XXX */ + pfatal("Unknown file system version: %x.%x", + block[43], block[42]); + exit(2); + } + boot->RootCl = block[44] + (block[45] << 8) + + (block[46] << 16) + (block[47] << 24); + boot->FSInfo = block[48] + (block[49] << 8); + boot->Backup = block[50] + (block[51] << 8); + + if (lseek(dosfs, boot->FSInfo * boot->BytesPerSec, SEEK_SET) + != boot->FSInfo * boot->BytesPerSec + || read(dosfs, fsinfo, sizeof fsinfo) + != sizeof fsinfo) { + perror("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(1, "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->FSInfo * boot->BytesPerSec, SEEK_SET) + != boot->FSInfo * boot->BytesPerSec + || write(dosfs, fsinfo, sizeof fsinfo) + != sizeof fsinfo) { + perror("Unable to write FSInfo"); + return FSFATAL; + } + ret = FSBOOTMOD; + } else + boot->FSInfo = 0; + } + if (boot->FSInfo) { + 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); + } + + if (lseek(dosfs, boot->Backup * boot->BytesPerSec, SEEK_SET) + != boot->Backup * boot->BytesPerSec + || read(dosfs, backup, sizeof backup) != sizeof backup) { + perror("could not read backup bootblock"); + return FSFATAL; + } + backup[65] = block[65]; /* XXX */ + if (memcmp(block + 11, backup + 11, 79)) { + char tmp[255]; + int i; + + /* + * For now, lets not bail out if they don't match + * It seems a lot of sdcards are formatted with + * the backup either empty or containing garbage. + */ + + pwarn("Primary/Backup bootblock miscompare\n"); + + strcpy(tmp, ""); + pwarn("Primary:\n"); + for (i = 0; i < 79; i++) { + char tmp2[16]; + snprintf(tmp2, sizeof(tmp2), "%.2x ", block[11 + i]); + strcat(tmp, tmp2); + } + pwarn("%s\n", tmp); + + strcpy(tmp, ""); + pwarn("Backup:\n"); + for (i = 0; i < 79; i++) { + char tmp2[16]; + snprintf(tmp2, sizeof(tmp2), "%.2x ", backup[11 + i]); + strcat(tmp, tmp2); + } + pwarn("%s\n", tmp); + } + /* Check backup FSInfo? XXX */ + } + + if (boot->BytesPerSec % DOSBOOTBLOCKSIZE != 0) { + pfatal("Invalid sector size: %u", boot->BytesPerSec); + return FSFATAL; + } + if (boot->SecPerClust == 0) { + pfatal("Invalid cluster size: %u", boot->SecPerClust); + return FSFATAL; + } + if (boot->BytesPerSec == 0) { + pfatal("Invalid sector size: %u", boot->BytesPerSec); + return FSFATAL; + } + if (boot->FATs == 0) { + pfatal("Invalid number of FATs: %u", boot->FATs); + return FSFATAL; + } + if (boot->Sectors) { + boot->HugeSectors = 0; + boot->NumSectors = boot->Sectors; + } else + boot->NumSectors = boot->HugeSectors; + + boot->ClusterOffset = (boot->RootDirEnts * 32 + boot->BytesPerSec - 1) + / boot->BytesPerSec + + boot->ResSectors + + boot->FATs * boot->FATsecs + - CLUST_FIRST * boot->SecPerClust; + + boot->NumClusters = (boot->NumSectors - boot->ClusterOffset) / boot->SecPerClust; + + if (boot->flags&FAT32) + boot->ClustMask = CLUST32_MASK; + else if (boot->NumClusters < (CLUST_RSRVD&CLUST12_MASK)) + boot->ClustMask = CLUST12_MASK; + else if (boot->NumClusters < (CLUST_RSRVD&CLUST16_MASK)) + boot->ClustMask = CLUST16_MASK; + 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->BytesPerSec) / 4; + break; + case CLUST16_MASK: + boot->NumFatEntries = (boot->FATsecs * boot->BytesPerSec) / 2; + break; + default: + boot->NumFatEntries = (boot->FATsecs * boot->BytesPerSec * 2) / 3; + break; + } + + if (boot->NumFatEntries < boot->NumClusters) { + pfatal("FAT size too small, %u entries won't fit into %u sectors\n", + boot->NumClusters, boot->FATsecs); + return FSFATAL; + } + boot->ClusterSize = boot->BytesPerSec * boot->SecPerClust; + + boot->NumFiles = 1; + boot->NumFree = 0; + + return ret; +} + +int +writefsinfo(dosfs, boot) + int dosfs; + struct bootblock *boot; +{ + u_char fsinfo[2 * DOSBOOTBLOCKSIZE]; + + if (lseek(dosfs, boot->FSInfo * boot->BytesPerSec, SEEK_SET) + != boot->FSInfo * boot->BytesPerSec + || read(dosfs, fsinfo, sizeof fsinfo) != sizeof fsinfo) { + perror("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->FSInfo * boot->BytesPerSec, SEEK_SET) + != boot->FSInfo * boot->BytesPerSec + || write(dosfs, fsinfo, sizeof fsinfo) + != sizeof fsinfo) { + perror("Unable to write FSInfo"); + 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; +} diff --git a/src/check.c b/src/check.c new file mode 100644 index 0000000..bb5fbc2 --- /dev/null +++ b/src/check.c @@ -0,0 +1,222 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 +#include +#include +#include +#include +#include + +#include "ext.h" +#include "fsutil.h" + +/* + * If the FAT > this size then skip comparing, lest we risk + * OOMing the framework. in the future we need to just re-write + * this whole thing and optimize for less memory + */ +#define FAT_COMPARE_MAX_KB 4096 + +int +checkfilesys(const char *fname) +{ + int dosfs; + struct bootblock boot; + struct fatEntry *fat = NULL; + int i, finish_dosdirsection=0; + int mod = 0; + int ret = 8; + int quiet = 0; + int skip_fat_compare = 0; + + rdonly = alwaysno; + if (!quiet) + 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 (!quiet) + printf("\n"); + rdonly = 1; + } else if (!quiet) + printf("\n"); + + if (dosfs < 0) { + perror("Can't open"); + 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 (((boot.FATsecs * boot.BytesPerSec) / 1024) > FAT_COMPARE_MAX_KB) + skip_fat_compare = 1; + + if (!quiet) { + if (skip_fat_compare) + printf("** Phase 1 - Read FAT (compare skipped)\n"); + else if (boot.ValidFat < 0) + printf("** Phase 1 - Read and Compare FATs\n"); + else + printf("** Phase 1 - Read FAT\n"); + } + + mod |= readfat(dosfs, &boot, boot.ValidFat >= 0 ? boot.ValidFat : 0, &fat); + if (mod & FSFATAL) { + printf("Fatal error during readfat()\n"); + if (fat) + free(fat); + close(dosfs); + return 8; + } + + if (!skip_fat_compare && boot.ValidFat < 0) + for (i = 1; i < (int)boot.FATs; i++) { + struct fatEntry *currentFat; + + mod |= readfat(dosfs, &boot, i, ¤tFat); + + if (mod & FSFATAL) { + printf("Fatal error during readfat() for comparison\n"); + goto out; + } + + mod |= comparefat(&boot, fat, currentFat, i); + free(currentFat); + if (mod & FSFATAL) { + printf("Fatal error during FAT comparison\n"); + goto out; + } + } + + if (!quiet) + printf("** Phase 2 - Check Cluster Chains\n"); + + mod |= checkfat(&boot, fat); + if (mod & FSFATAL) { + printf("Fatal error during FAT check\n"); + goto out; + } + /* delay writing FATs */ + + if (!quiet) + printf("** Phase 3 - Checking Directories\n"); + + mod |= resetDosDirSection(&boot, fat); + finish_dosdirsection = 1; + if (mod & FSFATAL) { + printf("Fatal error during resetDosDirSection()\n"); + goto out; + } + /* delay writing FATs */ + + mod |= handleDirTree(dosfs, &boot, fat); + if (mod & FSFATAL) + goto out; + + if (!quiet) + printf("** Phase 4 - Checking for Lost Files\n"); + + mod |= checklost(dosfs, &boot, fat); + if (mod & FSFATAL) + goto out; + + /* now write the FATs */ + if (mod & FSFATMOD) { + if (ask(1, "Update FATs")) { + mod |= writefat(dosfs, &boot, fat, mod & FSFIXFAT); + if (mod & FSFATAL) { + printf("Fatal error during writefat()\n"); + 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); + } 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 4; + } + + return ret; +} diff --git a/src/dir.c b/src/dir.c new file mode 100644 index 0000000..f5224dd --- /dev/null +++ b/src/dir.c @@ -0,0 +1,1100 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 +#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 *); + +/* + * Manage free dosDirEntry structures. + */ +static struct dosDirEntry *freede; + +static struct dosDirEntry * +newDosDirEntry(void) +{ + struct dosDirEntry *de; + + if (!(de = freede)) { + if (!(de = (struct dosDirEntry *)malloc(sizeof *de))) + return 0; + } else + freede = de->next; + return de; +} + +static void +freeDosDirEntry(struct dosDirEntry *de) +{ + de->next = freede; + freede = de; +} + +/* + * The same for dirTodoNode structures. + */ +static struct dirTodoNode *freedt; + +static struct dirTodoNode * +newDirTodo(void) +{ + struct dirTodoNode *dt; + + if (!(dt = freedt)) { + if (!(dt = (struct dirTodoNode *)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 + */ +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 - 1; + *cp = '\0'; + do { + np = dir->lname[0] ? dir->lname : dir->name; + nl = strlen(np); + if ((cp -= nl) <= namebuf + 1) + break; + memcpy(cp, np, nl); + *--cp = '/'; + } while ((dir = dir->parent) != NULL); + if (dir) + *--cp = '?'; + else + cp++; + return cp; +} + +/* + * Calculate a checksum over an 8.3 alias name + */ +static u_char +calcShortSum(u_char *p) +{ + u_char sum = 0; + int i; + + for (i = 0; i < 11; i++) { + sum = (sum << 7)|(sum >> 1); /* rotate right */ + sum += p[i]; + } + + return sum; +} + +/* + * Global variables temporarily used during a directory scan + */ +static char longName[DOSLONGNAMELEN] = ""; +static u_char *buffer = NULL; +static u_char *delbuf = NULL; + +struct dosDirEntry *rootDir; +static struct dosDirEntry *lostDir; + +/* + * Init internal state for a new directory scan. + */ +int +resetDosDirSection(struct bootblock *boot, struct fatEntry *fat) +{ + int b1, b2; + cl_t cl; + int ret = FSOK; + + b1 = boot->RootDirEnts * 32; + b2 = boot->SecPerClust * boot->BytesPerSec; + + if (!(buffer = malloc(b1 > b2 ? b1 : b2)) + || !(delbuf = malloc(b2)) + || !(rootDir = newDosDirEntry())) { + perror("No space for directory"); + return FSFATAL; + } + memset(rootDir, 0, sizeof *rootDir); + if (boot->flags & FAT32) { + if (boot->RootCl < CLUST_FIRST || boot->RootCl >= boot->NumClusters) { + pfatal("Root directory starts with cluster out of range(%u)", + boot->RootCl); + return FSFATAL; + } + cl = fat[boot->RootCl].next; + if (cl < CLUST_FIRST + || (cl >= CLUST_RSRVD && cl< CLUST_EOFS) + || fat[boot->RootCl].head != boot->RootCl) { + if (cl == CLUST_FREE) + pwarn("Root directory starts with free cluster\n"); + else if (cl >= CLUST_RSRVD) + pwarn("Root directory starts with cluster marked %s\n", + rsrvdcltype(cl)); + else { + pfatal("Root directory doesn't start a cluster chain"); + return FSFATAL; + } + if (ask(1, "Fix")) { + fat[boot->RootCl].next = CLUST_FREE; + ret = FSFATMOD; + } else + ret = FSFATAL; + } + + fat[boot->RootCl].flags |= FAT_USED; + rootDir->head = boot->RootCl; + } + + 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 = 0; + 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, + int startoff, cl_t endcl, int endoff, int notlast) +{ + u_char *s, *e; + loff_t off; + int clsz = boot->SecPerClust * boot->BytesPerSec; + + s = delbuf + startoff; + e = delbuf + clsz; + while (startcl >= CLUST_FIRST && startcl < boot->NumClusters) { + if (startcl == endcl) { + if (notlast) + break; + e = delbuf + endoff; + } + off = startcl * boot->SecPerClust + boot->ClusterOffset; + off *= boot->BytesPerSec; + if (lseek64(f, off, SEEK_SET) != off) { + printf("off = %llu\n", off); + perror("Unable to lseek64"); + return FSFATAL; + } + if (read(f, delbuf, clsz) != clsz) { + perror("Unable to read directory"); + return FSFATAL; + } + while (s < e) { + *s = SLOT_DELETED; + s += 32; + } + if (lseek64(f, off, SEEK_SET) != off) { + printf("off = %llu\n", off); + perror("Unable to lseek64"); + return FSFATAL; + } + if (write(f, delbuf, clsz) != clsz) { + perror("Unable to write directory"); + return FSFATAL; + } + if (startcl == endcl) + break; + startcl = fat[startcl].next; + 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) +{ + 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(1, "Remove")) { + if (startcl != curcl) { + if (delete(f, boot, fat, + startcl, start - buffer, + endcl, end - buffer, + endcl == curcl) == FSFATAL) + return FSFATAL; + start = buffer; + } + if (endcl == curcl) + 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) +{ + /* + * Check size on ordinary files + */ + int32_t physicalSize; + + if (dir->head == CLUST_FREE) + physicalSize = 0; + else { + if (dir->head < CLUST_FIRST || dir->head >= boot->NumClusters) + return FSERROR; + physicalSize = fat[dir->head].length * boot->ClusterSize; + } + if (physicalSize < dir->size) { + pwarn("size of %s is %u, should at most be %u\n", + fullpath(dir), dir->size, physicalSize); + if (ask(1, "Truncate")) { + dir->size = physicalSize; + p[28] = (u_char)physicalSize; + p[29] = (u_char)(physicalSize >> 8); + p[30] = (u_char)(physicalSize >> 16); + p[31] = (u_char)(physicalSize >> 24); + return FSDIRMOD; + } else + return FSERROR; + } else if (physicalSize - dir->size >= boot->ClusterSize) { + pwarn("%s has too many clusters allocated\n", + fullpath(dir)); + if (ask(1, "Drop superfluous clusters")) { + cl_t cl; + u_int32_t sz = 0; + + for (cl = dir->head; (sz += boot->ClusterSize) < dir->size;) + cl = fat[cl].next; + clearchain(boot, fat, fat[cl].next); + fat[cl].next = CLUST_EOF; + return FSFATMOD; + } else + return FSERROR; + } + return FSOK; +} + + +static u_char dot_header[16]={0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00}; +static u_char dot_dot_header[16]={0x2E, 0x2E, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00}; + +/* + * Check for missing or broken '.' and '..' entries. + */ +static int +check_dot_dot(int f, struct bootblock *boot, struct fatEntry *fat,struct dosDirEntry *dir) +{ + u_char *p, *buf; + loff_t off; + int last; + cl_t cl; + int rc=0, n_count; + + int dot, dotdot; + dot = dotdot = 0; + cl = dir->head; + + if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { + return rc; + } + + do { + if (!(boot->flags & FAT32) && !dir->parent) { + last = boot->RootDirEnts * 32; + off = boot->ResSectors + boot->FATs * boot->FATsecs; + } else { + last = boot->SecPerClust * boot->BytesPerSec; + off = cl * boot->SecPerClust + boot->ClusterOffset; + } + + off *= boot->BytesPerSec; + buf = malloc(last); + if (!buf) { + perror("Unable to malloc"); + return FSFATAL; + } + if (lseek64(f, off, SEEK_SET) != off) { + printf("off = %llu\n", off); + perror("Unable to lseek64"); + free(buf); + return FSFATAL; + } + if (read(f, buf, last) != last) { + perror("Unable to read"); + free(buf); + return FSFATAL; + } + last /= 32; + p = buf; + for (n_count=0, rc=0; n_count < 11; n_count++) { + if (dot_header[n_count] != p[n_count]) { + rc=-1; + break; + } + } + if(!rc) + dot=1; + + for (n_count = 0, rc = 0; n_count < 11; n_count++) { + if (dot_dot_header[n_count] != p[n_count+32]) { + rc=-1; + break; + } + } + if(!rc) + dotdot=1; + free(buf); + } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); + + if (!dot || !dotdot) { + if (!dot) + pwarn("%s: '.' absent for %s.\n",__func__,dir->name); + + if (!dotdot) + pwarn("%s: '..' absent for %s. \n",__func__,dir->name); + return -1; + } + return 0; +} + +/* + * 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) +{ + struct dosDirEntry dirent, *d; + u_char *p, *vallfn, *invlfn, *empty; + loff_t off; + int i, j, k, last; + cl_t cl, valcl = ~0, invcl = ~0, empcl = ~0; + char *t; + u_int lidx = 0; + int shortSum; + int mod = FSOK; + int n_count=0; + int rc=0; +#define THISMOD 0x8000 /* Only used within this routine */ + + cl = dir->head; + if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) { + /* + * Already handled somewhere else. + */ + return FSOK; + } + shortSum = -1; + vallfn = invlfn = empty = NULL; + int dot,dotdot; + dot = dotdot = 0; + + do { + if (!(boot->flags & FAT32) && !dir->parent) { + last = boot->RootDirEnts * 32; + off = boot->ResSectors + boot->FATs * boot->FATsecs; + } else { + last = boot->SecPerClust * boot->BytesPerSec; + off = cl * boot->SecPerClust + boot->ClusterOffset; + } + + off *= boot->BytesPerSec; + if (lseek64(f, off, SEEK_SET) != off) { + printf("off = %llu\n", off); + perror("Unable to lseek64"); + return FSFATAL; + } + if (read(f, buffer, last) != last) { + perror("Unable to read"); + return FSFATAL; + } + last /= 32; + for (p = buffer, i = 0; i < last; i++, p += 32) { + if (dir->fsckflags & DIREMPWARN) { + *p = SLOT_EMPTY; + continue; + } + + if (*p == SLOT_EMPTY || *p == SLOT_DELETED) { + if (*p == SLOT_EMPTY) { + dir->fsckflags |= DIREMPTY; + empty = p; + empcl = cl; + } + continue; + } + + if (dir->fsckflags & DIREMPTY) { + if (!(dir->fsckflags & DIREMPWARN)) { + pwarn("%s has entries after end of directory\n", + fullpath(dir)); + if (ask(1, "Extend")) { + u_char *q; + + dir->fsckflags &= ~DIREMPTY; + if (delete(f, boot, fat, + empcl, empty - buffer, + cl, p - buffer, 1) == FSFATAL) + return FSFATAL; + q = empcl == cl ? empty : buffer; + for (; q < p; q += 32) + *q = SLOT_DELETED; + mod |= THISMOD|FSDIRMOD; + } else if (ask(1, "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; + 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 (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, + 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) { + strcpy(dirent.lname, longName); + longName[0] = '\0'; + shortSum = -1; + } + + dirent.parent = dir; + dirent.next = dir->child; + + if (invlfn) { + mod |= k = removede(f, boot, fat, + invlfn, vallfn ? vallfn : p, + invcl, vallfn ? valcl : cl, cl, + fullpath(&dirent), 0); + if (mod & FSFATAL) + return FSFATAL; + if (vallfn + ? (valcl == cl && vallfn != buffer) + : p != buffer) + if (k & FSDIRMOD) + mod |= THISMOD; + } + + vallfn = NULL; /* not used any longer */ + invlfn = NULL; + + if (dirent.size == 0 && !(dirent.flags & ATTR_DIRECTORY)) { + if (dirent.head != 0) { + pwarn("%s has clusters, but size 0\n", + fullpath(&dirent)); + if (ask(1, "Drop allocated clusters")) { + p[26] = p[27] = 0; + if (boot->ClustMask == CLUST32_MASK) + p[20] = p[21] = 0; + clearchain(boot, fat, dirent.head); + dirent.head = 0; + mod |= THISMOD|FSDIRMOD|FSFATMOD; + } else + mod |= FSERROR; + } + } else if (dirent.head == 0 + && !strcmp(dirent.name, "..") + && dir->parent /* XXX */ + && !dir->parent->parent) { + /* + * Do nothing, the parent is the root + */ + } else if (dirent.head < CLUST_FIRST + || dirent.head >= boot->NumClusters + || fat[dirent.head].next == CLUST_FREE + || (fat[dirent.head].next >= CLUST_RSRVD + && fat[dirent.head].next < CLUST_EOFS) + || fat[dirent.head].head != dirent.head) { + if (dirent.head == 0) + pwarn("%s has no clusters\n", + fullpath(&dirent)); + else if (dirent.head < CLUST_FIRST + || dirent.head >= boot->NumClusters) + pwarn("%s starts with cluster out of range(%u)\n", + fullpath(&dirent), + dirent.head); + else if (fat[dirent.head].next == CLUST_FREE) + pwarn("%s starts with free cluster\n", + fullpath(&dirent)); + else if (fat[dirent.head].next >= CLUST_RSRVD) + pwarn("%s starts with cluster marked %s\n", + fullpath(&dirent), + rsrvdcltype(fat[dirent.head].next)); + else + pwarn("%s doesn't start a cluster chain\n", + fullpath(&dirent)); + if (dirent.flags & ATTR_DIRECTORY) { + if (ask(1, "Remove")) { + *p = SLOT_DELETED; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + continue; + } else { + if (ask(1, "Truncate")) { + p[28] = p[29] = p[30] = p[31] = 0; + p[26] = p[27] = 0; + if (boot->ClustMask == CLUST32_MASK) + p[20] = p[21] = 0; + dirent.size = 0; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + } + } + + if (dirent.head >= CLUST_FIRST && dirent.head < boot->NumClusters) + fat[dirent.head].flags |= FAT_USED; + + if (dirent.flags & ATTR_DIRECTORY) { + /* + * gather more info for directories + */ + struct dirTodoNode *n; + + if (dirent.size) { + pwarn("Directory %s has size != 0\n", + fullpath(&dirent)); + if (ask(1, "Correct")) { + p[28] = p[29] = p[30] = p[31] = 0; + dirent.size = 0; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + } + /* + * handle '.' and '..' specially + */ + if (strcmp(dirent.name, ".") == 0) { + if (dirent.head != dir->head) { + pwarn("'.' entry in %s has incorrect start cluster\n", + fullpath(dir)); + if (ask(1, "Correct")) { + dirent.head = dir->head; + p[26] = (u_char)dirent.head; + p[27] = (u_char)(dirent.head >> 8); + if (boot->ClustMask == CLUST32_MASK) { + p[20] = (u_char)(dirent.head >> 16); + p[21] = (u_char)(dirent.head >> 24); + } + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + } + continue; + } else if (strcmp(dirent.name, "..") == 0) { + if (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.remove\n", + dirent.name, fullpath(dir)); + //we have to remove this directory entry rigth now rigth here + if (ask(1, "Remove")) { + *p = SLOT_DELETED; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + continue; + } + /* Consistency checking. a directory must have at least two entries: + a dot (.) entry that points to itself, and a dot-dot (..) + entry that points to its parent. + */ + if (check_dot_dot(f,boot,fat,&dirent)) { + //mark directory entry as deleted. + if (ask(1, "Remove")) { + *p = SLOT_DELETED; + mod |= THISMOD|FSDIRMOD; + } else + mod |= FSERROR; + continue; + } + } + + /* create directory tree node */ + if (!(d = newDosDirEntry())) { + perror("No space for directory"); + return FSFATAL; + } + memcpy(d, &dirent, sizeof(struct dosDirEntry)); + /* link it into the tree */ + dir->child = d; +#if 0 + printf("%s: %s : 0x%02x:head %d, next 0x%0x parent 0x%0x child 0x%0x\n", + __func__,d->name,d->flags,d->head,d->next,d->parent,d->child); +#endif + /* Enter this directory into the todo list */ + if (!(n = newDirTodo())) { + perror("No space for todo list"); + return FSFATAL; + } + n->next = pendingDirectories; + n->dir = d; + pendingDirectories = n; + } else { + mod |= k = checksize(boot, fat, p, &dirent); + if (k & FSDIRMOD) + mod |= THISMOD; + } + boot->NumFiles++; + } + if (mod & THISMOD) { + last *= 32; + if (lseek64(f, off, SEEK_SET) != off + || write(f, buffer, last) != last) { + perror("Unable to write directory"); + return FSFATAL; + } + mod &= ~THISMOD; + } + } while ((cl = fat[cl].next) >= CLUST_FIRST && cl < boot->NumClusters); + if (invlfn || vallfn) + mod |= removede(f, boot, fat, + invlfn ? invlfn : vallfn, p, + invlfn ? invcl : valcl, -1, 0, + fullpath(dir), 1); + return mod & ~THISMOD; +} + +int +handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat) +{ + int mod; + + mod = readDosDirSection(dosfs, boot, fat, rootDir); + if (mod & FSFATAL) + return FSFATAL; + + /* + * process the directory todo list + */ + while (pendingDirectories) { + struct dosDirEntry *dir = pendingDirectories->dir; + struct dirTodoNode *n = pendingDirectories->next; + + /* + * remove TODO entry now, the list might change during + * directory reads + */ + freeDirTodo(pendingDirectories); + pendingDirectories = n; + + /* + * handle subdirectory + */ + mod |= readDosDirSection(dosfs, boot, fat, dir); + if (mod & FSFATAL) + return FSFATAL; + } + + return mod; +} + +/* + * Try to reconnect a FAT chain into dir + */ +static u_char *lfbuf; +static cl_t lfcl; +static loff_t lfoff; + +int +reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head) +{ + struct dosDirEntry d; + u_char *p; + + 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) { + perror("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; + 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 * boot->ClusterSize + + boot->ClusterOffset * boot->BytesPerSec; + if (lseek64(dosfs, lfoff, SEEK_SET) != lfoff + || read(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { + perror("could not read LOST.DIR"); + return FSFATAL; + } + p = lfbuf; + } + + boot->NumFiles++; + /* Ensure uniqueness of entry here! XXX */ + memset(&d, 0, sizeof d); + (void)snprintf(d.name, sizeof(d.name), "%u", head); + d.flags = 0; + d.head = head; + d.size = fat[head].length * boot->ClusterSize; + + memset(p, 0, 32); + memset(p, ' ', 11); + memcpy(p, d.name, strlen(d.name)); + 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 (lseek64(dosfs, lfoff, SEEK_SET) != lfoff + || write(dosfs, lfbuf, boot->ClusterSize) != boot->ClusterSize) { + perror("could not write LOST.DIR"); + return FSFATAL; + } + return FSDIRMOD; +} + +void +finishlf(void) +{ + if (lfbuf) + free(lfbuf); + lfbuf = NULL; +} diff --git a/src/dosfs.h b/src/dosfs.h new file mode 100644 index 0000000..5420e25 --- /dev/null +++ b/src/dosfs.h @@ -0,0 +1,144 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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: src/sbin/fsck_msdosfs/dosfs.h,v 1.3 2003/12/26 17:24:37 trhodes Exp $ + */ + +#ifndef DOSFS_H +#define DOSFS_H + +#define DOSBOOTBLOCKSIZE 512 + +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 BytesPerSec; /* bytes per sector */ + u_int SecPerClust; /* sectors per cluster */ + u_int ResSectors; /* number of reserved sectors */ + u_int FATs; /* number of FATs */ + u_int RootDirEnts; /* number of root directory entries */ + u_int Media; /* media descriptor */ + u_int FATsmall; /* number of sectors per FAT */ + u_int SecPerTrack; /* sectors per track */ + u_int Heads; /* number of heads */ + u_int32_t Sectors; /* total number of sectors */ + u_int32_t HiddenSecs; /* # of hidden sectors */ + u_int32_t HugeSectors; /* # of sectors if bpbSectors == 0 */ + u_int FSInfo; /* FSInfo sector */ + u_int Backup; /* Backup of Bootblocks */ + cl_t RootCl; /* Start of Root Directory */ + 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 ClusterOffset; /* at what sector would sector 0 start */ + 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 */ + +/* + * 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 diff --git a/src/ext.h b/src/ext.h new file mode 100644 index 0000000..6d183e9 --- /dev/null +++ b/src/ext.h @@ -0,0 +1,150 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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: src/sbin/fsck_msdosfs/ext.h,v 1.10.20.1 2009/04/15 03:14:26 kensmith Exp $ + */ + +#ifndef EXT_H +#define EXT_H + +#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 struct dosDirEntry *rootDir; + +/* + * function declarations + */ +int ask(int, const char *, ...); + +/* + * 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 occured */ +#define FSDIRTY 32 /* File system is dirty */ +#define FSFIXFAT 64 /* Fix file system FAT */ + +/* + * read a boot block in a machine independend 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 *, int, struct fatEntry **); + +/* + * Check two FAT copies for consistency and merge changes into the + * first if neccessary. + */ +int comparefat(struct bootblock *, struct fatEntry *, struct fatEntry *, int); + +/* + * Check a FAT + */ +int checkfat(struct bootblock *, struct fatEntry *); + +/* + * Write back FAT entries + */ +int writefat(int, struct bootblock *, struct fatEntry *, int); + +/* + * Read a directory + */ +int resetDosDirSection(struct bootblock *, struct fatEntry *); +void finishDosDirSection(void); +int handleDirTree(int, struct bootblock *, struct fatEntry *); + +/* + * Cross-check routines run after everything is completely in memory + */ +/* + * Check for lost cluster chains + */ +int checklost(int, struct bootblock *, struct fatEntry *); +/* + * Try to reconnect a lost cluster chain + */ +int reconnect(int, struct bootblock *, struct fatEntry *, cl_t); +void finishlf(void); + +/* + * Small helper functions + */ +/* + * Return the type of a reserved cluster as text + */ +char *rsrvdcltype(cl_t); + +/* + * Clear a cluster chain in a FAT + */ +void clearchain(struct bootblock *, struct fatEntry *, cl_t); + +#endif diff --git a/src/fat.c b/src/fat.c new file mode 100644 index 0000000..7be04e5 --- /dev/null +++ b/src/fat.c @@ -0,0 +1,735 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 +#include +#include +#include +#include + +#include "ext.h" +#include "fsutil.h" + +static int checkclnum(struct bootblock *, int, cl_t, cl_t *); +static int clustdiffer(cl_t, cl_t *, cl_t *, int); +static int tryclear(struct bootblock *, struct fatEntry *, cl_t, cl_t *); +static int _readfat(int, struct bootblock *, int, u_char **); + +/*- + * 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; + + if (boot->ClustMask != CLUST16_MASK && boot->ClustMask != CLUST32_MASK) + return 0; + + off = boot->ResSectors; + off *= boot->BytesPerSec; + + buffer = malloc(boot->BytesPerSec); + if (buffer == NULL) { + perror("No space for FAT"); + return 1; + } + + if (lseek(fs, off, SEEK_SET) != off) { + perror("Unable to read FAT"); + goto err; + } + + if (read(fs, buffer, boot->BytesPerSec) != boot->BytesPerSec) { + perror("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->Media || 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 + */ +static int +checkclnum(struct bootblock *boot, int fat, cl_t cl, cl_t *next) +{ + 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(1, "Truncate")) { + *next = CLUST_EOF; + return FSFATMOD; + } + return FSERROR; + } + return FSOK; +} + +/* + * Read a FAT from disk. Returns 1 if successful, 0 otherwise. + */ +static int +_readfat(int fs, struct bootblock *boot, int no, u_char **buffer) +{ + off_t off; + + printf("Attempting to allocate %u KB for FAT\n", + (boot->FATsecs * boot->BytesPerSec) / 1024); + + *buffer = malloc(boot->FATsecs * boot->BytesPerSec); + if (*buffer == NULL) { + perror("No space for FAT"); + return 0; + } + + off = boot->ResSectors + no * boot->FATsecs; + off *= boot->BytesPerSec; + + if (lseek(fs, off, SEEK_SET) != off) { + perror("Unable to read FAT"); + goto err; + } + + if (read(fs, *buffer, boot->FATsecs * boot->BytesPerSec) + != boot->FATsecs * boot->BytesPerSec) { + perror("Unable to read FAT"); + goto err; + } + + return 1; + + err: + free(*buffer); + return 0; +} + +/* + * Read a FAT and decode it into internal format + */ +int +readfat(int fs, struct bootblock *boot, int no, struct fatEntry **fp) +{ + struct fatEntry *fat; + u_char *buffer, *p; + cl_t cl; + int ret = FSOK; + + boot->NumFree = boot->NumBad = 0; + + if (!_readfat(fs, boot, no, &buffer)) + return FSFATAL; + + fat = calloc(boot->NumClusters, sizeof(struct fatEntry)); + if (fat == NULL) { + perror("No space for FAT"); + free(buffer); + return FSFATAL; + } + + if (buffer[0] != boot->Media + || 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->Media && 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 |= FSFIXFAT; + } + } + switch (boot->ClustMask) { + case CLUST32_MASK: + p = buffer + 8; + break; + case CLUST16_MASK: + p = buffer + 4; + break; + default: + p = buffer + 3; + break; + } + for (cl = CLUST_FIRST; cl < boot->NumClusters;) { + switch (boot->ClustMask) { + case CLUST32_MASK: + fat[cl].next = p[0] + (p[1] << 8) + + (p[2] << 16) + (p[3] << 24); + fat[cl].next &= boot->ClustMask; + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + p += 4; + break; + case CLUST16_MASK: + fat[cl].next = p[0] + (p[1] << 8); + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + p += 2; + break; + default: + fat[cl].next = (p[0] + (p[1] << 8)) & 0x0fff; + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + if (cl >= boot->NumClusters) + break; + fat[cl].next = ((p[1] >> 4) + (p[2] << 4)) & 0x0fff; + ret |= checkclnum(boot, no, cl, &fat[cl].next); + cl++; + p += 3; + break; + } + } + + free(buffer); + *fp = fat; + return ret; +} + +/* + * Get type of reserved cluster + */ +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, int fatnum) +{ + 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 %d\n", + cl, rsrvdcltype(*cp1), rsrvdcltype(*cp2), fatnum); + if (ask(1, "Use FAT 0's entry")) { + *cp2 = *cp1; + return FSFATMOD; + } + if (ask(1, "Use FAT %d'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(1, "Use continuation from FAT %d", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } + if (ask(1, "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 %d\n", + cl, *cp1, rsrvdcltype(*cp2), fatnum); + if (ask(1, "Use continuation from FAT 0")) { + *cp2 = *cp1; + return FSFATMOD; + } + if (ask(1, "Use mark from FAT %d", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } + return FSERROR; + } + pwarn("Cluster %u continues with cluster %u in FAT 0, but with cluster %u in FAT %d\n", + cl, *cp1, *cp2, fatnum); + if (ask(1, "Use continuation from FAT 0")) { + *cp2 = *cp1; + return FSFATMOD; + } + if (ask(1, "Use continuation from FAT %d", fatnum)) { + *cp1 = *cp2; + return FSFATMOD; + } + return FSERROR; +} + +/* + * Compare two FAT copies in memory. Resolve any conflicts and merge them + * into the first one. + */ +int +comparefat(struct bootblock *boot, struct fatEntry *first, + struct fatEntry *second, int fatnum) +{ + cl_t cl; + int ret = FSOK; + + 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; +} + +void +clearchain(struct bootblock *boot, struct fatEntry *fat, cl_t head) +{ + cl_t p, q; + + for (p = head; p >= CLUST_FIRST && p < boot->NumClusters; p = q) { + if (fat[p].head != head) + break; + q = fat[p].next; + fat[p].next = fat[p].head = CLUST_FREE; + fat[p].length = 0; + } +} + +int +tryclear(struct bootblock *boot, struct fatEntry *fat, cl_t head, cl_t *trunc) +{ + if (ask(1, "Clear chain starting at %u", head)) { + clearchain(boot, fat, head); + return FSFATMOD; + } else if (ask(1, "Truncate")) { + *trunc = CLUST_EOF; + return FSFATMOD; + } else + return FSERROR; +} + +/* + * Check a complete FAT in-memory for crosslinks + */ +int +checkfat(struct bootblock *boot, struct fatEntry *fat) +{ + cl_t head, p, h, n, wdk; + u_int len; + int ret = 0; + int conf; + + /* + * 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. */ + + /* follow the chain and mark all clusters on the way */ + for (len = 0, p = head; + p >= CLUST_FIRST && p < boot->NumClusters; + p = fat[p].next) { + /* we have to check the len, to avoid infinite loop */ + if (len > boot->NumClusters) { + printf("detect cluster chain loop: head %u for p %u\n", head, p); + break; + } + + fat[p].head = head; + len++; + } + + /* the head record gets the length */ + fat[head].length = fat[head].next == CLUST_FREE ? 0 : len; + } + + /* + * 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; + + /* follow the chain to its end (hopefully) */ + /* also possible infinite loop, that's why I insert wdk counter */ + for (p = head,wdk=boot->NumClusters; + (n = fat[p].next) >= CLUST_FIRST && n < boot->NumClusters && wdk; + p = n,wdk--) { + if (fat[n].head != head) + break; + } + + if (n >= CLUST_EOFS) + continue; + + if (n == CLUST_FREE || n >= CLUST_RSRVD) { + pwarn("Cluster chain starting at %u ends with cluster marked %s\n", + head, rsrvdcltype(n)); + 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); + ret |= tryclear(boot, fat, head, &fat[p].next); + continue; + } + 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(1, "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; +} + +/* + * Write out FATs encoding them from the internal format + */ +int +writefat(int fs, struct bootblock *boot, struct fatEntry *fat, int correct_fat) +{ + u_char *buffer, *p; + cl_t cl; + int i; + u_int32_t fatsz; + off_t off; + int ret = FSOK; + + buffer = malloc(fatsz = boot->FATsecs * boot->BytesPerSec); + if (buffer == NULL) { + perror("No space for FAT"); + return FSFATAL; + } + memset(buffer, 0, fatsz); + boot->NumFree = 0; + p = buffer; + if (correct_fat) { + *p++ = (u_char)boot->Media; + *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; + + switch (boot->ClustMask) { + case CLUST32_MASK: + count = 8; + break; + case CLUST16_MASK: + count = 4; + break; + default: + count = 3; + break; + } + + if (!_readfat(fs, boot, boot->ValidFat >= 0 ? boot->ValidFat :0, + &old_fat)) { + free(buffer); + return FSFATAL; + } + + memcpy(p, old_fat, count); + free(old_fat); + p += count; + } + + for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) { + switch (boot->ClustMask) { + case CLUST32_MASK: + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + *p++ = (u_char)fat[cl].next; + *p++ = (u_char)(fat[cl].next >> 8); + *p++ = (u_char)(fat[cl].next >> 16); + *p &= 0xf0; + *p++ |= (fat[cl].next >> 24)&0x0f; + break; + case CLUST16_MASK: + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + *p++ = (u_char)fat[cl].next; + *p++ = (u_char)(fat[cl].next >> 8); + break; + default: + if (fat[cl].next == CLUST_FREE) + boot->NumFree++; + if (cl + 1 < boot->NumClusters + && fat[cl + 1].next == CLUST_FREE) + boot->NumFree++; + *p++ = (u_char)fat[cl].next; + *p++ = (u_char)((fat[cl].next >> 8) & 0xf) + |(u_char)(fat[cl+1].next << 4); + *p++ = (u_char)(fat[++cl].next >> 4); + break; + } + } + for (i = 0; i < boot->FATs; i++) { + off = boot->ResSectors + i * boot->FATsecs; + off *= boot->BytesPerSec; + if (lseek(fs, off, SEEK_SET) != off + || write(fs, buffer, fatsz) != fatsz) { + perror("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) +{ + cl_t head; + int mod = FSOK; + int ret; + + for (head = CLUST_FIRST; head < boot->NumClusters; head++) { + /* find next untravelled chain */ + if (fat[head].head != head + || fat[head].next == CLUST_FREE + || (fat[head].next >= CLUST_RSRVD + && fat[head].next < CLUST_EOFS) + || (fat[head].flags & FAT_USED)) + continue; + + pwarn("Lost cluster chain at cluster %u\n%d Cluster(s) lost\n", + head, fat[head].length); + mod |= ret = reconnect(dosfs, boot, fat, head); + if (mod & FSFATAL) { + /* If the reconnect failed, then just clear the chain */ + pwarn("Error reconnecting chain - clearing\n"); + mod &= ~FSFATAL; + clearchain(boot, fat, head); + mod |= FSFATMOD; + continue; + } + if (ret == FSERROR && ask(1, "Clear")) { + clearchain(boot, fat, head); + mod |= FSFATMOD; + } + } + finishlf(); + + if (boot->FSInfo) { + ret = 0; + if (boot->FSFree != boot->NumFree) { + pwarn("Free space in FSInfo block (%d) not correct (%d)\n", + boot->FSFree, boot->NumFree); + if (ask(1, "Fix")) { + boot->FSFree = boot->NumFree; + ret = 1; + } + } + + if (boot->NumFree) { + if ((boot->FSNext >= boot->NumClusters) || (fat[boot->FSNext].next != CLUST_FREE)) { + pwarn("Next free cluster in FSInfo block (%u) not free\n", + boot->FSNext); + if (ask(1, "Fix")) + for (head = CLUST_FIRST; head < boot->NumClusters; head++) + if (fat[head].next == CLUST_FREE) { + boot->FSNext = head; + ret = 1; + break; + } + } + } + + if (boot->FSNext > boot->NumClusters ) { + pwarn("FSNext block (%d) not correct NumClusters (%d)\n", + boot->FSNext, boot->NumClusters); + boot->FSNext=CLUST_FIRST; // boot->FSNext can have -1 value. + } + + if (boot->NumFree && fat[boot->FSNext].next != CLUST_FREE) { + pwarn("Next free cluster in FSInfo block (%u) not free\n", + boot->FSNext); + if (ask(1, "Fix")) + for (head = CLUST_FIRST; head < boot->NumClusters; head++) + if (fat[head].next == CLUST_FREE) { + boot->FSNext = head; + ret = 1; + break; + } + } + + if (ret) + mod |= writefsinfo(dosfs, boot); + } + + return mod; +} diff --git a/src/fsutil.h b/src/fsutil.h new file mode 100644 index 0000000..7acfdd6 --- /dev/null +++ b/src/fsutil.h @@ -0,0 +1,7 @@ +#ifndef _FS_UTIL_H +#define _FS_UTIL_H + +#define pwarn printf +#define pfatal printf + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..9539d8f --- /dev/null +++ b/src/main.c @@ -0,0 +1,154 @@ +/* + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Martin Husemann + * and Wolfgang Solfrank. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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 +#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 */ + +static void usage(void); + +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) { + 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 = preen = 0; + break; + case 'y': + alwaysyes = 1; + alwaysno = preen = 0; + break; + + case 'p': + preen = 1; + alwaysyes = alwaysno = 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 (preen) { + if (rdonly) + def = 0; + if (def) + printf("FIXED\n"); + return def; + } + + va_start(ap, fmt); + vsnprintf(prompt, sizeof(prompt), fmt, ap); + if (alwaysyes || rdonly) { + printf("%s? %s\n", prompt, rdonly ? "no" : "yes"); + return !rdonly; + } + 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'; +} -- 2.7.4