/*- * 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 #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 #include #include #include #include #include #include #include #include #include #include #include #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; } int cleardirty(struct fat_descriptor *fat) { int fd, ret = FSERROR; struct bootblock *boot; u_char *buffer; size_t len; off_t off; boot = boot_of_(fat); fd = fd_of_(fat); 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 memory for FAT sectors (%zu)", len); return 1; } if ((size_t)pread(fd, buffer, len, off) != len) { perr("Unable to read FAT"); goto err; } if (boot->ClustMask == CLUST16_MASK) { buffer[3] |= 0x80; } else { buffer[7] |= 0x08; } if ((size_t)pwrite(fd, buffer, len, off) != len) { perr("Unable to write FAT"); goto err; } ret = FSOK; 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; }