summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2019-04-10 13:38:23 -0700
committerXin Li <delphij@google.com>2019-04-10 13:38:23 -0700
commit1e1dad6992abea357715fdae4f747ae2051025a0 (patch)
tree1032d98650dc82331ccdcba50ae36e98c507ad84
downloadfsck_msdos-1e1dad6992abea357715fdae4f747ae2051025a0.tar.gz
Import revision 9131ba637f003fb5894e3f6343a27d6322205f18 from FreeBSD.
Change-Id: I8ee418314ea407ca679bcd1bed85bd4cff435c22
-rw-r--r--Makefile14
-rw-r--r--Makefile.depend17
-rw-r--r--boot.c297
-rw-r--r--check.c196
-rw-r--r--dir.c1016
-rw-r--r--dosfs.h141
-rw-r--r--ext.h143
-rw-r--r--fat.c713
-rw-r--r--fsck_msdosfs.8126
-rw-r--r--main.c157
10 files changed, 2820 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b101724
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+# $NetBSD: Makefile,v 1.6 1997/05/08 21:11:11 gwr Exp $
+# $FreeBSD$
+
+FSCK= ${.CURDIR:H}/fsck
+.PATH: ${FSCK}
+
+PACKAGE=runtime
+PROG= fsck_msdosfs
+MAN= fsck_msdosfs.8
+SRCS= main.c check.c boot.c fat.c dir.c fsutil.c
+
+CFLAGS+= -I${FSCK}
+
+.include <bsd.prog.mk>
diff --git a/Makefile.depend b/Makefile.depend
new file mode 100644
index 0000000..6cfaab1
--- /dev/null
+++ b/Makefile.depend
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ gnu/lib/csu \
+ include \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/boot.c b/boot.c
new file mode 100644
index 0000000..56bd57d
--- /dev/null
+++ b/boot.c
@@ -0,0 +1,297 @@
+/*-
+ * 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 <sys/cdefs.h>
+#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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "ext.h"
+#include "fsutil.h"
+
+int
+readboot(int dosfs, struct bootblock *boot)
+{
+ u_char block[DOSBOOTBLOCKSIZE];
+ u_char fsinfo[2 * DOSBOOTBLOCKSIZE];
+ u_char backup[DOSBOOTBLOCKSIZE];
+ int ret = FSOK;
+ int i;
+
+ 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 */
+ boot->bpbBytesPerSec = block[11] + (block[12] << 8);
+ boot->bpbSecPerClust = block[13];
+ boot->bpbResSectors = block[14] + (block[15] << 8);
+ boot->bpbFATs = block[16];
+ boot->bpbRootDirEnts = block[17] + (block[18] << 8);
+ boot->bpbSectors = block[19] + (block[20] << 8);
+ boot->bpbMedia = block[21];
+ boot->bpbFATsmall = block[22] + (block[23] << 8);
+ boot->SecPerTrack = block[24] + (block[25] << 8);
+ boot->bpbHeads = block[26] + (block[27] << 8);
+ boot->bpbHiddenSecs = block[28] + (block[29] << 8) +
+ (block[30] << 16) + (block[31] << 24);
+ boot->bpbHugeSectors = block[32] + (block[33] << 8) +
+ (block[34] << 16) + (block[35] << 24);
+
+ boot->FATsecs = boot->bpbFATsmall;
+
+ if (boot->bpbBytesPerSec % DOSBOOTBLOCKSIZE_REAL != 0 ||
+ boot->bpbBytesPerSec / DOSBOOTBLOCKSIZE_REAL == 0) {
+ pfatal("Invalid sector size: %u", boot->bpbBytesPerSec);
+ return FSFATAL;
+ }
+ if (boot->bpbFATs == 0) {
+ pfatal("Invalid number of FATs: %u", boot->bpbFATs);
+ return FSFATAL;
+ }
+ if (!boot->bpbRootDirEnts)
+ 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]);
+ return FSFATAL;
+ }
+ boot->bpbRootClust = block[44] + (block[45] << 8)
+ + (block[46] << 16) + (block[47] << 24);
+ boot->bpbFSInfo = block[48] + (block[49] << 8);
+ boot->bpbBackup = block[50] + (block[51] << 8);
+
+ /* 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;
+ }
+
+ /* check basic parameters */
+ if ((boot->bpbFSInfo == 0) || (boot->bpbSecPerClust == 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;
+ }
+ 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;
+ }
+ if (boot->bpbFSInfo) {
+ 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->bpbBackup * boot->bpbBytesPerSec,
+ SEEK_SET)
+ != boot->bpbBackup * boot->bpbBytesPerSec
+ || read(dosfs, backup, sizeof backup) != sizeof backup) {
+ perr("could not read backup bootblock");
+ return FSFATAL;
+ }
+ backup[65] = block[65]; /* XXX */
+ if (memcmp(block + 11, backup + 11, 79)) {
+ /*
+ * XXX We require a reference that explains
+ * that these bytes need to match, or should
+ * drop the check. gdt@NetBSD has observed
+ * filesystems that work fine under Windows XP
+ * and NetBSD that do not match, so the
+ * requirement is suspect. For now, just
+ * print out useful information and continue.
+ */
+ pwarn("backup (block %d) mismatch with primary bootblock:\n",
+ boot->bpbBackup);
+ for (i = 11; i < 11 + 90; i++) {
+ if (block[i] != backup[i])
+ pwarn("\ti=%d\tprimary 0x%02x\tbackup 0x%02x\n",
+ i, block[i], backup[i]);
+ }
+ }
+ /* Check backup bpbFSInfo? XXX */
+ }
+
+ if (boot->bpbSecPerClust == 0) {
+ pfatal("Invalid cluster size: %u", boot->bpbSecPerClust);
+ return FSFATAL;
+ }
+ if (boot->bpbSectors) {
+ boot->bpbHugeSectors = 0;
+ boot->NumSectors = boot->bpbSectors;
+ } else
+ boot->NumSectors = boot->bpbHugeSectors;
+ boot->ClusterOffset = (boot->bpbRootDirEnts * 32 +
+ boot->bpbBytesPerSec - 1) / boot->bpbBytesPerSec +
+ boot->bpbResSectors + boot->bpbFATs * boot->FATsecs -
+ CLUST_FIRST * boot->bpbSecPerClust;
+ boot->NumClusters = (boot->NumSectors - boot->ClusterOffset) /
+ boot->bpbSecPerClust;
+
+ 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->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;
+}
diff --git a/check.c b/check.c
new file mode 100644
index 0000000..2431bd3
--- /dev/null
+++ b/check.c
@@ -0,0 +1,196 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <sys/cdefs.h>
+#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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "ext.h"
+#include "fsutil.h"
+
+int
+checkfilesys(const char *fname)
+{
+ int dosfs;
+ struct bootblock boot;
+ struct fatEntry *fat = NULL;
+ int finish_dosdirsection=0;
+ u_int i;
+ int mod = 0;
+ int ret = 8;
+
+ rdonly = alwaysno;
+ if (!preen)
+ printf("** %s", fname);
+
+ dosfs = open(fname, rdonly ? O_RDONLY : O_RDWR, 0);
+ if (dosfs < 0 && !rdonly) {
+ dosfs = open(fname, O_RDONLY, 0);
+ if (dosfs >= 0)
+ pwarn(" (NO WRITE)\n");
+ else if (!preen)
+ printf("\n");
+ rdonly = 1;
+ } else if (!preen)
+ printf("\n");
+
+ if (dosfs < 0) {
+ perr("Can't open `%s'", fname);
+ printf("\n");
+ return 8;
+ }
+
+ if (readboot(dosfs, &boot) == FSFATAL) {
+ close(dosfs);
+ printf("\n");
+ return 8;
+ }
+
+ if (skipclean && preen && checkdirty(dosfs, &boot)) {
+ printf("%s: ", fname);
+ printf("FILESYSTEM CLEAN; SKIPPING CHECKS\n");
+ ret = 0;
+ goto out;
+ }
+
+ if (!preen) {
+ 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) {
+ close(dosfs);
+ return 8;
+ }
+
+ if (boot.ValidFat < 0)
+ for (i = 1; i < boot.bpbFATs; i++) {
+ struct fatEntry *currentFat;
+
+ mod |= readfat(dosfs, &boot, i, &currentFat);
+
+ 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");
+
+ mod |= checkfat(&boot, fat);
+ if (mod & FSFATAL)
+ goto out;
+ /* delay writing FATs */
+
+ if (!preen)
+ printf("** Phase 3 - Checking Directories\n");
+
+ mod |= resetDosDirSection(&boot, fat);
+ finish_dosdirsection = 1;
+ if (mod & FSFATAL)
+ goto out;
+ /* delay writing FATs */
+
+ mod |= handleDirTree(dosfs, &boot, fat);
+ if (mod & FSFATAL)
+ goto out;
+
+ if (!preen)
+ 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|FSFIXFAT)) {
+ if (ask(1, "Update FATs")) {
+ mod |= writefat(dosfs, &boot, fat, mod & FSFIXFAT);
+ if (mod & FSFATAL)
+ goto out;
+ } else
+ mod |= FSERROR;
+ }
+
+ if (boot.NumBad)
+ pwarn("%d files, %d free (%d clusters), %d bad (%d clusters)\n",
+ boot.NumFiles,
+ boot.NumFree * boot.ClusterSize / 1024, boot.NumFree,
+ boot.NumBad * boot.ClusterSize / 1024, boot.NumBad);
+ else
+ pwarn("%d files, %d free (%d clusters)\n",
+ boot.NumFiles,
+ boot.NumFree * boot.ClusterSize / 1024, boot.NumFree);
+
+ if (mod && (mod & FSERROR) == 0) {
+ if (mod & FSDIRTY) {
+ if (ask(1, "MARK FILE SYSTEM CLEAN") == 0)
+ mod &= ~FSDIRTY;
+
+ if (mod & FSDIRTY) {
+ pwarn("MARKING FILE SYSTEM CLEAN\n");
+ mod |= writefat(dosfs, &boot, fat, 1);
+ } else {
+ pwarn("\n***** FILE SYSTEM IS LEFT MARKED AS DIRTY *****\n");
+ mod |= FSERROR; /* file system not clean */
+ }
+ }
+ }
+
+ if (mod & (FSFATAL | FSERROR))
+ goto out;
+
+ ret = 0;
+
+ out:
+ if (finish_dosdirsection)
+ finishDosDirSection();
+ free(fat);
+ close(dosfs);
+
+ if (mod & (FSFATMOD|FSDIRMOD))
+ pwarn("\n***** FILE SYSTEM WAS MODIFIED *****\n");
+
+ return ret;
+}
diff --git a/dir.c b/dir.c
new file mode 100644
index 0000000..38c7014
--- /dev/null
+++ b/dir.c
@@ -0,0 +1,1016 @@
+/*-
+ * 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.
+ */
+
+
+#include <sys/cdefs.h>
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <sys/param.h>
+
+#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
+ */
+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 - 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;
+
+static 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;
+ size_t len;
+
+ b1 = boot->bpbRootDirEnts * 32;
+ b2 = boot->bpbSecPerClust * boot->bpbBytesPerSec;
+
+ if ((buffer = malloc(len = MAX(b1, b2))) == NULL) {
+ perr("No space for directory buffer (%zu)", len);
+ return FSFATAL;
+ }
+
+ if ((delbuf = malloc(len = b2)) == NULL) {
+ free(buffer);
+ perr("No space for directory delbuf (%zu)", len);
+ return FSFATAL;
+ }
+
+ if ((rootDir = newDosDirEntry()) == NULL) {
+ free(buffer);
+ free(delbuf);
+ perr("No space for directory entry");
+ return FSFATAL;
+ }
+
+ memset(rootDir, 0, sizeof *rootDir);
+ if (boot->flags & FAT32) {
+ if (boot->bpbRootClust < CLUST_FIRST ||
+ boot->bpbRootClust >= boot->NumClusters) {
+ pfatal("Root directory starts with cluster out of range(%u)",
+ boot->bpbRootClust);
+ return FSFATAL;
+ }
+ cl = fat[boot->bpbRootClust].next;
+ if (cl < CLUST_FIRST
+ || (cl >= CLUST_RSRVD && cl< CLUST_EOFS)
+ || fat[boot->bpbRootClust].head != boot->bpbRootClust) {
+ 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->bpbRootClust].next = CLUST_FREE;
+ ret = FSFATMOD;
+ } else
+ ret = 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,
+ int startoff, cl_t endcl, int endoff, int notlast)
+{
+ u_char *s, *e;
+ off_t off;
+ int clsz = boot->bpbSecPerClust * boot->bpbBytesPerSec;
+
+ 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->bpbSecPerClust + boot->ClusterOffset;
+ off *= boot->bpbBytesPerSec;
+ if (lseek(f, off, SEEK_SET) != off
+ || read(f, 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
+ || write(f, delbuf, clsz) != clsz) {
+ perr("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(0, "Remove")) {
+ if (startcl != curcl) {
+ if (delete(f, boot, fat,
+ startcl, start - buffer,
+ endcl, end - buffer,
+ endcl == curcl) == FSFATAL)
+ return FSFATAL;
+ start = buffer;
+ }
+ /* startcl is < CLUST_FIRST for !fat32 root */
+ 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)
+{
+ /*
+ * Check size on ordinary files
+ */
+ u_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, 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;
+ } else
+ return FSERROR;
+ }
+ return FSOK;
+}
+
+/*
+ * 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;
+ off_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;
+#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;
+ do {
+ if (!(boot->flags & FAT32) && !dir->parent) {
+ last = boot->bpbRootDirEnts * 32;
+ off = boot->bpbResSectors + boot->bpbFATs *
+ boot->FATsecs;
+ } else {
+ last = boot->bpbSecPerClust * boot->bpbBytesPerSec;
+ off = cl * boot->bpbSecPerClust + boot->ClusterOffset;
+ }
+
+ off *= boot->bpbBytesPerSec;
+ if (lseek(f, off, SEEK_SET) != off
+ || read(f, buffer, last) != last) {
+ perr("Unable to read directory");
+ return FSFATAL;
+ }
+ last /= 32;
+ /*
+ * Check `.' and `..' entries here? XXX
+ */
+ 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(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;
+ 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,
+ 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,
+ 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(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) {
+ 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;
+ }
+
+ /* 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);
+ if (k & FSDIRMOD)
+ mod |= THISMOD;
+ }
+ boot->NumFiles++;
+ }
+
+ if (!(boot->flags & FAT32) && !dir->parent)
+ break;
+
+ if (mod & THISMOD) {
+ last *= 32;
+ if (lseek(f, off, SEEK_SET) != off
+ || write(f, buffer, last) != last) {
+ perr("Unable to write directory");
+ return FSFATAL;
+ }
+ mod &= ~THISMOD;
+ }
+ } while ((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);
+
+ /* The root directory of non fat32 filesystems is in a special
+ * area and may have been modified above without being written out.
+ */
+ if ((mod & FSDIRMOD) && !(boot->flags & FAT32) && !dir->parent) {
+ last *= 32;
+ if (lseek(f, off, SEEK_SET) != off
+ || write(f, buffer, last) != last) {
+ perr("Unable to write directory");
+ return FSFATAL;
+ }
+ mod &= ~THISMOD;
+ }
+ return mod & ~THISMOD;
+}
+
+int
+handleDirTree(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 off_t lfoff;
+
+int
+reconnect(int dosfs, struct bootblock *boot, struct fatEntry *fat, cl_t head)
+{
+ struct dosDirEntry d;
+ int len;
+ 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) {
+ 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;
+ if (lfcl < CLUST_FIRST || lfcl >= boot->NumClusters) {
+ /* Extend LOSTDIR? XXX */
+ pwarn("No space in %s\n", LOSTDIR);
+ return FSERROR;
+ }
+ lfoff = lfcl * boot->ClusterSize
+ + boot->ClusterOffset * 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;
+
+ 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;
+}
diff --git a/dosfs.h b/dosfs.h
new file mode 100644
index 0000000..3d84ea0
--- /dev/null
+++ b/dosfs.h
@@ -0,0 +1,141 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ * Some structure declaration borrowed from Paul Popelka
+ * (paulp@uts.amdahl.com), see /sys/msdosfs/ for reference.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * $NetBSD: dosfs.h,v 1.4 1997/01/03 14:32:48 ws Exp $
+ * $FreeBSD$
+ */
+
+#ifndef DOSFS_H
+#define DOSFS_H
+
+/* support 4Kn disk reads */
+#define DOSBOOTBLOCKSIZE_REAL 512
+#define DOSBOOTBLOCKSIZE 4096
+
+typedef u_int32_t cl_t; /* type holding a cluster number */
+
+/*
+ * architecture independent description of all the info stored in a
+ * FAT boot block.
+ */
+struct bootblock {
+ u_int bpbBytesPerSec; /* bytes per sector */
+ u_int bpbSecPerClust; /* sectors per cluster */
+ u_int bpbResSectors; /* number of reserved sectors */
+ u_int bpbFATs; /* number of bpbFATs */
+ u_int bpbRootDirEnts; /* number of root directory entries */
+ u_int32_t bpbSectors; /* total number of sectors */
+ u_int bpbMedia; /* media descriptor */
+ u_int bpbFATsmall; /* number of sectors per FAT */
+ u_int SecPerTrack; /* sectors per track */
+ u_int bpbHeads; /* number of heads */
+ u_int32_t bpbHiddenSecs; /* # of hidden sectors */
+ u_int32_t bpbHugeSectors; /* # of sectors if bpbbpbSectors == 0 */
+ cl_t bpbRootClust; /* Start of Root Directory */
+ u_int bpbFSInfo; /* FSInfo sector */
+ u_int bpbBackup; /* Backup of Bootblocks */
+ cl_t FSFree; /* Number of free clusters acc. FSInfo */
+ cl_t FSNext; /* Next free cluster acc. FSInfo */
+
+ /* and some more calculated values */
+ u_int flags; /* some flags: */
+#define FAT32 1 /* this is a FAT32 file system */
+ /*
+ * Maybe, we should separate out
+ * various parts of FAT32? XXX
+ */
+ int ValidFat; /* valid fat if FAT32 non-mirrored */
+ cl_t ClustMask; /* mask for entries in FAT */
+ cl_t NumClusters; /* # of entries in a FAT */
+ u_int32_t NumSectors; /* how many sectors are there */
+ u_int32_t FATsecs; /* how many sectors are in FAT */
+ u_int32_t NumFatEntries; /* how many entries really are there */
+ u_int 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/ext.h b/ext.h
new file mode 100644
index 0000000..ebc9467
--- /dev/null
+++ b/ext.h
@@ -0,0 +1,143 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (C) 1995, 1996, 1997 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * $NetBSD: ext.h,v 1.6 2000/04/25 23:02:51 jdolecek Exp $
+ * $FreeBSD$
+ */
+
+#ifndef EXT_H
+#define EXT_H
+
+#include <sys/types.h>
+
+#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 */
+
+/*
+ * 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 **);
+
+/*
+ * Check two FAT copies for consistency and merge changes into the
+ * first if necessary.
+ */
+int comparefat(struct bootblock *, struct fatEntry *, struct fatEntry *, u_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
+ */
+const char *rsrvdcltype(cl_t);
+
+/*
+ * Clear a cluster chain in a FAT
+ */
+void clearchain(struct bootblock *, struct fatEntry *, cl_t);
+
+#endif
diff --git a/fat.c b/fat.c
new file mode 100644
index 0000000..8a54837
--- /dev/null
+++ b/fat.c
@@ -0,0 +1,713 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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 <sys/cdefs.h>
+#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 <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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 **);
+
+/*-
+ * 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
+ */
+static int
+checkclnum(struct bootblock *boot, u_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(0, "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, u_int no, u_char **buffer)
+{
+ off_t off;
+ size_t len;
+
+ *buffer = malloc(len = boot->FATsecs * boot->bpbBytesPerSec);
+ if (*buffer == NULL) {
+ perr("No space for FAT sectors (%zu)", len);
+ return 0;
+ }
+
+ off = boot->bpbResSectors + no * boot->FATsecs;
+ off *= boot->bpbBytesPerSec;
+
+ if (lseek(fs, off, SEEK_SET) != off) {
+ perr("Unable to read FAT");
+ goto err;
+ }
+
+ if ((size_t)read(fs, *buffer, boot->FATsecs * boot->bpbBytesPerSec)
+ != boot->FATsecs * boot->bpbBytesPerSec) {
+ perr("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, u_int no, struct fatEntry **fp)
+{
+ struct fatEntry *fat;
+ u_char *buffer, *p;
+ cl_t cl;
+ int ret = FSOK;
+ size_t len;
+
+ boot->NumFree = boot->NumBad = 0;
+
+ if (!_readfat(fs, boot, no, &buffer))
+ return FSFATAL;
+
+ fat = malloc(len = boot->NumClusters * sizeof(struct fatEntry));
+ if (fat == NULL) {
+ perr("No space for FAT clusters (%zu)", len);
+ free(buffer);
+ return FSFATAL;
+ }
+ (void)memset(fat, 0, len);
+
+ 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 |= 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);
+ if (ret & FSFATAL) {
+ 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)
+{
+ if (*cp1 == CLUST_FREE || *cp1 >= CLUST_RSRVD) {
+ if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) {
+ if ((*cp1 != CLUST_FREE && *cp1 < CLUST_BAD
+ && *cp2 != CLUST_FREE && *cp2 < CLUST_BAD)
+ || (*cp1 > CLUST_BAD && *cp2 > CLUST_BAD)) {
+ pwarn("Cluster %u is marked %s with different indicators\n",
+ cl, rsrvdcltype(*cp1));
+ if (ask(1, "Fix")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ return FSFATAL;
+ }
+ pwarn("Cluster %u is marked %s in FAT 0, %s in FAT %u\n",
+ cl, rsrvdcltype(*cp1), rsrvdcltype(*cp2), fatnum);
+ if (ask(0, "Use FAT 0's entry")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use FAT %u's entry", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ return FSFATAL;
+ }
+ pwarn("Cluster %u is marked %s in FAT 0, but continues with cluster %u in FAT %d\n",
+ cl, rsrvdcltype(*cp1), *cp2, fatnum);
+ if (ask(0, "Use continuation from FAT %u", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use mark from FAT 0")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ return FSFATAL;
+ }
+ if (*cp2 == CLUST_FREE || *cp2 >= CLUST_RSRVD) {
+ pwarn("Cluster %u continues with cluster %u in FAT 0, but is marked %s in FAT %u\n",
+ cl, *cp1, rsrvdcltype(*cp2), fatnum);
+ if (ask(0, "Use continuation from FAT 0")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use mark from FAT %d", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ return FSERROR;
+ }
+ pwarn("Cluster %u continues with cluster %u in FAT 0, but with cluster %u in FAT %u\n",
+ cl, *cp1, *cp2, fatnum);
+ if (ask(0, "Use continuation from FAT 0")) {
+ *cp2 = *cp1;
+ return FSFATMOD;
+ }
+ if (ask(0, "Use continuation from FAT %u", fatnum)) {
+ *cp1 = *cp2;
+ return FSFATMOD;
+ }
+ return FSERROR;
+}
+
+/*
+ * 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, u_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 *truncp)
+{
+ if (ask(0, "Clear chain starting at %u", head)) {
+ clearchain(boot, fat, head);
+ return FSFATMOD;
+ } else if (ask(0, "Truncate")) {
+ uint32_t len;
+ cl_t p;
+
+ for (p = head, len = 0;
+ p >= CLUST_FIRST && p < boot->NumClusters;
+ p = fat[p].next, len++)
+ continue;
+ *truncp = CLUST_EOF;
+ fat[head].length = len;
+ return FSFATMOD;
+ } else
+ return FSERROR;
+}
+
+/*
+ * Check a complete FAT in-memory for crosslinks
+ */
+int
+checkfat(struct bootblock *boot, struct fatEntry *fat)
+{
+ cl_t head, p, h, n;
+ 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 &&
+ 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;
+ }
+
+ /*
+ * 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) */
+ for (len = fat[head].length, p = head;
+ (n = fat[p].next) >= CLUST_FIRST && n < boot->NumClusters;
+ p = n)
+ if (fat[n].head != head || len-- < 2)
+ break;
+ if (n >= CLUST_EOFS)
+ continue;
+
+ if (n == CLUST_FREE || n >= CLUST_RSRVD) {
+ pwarn("Cluster chain starting at %u ends with cluster marked %s\n",
+ head, rsrvdcltype(n));
+clear:
+ ret |= tryclear(boot, fat, head, &fat[p].next);
+ continue;
+ }
+ if (n < CLUST_FIRST || n >= boot->NumClusters) {
+ pwarn("Cluster chain starting at %u ends with cluster out of range (%u)\n",
+ head, n);
+ goto clear;
+ }
+ if (head == fat[n].head) {
+ pwarn("Cluster chain starting at %u loops at cluster %u\n",
+
+ head, p);
+ goto clear;
+ }
+ pwarn("Cluster chains starting at %u and %u are linked at cluster %u\n",
+ head, fat[n].head, n);
+ conf = tryclear(boot, fat, head, &fat[p].next);
+ if (ask(0, "Clear chain starting at %u", h = fat[n].head)) {
+ if (conf == FSERROR) {
+ /*
+ * Transfer the common chain to the one not cleared above.
+ */
+ for (p = n;
+ p >= CLUST_FIRST && p < boot->NumClusters;
+ p = fat[p].next) {
+ if (h != fat[p].head) {
+ /*
+ * Have to reexamine this chain.
+ */
+ head--;
+ break;
+ }
+ fat[p].head = head;
+ }
+ }
+ clearchain(boot, fat, h);
+ conf |= FSFATMOD;
+ }
+ ret |= conf;
+ }
+
+ return ret;
+}
+
+/*
+ * 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;
+ u_int i;
+ size_t fatsz;
+ off_t off;
+ int ret = FSOK;
+
+ buffer = malloc(fatsz = boot->FATsecs * boot->bpbBytesPerSec);
+ if (buffer == NULL) {
+ perr("No space for FAT sectors (%zu)", fatsz);
+ return FSFATAL;
+ }
+ memset(buffer, 0, fatsz);
+ 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;
+
+ 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++;
+ *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 + 1].next << 4);
+ *p++ = (u_char)(fat[cl + 1].next >> 4);
+ break;
+ }
+ }
+ for (i = 0; i < boot->bpbFATs; i++) {
+ off = boot->bpbResSectors + i * boot->FATsecs;
+ off *= boot->bpbBytesPerSec;
+ if (lseek(fs, off, SEEK_SET) != off
+ || (size_t)write(fs, buffer, fatsz) != fatsz) {
+ perr("Unable to write FAT");
+ ret = FSFATAL; /* Return immediately? XXX */
+ }
+ }
+ free(buffer);
+ return ret;
+}
+
+/*
+ * Check a complete in-memory FAT for lost cluster chains
+ */
+int
+checklost(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)
+ break;
+ if (ret == FSERROR && ask(0, "Clear")) {
+ clearchain(boot, fat, head);
+ mod |= FSFATMOD;
+ }
+ }
+ 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 (ret)
+ mod |= writefsinfo(dosfs, boot);
+ }
+
+ return mod;
+}
diff --git a/fsck_msdosfs.8 b/fsck_msdosfs.8
new file mode 100644
index 0000000..f953dbd
--- /dev/null
+++ b/fsck_msdosfs.8
@@ -0,0 +1,126 @@
+.\" $NetBSD: fsck_msdos.8,v 1.9 1997/10/17 11:19:58 ws Exp $
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd October 3, 2016
+.Dt FSCK_MSDOSFS 8
+.Os
+.Sh NAME
+.Nm fsck_msdosfs
+.Nd DOS/Windows (FAT) file system consistency checker
+.Sh SYNOPSIS
+.Nm
+.Fl p
+.Op Fl Cf
+.Ar filesystem ...
+.Nm
+.Op Fl Cny
+.Ar filesystem ...
+.Sh DESCRIPTION
+The
+.Nm
+utility verifies and repairs
+.Tn FAT
+file systems (more commonly known
+as
+.Tn DOS
+file systems).
+.Pp
+The first form of
+.Nm
+preens the specified file systems.
+It is normally started by
+.Xr fsck 8
+run from
+.Pa /etc/rc
+during automatic reboot, when a FAT file system is detected.
+When preening file systems,
+.Nm
+will fix common inconsistencies non-interactively.
+If more serious problems are found,
+.Nm
+does not try to fix them, indicates that it was not
+successful, and exits.
+.Pp
+The second form of
+.Nm
+checks the specified file systems and tries to repair all
+detected inconsistencies, requesting confirmation before
+making any changes.
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl C
+Compatibility with the corresponding
+.Xr fsck 8
+option (skip check if clean), defined to no-op.
+.It Fl F
+Compatibility with the wrapper
+.Xr fsck 8
+which seeks to determine whether the file system needs to be cleaned
+immediately in foreground, or if its cleaning can be deferred to background.
+FAT (MS-DOS) file systems must always be cleaned in the foreground.
+A non-zero exit code is always returned for this option.
+.It Fl f
+Force
+.Nm
+to check
+.Dq clean
+file systems when preening.
+.It Fl n
+Causes
+.Nm
+to assume
+.Dq Li no
+as the answer to all operator
+questions, except
+.Dq Li CONTINUE? .
+.It Fl p
+Preen the specified file systems.
+.It Fl y
+Causes
+.Nm
+to assume
+.Dq Li yes
+as the answer to all operator questions.
+.El
+.Sh SEE ALSO
+.Xr fsck 8 ,
+.Xr fsck_ffs 8 ,
+.Xr mount_msdosfs 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Nx 1.2 .
+.Nm
+first appeared in
+.Fx 4.4 .
+.Sh BUGS
+The
+.Nm
+utility is
+.Ud
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..8425d8a
--- /dev/null
+++ b/main.c
@@ -0,0 +1,157 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 1995 Wolfgang Solfrank
+ * Copyright (c) 1995 Martin Husemann
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <sys/cdefs.h>
+#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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#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) __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) {
+ 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);
+ va_end(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';
+}