b60894b10a
sbin/fsck_msdosfs/fat.c: - readfat: * Only truncate out-of-range cluster pointers (1, or greater than NumClusters but smaller than CLUST_RSRVD), as the current cluster may contain some data. We can't fix reserved cluster pointers at this pass, because we do no know the potential cluster preceding it. * Accept valid cluster for head bitmap. This is a no-op, and mainly to improve code readability, because the 1 is already handled in the previous else if block. - truncate_at: absorbed into checkchain. - checkchain: save the previous node we have traversed in case that we have a chain that ends with a special (>= CLUST_RSRVD) cluster, or is free. In these cases, we need to truncate at the cluster preceding the current cluster, as the current cluster contains a marker instead of a next pointer and can not be changed to CLUST_EOF (the else case can happen if the user answered "no" at some point in readfat()). - clearchain: correct the iterator for next cluster so that we don't stop after clearing the first cluster. - checklost: If checkchain() thinks the chain have no cluster, it doesn't make sense to reconnect it, so don't bother asking. Reviewed by: kevlo MFC after: 24 days X-MFC-With: r356313 Differential Revision: https://reviews.freebsd.org/D23065
1283 lines
30 KiB
C
1283 lines
30 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2019 Google LLC
|
|
* 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 <sys/endian.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/param.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "ext.h"
|
|
#include "fsutil.h"
|
|
|
|
static int _readfat(struct fat_descriptor *);
|
|
static inline struct bootblock* boot_of_(struct fat_descriptor *);
|
|
static inline int fd_of_(struct fat_descriptor *);
|
|
static inline bool valid_cl(struct fat_descriptor *, cl_t);
|
|
|
|
|
|
/*
|
|
* Head bitmap for FAT scanning.
|
|
*
|
|
* FAT32 have up to 2^28 = 256M entries, and FAT16/12 have much less.
|
|
* For each cluster, we use 1 bit to represent if it's a head cluster
|
|
* (the first cluster of a cluster chain).
|
|
*
|
|
* Head bitmap
|
|
* ===========
|
|
* Initially, we set all bits to 1. In readfat(), we traverse the
|
|
* whole FAT and mark each cluster identified as "next" cluster as
|
|
* 0. After the scan, we have a bitmap with 1's to indicate the
|
|
* corresponding cluster was a "head" cluster.
|
|
*
|
|
* We use head bitmap to identify lost chains: a head cluster that was
|
|
* not being claimed by any file or directories is the head cluster of
|
|
* a lost chain.
|
|
*
|
|
* Handle of lost chains
|
|
* =====================
|
|
* At the end of scanning, we can easily find all lost chain's heads
|
|
* by finding out the 1's in the head bitmap.
|
|
*/
|
|
|
|
typedef struct long_bitmap {
|
|
unsigned long *map;
|
|
size_t count; /* Total set bits in the map */
|
|
} long_bitmap_t;
|
|
|
|
static inline void
|
|
bitmap_clear(long_bitmap_t *lbp, cl_t cl)
|
|
{
|
|
cl_t i = cl / LONG_BIT;
|
|
unsigned long clearmask = ~(1UL << (cl % LONG_BIT));
|
|
|
|
assert((lbp->map[i] & ~clearmask) != 0);
|
|
lbp->map[i] &= clearmask;
|
|
lbp->count--;
|
|
}
|
|
|
|
static inline bool
|
|
bitmap_get(long_bitmap_t *lbp, cl_t cl)
|
|
{
|
|
cl_t i = cl / LONG_BIT;
|
|
unsigned long usedbit = 1UL << (cl % LONG_BIT);
|
|
|
|
return ((lbp->map[i] & usedbit) == usedbit);
|
|
}
|
|
|
|
static inline bool
|
|
bitmap_none_in_range(long_bitmap_t *lbp, cl_t cl)
|
|
{
|
|
cl_t i = cl / LONG_BIT;
|
|
|
|
return (lbp->map[i] == 0);
|
|
}
|
|
|
|
static inline size_t
|
|
bitmap_count(long_bitmap_t *lbp)
|
|
{
|
|
return (lbp->count);
|
|
}
|
|
|
|
static int
|
|
bitmap_ctor(long_bitmap_t *lbp, size_t bits, bool allone)
|
|
{
|
|
size_t bitmap_size = roundup2(bits, LONG_BIT) / (LONG_BIT / 8);
|
|
|
|
free(lbp->map);
|
|
lbp->map = calloc(1, bitmap_size);
|
|
if (lbp->map == NULL)
|
|
return FSFATAL;
|
|
|
|
if (allone) {
|
|
memset(lbp->map, 0xff, bitmap_size);
|
|
lbp->count = bits;
|
|
} else {
|
|
lbp->count = 0;
|
|
}
|
|
return FSOK;
|
|
}
|
|
|
|
static void
|
|
bitmap_dtor(long_bitmap_t *lbp)
|
|
{
|
|
free(lbp->map);
|
|
lbp->map = NULL;
|
|
}
|
|
|
|
/*
|
|
* FAT32 can be as big as 256MiB (2^26 entries * 4 bytes), when we
|
|
* can not ask the kernel to manage the access, use a simple LRU
|
|
* cache with chunk size of 128 KiB to manage it.
|
|
*/
|
|
struct fat32_cache_entry {
|
|
TAILQ_ENTRY(fat32_cache_entry) entries;
|
|
uint8_t *chunk; /* pointer to chunk */
|
|
off_t addr; /* offset */
|
|
bool dirty; /* dirty bit */
|
|
};
|
|
|
|
static const size_t fat32_cache_chunk_size = 131072; /* MAXPHYS */
|
|
static const size_t fat32_cache_size = 4194304;
|
|
static const size_t fat32_cache_entries = 32; /* XXXgcc: cache_size / cache_chunk_size */
|
|
|
|
/*
|
|
* FAT table descriptor, represents a FAT table that is already loaded
|
|
* into memory.
|
|
*/
|
|
struct fat_descriptor {
|
|
struct bootblock *boot;
|
|
uint8_t *fatbuf;
|
|
cl_t (*get)(struct fat_descriptor *, cl_t);
|
|
int (*set)(struct fat_descriptor *, cl_t, cl_t);
|
|
long_bitmap_t headbitmap;
|
|
int fd;
|
|
bool is_mmapped;
|
|
bool use_cache;
|
|
size_t fatsize;
|
|
|
|
size_t fat32_cached_chunks;
|
|
TAILQ_HEAD(cachehead, fat32_cache_entry) fat32_cache_head;
|
|
struct fat32_cache_entry *fat32_cache_allentries;
|
|
off_t fat32_offset;
|
|
off_t fat32_lastaddr;
|
|
};
|
|
|
|
void
|
|
fat_clear_cl_head(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
bitmap_clear(&fat->headbitmap, cl);
|
|
}
|
|
|
|
bool
|
|
fat_is_cl_head(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
return (bitmap_get(&fat->headbitmap, cl));
|
|
}
|
|
|
|
static inline bool
|
|
fat_is_cl_head_in_range(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
return (!(bitmap_none_in_range(&fat->headbitmap, cl)));
|
|
}
|
|
|
|
static size_t
|
|
fat_get_head_count(struct fat_descriptor *fat)
|
|
{
|
|
return (bitmap_count(&fat->headbitmap));
|
|
}
|
|
|
|
/*
|
|
* FAT12 accessors.
|
|
*
|
|
* FAT12s are sufficiently small, expect it to always fit in the RAM.
|
|
*/
|
|
static inline uint8_t *
|
|
fat_get_fat12_ptr(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
return (fat->fatbuf + ((cl + (cl >> 1))));
|
|
}
|
|
|
|
static cl_t
|
|
fat_get_fat12_next(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
const uint8_t *p;
|
|
cl_t retval;
|
|
|
|
p = fat_get_fat12_ptr(fat, cl);
|
|
retval = le16dec(p);
|
|
/* Odd cluster: lower 4 bits belongs to the subsequent cluster */
|
|
if ((cl & 1) == 1)
|
|
retval >>= 4;
|
|
retval &= CLUST12_MASK;
|
|
|
|
if (retval >= (CLUST_BAD & CLUST12_MASK))
|
|
retval |= ~CLUST12_MASK;
|
|
|
|
return (retval);
|
|
}
|
|
|
|
static int
|
|
fat_set_fat12_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl)
|
|
{
|
|
uint8_t *p;
|
|
|
|
/* Truncate 'nextcl' value, if needed */
|
|
nextcl &= CLUST12_MASK;
|
|
|
|
p = fat_get_fat12_ptr(fat, cl);
|
|
|
|
/*
|
|
* Read in the 4 bits from the subsequent (for even clusters)
|
|
* or the preceding (for odd clusters) cluster and combine
|
|
* it to the nextcl value for encoding
|
|
*/
|
|
if ((cl & 1) == 0) {
|
|
nextcl |= ((p[1] & 0xf0) << 8);
|
|
} else {
|
|
nextcl <<= 4;
|
|
nextcl |= (p[0] & 0x0f);
|
|
}
|
|
|
|
le16enc(p, (uint16_t)nextcl);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* FAT16 accessors.
|
|
*
|
|
* FAT16s are sufficiently small, expect it to always fit in the RAM.
|
|
*/
|
|
static inline uint8_t *
|
|
fat_get_fat16_ptr(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
return (fat->fatbuf + (cl << 1));
|
|
}
|
|
|
|
static cl_t
|
|
fat_get_fat16_next(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
const uint8_t *p;
|
|
cl_t retval;
|
|
|
|
p = fat_get_fat16_ptr(fat, cl);
|
|
retval = le16dec(p) & CLUST16_MASK;
|
|
|
|
if (retval >= (CLUST_BAD & CLUST16_MASK))
|
|
retval |= ~CLUST16_MASK;
|
|
|
|
return (retval);
|
|
}
|
|
|
|
static int
|
|
fat_set_fat16_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl)
|
|
{
|
|
uint8_t *p;
|
|
|
|
/* Truncate 'nextcl' value, if needed */
|
|
nextcl &= CLUST16_MASK;
|
|
|
|
p = fat_get_fat16_ptr(fat, cl);
|
|
|
|
le16enc(p, (uint16_t)nextcl);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* FAT32 accessors.
|
|
*/
|
|
static inline uint8_t *
|
|
fat_get_fat32_ptr(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
return (fat->fatbuf + (cl << 2));
|
|
}
|
|
|
|
static cl_t
|
|
fat_get_fat32_next(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
const uint8_t *p;
|
|
cl_t retval;
|
|
|
|
p = fat_get_fat32_ptr(fat, cl);
|
|
retval = le32dec(p) & CLUST32_MASK;
|
|
|
|
if (retval >= (CLUST_BAD & CLUST32_MASK))
|
|
retval |= ~CLUST32_MASK;
|
|
|
|
return (retval);
|
|
}
|
|
|
|
static int
|
|
fat_set_fat32_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl)
|
|
{
|
|
uint8_t *p;
|
|
|
|
/* Truncate 'nextcl' value, if needed */
|
|
nextcl &= CLUST32_MASK;
|
|
|
|
p = fat_get_fat32_ptr(fat, cl);
|
|
|
|
le32enc(p, (uint32_t)nextcl);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static inline size_t
|
|
fat_get_iosize(struct fat_descriptor *fat, off_t address)
|
|
{
|
|
|
|
if (address == fat->fat32_lastaddr) {
|
|
return (fat->fatsize & ((off_t)fat32_cache_chunk_size - 1));
|
|
} else {
|
|
return (fat32_cache_chunk_size);
|
|
}
|
|
}
|
|
|
|
static int
|
|
fat_flush_fat32_cache_entry(struct fat_descriptor *fat,
|
|
struct fat32_cache_entry *entry)
|
|
{
|
|
int fd;
|
|
off_t fat_addr;
|
|
size_t writesize;
|
|
|
|
fd = fd_of_(fat);
|
|
|
|
if (!entry->dirty)
|
|
return (FSOK);
|
|
|
|
writesize = fat_get_iosize(fat, entry->addr);
|
|
|
|
fat_addr = fat->fat32_offset + entry->addr;
|
|
if (lseek(fd, fat_addr, SEEK_SET) != fat_addr ||
|
|
(size_t)write(fd, entry->chunk, writesize) != writesize) {
|
|
pfatal("Unable to write FAT");
|
|
return (FSFATAL);
|
|
}
|
|
|
|
entry->dirty = false;
|
|
return (FSOK);
|
|
}
|
|
|
|
static struct fat32_cache_entry *
|
|
fat_get_fat32_cache_entry(struct fat_descriptor *fat, off_t addr,
|
|
bool writing)
|
|
{
|
|
int fd;
|
|
struct fat32_cache_entry *entry, *first;
|
|
off_t fat_addr;
|
|
size_t rwsize;
|
|
|
|
addr &= ~(fat32_cache_chunk_size - 1);
|
|
|
|
first = TAILQ_FIRST(&fat->fat32_cache_head);
|
|
|
|
/*
|
|
* Cache hit: if we already have the chunk, move it to list head
|
|
*/
|
|
TAILQ_FOREACH(entry, &fat->fat32_cache_head, entries) {
|
|
if (entry->addr == addr) {
|
|
if (writing) {
|
|
entry->dirty = true;
|
|
}
|
|
if (entry != first) {
|
|
|
|
TAILQ_REMOVE(&fat->fat32_cache_head, entry, entries);
|
|
TAILQ_INSERT_HEAD(&fat->fat32_cache_head, entry, entries);
|
|
}
|
|
return (entry);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cache miss: detach the chunk at tail of list, overwrite with
|
|
* the located chunk, and populate with data from disk.
|
|
*/
|
|
entry = TAILQ_LAST(&fat->fat32_cache_head, cachehead);
|
|
TAILQ_REMOVE(&fat->fat32_cache_head, entry, entries);
|
|
if (fat_flush_fat32_cache_entry(fat, entry) != FSOK) {
|
|
return (NULL);
|
|
}
|
|
|
|
rwsize = fat_get_iosize(fat, addr);
|
|
fat_addr = fat->fat32_offset + addr;
|
|
entry->addr = addr;
|
|
fd = fd_of_(fat);
|
|
if (lseek(fd, fat_addr, SEEK_SET) != fat_addr ||
|
|
(size_t)read(fd, entry->chunk, rwsize) != rwsize) {
|
|
pfatal("Unable to read FAT");
|
|
return (NULL);
|
|
}
|
|
if (writing) {
|
|
entry->dirty = true;
|
|
}
|
|
TAILQ_INSERT_HEAD(&fat->fat32_cache_head, entry, entries);
|
|
|
|
return (entry);
|
|
}
|
|
|
|
static inline uint8_t *
|
|
fat_get_fat32_cached_ptr(struct fat_descriptor *fat, cl_t cl, bool writing)
|
|
{
|
|
off_t addr, off;
|
|
struct fat32_cache_entry *entry;
|
|
|
|
addr = cl << 2;
|
|
entry = fat_get_fat32_cache_entry(fat, addr, writing);
|
|
|
|
if (entry != NULL) {
|
|
off = addr & (fat32_cache_chunk_size - 1);
|
|
return (entry->chunk + off);
|
|
} else {
|
|
return (NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static cl_t
|
|
fat_get_fat32_cached_next(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
const uint8_t *p;
|
|
cl_t retval;
|
|
|
|
p = fat_get_fat32_cached_ptr(fat, cl, false);
|
|
if (p != NULL) {
|
|
retval = le32dec(p) & CLUST32_MASK;
|
|
if (retval >= (CLUST_BAD & CLUST32_MASK))
|
|
retval |= ~CLUST32_MASK;
|
|
} else {
|
|
retval = CLUST_DEAD;
|
|
}
|
|
|
|
return (retval);
|
|
}
|
|
|
|
static int
|
|
fat_set_fat32_cached_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl)
|
|
{
|
|
uint8_t *p;
|
|
|
|
/* Truncate 'nextcl' value, if needed */
|
|
nextcl &= CLUST32_MASK;
|
|
|
|
p = fat_get_fat32_cached_ptr(fat, cl, true);
|
|
if (p != NULL) {
|
|
le32enc(p, (uint32_t)nextcl);
|
|
return FSOK;
|
|
} else {
|
|
return FSFATAL;
|
|
}
|
|
}
|
|
|
|
cl_t fat_get_cl_next(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
|
|
if (!valid_cl(fat, cl)) {
|
|
pfatal("Invalid cluster: %ud", cl);
|
|
return CLUST_DEAD;
|
|
}
|
|
|
|
return (fat->get(fat, cl));
|
|
}
|
|
|
|
int fat_set_cl_next(struct fat_descriptor *fat, cl_t cl, cl_t nextcl)
|
|
{
|
|
|
|
if (rdonly) {
|
|
pwarn(" (NO WRITE)\n");
|
|
return FSFATAL;
|
|
}
|
|
|
|
if (!valid_cl(fat, cl)) {
|
|
pfatal("Invalid cluster: %ud", cl);
|
|
return FSFATAL;
|
|
}
|
|
|
|
return (fat->set(fat, cl, nextcl));
|
|
}
|
|
|
|
static inline struct bootblock*
|
|
boot_of_(struct fat_descriptor *fat) {
|
|
|
|
return (fat->boot);
|
|
}
|
|
|
|
struct bootblock*
|
|
fat_get_boot(struct fat_descriptor *fat) {
|
|
|
|
return (boot_of_(fat));
|
|
}
|
|
|
|
static inline int
|
|
fd_of_(struct fat_descriptor *fat)
|
|
{
|
|
return (fat->fd);
|
|
}
|
|
|
|
int
|
|
fat_get_fd(struct fat_descriptor * fat)
|
|
{
|
|
return (fd_of_(fat));
|
|
}
|
|
|
|
/*
|
|
* Whether a cl is in valid data range.
|
|
*/
|
|
bool
|
|
fat_is_valid_cl(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
|
|
return (valid_cl(fat, cl));
|
|
}
|
|
|
|
static inline bool
|
|
valid_cl(struct fat_descriptor *fat, cl_t cl)
|
|
{
|
|
const struct bootblock *boot = boot_of_(fat);
|
|
|
|
return (cl >= CLUST_FIRST && cl < boot->NumClusters);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Read a FAT from disk. Returns 1 if successful, 0 otherwise.
|
|
*/
|
|
static int
|
|
_readfat(struct fat_descriptor *fat)
|
|
{
|
|
int fd;
|
|
size_t i;
|
|
off_t off;
|
|
size_t readsize;
|
|
struct bootblock *boot;
|
|
struct fat32_cache_entry *entry;
|
|
|
|
boot = boot_of_(fat);
|
|
fd = fd_of_(fat);
|
|
fat->fatsize = boot->FATsecs * boot->bpbBytesPerSec;
|
|
|
|
off = boot->bpbResSectors;
|
|
off *= boot->bpbBytesPerSec;
|
|
|
|
fat->is_mmapped = false;
|
|
fat->use_cache = false;
|
|
|
|
/* Attempt to mmap() first */
|
|
if (allow_mmap) {
|
|
fat->fatbuf = mmap(NULL, fat->fatsize,
|
|
PROT_READ | (rdonly ? 0 : PROT_WRITE),
|
|
MAP_SHARED, fd_of_(fat), off);
|
|
if (fat->fatbuf != MAP_FAILED) {
|
|
fat->is_mmapped = true;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unfortunately, we were unable to mmap().
|
|
*
|
|
* Only use the cache manager when it's necessary, that is,
|
|
* when the FAT is sufficiently large; in that case, only
|
|
* read in the first 4 MiB of FAT into memory, and split the
|
|
* buffer into chunks and insert to the LRU queue to populate
|
|
* the cache with data.
|
|
*/
|
|
if (boot->ClustMask == CLUST32_MASK &&
|
|
fat->fatsize >= fat32_cache_size) {
|
|
readsize = fat32_cache_size;
|
|
fat->use_cache = true;
|
|
|
|
fat->fat32_offset = boot->bpbResSectors * boot->bpbBytesPerSec;
|
|
fat->fat32_lastaddr = fat->fatsize & ~(fat32_cache_chunk_size);
|
|
} else {
|
|
readsize = fat->fatsize;
|
|
}
|
|
fat->fatbuf = malloc(readsize);
|
|
if (fat->fatbuf == NULL) {
|
|
perr("No space for FAT (%zu)", readsize);
|
|
return 0;
|
|
}
|
|
|
|
if (lseek(fd, off, SEEK_SET) != off) {
|
|
perr("Unable to read FAT");
|
|
goto err;
|
|
}
|
|
if ((size_t)read(fd, fat->fatbuf, readsize) != readsize) {
|
|
perr("Unable to read FAT");
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* When cache is used, split the buffer into chunks, and
|
|
* connect the buffer into the cache.
|
|
*/
|
|
if (fat->use_cache) {
|
|
TAILQ_INIT(&fat->fat32_cache_head);
|
|
entry = calloc(fat32_cache_entries, sizeof(*entry));
|
|
if (entry == NULL) {
|
|
perr("No space for FAT cache (%zu of %zu)",
|
|
fat32_cache_entries, sizeof(entry));
|
|
goto err;
|
|
}
|
|
for (i = 0; i < fat32_cache_entries; i++) {
|
|
entry[i].addr = fat32_cache_chunk_size * i;
|
|
entry[i].chunk = &fat->fatbuf[entry[i].addr];
|
|
TAILQ_INSERT_TAIL(&fat->fat32_cache_head,
|
|
&entry[i], entries);
|
|
}
|
|
fat->fat32_cache_allentries = entry;
|
|
}
|
|
|
|
return 1;
|
|
|
|
err:
|
|
free(fat->fatbuf);
|
|
fat->fatbuf = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
releasefat(struct fat_descriptor *fat)
|
|
{
|
|
if (fat->is_mmapped) {
|
|
munmap(fat->fatbuf, fat->fatsize);
|
|
} else {
|
|
if (fat->use_cache) {
|
|
free(fat->fat32_cache_allentries);
|
|
fat->fat32_cache_allentries = NULL;
|
|
}
|
|
free(fat->fatbuf);
|
|
}
|
|
fat->fatbuf = NULL;
|
|
bitmap_dtor(&fat->headbitmap);
|
|
}
|
|
|
|
/*
|
|
* Read or map a FAT and populate head bitmap
|
|
*/
|
|
int
|
|
readfat(int fs, struct bootblock *boot, struct fat_descriptor **fp)
|
|
{
|
|
struct fat_descriptor *fat;
|
|
u_char *buffer, *p;
|
|
cl_t cl, nextcl;
|
|
int ret = FSOK;
|
|
|
|
boot->NumFree = boot->NumBad = 0;
|
|
|
|
fat = calloc(1, sizeof(struct fat_descriptor));
|
|
if (fat == NULL) {
|
|
perr("No space for FAT descriptor");
|
|
return FSFATAL;
|
|
}
|
|
|
|
fat->fd = fs;
|
|
fat->boot = boot;
|
|
|
|
if (!_readfat(fat)) {
|
|
free(fat);
|
|
return FSFATAL;
|
|
}
|
|
buffer = fat->fatbuf;
|
|
|
|
/* Populate accessors */
|
|
switch(boot->ClustMask) {
|
|
case CLUST12_MASK:
|
|
fat->get = fat_get_fat12_next;
|
|
fat->set = fat_set_fat12_next;
|
|
break;
|
|
case CLUST16_MASK:
|
|
fat->get = fat_get_fat16_next;
|
|
fat->set = fat_set_fat16_next;
|
|
break;
|
|
case CLUST32_MASK:
|
|
if (fat->is_mmapped || !fat->use_cache) {
|
|
fat->get = fat_get_fat32_next;
|
|
fat->set = fat_set_fat32_next;
|
|
} else {
|
|
fat->get = fat_get_fat32_cached_next;
|
|
fat->set = fat_set_fat32_cached_next;
|
|
}
|
|
break;
|
|
default:
|
|
pfatal("Invalid ClustMask: %d", boot->ClustMask);
|
|
releasefat(fat);
|
|
free(fat);
|
|
return FSFATAL;
|
|
}
|
|
|
|
if (bitmap_ctor(&fat->headbitmap, boot->NumClusters,
|
|
true) != FSOK) {
|
|
perr("No space for head bitmap for FAT clusters (%zu)",
|
|
(size_t)boot->NumClusters);
|
|
releasefat(fat);
|
|
free(fat);
|
|
return FSFATAL;
|
|
}
|
|
|
|
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 |= FSFATMOD;
|
|
p = buffer;
|
|
|
|
*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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Traverse the FAT table and populate head map. Initially, we
|
|
* consider all clusters as possible head cluster (beginning of
|
|
* a file or directory), and traverse the whole allocation table
|
|
* by marking every non-head nodes as such (detailed below) and
|
|
* fix obvious issues while we walk.
|
|
*
|
|
* For each "next" cluster, the possible values are:
|
|
*
|
|
* a) CLUST_FREE or CLUST_BAD. The *current* cluster can't be a
|
|
* head node.
|
|
* b) An out-of-range value. The only fix would be to truncate at
|
|
* the cluster.
|
|
* c) A valid cluster. It means that cluster (nextcl) is not a
|
|
* head cluster. Note that during the scan, every cluster is
|
|
* expected to be seen for at most once, and when we saw them
|
|
* twice, it means a cross-linked chain which should be
|
|
* truncated at the current cluster.
|
|
*
|
|
* After scan, the remaining set bits indicates all possible
|
|
* head nodes, because they were never claimed by any other
|
|
* node as the next node, but we do not know if these chains
|
|
* would end with a valid EOF marker. We will check that in
|
|
* checkchain() at a later time when checking directories,
|
|
* where these head nodes would be marked as non-head.
|
|
*
|
|
* In the final pass, all head nodes should be cleared, and if
|
|
* there is still head nodes, these would be leaders of lost
|
|
* chain.
|
|
*/
|
|
for (cl = CLUST_FIRST; cl < boot->NumClusters; cl++) {
|
|
nextcl = fat_get_cl_next(fat, cl);
|
|
|
|
/* Check if the next cluster number is valid */
|
|
if (nextcl == CLUST_FREE) {
|
|
/* Save a hint for next free cluster */
|
|
if (boot->FSNext == 0) {
|
|
boot->FSNext = cl;
|
|
}
|
|
if (fat_is_cl_head(fat, cl)) {
|
|
fat_clear_cl_head(fat, cl);
|
|
}
|
|
boot->NumFree++;
|
|
} else if (nextcl == CLUST_BAD) {
|
|
if (fat_is_cl_head(fat, cl)) {
|
|
fat_clear_cl_head(fat, cl);
|
|
}
|
|
boot->NumBad++;
|
|
} else if (!valid_cl(fat, nextcl) && nextcl < CLUST_RSRVD) {
|
|
pwarn("Cluster %u continues with out of range "
|
|
"cluster number %u\n",
|
|
cl,
|
|
nextcl & boot->ClustMask);
|
|
if (ask(0, "Truncate")) {
|
|
ret |= fat_set_cl_next(fat, cl, CLUST_EOF);
|
|
ret |= FSFATMOD;
|
|
}
|
|
} else if (valid_cl(fat, nextcl)) {
|
|
if (fat_is_cl_head(fat, nextcl)) {
|
|
fat_clear_cl_head(fat, nextcl);
|
|
} else {
|
|
pwarn("Cluster %u crossed another chain at %u\n",
|
|
cl, nextcl);
|
|
if (ask(0, "Truncate")) {
|
|
ret |= fat_set_cl_next(fat, cl, CLUST_EOF);
|
|
ret |= FSFATMOD;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (ret & FSFATAL) {
|
|
releasefat(fat);
|
|
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";
|
|
}
|
|
|
|
/*
|
|
* Examine a cluster chain for errors and count its size.
|
|
*/
|
|
int
|
|
checkchain(struct fat_descriptor *fat, cl_t head, size_t *chainsize)
|
|
{
|
|
cl_t prev_cl, current_cl, next_cl;
|
|
const char *op;
|
|
|
|
/*
|
|
* We expect that the caller to give us a real, unvisited 'head'
|
|
* cluster, and it must be a valid cluster. While scanning the
|
|
* FAT table, we already excluded all clusters that was claimed
|
|
* as a "next" cluster. Assert all the three conditions.
|
|
*/
|
|
assert(valid_cl(fat, head));
|
|
assert(fat_is_cl_head(fat, head));
|
|
|
|
/*
|
|
* Immediately mark the 'head' cluster that we are about to visit.
|
|
*/
|
|
fat_clear_cl_head(fat, head);
|
|
|
|
/*
|
|
* The allocation of a non-zero sized file or directory is
|
|
* represented as a singly linked list, and the tail node
|
|
* would be the EOF marker (>=CLUST_EOFS).
|
|
*
|
|
* With a valid head node at hand, we expect all subsequent
|
|
* cluster to be either a not yet seen and valid cluster (we
|
|
* would continue counting), or the EOF marker (we conclude
|
|
* the scan of this chain).
|
|
*
|
|
* For all other cases, the chain is invalid, and the only
|
|
* viable fix would be to truncate at the current node (mark
|
|
* it as EOF) when the next node violates that.
|
|
*/
|
|
*chainsize = 0;
|
|
prev_cl = current_cl = head;
|
|
for (next_cl = fat_get_cl_next(fat, current_cl);
|
|
valid_cl(fat, next_cl);
|
|
prev_cl = current_cl, current_cl = next_cl, next_cl = fat_get_cl_next(fat, current_cl))
|
|
(*chainsize)++;
|
|
|
|
/* A natural end */
|
|
if (next_cl >= CLUST_EOFS) {
|
|
(*chainsize)++;
|
|
return FSOK;
|
|
}
|
|
|
|
/*
|
|
* The chain ended with an out-of-range cluster number.
|
|
*
|
|
* If the current node is e.g. CLUST_FREE, CLUST_BAD, etc.,
|
|
* it should not be present in a chain and we has to truncate
|
|
* at the previous node.
|
|
*
|
|
* If the current cluster points to an invalid cluster, the
|
|
* current cluster might have useful data and we truncate at
|
|
* the current cluster instead.
|
|
*/
|
|
if (next_cl == CLUST_FREE || next_cl >= CLUST_RSRVD) {
|
|
pwarn("Cluster chain starting at %u ends with cluster marked %s\n",
|
|
head, rsrvdcltype(next_cl));
|
|
current_cl = prev_cl;
|
|
} else {
|
|
pwarn("Cluster chain starting at %u ends with cluster out of range (%u)\n",
|
|
head,
|
|
next_cl & boot_of_(fat)->ClustMask);
|
|
(*chainsize)++;
|
|
}
|
|
|
|
if (*chainsize > 0) {
|
|
op = "Truncate";
|
|
next_cl = CLUST_EOF;
|
|
} else {
|
|
op = "Clear";
|
|
next_cl = CLUST_FREE;
|
|
}
|
|
if (ask(0, "%s", op)) {
|
|
return (fat_set_cl_next(fat, current_cl, next_cl) | FSFATMOD);
|
|
} else {
|
|
return (FSERROR);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear cluster chain from head.
|
|
*/
|
|
void
|
|
clearchain(struct fat_descriptor *fat, cl_t head)
|
|
{
|
|
cl_t current_cl, next_cl;
|
|
struct bootblock *boot = boot_of_(fat);
|
|
|
|
current_cl = head;
|
|
|
|
while (valid_cl(fat, current_cl)) {
|
|
next_cl = fat_get_cl_next(fat, current_cl);
|
|
(void)fat_set_cl_next(fat, current_cl, CLUST_FREE);
|
|
boot->NumFree++;
|
|
current_cl = next_cl;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Overwrite the n-th FAT with FAT0
|
|
*/
|
|
static int
|
|
copyfat(struct fat_descriptor *fat, int n)
|
|
{
|
|
size_t rwsize, tailsize, blobs, i;
|
|
off_t dst_off, src_off;
|
|
struct bootblock *boot;
|
|
int ret, fd;
|
|
|
|
ret = FSOK;
|
|
fd = fd_of_(fat);
|
|
boot = boot_of_(fat);
|
|
|
|
blobs = howmany(fat->fatsize, fat32_cache_size);
|
|
tailsize = fat->fatsize % fat32_cache_size;
|
|
if (tailsize == 0) {
|
|
tailsize = fat32_cache_size;
|
|
}
|
|
rwsize = fat32_cache_size;
|
|
|
|
src_off = fat->fat32_offset;
|
|
dst_off = boot->bpbResSectors + n * boot->FATsecs;
|
|
dst_off *= boot->bpbBytesPerSec;
|
|
|
|
for (i = 0; i < blobs;
|
|
i++, src_off += fat32_cache_size, dst_off += fat32_cache_size) {
|
|
if (i == blobs - 1) {
|
|
rwsize = tailsize;
|
|
}
|
|
if ((lseek(fd, src_off, SEEK_SET) != src_off ||
|
|
(size_t)read(fd, fat->fatbuf, rwsize) != rwsize) &&
|
|
ret == FSOK) {
|
|
perr("Unable to read FAT0");
|
|
ret = FSFATAL;
|
|
continue;
|
|
}
|
|
if ((lseek(fd, dst_off, SEEK_SET) != dst_off ||
|
|
(size_t)write(fd, fat->fatbuf, rwsize) != rwsize) &&
|
|
ret == FSOK) {
|
|
perr("Unable to write FAT %d", n);
|
|
ret = FSERROR;
|
|
}
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Write out FAT
|
|
*/
|
|
int
|
|
writefat(struct fat_descriptor *fat)
|
|
{
|
|
u_int i;
|
|
size_t writesz;
|
|
off_t dst_base;
|
|
int ret = FSOK, fd;
|
|
struct bootblock *boot;
|
|
struct fat32_cache_entry *entry;
|
|
|
|
boot = boot_of_(fat);
|
|
fd = fd_of_(fat);
|
|
|
|
if (fat->use_cache) {
|
|
/*
|
|
* Attempt to flush all in-flight cache, and bail out
|
|
* if we encountered an error (but only emit error
|
|
* message once). Stop proceeding with copyfat()
|
|
* if any flush failed.
|
|
*/
|
|
TAILQ_FOREACH(entry, &fat->fat32_cache_head, entries) {
|
|
if (fat_flush_fat32_cache_entry(fat, entry) != FSOK) {
|
|
if (ret == FSOK) {
|
|
perr("Unable to write FAT");
|
|
ret = FSFATAL;
|
|
}
|
|
}
|
|
}
|
|
if (ret != FSOK)
|
|
return (ret);
|
|
|
|
/* Update backup copies of FAT, error is not fatal */
|
|
for (i = 1; i < boot->bpbFATs; i++) {
|
|
if (copyfat(fat, i) != FSOK)
|
|
ret = FSERROR;
|
|
}
|
|
} else {
|
|
writesz = fat->fatsize;
|
|
|
|
for (i = fat->is_mmapped ? 1 : 0; i < boot->bpbFATs; i++) {
|
|
dst_base = boot->bpbResSectors + i * boot->FATsecs;
|
|
dst_base *= boot->bpbBytesPerSec;
|
|
if ((lseek(fd, dst_base, SEEK_SET) != dst_base ||
|
|
(size_t)write(fd, fat->fatbuf, writesz) != writesz) &&
|
|
ret == FSOK) {
|
|
perr("Unable to write FAT %d", i);
|
|
ret = ((i == 0) ? FSFATAL : FSERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Check a complete in-memory FAT for lost cluster chains
|
|
*/
|
|
int
|
|
checklost(struct fat_descriptor *fat)
|
|
{
|
|
cl_t head;
|
|
int mod = FSOK;
|
|
int dosfs, ret;
|
|
size_t chains, chainlength;
|
|
struct bootblock *boot;
|
|
|
|
dosfs = fd_of_(fat);
|
|
boot = boot_of_(fat);
|
|
|
|
/*
|
|
* At this point, we have already traversed all directories.
|
|
* All remaining chain heads in the bitmap are heads of lost
|
|
* chains.
|
|
*/
|
|
chains = fat_get_head_count(fat);
|
|
for (head = CLUST_FIRST;
|
|
chains > 0 && head < boot->NumClusters;
|
|
) {
|
|
/*
|
|
* We expect the bitmap to be very sparse, so skip if
|
|
* the range is full of 0's
|
|
*/
|
|
if (head % LONG_BIT == 0 &&
|
|
!fat_is_cl_head_in_range(fat, head)) {
|
|
head += LONG_BIT;
|
|
continue;
|
|
}
|
|
if (fat_is_cl_head(fat, head)) {
|
|
ret = checkchain(fat, head, &chainlength);
|
|
if (ret != FSERROR && chainlength > 0) {
|
|
pwarn("Lost cluster chain at cluster %u\n"
|
|
"%zd Cluster(s) lost\n",
|
|
head, chainlength);
|
|
mod |= ret = reconnect(fat, head,
|
|
chainlength);
|
|
}
|
|
if (mod & FSFATAL)
|
|
break;
|
|
if (ret == FSERROR && ask(0, "Clear")) {
|
|
clearchain(fat, head);
|
|
mod |= FSFATMOD;
|
|
}
|
|
chains--;
|
|
}
|
|
head++;
|
|
}
|
|
|
|
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 (boot->FSNext != 0xffffffffU &&
|
|
(boot->FSNext >= boot->NumClusters ||
|
|
(boot->NumFree && fat_get_cl_next(fat, boot->FSNext) != CLUST_FREE))) {
|
|
pwarn("Next free cluster in FSInfo block (%u) %s\n",
|
|
boot->FSNext,
|
|
(boot->FSNext >= boot->NumClusters) ? "invalid" : "not free");
|
|
if (ask(1, "Fix"))
|
|
for (head = CLUST_FIRST; head < boot->NumClusters; head++)
|
|
if (fat_get_cl_next(fat, head) == CLUST_FREE) {
|
|
boot->FSNext = head;
|
|
ret = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (ret)
|
|
mod |= writefsinfo(dosfs, boot);
|
|
}
|
|
|
|
return mod;
|
|
}
|