1
0
mirror of https://git.FreeBSD.org/src.git synced 2024-12-23 11:18:54 +00:00
freebsd/sbin/fsck_msdosfs/fat.c
Konstantin Belousov 75fb535394 Rename fields to match better the msdosfs headers. This work is still
incomplete as some info doesn't really belong to the structs where it is
defined.

Submitted by:	Pedro F. Giffuni <giffunip tutopia com>
Reviewed by:	bde
MFC after:	2 weeks
2010-02-14 12:31:28 +00:00

691 lines
17 KiB
C

/*
* 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.12 2000/10/10 20:24:52 is 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;
if (boot->ClustMask != CLUST16_MASK && boot->ClustMask != CLUST32_MASK)
return 0;
off = boot->bpbResSectors;
off *= boot->bpbBytesPerSec;
buffer = malloc(boot->bpbBytesPerSec);
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->bpbBytesPerSec) != boot->bpbBytesPerSec) {
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->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;
*buffer = malloc(boot->FATsecs * boot->bpbBytesPerSec);
if (*buffer == NULL) {
perror("No space for FAT");
return 0;
}
off = boot->bpbResSectors + no * boot->FATsecs;
off *= boot->bpbBytesPerSec;
if (lseek(fs, off, SEEK_SET) != off) {
perror("Unable to read FAT");
goto err;
}
if ((size_t)read(fs, *buffer, boot->FATsecs * boot->bpbBytesPerSec)
!= boot->FATsecs * boot->bpbBytesPerSec) {
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, 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) {
perror("No space for FAT");
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
*/
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 *trunc)
{
if (ask(0, "Clear chain starting at %u", head)) {
clearchain(boot, fat, head);
return FSFATMOD;
} else if (ask(0, "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;
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) {
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 (p = head;
(n = fat[p].next) >= CLUST_FIRST && n < boot->NumClusters;
p = n)
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(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) {
perror("No space for FAT");
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++;
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->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) {
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)
break;
if (ret == FSERROR && ask(0, "Clear")) {
clearchain(boot, fat, head);
mod |= FSFATMOD;
}
}
finishlf();
if (boot->bpbFSInfo) {
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 (ret)
mod |= writefsinfo(dosfs, boot);
}
return mod;
}