#include <sys/cdefs.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/param.h>
#include "ext.h"
#include "fsutil.h"
#define FAT_CHUNK_SIZE (64*1024)
ssize_t deblock_read(int d, void *buf, size_t nbytes);
ssize_t deblock_write(int d, void *buf, size_t nbytes);
static cl_t fat32_get(cl_t cluster);
static int fat32_set(cl_t cluster, cl_t value);
static cl_t fat16_get(cl_t cluster);
static int fat16_set(cl_t cluster, cl_t value);
static cl_t fat12_get(cl_t cluster);
static int fat12_set(cl_t cluster, cl_t value);
cl_t (*fat_get)(cl_t cluster);
int (*fat_set)(cl_t cluster, cl_t value);
static int gFS;
static struct bootblock *gBoot;
static size_t gUseMapBytes;
static int gNumCacheBlocks;
struct fat_cache_block {
unsigned int dirty:1;
unsigned int chunk:31;
struct fat_cache_block *next;
uint8_t * buffer;
uint32_t length;
};
enum { FAT_CHUNK_MAX = 0x7FFFFFFF };
static uint8_t *fat_cache_buffers;
static struct fat_cache_block *fat_cache;
static struct fat_cache_block *fat_cache_mru;
int fat_init(int fs, struct bootblock *boot)
{
int mod = 0;
int i;
cl_t temp;
cl_t value;
fat_uninit();
gFS = fs;
gBoot = boot;
switch (boot->ClustMask)
{
case CLUST12_MASK:
fat_get = fat12_get;
fat_set = fat12_set;
break;
case CLUST16_MASK:
fat_get = fat16_get;
fat_set = fat16_set;
break;
case CLUST32_MASK:
fat_get = fat32_get;
fat_set = fat32_set;
break;
default:
pfatal("Unknown cluster mask (0x%08X)\n", boot->ClustMask);
return FSFATAL;
}
if (initUseMap(boot))
return FSFATAL;
if (maxmem)
{
gNumCacheBlocks = (maxmem-gUseMapBytes)/FAT_CHUNK_SIZE;
}
else
{
gNumCacheBlocks = (boot->FATsecs*boot->BytesPerSec+FAT_CHUNK_SIZE-1)/FAT_CHUNK_SIZE;
}
fat_cache = calloc(gNumCacheBlocks, sizeof(struct fat_cache_block));
if (fat_cache == NULL)
{
freeUseMap();
perr("No memory for FAT cache headers\n");
return FSFATAL;
}
fat_cache_buffers = calloc(gNumCacheBlocks, FAT_CHUNK_SIZE);
if (fat_cache_buffers == NULL)
{
free(fat_cache);
fat_cache = NULL;
freeUseMap();
perr("No memory for FAT cache buffers\n");
return FSFATAL;
}
for (i=0; i<gNumCacheBlocks; ++i)
{
fat_cache[i].dirty = 0;
fat_cache[i].chunk = FAT_CHUNK_MAX;
fat_cache[i].buffer = fat_cache_buffers + i * FAT_CHUNK_SIZE;
if (i != gNumCacheBlocks-1)
fat_cache[i].next = &fat_cache[i+1];
}
fat_cache_mru = &fat_cache[0];
value = fat_get(0);
if (value == CLUST_ERROR)
return FSFATAL;
value &= boot->ClustMask;
temp = boot->ClustMask & (0xFFFFFF00+boot->Media);
if (value != temp)
{
pwarn("FAT[0] is incorrect (is 0x%X; should be 0x%X)\n", value, temp);
if (ask(1, "Correct"))
{
mod = fat_set(0, temp);
if (!mod)
mod = FSFATMOD;
}
else
{
mod = FSERROR;
}
}
value = fat_get(1);
if (value == CLUST_ERROR)
return FSFATAL;
switch (boot->ClustMask)
{
case CLUST16_MASK:
if ((value & 0x8000) == 0)
mod |= FSDIRTY;
break;
case CLUST32_MASK:
if ((value & 0x08000000) == 0)
mod |= FSDIRTY;
break;
default:
break;
}
if (boot->ClustMask == CLUST12_MASK)
temp = boot->ClustMask;
else
temp = boot->ClustMask >> 2;
if ((value & temp) < (CLUST_EOFS & temp))
{
pwarn("FAT[1] is incorrect\n");
if (ask(1, "Correct"))
{
i = fat_set(1, value | temp);
if (i)
mod |= i;
else
mod |= FSFATMOD;
}
else
{
mod |= FSERROR;
}
}
return mod;
}
static struct fat_cache_block *
fat_cache_find(uint32_t offset)
{
struct fat_cache_block *found;
struct fat_cache_block *prev;
uint32_t chunk;
uint32_t length;
chunk = offset / FAT_CHUNK_SIZE;
length = (gBoot->FATsecs * gBoot->BytesPerSec) - (chunk * FAT_CHUNK_SIZE);
if (length > FAT_CHUNK_SIZE)
length = FAT_CHUNK_SIZE;
prev = NULL;
found = fat_cache_mru;
while (found->chunk != chunk && found->next != NULL)
{
prev = found;
found = found->next;
}
if (found->chunk != chunk)
{
int activeFAT;
off_t io_offset;
activeFAT = gBoot->ValidFat >= 0 ? gBoot->ValidFat : 0;
if (found->dirty)
{
io_offset = (gBoot->ResSectors + activeFAT * gBoot->FATsecs) * gBoot->BytesPerSec;
io_offset += found->chunk * FAT_CHUNK_SIZE;
if (lseek(gFS, io_offset, SEEK_SET) != io_offset)
{
perr("Unable to seek FAT");
return NULL;
}
if (deblock_write(gFS, found->buffer, found->length) != found->length)
{
perr("Unable to write FAT");
return NULL;
}
found->dirty = 0;
}
found->chunk = chunk;
found->length = length;
io_offset = (gBoot->ResSectors + activeFAT * gBoot->FATsecs) * gBoot->BytesPerSec;
io_offset += chunk * FAT_CHUNK_SIZE;
if (lseek(gFS, io_offset, SEEK_SET) != io_offset)
{
perr("Unable to seek FAT");
return NULL;
}
if (deblock_read(gFS, found->buffer, length) != length)
{
perr("Unable to read FAT");
return NULL;
}
}
if (found != fat_cache_mru)
{
if (prev)
prev->next = found->next;
found->next = fat_cache_mru;
fat_cache_mru = found;
}
return found;
}
void fat_uninit(void)
{
if (fat_cache != NULL)
{
free(fat_cache);
fat_cache = NULL;
}
if (fat_cache_buffers != NULL)
{
free(fat_cache_buffers);
fat_cache_buffers = NULL;
}
freeUseMap();
}
static cl_t fat32_get(cl_t cluster)
{
struct fat_cache_block *block;
uint8_t *p;
cl_t value;
if (cluster >= gBoot->NumClusters)
{
fprintf(stderr, "fat32_get: invalid cluster (%u)\n", cluster);
return CLUST_ERROR;
}
block = fat_cache_find(cluster*4);
if (block == NULL)
return CLUST_ERROR;
p = block->buffer + (cluster * 4) % FAT_CHUNK_SIZE;
value = (p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24)) & CLUST32_MASK;
if (value >= (CLUST_RSRVD & CLUST32_MASK))
value |= ~CLUST32_MASK;
return value;
}
static cl_t fat16_get(cl_t cluster)
{
struct fat_cache_block *block;
uint8_t *p;
cl_t value;
if (cluster >= gBoot->NumClusters)
{
fprintf(stderr, "fat16_get: invalid cluster (%u)\n", cluster);
return CLUST_ERROR;
}
block = fat_cache_find(cluster*2);
if (block == NULL)
return CLUST_ERROR;
p = block->buffer + (cluster * 2) % FAT_CHUNK_SIZE;
value = p[0] + (p[1] << 8);
if (value >= (CLUST_RSRVD & CLUST16_MASK))
value |= ~CLUST16_MASK;
return value;
}
static cl_t fat12_get(cl_t cluster)
{
struct fat_cache_block *block;
uint8_t *p;
cl_t value;
if (cluster >= gBoot->NumClusters)
{
fprintf(stderr, "fat16_get: invalid cluster (%u)\n", cluster);
return CLUST_ERROR;
}
block = fat_cache_find(cluster + cluster/2);
if (block == NULL)
return CLUST_ERROR;
p = block->buffer + (cluster + cluster/2) % FAT_CHUNK_SIZE;
value = p[0] + (p[1] << 8);
if (cluster & 1)
value >>= 4;
else
value &= 0x0FFF;
if (value >= (CLUST_RSRVD & CLUST12_MASK))
value |= ~CLUST12_MASK;
return value;
}
static int fat32_set(cl_t cluster, cl_t value)
{
struct fat_cache_block *block;
uint8_t *p;
if (cluster >= gBoot->NumClusters)
{
fprintf(stderr, "fat32_set: invalid cluster (%u)\n", cluster);
return FSFATAL;
}
block = fat_cache_find(cluster*4);
if (block == NULL)
return FSFATAL;
p = block->buffer + (cluster * 4) % FAT_CHUNK_SIZE;
*p++ = (uint8_t) value;
*p++ = (uint8_t) (value >> 8);
*p++ = (uint8_t) (value >> 16);
*p &= 0xF0;
*p |= (value >> 24) & 0x0F;
if (value == CLUST_FREE)
markFree(cluster);
else
markUsed(cluster);
block->dirty = 1;
return 0;
}
static int fat16_set(cl_t cluster, cl_t value)
{
struct fat_cache_block *block;
uint8_t *p;
if (cluster >= gBoot->NumClusters)
{
fprintf(stderr, "fat16_set: invalid cluster (%u)\n", cluster);
return FSFATAL;
}
block = fat_cache_find(cluster*2);
if (block == NULL)
return FSFATAL;
p = block->buffer + (cluster * 2) % FAT_CHUNK_SIZE;
*p++ = (uint8_t) value;
*p = (uint8_t) (value >> 8);
if (value == CLUST_FREE)
markFree(cluster);
else
markUsed(cluster);
block->dirty = 1;
return 0;
}
static int fat12_set(cl_t cluster, cl_t value)
{
struct fat_cache_block *block;
uint8_t *p;
if (cluster >= gBoot->NumClusters)
{
fprintf(stderr, "fat16_set: invalid cluster (%u)\n", cluster);
return FSFATAL;
}
block = fat_cache_find(cluster + cluster/2);
if (block == NULL)
return FSFATAL;
p = block->buffer + (cluster + cluster/2) % FAT_CHUNK_SIZE;
if (cluster & 1)
value = (value << 4) | (p[0] & 0x0F);
else
value |= (p[1] & 0xF0) << 8;
*p++ = (uint8_t) value;
*p = (uint8_t) (value >> 8);
if (value == CLUST_FREE)
markFree(cluster);
else
markUsed(cluster);
block->dirty = 1;
return 0;
}
int fat_flush(void)
{
int i;
int activeFAT;
off_t offset;
activeFAT = gBoot->ValidFat >= 0 ? gBoot->ValidFat : 0;
for (i=0; i<gNumCacheBlocks; ++i)
{
if (fat_cache[i].dirty)
{
offset = (gBoot->ResSectors + activeFAT * gBoot->FATsecs) * gBoot->BytesPerSec;
offset += fat_cache[i].chunk * FAT_CHUNK_SIZE;
if (lseek(gFS, offset, SEEK_SET) != offset)
{
perr("Unable to seek FAT");
return FSFATAL;
}
if (deblock_write(gFS, fat_cache[i].buffer, fat_cache[i].length) != fat_cache[i].length)
{
perr("Unable to write FAT");
return FSFATAL;
}
fat_cache[i].dirty = 0;
}
}
return 0;
}
int fat_free_unused(void)
{
int err = 0;
cl_t cluster, value;
cl_t count = 0;
int fix=0;
for (cluster = CLUST_FIRST; cluster < gBoot->NumClusters; ++cluster)
{
value = fat_get(cluster);
if (value == CLUST_ERROR)
break;
if (!isUsed(cluster))
{
if (value == CLUST_BAD)
{
gBoot->NumBad++;
}
else if (value == CLUST_FREE)
{
gBoot->NumFree++;
}
else
{
if (count == 0)
{
pwarn("Found orphan cluster(s)\n");
fix = ask(1, "Fix");
}
++count;
if (fix)
{
err = fat_set(cluster, CLUST_FREE);
if (err)
break;
gBoot->NumFree++;
}
}
}
}
if (count)
{
if (fix)
{
pwarn("Marked %u clusters as free\n", count);
err |= FSFATMOD;
}
else
{
pwarn("Found %u orphaned clusters\n", count);
err |= FSERROR;
}
}
if (gBoot->FSInfo) {
fix = 0;
if (gBoot->FSFree != gBoot->NumFree) {
pwarn("Free space in FSInfo block (%d) not correct (%d)\n",
gBoot->FSFree, gBoot->NumFree);
if (ask(1, "Fix")) {
gBoot->FSFree = gBoot->NumFree;
fix = 1;
}
}
if (fix)
err |= writefsinfo(gFS, gBoot);
}
return err;
}
int isdirty(int fs, struct bootblock *boot, int fat)
{
int result;
u_char *buffer;
off_t offset;
result = 1;
if (boot->ClustMask == CLUST12_MASK)
return 1;
buffer = malloc(boot->BytesPerSec);
if (buffer == NULL) {
perr("No space for FAT sector");
return 1;
}
offset = boot->ResSectors + fat * boot->FATsecs;
offset *= boot->BytesPerSec;
if (lseek(fs, offset, SEEK_SET) != offset) {
perr("Unable to read FAT");
goto ERROR;
}
if (deblock_read(fs, buffer, boot->BytesPerSec) != boot->BytesPerSec) {
perr("Unable to read FAT");
goto ERROR;
}
switch (boot->ClustMask) {
case CLUST32_MASK:
if ((buffer[7] & 0x08) != 0)
result = 0;
break;
case CLUST16_MASK:
if ((buffer[3] & 0x80) != 0)
result = 0;
break;
}
ERROR:
free(buffer);
return result;
}
int fat_mark_clean(void)
{
cl_t value;
if (gBoot->ClustMask == CLUST12_MASK)
return 0;
value = fat_get(1);
if (value == CLUST_ERROR)
return FSERROR;
if (gBoot->ClustMask == CLUST16_MASK)
value |= 0x8000;
else
value |= 0x08000000;
return fat_set(1, value);
}
char *
rsrvdcltype(cl)
cl_t cl;
{
if (cl == CLUST_FREE)
return "free";
if (cl < CLUST_BAD)
return "reserved";
if (cl > CLUST_BAD)
return "as EOF";
return "bad";
}
#define DEBLOCK_SIZE (MAXPHYSIO>>2)
ssize_t deblock_read(int d, void *buf, size_t nbytes) {
ssize_t totbytes = 0, readbytes;
char *b = buf;
while (nbytes > 0) {
size_t rbytes = nbytes < DEBLOCK_SIZE? nbytes : DEBLOCK_SIZE;
readbytes = read(d, b, rbytes);
if (readbytes < 0)
return readbytes;
else if (readbytes == 0)
break;
else {
nbytes-=readbytes;
totbytes += readbytes;
b += readbytes;
}
}
return totbytes;
}
ssize_t deblock_write(int d, void *buf, size_t nbytes) {
ssize_t totbytes = 0, writebytes;
char *b = buf;
while (nbytes > 0) {
size_t wbytes = nbytes < DEBLOCK_SIZE ? nbytes : DEBLOCK_SIZE;
writebytes = write(d, b, wbytes);
if (writebytes < 0)
return writebytes;
else if (writebytes == 0)
break;
else {
nbytes-=writebytes;
totbytes += writebytes;
b += writebytes;
}
}
return totbytes;
}
static uint32_t *useMap = NULL;
int initUseMap(struct bootblock *boot)
{
cl_t clusters = (boot->NumClusters + 31) & ~31;
if (useMap != NULL)
free(useMap);
gUseMapBytes = clusters/8;
if (maxmem != 0 && maxmem < (gUseMapBytes + FAT_CHUNK_SIZE))
{
pfatal("Cannot allocate %zd bytes for usemap (maxmem=%zd, clusters=%d)\n"
"maxmem must be at least %zd\n",
gUseMapBytes, maxmem, clusters, gUseMapBytes + FAT_CHUNK_SIZE);
useMap = NULL;
}
else
{
useMap = calloc(clusters/32, sizeof(uint32_t));
}
return useMap==NULL;
}
void freeUseMap(void)
{
if (useMap == NULL)
free(useMap);
useMap = NULL;
}
int isUsed(cl_t cluster)
{
return (useMap[cluster/32] >> cluster%32) & 1;
}
int markUsed(cl_t cluster)
{
int error = 0;
cl_t index = cluster / 32;
uint32_t mask = 1 << (cluster % 32);
if (useMap[index] & mask)
error = 1;
else
useMap[index] |= mask;
return error;
}
int markFree(cl_t cluster)
{
int error = 0;
cl_t index = cluster / 32;
uint32_t mask = 1 << (cluster % 32);
if (useMap[index] & mask)
useMap[index] &= ~mask;
else
error = 1;
return error;
}