FreeDOS/kernel/fattab.c
Bart Oldeman aab06b8a4d Do not use FSINFO structure if it is not present.
Do some range checks on FSINFO values to make sure they are valid. Fixes
issue reported by dos386 on the mailing list.


git-svn-id: https://svn.code.sf.net/p/freedos/svn/kernel/trunk@1631 6ac86273-5f31-0410-b378-82cca8765d1b
2011-06-13 16:27:34 +00:00

414 lines
13 KiB
C

/****************************************************************/
/* */
/* fattab.c */
/* */
/* FAT File System Table Functions */
/* */
/* Copyright (c) 1995 */
/* Pasquale J. Villani */
/* All Rights Reserved */
/* */
/* This file is part of DOS-C. */
/* */
/* DOS-C is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU General Public License */
/* as published by the Free Software Foundation; either version */
/* 2, or (at your option) any later version. */
/* */
/* DOS-C is distributed in the hope that it will be useful, but */
/* WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See */
/* the GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public */
/* License along with DOS-C; see the file COPYING. If not, */
/* write to the Free Software Foundation, 675 Mass Ave, */
/* Cambridge, MA 02139, USA. */
/****************************************************************/
#include "portab.h"
#include "globals.h"
#ifdef VERSION_STRINGS
static BYTE *RcsId =
"$Id$";
#endif
/************************************************************************/
/* */
/* cluster/sector routines */
/* */
/************************************************************************/
/* special "impossible" "Cluster2" value of 1 denotes reading the
cluster number rather than overwriting it */
#define READ_CLUSTER 1
#ifndef ISFAT32
int ISFAT32(struct dpb FAR * dpbp)
{
return _ISFAT32(dpbp);
}
#endif
void clusterMessage(const char * msg, CLUSTER clussec)
{
put_string("Run chkdsk: Bad FAT ");
put_string(msg);
#ifdef WITHFAT32
put_unsigned((unsigned)(clussec >> 16), 16, 4);
#endif
put_unsigned((unsigned)(clussec & 0xffffu), 16, 4);
put_console('\n');
}
struct buffer FAR *getFATblock(struct dpb FAR * dpbp, CLUSTER clussec)
{
/* *** why dpbp->dpb_unit? only useful to know in context of the dpbp...? *** */
struct buffer FAR *bp = getblock(clussec, dpbp->dpb_unit);
if (bp)
{
bp->b_flag &= ~(BFR_DATA | BFR_DIR);
bp->b_flag |= BFR_FAT | BFR_VALID;
bp->b_dpbp = dpbp;
bp->b_copies = dpbp->dpb_fats;
bp->b_offset = dpbp->dpb_fatsize; /* 0 for FAT32 but blockio.c knows that */
#ifdef WITHFAT32
if (ISFAT32(dpbp))
{
if (dpbp->dpb_xflags & FAT_NO_MIRRORING)
bp->b_copies = 1;
}
#endif
} else {
clusterMessage("I/O: 0x",clussec);
}
return bp;
}
#ifdef WITHFAT32
void read_fsinfo(struct dpb FAR * dpbp)
{
struct buffer FAR *bp;
struct fsinfo FAR *fip;
CLUSTER cluster;
if (dpbp->dpb_xfsinfosec == 0xffff)
return;
bp = getblock(dpbp->dpb_xfsinfosec, dpbp->dpb_unit);
bp->b_flag &= ~(BFR_DATA | BFR_DIR | BFR_FAT | BFR_DIRTY);
bp->b_flag |= BFR_VALID;
fip = (struct fsinfo FAR *)&bp->b_buffer[0x1e4];
/* need to range check values because they may not be correct */
cluster = fip->fi_nfreeclst;
if (cluster >= dpbp->dpb_xsize)
cluster = XUNKNCLSTFREE;
dpbp->dpb_xnfreeclst = cluster;
cluster = fip->fi_cluster;
if (cluster < 2 || cluster > dpbp->dpb_xsize)
cluster = UNKNCLUSTER;
dpbp->dpb_xcluster = cluster;
}
void write_fsinfo(struct dpb FAR * dpbp)
{
struct buffer FAR *bp;
struct fsinfo FAR *fip;
if (dpbp->dpb_xfsinfosec == 0xffff)
return;
bp = getblock(dpbp->dpb_xfsinfosec, dpbp->dpb_unit);
bp->b_flag &= ~(BFR_DATA | BFR_DIR | BFR_FAT);
bp->b_flag |= BFR_VALID;
fip = (struct fsinfo FAR *)&bp->b_buffer[0x1e4];
if (fip->fi_nfreeclst != dpbp->dpb_xnfreeclst ||
fip->fi_cluster != dpbp->dpb_xcluster)
bp->b_flag |= BFR_DIRTY; /* only flag for update if we had real news */
fip->fi_nfreeclst = dpbp->dpb_xnfreeclst;
fip->fi_cluster = dpbp->dpb_xcluster;
}
#endif
/* */
/* The FAT file system is difficult to trace through FAT table. */
/* There are two kinds of FATs, 12 bit and 16 bit. The 16 bit */
/* FAT is the easiest, since it is nothing more than a series */
/* of UWORDs. The 12 bit FAT is difficult, because it packs 3 */
/* FAT entries into two BYTEs. These are packed as follows: */
/* */
/* 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 0x0009 ... */
/* */
/* are packed as */
/* */
/* 0x03 0x40 0x00 0x05 0x60 0x00 0x07 0x80 0x00 0x09 ... */
/* */
/* 12 bytes are compressed to 9 bytes */
/* */
/* either read the value at Cluster1 (if Cluster2 is READ_CLUSTER) */
/* or write the Cluster2 value to the FAT entry at Cluster1 */
/* Read is always via next_cluster wrapper which has extra checks */
/* It might make sense to manually check old values before a write */
/* returns: the cluster number (or 1 on error) for read mode */
/* returns: SUCCESS (or 1 on error) for write mode */
CLUSTER link_fat(struct dpb FAR * dpbp, CLUSTER Cluster1,
REG CLUSTER Cluster2)
{
struct buffer FAR *bp;
unsigned idx;
unsigned secdiv; /* FAT entries per sector; nibbles for FAT12! */
unsigned char wasfree;
CLUSTER clussec = Cluster1;
CLUSTER max_cluster = dpbp->dpb_size;
#ifdef WITHFAT32
if (ISFAT32(dpbp))
max_cluster = dpbp->dpb_xsize;
#endif
if (clussec <= 1 || clussec > max_cluster) /* try to read out of range? */
{
clusterMessage("index: 0x",clussec); /* bad array offset */
return 1;
}
/* Cluster2 can 0 (FREE) or 1 (READ_CLUSTER), a cluster nr. >= 2, */
/* (range check this case!) LONG_LAST_CLUSTER or LONG_BAD here... */
if (Cluster2 < LONG_BAD && Cluster2 > max_cluster) /* writing bad value? */
{
clusterMessage("write: 0x",Cluster2); /* refuse to write bad value */
return 1;
}
secdiv = dpbp->dpb_secsize;
if (ISFAT12(dpbp))
{
clussec = (unsigned)clussec * 3;
secdiv *= 2;
}
else /* FAT16 or FAT32 */
{
secdiv /= 2;
#ifdef WITHFAT32
if (ISFAT32(dpbp))
secdiv /= 2;
#endif
}
/* idx is a pointer to an index which is the nibble offset of the FAT
entry within the sector for FAT12, or word offset for FAT16, or
dword offset for FAT32 */
idx = (unsigned)(clussec % secdiv);
clussec /= secdiv;
clussec += dpbp->dpb_fatstrt;
#ifdef WITHFAT32
if (ISFAT32(dpbp) && (dpbp->dpb_xflags & FAT_NO_MIRRORING))
{
/* we must modify the active fat,
it's number is in the 0-3 bits of dpb_xflags */
clussec += (dpbp->dpb_xflags & 0xf) * dpbp->dpb_xfatsize;
}
#endif
/* Get the block that this cluster is in */
bp = getFATblock(dpbp, clussec);
if (bp == NULL) {
return 1; /* the only error code possible here */
}
if (ISFAT12(dpbp))
{
REG UBYTE FAR *fbp0, FAR * fbp1;
struct buffer FAR * bp1;
unsigned cluster, cluster2;
/* form an index so that we can read the block as a */
/* byte array */
idx /= 2;
/* Test to see if the cluster straddles the block. If */
/* it does, get the next block and use both to form the */
/* the FAT word. Otherwise, just point to the next */
/* block. */
fbp0 = &bp->b_buffer[idx];
/* pointer to next byte, will be overwritten, if not valid */
fbp1 = fbp0 + 1;
if (idx >= (unsigned)dpbp->dpb_secsize - 1)
{
/* blockio.c LRU logic ensures that bp != bp1 */
bp1 = getFATblock(dpbp, (unsigned)clussec + 1);
if (bp1 == 0)
return 1; /* the only error code possible here */
if (Cluster2 != READ_CLUSTER)
bp1->b_flag |= BFR_DIRTY | BFR_VALID;
fbp1 = &bp1->b_buffer[0];
}
cluster = *fbp0 | (*fbp1 << 8);
{
unsigned res = cluster;
/* Now to unpack the contents of the FAT entry. Odd and */
/* even bytes are packed differently. */
if (Cluster1 & 0x01)
cluster >>= 4;
cluster &= 0x0fff;
if ((unsigned)Cluster2 == READ_CLUSTER)
{
if (cluster >= MASK12)
return LONG_LAST_CLUSTER;
if (cluster == BAD12)
return LONG_BAD;
return cluster;
}
wasfree = 0;
if (cluster == FREE)
wasfree = 1;
cluster = res;
}
/* Cluster2 may be set to LONG_LAST_CLUSTER == 0x0FFFFFFFUL or 0xFFFF */
/* -- please don't remove this mask! */
cluster2 = (unsigned)Cluster2 & 0x0fff;
/* Now pack the value in */
if ((unsigned)Cluster1 & 0x01)
{
cluster &= 0x000f;
cluster2 <<= 4;
}
else
{
cluster &= 0xf000;
}
cluster |= cluster2;
*fbp0 = (UBYTE)cluster;
*fbp1 = (UBYTE)(cluster >> 8);
}
else if (ISFAT16(dpbp))
{
/* form an index so that we can read the block as a */
/* byte array */
/* and get the cluster number */
UWORD res = fgetword(&bp->b_buffer[idx * 2]);
if ((unsigned)Cluster2 == READ_CLUSTER)
{
if (res >= MASK16)
return LONG_LAST_CLUSTER;
if (res == BAD16)
return LONG_BAD;
return res;
}
/* Finally, put the word into the buffer and mark the */
/* buffer as dirty. */
fputword(&bp->b_buffer[idx * 2], (UWORD)Cluster2);
wasfree = 0;
if (res == FREE)
wasfree = 1;
}
#ifdef WITHFAT32
else if (ISFAT32(dpbp))
{
/* form an index so that we can read the block as a */
/* byte array */
UDWORD res = fgetlong(&bp->b_buffer[idx * 4]) & LONG_LAST_CLUSTER;
if (Cluster2 == READ_CLUSTER)
{
if (res > LONG_BAD)
return LONG_LAST_CLUSTER;
return res;
}
/* Finally, put the word into the buffer and mark the */
/* buffer as dirty. */
fputlong(&bp->b_buffer[idx * 4], Cluster2 & LONG_LAST_CLUSTER);
wasfree = 0;
if (res == FREE)
wasfree = 1;
}
#endif
else {
put_string("Bad DPB!\n"); /* FAT1x size field > 65525U (see fat.h) */
return 1;
}
/* update the free space count */
bp->b_flag |= BFR_DIRTY | BFR_VALID;
if (Cluster2 == FREE || wasfree)
{
int adjust = 0;
if (!wasfree)
adjust = 1;
else if (Cluster2 != FREE)
adjust = -1;
#ifdef WITHFAT32
if (ISFAT32(dpbp) && dpbp->dpb_xnfreeclst != XUNKNCLSTFREE)
{
/* update the free space count for returned */
/* cluster */
dpbp->dpb_xnfreeclst += adjust;
write_fsinfo(dpbp);
}
else
#endif
if (dpbp->dpb_nfreeclst != UNKNCLSTFREE)
dpbp->dpb_nfreeclst += adjust;
}
return SUCCESS;
}
/* Given the disk parameters, and a cluster number, this function */
/* looks at the FAT, and returns the next cluster in the clain or */
/* 0 if there is no chain, 1 on error, LONG_LAST_CLUSTER at end. */
CLUSTER next_cluster(struct dpb FAR * dpbp, CLUSTER ClusterNum)
{
CLUSTER candidate, following, max_cluster;
candidate = link_fat(dpbp, ClusterNum, READ_CLUSTER);
/* empty (0) error (1) bad (LONG_BAD) last (>LONG_BAD) need no checks */
#if 0
if (candidate == ClusterNum)
return 1; /* chain has a tiny loop - easy but boring error check */
#endif
if (candidate < 2 || candidate >= LONG_BAD)
return candidate;
max_cluster = dpbp->dpb_size;
#ifdef WITHFAT32
if (ISFAT32(dpbp))
max_cluster = dpbp->dpb_xsize;
#endif
/* FAT entry points to a possibly invalid next cluster */
following = link_fat(dpbp, candidate, READ_CLUSTER);
if (following<2 || (following < LONG_BAD && following > max_cluster))
{
/* chain must not contain free or out of range clusters */
clusterMessage("value: 0x",following); /* read returned bad value */
return 1; /* only possible error code here */
}
/* without checking "following", a chain can dangle to a free cluster: */
/* if that cluster is later used by another chain, you get cross links */
return candidate;
}
/* check if the selected cluster is free (faster than next_cluster) */
BOOL is_free_cluster(struct dpb FAR * dpbp, CLUSTER ClusterNum)
{
return (link_fat(dpbp, ClusterNum, READ_CLUSTER) == FREE);
}