| /*************************************************************************** |
| * __________ __ ___. |
| * Open \______ \ ____ ____ | | _\_ |__ _______ ___ |
| * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / |
| * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < |
| * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ |
| * \/ \/ \/ \/ \/ |
| * $Id$ |
| * |
| * Copyright (C) 2002 by Linus Nielsen Feltzing |
| * |
| * All files in this archive are subject to the GNU General Public License. |
| * See the file COPYING in the source tree root for full license agreement. |
| * |
| * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
| * KIND, either express or implied. |
| * |
| ****************************************************************************/ |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <stdbool.h> |
| #include "fat.h" |
| #include "ata.h" |
| #include "debug.h" |
| #include "panic.h" |
| #include "system.h" |
| #include "timefuncs.h" |
| #include "kernel.h" |
| #include "rbunicode.h" |
| #include "logf.h" |
| |
| #define BYTES2INT16(array,pos) \ |
| (array[pos] | (array[pos+1] << 8 )) |
| #define BYTES2INT32(array,pos) \ |
| ((long)array[pos] | ((long)array[pos+1] << 8 ) | \ |
| ((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 )) |
| |
| #define FATTYPE_FAT12 0 |
| #define FATTYPE_FAT16 1 |
| #define FATTYPE_FAT32 2 |
| |
| /* BPB offsets; generic */ |
| #define BS_JMPBOOT 0 |
| #define BS_OEMNAME 3 |
| #define BPB_BYTSPERSEC 11 |
| #define BPB_SECPERCLUS 13 |
| #define BPB_RSVDSECCNT 14 |
| #define BPB_NUMFATS 16 |
| #define BPB_ROOTENTCNT 17 |
| #define BPB_TOTSEC16 19 |
| #define BPB_MEDIA 21 |
| #define BPB_FATSZ16 22 |
| #define BPB_SECPERTRK 24 |
| #define BPB_NUMHEADS 26 |
| #define BPB_HIDDSEC 28 |
| #define BPB_TOTSEC32 32 |
| |
| /* fat12/16 */ |
| #define BS_DRVNUM 36 |
| #define BS_RESERVED1 37 |
| #define BS_BOOTSIG 38 |
| #define BS_VOLID 39 |
| #define BS_VOLLAB 43 |
| #define BS_FILSYSTYPE 54 |
| |
| /* fat32 */ |
| #define BPB_FATSZ32 36 |
| #define BPB_EXTFLAGS 40 |
| #define BPB_FSVER 42 |
| #define BPB_ROOTCLUS 44 |
| #define BPB_FSINFO 48 |
| #define BPB_BKBOOTSEC 50 |
| #define BS_32_DRVNUM 64 |
| #define BS_32_BOOTSIG 66 |
| #define BS_32_VOLID 67 |
| #define BS_32_VOLLAB 71 |
| #define BS_32_FILSYSTYPE 82 |
| |
| #define BPB_LAST_WORD 510 |
| |
| |
| /* attributes */ |
| #define FAT_ATTR_LONG_NAME (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \ |
| FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID) |
| #define FAT_ATTR_LONG_NAME_MASK (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \ |
| FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID | \ |
| FAT_ATTR_DIRECTORY | FAT_ATTR_ARCHIVE ) |
| |
| /* NTRES flags */ |
| #define FAT_NTRES_LC_NAME 0x08 |
| #define FAT_NTRES_LC_EXT 0x10 |
| |
| #define FATDIR_NAME 0 |
| #define FATDIR_ATTR 11 |
| #define FATDIR_NTRES 12 |
| #define FATDIR_CRTTIMETENTH 13 |
| #define FATDIR_CRTTIME 14 |
| #define FATDIR_CRTDATE 16 |
| #define FATDIR_LSTACCDATE 18 |
| #define FATDIR_FSTCLUSHI 20 |
| #define FATDIR_WRTTIME 22 |
| #define FATDIR_WRTDATE 24 |
| #define FATDIR_FSTCLUSLO 26 |
| #define FATDIR_FILESIZE 28 |
| |
| #define FATLONG_ORDER 0 |
| #define FATLONG_TYPE 12 |
| #define FATLONG_CHKSUM 13 |
| |
| #define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4) |
| #define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2) |
| #define DIR_ENTRIES_PER_SECTOR (SECTOR_SIZE / DIR_ENTRY_SIZE) |
| #define DIR_ENTRY_SIZE 32 |
| #define NAME_BYTES_PER_ENTRY 13 |
| #define FAT_BAD_MARK 0x0ffffff7 |
| #define FAT_EOF_MARK 0x0ffffff8 |
| #define FAT_LONGNAME_PAD_BYTE 0xff |
| #define FAT_LONGNAME_PAD_UCS 0xffff |
| |
| struct fsinfo { |
| unsigned long freecount; /* last known free cluster count */ |
| unsigned long nextfree; /* first cluster to start looking for free |
| clusters, or 0xffffffff for no hint */ |
| }; |
| /* fsinfo offsets */ |
| #define FSINFO_FREECOUNT 488 |
| #define FSINFO_NEXTFREE 492 |
| |
| /* Note: This struct doesn't hold the raw values after mounting if |
| * bpb_bytspersec isn't 512. All sector counts are normalized to 512 byte |
| * physical sectors. */ |
| struct bpb |
| { |
| int bpb_bytspersec; /* Bytes per sector, typically 512 */ |
| unsigned int bpb_secperclus; /* Sectors per cluster */ |
| int bpb_rsvdseccnt; /* Number of reserved sectors */ |
| int bpb_numfats; /* Number of FAT structures, typically 2 */ |
| int bpb_totsec16; /* Number of sectors on the volume (old 16-bit) */ |
| int bpb_media; /* Media type (typically 0xf0 or 0xf8) */ |
| int bpb_fatsz16; /* Number of used sectors per FAT structure */ |
| unsigned long bpb_totsec32; /* Number of sectors on the volume |
| (new 32-bit) */ |
| unsigned int last_word; /* 0xAA55 */ |
| |
| /**** FAT32 specific *****/ |
| long bpb_fatsz32; |
| long bpb_rootclus; |
| long bpb_fsinfo; |
| |
| /* variables for internal use */ |
| unsigned long fatsize; |
| unsigned long totalsectors; |
| unsigned long rootdirsector; |
| unsigned long firstdatasector; |
| unsigned long startsector; |
| unsigned long dataclusters; |
| struct fsinfo fsinfo; |
| #ifdef HAVE_FAT16SUPPORT |
| int bpb_rootentcnt; /* Number of dir entries in the root */ |
| /* internals for FAT16 support */ |
| bool is_fat16; /* true if we mounted a FAT16 partition, false if FAT32 */ |
| unsigned int rootdiroffset; /* sector offset of root dir relative to start |
| * of first pseudo cluster */ |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| #ifdef HAVE_MULTIVOLUME |
| int drive; /* on which physical device is this located */ |
| bool mounted; /* flag if this volume is mounted */ |
| #endif |
| }; |
| |
| static struct bpb fat_bpbs[NUM_VOLUMES]; /* mounted partition info */ |
| |
| static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb)); |
| static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb)); |
| static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb)); |
| static void *cache_fat_sector(IF_MV2(struct bpb* fat_bpb,) long secnum, bool dirty); |
| static void create_dos_name(const unsigned char *name, unsigned char *newname); |
| static void randomize_dos_name(unsigned char *name); |
| static unsigned long find_free_cluster(IF_MV2(struct bpb* fat_bpb,) unsigned long start); |
| static int transfer(IF_MV2(struct bpb* fat_bpb,) unsigned long start, long count, char* buf, bool write ); |
| |
| #define FAT_CACHE_SIZE 0x20 |
| #define FAT_CACHE_MASK (FAT_CACHE_SIZE-1) |
| |
| struct fat_cache_entry |
| { |
| long secnum; |
| bool inuse; |
| bool dirty; |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_vol ; /* shared cache for all volumes */ |
| #endif |
| }; |
| |
| static char fat_cache_sectors[FAT_CACHE_SIZE][SECTOR_SIZE]; |
| static struct fat_cache_entry fat_cache[FAT_CACHE_SIZE]; |
| static struct mutex cache_mutex; |
| |
| static long cluster2sec(IF_MV2(struct bpb* fat_bpb,) long cluster) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| #ifdef HAVE_FAT16SUPPORT |
| /* negative clusters (FAT16 root dir) don't get the 2 offset */ |
| int zerocluster = cluster < 0 ? 0 : 2; |
| #else |
| const long zerocluster = 2; |
| #endif |
| |
| if (cluster > (long)(fat_bpb->dataclusters + 1)) |
| { |
| DEBUGF( "cluster2sec() - Bad cluster number (%ld)\n", cluster); |
| return -1; |
| } |
| |
| return (cluster - zerocluster) * fat_bpb->bpb_secperclus |
| + fat_bpb->firstdatasector; |
| } |
| |
| long fat_startsector(IF_MV_NONVOID(int volume)) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| const int volume = 0; |
| #endif |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| return fat_bpb->startsector; |
| } |
| |
| void fat_size(IF_MV2(int volume,) unsigned long* size, unsigned long* free) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| const int volume = 0; |
| #endif |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| if (size) |
| *size = fat_bpb->dataclusters * fat_bpb->bpb_secperclus / 2; |
| if (free) |
| *free = fat_bpb->fsinfo.freecount * fat_bpb->bpb_secperclus / 2; |
| } |
| |
| void fat_init(void) |
| { |
| unsigned int i; |
| |
| mutex_init(&cache_mutex); |
| |
| /* mark the FAT cache as unused */ |
| for(i = 0;i < FAT_CACHE_SIZE;i++) |
| { |
| fat_cache[i].secnum = 8; /* We use a "safe" sector just in case */ |
| fat_cache[i].inuse = false; |
| fat_cache[i].dirty = false; |
| #ifdef HAVE_MULTIVOLUME |
| fat_cache[i].fat_vol = NULL; |
| #endif |
| } |
| #ifdef HAVE_MULTIVOLUME |
| /* mark the possible volumes as not mounted */ |
| for (i=0; i<NUM_VOLUMES;i++) |
| { |
| fat_bpbs[i].mounted = false; |
| } |
| #endif |
| } |
| |
| int fat_mount(IF_MV2(int volume,) IF_MV2(int drive,) long startsector) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| const int volume = 0; |
| #endif |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| unsigned char buf[SECTOR_SIZE]; |
| int rc; |
| int secmult; |
| long datasec; |
| #ifdef HAVE_FAT16SUPPORT |
| int rootdirsectors; |
| #endif |
| |
| /* Read the sector */ |
| rc = ata_read_sectors(IF_MV2(drive,) startsector,1,buf); |
| if(rc) |
| { |
| DEBUGF( "fat_mount() - Couldn't read BPB (error code %d)\n", rc); |
| return rc * 10 - 1; |
| } |
| |
| memset(fat_bpb, 0, sizeof(struct bpb)); |
| fat_bpb->startsector = startsector; |
| #ifdef HAVE_MULTIVOLUME |
| fat_bpb->drive = drive; |
| #endif |
| |
| fat_bpb->bpb_bytspersec = BYTES2INT16(buf,BPB_BYTSPERSEC); |
| secmult = fat_bpb->bpb_bytspersec / SECTOR_SIZE; |
| /* Sanity check is performed later */ |
| |
| fat_bpb->bpb_secperclus = secmult * buf[BPB_SECPERCLUS]; |
| fat_bpb->bpb_rsvdseccnt = secmult * BYTES2INT16(buf,BPB_RSVDSECCNT); |
| fat_bpb->bpb_numfats = buf[BPB_NUMFATS]; |
| fat_bpb->bpb_media = buf[BPB_MEDIA]; |
| fat_bpb->bpb_fatsz16 = secmult * BYTES2INT16(buf,BPB_FATSZ16); |
| fat_bpb->bpb_fatsz32 = secmult * BYTES2INT32(buf,BPB_FATSZ32); |
| fat_bpb->bpb_totsec16 = secmult * BYTES2INT16(buf,BPB_TOTSEC16); |
| fat_bpb->bpb_totsec32 = secmult * BYTES2INT32(buf,BPB_TOTSEC32); |
| fat_bpb->last_word = BYTES2INT16(buf,BPB_LAST_WORD); |
| |
| /* calculate a few commonly used values */ |
| if (fat_bpb->bpb_fatsz16 != 0) |
| fat_bpb->fatsize = fat_bpb->bpb_fatsz16; |
| else |
| fat_bpb->fatsize = fat_bpb->bpb_fatsz32; |
| |
| if (fat_bpb->bpb_totsec16 != 0) |
| fat_bpb->totalsectors = fat_bpb->bpb_totsec16; |
| else |
| fat_bpb->totalsectors = fat_bpb->bpb_totsec32; |
| |
| #ifdef HAVE_FAT16SUPPORT |
| fat_bpb->bpb_rootentcnt = BYTES2INT16(buf,BPB_ROOTENTCNT); |
| rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE |
| + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec); |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| |
| fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt |
| #ifdef HAVE_FAT16SUPPORT |
| + rootdirsectors |
| #endif |
| + fat_bpb->bpb_numfats * fat_bpb->fatsize; |
| |
| /* Determine FAT type */ |
| datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector; |
| fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus; |
| |
| #ifdef TEST_FAT |
| /* |
| we are sometimes testing with "illegally small" fat32 images, |
| so we don't use the proper fat32 test case for test code |
| */ |
| if ( fat_bpb->bpb_fatsz16 ) |
| #else |
| if ( fat_bpb->dataclusters < 65525 ) |
| #endif |
| { /* FAT16 */ |
| #ifdef HAVE_FAT16SUPPORT |
| fat_bpb->is_fat16 = true; |
| if (fat_bpb->dataclusters < 4085) |
| { /* FAT12 */ |
| DEBUGF("This is FAT12. Go away!\n"); |
| return -2; |
| } |
| #else /* #ifdef HAVE_FAT16SUPPORT */ |
| DEBUGF("This is not FAT32. Go away!\n"); |
| return -2; |
| #endif /* #ifndef HAVE_FAT16SUPPORT */ |
| } |
| |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| { /* FAT16 specific part of BPB */ |
| int dirclusters; |
| fat_bpb->rootdirsector = fat_bpb->bpb_rsvdseccnt |
| + fat_bpb->bpb_numfats * fat_bpb->bpb_fatsz16; |
| dirclusters = ((rootdirsectors + fat_bpb->bpb_secperclus - 1) |
| / fat_bpb->bpb_secperclus); /* rounded up, to full clusters */ |
| /* I assign negative pseudo cluster numbers for the root directory, |
| their range is counted upward until -1. */ |
| fat_bpb->bpb_rootclus = 0 - dirclusters; /* backwards, before the data */ |
| fat_bpb->rootdiroffset = dirclusters * fat_bpb->bpb_secperclus |
| - rootdirsectors; |
| } |
| else |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| { /* FAT32 specific part of BPB */ |
| fat_bpb->bpb_rootclus = BYTES2INT32(buf,BPB_ROOTCLUS); |
| fat_bpb->bpb_fsinfo = secmult * BYTES2INT16(buf,BPB_FSINFO); |
| fat_bpb->rootdirsector = cluster2sec(IF_MV2(fat_bpb,) fat_bpb->bpb_rootclus); |
| } |
| |
| rc = bpb_is_sane(IF_MV(fat_bpb)); |
| if (rc < 0) |
| { |
| DEBUGF( "fat_mount() - BPB is not sane\n"); |
| return rc * 10 - 3; |
| } |
| |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| { |
| fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc below */ |
| fat_bpb->fsinfo.nextfree = 0xffffffff; |
| } |
| else |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| { |
| /* Read the fsinfo sector */ |
| rc = ata_read_sectors(IF_MV2(drive,) |
| startsector + fat_bpb->bpb_fsinfo, 1, buf); |
| if (rc < 0) |
| { |
| DEBUGF( "fat_mount() - Couldn't read FSInfo (error code %d)\n", rc); |
| return rc * 10 - 4; |
| } |
| fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT); |
| fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE); |
| } |
| |
| /* calculate freecount if unset */ |
| if ( fat_bpb->fsinfo.freecount == 0xffffffff ) |
| { |
| fat_recalc_free(IF_MV(volume)); |
| } |
| |
| LDEBUGF("Freecount: %ld\n",fat_bpb->fsinfo.freecount); |
| LDEBUGF("Nextfree: 0x%lx\n",fat_bpb->fsinfo.nextfree); |
| LDEBUGF("Cluster count: 0x%lx\n",fat_bpb->dataclusters); |
| LDEBUGF("Sectors per cluster: %d\n",fat_bpb->bpb_secperclus); |
| LDEBUGF("FAT sectors: 0x%lx\n",fat_bpb->fatsize); |
| |
| #ifdef HAVE_MULTIVOLUME |
| fat_bpb->mounted = true; |
| #endif |
| |
| return 0; |
| } |
| |
| #ifdef HAVE_HOTSWAP |
| int fat_unmount(int volume, bool flush) |
| { |
| int rc; |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| |
| if(flush) |
| { |
| rc = flush_fat(fat_bpb); /* the clean way, while still alive */ |
| } |
| else |
| { /* volume is not accessible any more, e.g. MMC removed */ |
| int i; |
| mutex_lock(&cache_mutex); |
| for(i = 0;i < FAT_CACHE_SIZE;i++) |
| { |
| struct fat_cache_entry *fce = &fat_cache[i]; |
| if(fce->inuse && fce->fat_vol == fat_bpb) |
| { |
| fce->inuse = false; /* discard all from that volume */ |
| fce->dirty = false; |
| } |
| } |
| mutex_unlock(&cache_mutex); |
| rc = 0; |
| } |
| fat_bpb->mounted = false; |
| return rc; |
| } |
| #endif /* #ifdef HAVE_HOTSWAP */ |
| |
| void fat_recalc_free(IF_MV_NONVOID(int volume)) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| const int volume = 0; |
| #endif |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| long free = 0; |
| unsigned long i; |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| { |
| for (i = 0; i<fat_bpb->fatsize; i++) { |
| unsigned int j; |
| unsigned short* fat = cache_fat_sector(IF_MV2(fat_bpb,) i, false); |
| for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) { |
| unsigned int c = i * CLUSTERS_PER_FAT16_SECTOR + j; |
| if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */ |
| break; |
| |
| if (letoh16(fat[j]) == 0x0000) { |
| free++; |
| if ( fat_bpb->fsinfo.nextfree == 0xffffffff ) |
| fat_bpb->fsinfo.nextfree = c; |
| } |
| } |
| } |
| } |
| else |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| { |
| for (i = 0; i<fat_bpb->fatsize; i++) { |
| unsigned int j; |
| unsigned long* fat = cache_fat_sector(IF_MV2(fat_bpb,) i, false); |
| for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) { |
| unsigned long c = i * CLUSTERS_PER_FAT_SECTOR + j; |
| if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */ |
| break; |
| |
| if (!(letoh32(fat[j]) & 0x0fffffff)) { |
| free++; |
| if ( fat_bpb->fsinfo.nextfree == 0xffffffff ) |
| fat_bpb->fsinfo.nextfree = c; |
| } |
| } |
| } |
| } |
| fat_bpb->fsinfo.freecount = free; |
| update_fsinfo(IF_MV(fat_bpb)); |
| } |
| |
| static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb)) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| if(fat_bpb->bpb_bytspersec % SECTOR_SIZE) |
| { |
| DEBUGF( "bpb_is_sane() - Error: sector size is not sane (%d)\n", |
| fat_bpb->bpb_bytspersec); |
| return -1; |
| } |
| if((long)fat_bpb->bpb_secperclus * (long)fat_bpb->bpb_bytspersec > 128L*1024L) |
| { |
| DEBUGF( "bpb_is_sane() - Error: cluster size is larger than 128K " |
| "(%d * %d = %d)\n", |
| fat_bpb->bpb_bytspersec, fat_bpb->bpb_secperclus, |
| fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus); |
| return -2; |
| } |
| if(fat_bpb->bpb_numfats != 2) |
| { |
| DEBUGF( "bpb_is_sane() - Warning: NumFATS is not 2 (%d)\n", |
| fat_bpb->bpb_numfats); |
| } |
| if(fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8) |
| { |
| DEBUGF( "bpb_is_sane() - Warning: Non-standard " |
| "media type (0x%02x)\n", |
| fat_bpb->bpb_media); |
| } |
| if(fat_bpb->last_word != 0xaa55) |
| { |
| DEBUGF( "bpb_is_sane() - Error: Last word is not " |
| "0xaa55 (0x%04x)\n", fat_bpb->last_word); |
| return -3; |
| } |
| |
| if (fat_bpb->fsinfo.freecount > |
| (fat_bpb->totalsectors - fat_bpb->firstdatasector)/ |
| fat_bpb->bpb_secperclus) |
| { |
| DEBUGF( "bpb_is_sane() - Error: FSInfo.Freecount > disk size " |
| "(0x%04lx)\n", fat_bpb->fsinfo.freecount); |
| return -4; |
| } |
| |
| return 0; |
| } |
| |
| static void flush_fat_sector(struct fat_cache_entry *fce, |
| unsigned char *sectorbuf) |
| { |
| int rc; |
| long secnum; |
| |
| /* With multivolume, use only the FAT info from the cached sector! */ |
| #ifdef HAVE_MULTIVOLUME |
| secnum = fce->secnum + fce->fat_vol->startsector; |
| #else |
| secnum = fce->secnum + fat_bpbs[0].startsector; |
| #endif |
| |
| /* Write to the first FAT */ |
| rc = ata_write_sectors(IF_MV2(fce->fat_vol->drive,) |
| secnum, 1, |
| sectorbuf); |
| if(rc < 0) |
| { |
| panicf("flush_fat_sector() - Could not write sector %ld" |
| " (error %d)\n", |
| secnum, rc); |
| } |
| #ifdef HAVE_MULTIVOLUME |
| if(fce->fat_vol->bpb_numfats > 1) |
| #else |
| if(fat_bpbs[0].bpb_numfats > 1) |
| #endif |
| { |
| /* Write to the second FAT */ |
| #ifdef HAVE_MULTIVOLUME |
| secnum += fce->fat_vol->fatsize; |
| #else |
| secnum += fat_bpbs[0].fatsize; |
| #endif |
| rc = ata_write_sectors(IF_MV2(fce->fat_vol->drive,) |
| secnum, 1, sectorbuf); |
| if(rc < 0) |
| { |
| panicf("flush_fat_sector() - Could not write sector %ld" |
| " (error %d)\n", |
| secnum, rc); |
| } |
| } |
| fce->dirty = false; |
| } |
| |
| /* Note: The returned pointer is only safely valid until the next |
| task switch! (Any subsequent ata read/write may yield.) */ |
| static void *cache_fat_sector(IF_MV2(struct bpb* fat_bpb,) |
| long fatsector, bool dirty) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| long secnum = fatsector + fat_bpb->bpb_rsvdseccnt; |
| int cache_index = secnum & FAT_CACHE_MASK; |
| struct fat_cache_entry *fce = &fat_cache[cache_index]; |
| unsigned char *sectorbuf = &fat_cache_sectors[cache_index][0]; |
| int rc; |
| |
| mutex_lock(&cache_mutex); /* make changes atomic */ |
| |
| /* Delete the cache entry if it isn't the sector we want */ |
| if(fce->inuse && (fce->secnum != secnum |
| #ifdef HAVE_MULTIVOLUME |
| || fce->fat_vol != fat_bpb |
| #endif |
| )) |
| { |
| /* Write back if it is dirty */ |
| if(fce->dirty) |
| { |
| flush_fat_sector(fce, sectorbuf); |
| } |
| fce->inuse = false; |
| } |
| |
| /* Load the sector if it is not cached */ |
| if(!fce->inuse) |
| { |
| rc = ata_read_sectors(IF_MV2(fat_bpb->drive,) |
| secnum + fat_bpb->startsector,1, |
| sectorbuf); |
| if(rc < 0) |
| { |
| DEBUGF( "cache_fat_sector() - Could not read sector %ld" |
| " (error %d)\n", secnum, rc); |
| mutex_unlock(&cache_mutex); |
| return NULL; |
| } |
| fce->inuse = true; |
| fce->secnum = secnum; |
| #ifdef HAVE_MULTIVOLUME |
| fce->fat_vol = fat_bpb; |
| #endif |
| } |
| if (dirty) |
| fce->dirty = true; /* dirt remains, sticky until flushed */ |
| mutex_unlock(&cache_mutex); |
| return sectorbuf; |
| } |
| |
| static unsigned long find_free_cluster(IF_MV2(struct bpb* fat_bpb,) unsigned long startcluster) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| unsigned long sector; |
| unsigned long offset; |
| unsigned long i; |
| |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| { |
| sector = startcluster / CLUSTERS_PER_FAT16_SECTOR; |
| offset = startcluster % CLUSTERS_PER_FAT16_SECTOR; |
| |
| for (i = 0; i<fat_bpb->fatsize; i++) { |
| unsigned int j; |
| unsigned int nr = (i + sector) % fat_bpb->fatsize; |
| unsigned short* fat = cache_fat_sector(IF_MV2(fat_bpb,) nr, false); |
| if ( !fat ) |
| break; |
| for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) { |
| int k = (j + offset) % CLUSTERS_PER_FAT16_SECTOR; |
| if (letoh16(fat[k]) == 0x0000) { |
| unsigned int c = nr * CLUSTERS_PER_FAT16_SECTOR + k; |
| /* Ignore the reserved clusters 0 & 1, and also |
| cluster numbers out of bounds */ |
| if ( c < 2 || c > fat_bpb->dataclusters+1 ) |
| continue; |
| LDEBUGF("find_free_cluster(%x) == %x\n",startcluster,c); |
| fat_bpb->fsinfo.nextfree = c; |
| return c; |
| } |
| } |
| offset = 0; |
| } |
| } |
| else |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| { |
| sector = startcluster / CLUSTERS_PER_FAT_SECTOR; |
| offset = startcluster % CLUSTERS_PER_FAT_SECTOR; |
| |
| for (i = 0; i<fat_bpb->fatsize; i++) { |
| unsigned int j; |
| unsigned long nr = (i + sector) % fat_bpb->fatsize; |
| unsigned long* fat = cache_fat_sector(IF_MV2(fat_bpb,) nr, false); |
| if ( !fat ) |
| break; |
| for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) { |
| int k = (j + offset) % CLUSTERS_PER_FAT_SECTOR; |
| if (!(letoh32(fat[k]) & 0x0fffffff)) { |
| unsigned long c = nr * CLUSTERS_PER_FAT_SECTOR + k; |
| /* Ignore the reserved clusters 0 & 1, and also |
| cluster numbers out of bounds */ |
| if ( c < 2 || c > fat_bpb->dataclusters+1 ) |
| continue; |
| LDEBUGF("find_free_cluster(%lx) == %lx\n",startcluster,c); |
| fat_bpb->fsinfo.nextfree = c; |
| return c; |
| } |
| } |
| offset = 0; |
| } |
| } |
| |
| LDEBUGF("find_free_cluster(%lx) == 0\n",startcluster); |
| return 0; /* 0 is an illegal cluster number */ |
| } |
| |
| static int update_fat_entry(IF_MV2(struct bpb* fat_bpb,) unsigned long entry, unsigned long val) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| { |
| int sector = entry / CLUSTERS_PER_FAT16_SECTOR; |
| int offset = entry % CLUSTERS_PER_FAT16_SECTOR; |
| unsigned short* sec; |
| |
| val &= 0xFFFF; |
| |
| LDEBUGF("update_fat_entry(%x,%x)\n",entry,val); |
| |
| if (entry==val) |
| panicf("Creating FAT loop: %lx,%lx\n",entry,val); |
| |
| if ( entry < 2 ) |
| panicf("Updating reserved FAT entry %ld.\n",entry); |
| |
| sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, true); |
| if (!sec) |
| { |
| DEBUGF( "update_fat_entry() - Could not cache sector %d\n", sector); |
| return -1; |
| } |
| |
| if ( val ) { |
| if (letoh16(sec[offset]) == 0x0000 && fat_bpb->fsinfo.freecount > 0) |
| fat_bpb->fsinfo.freecount--; |
| } |
| else { |
| if (letoh16(sec[offset])) |
| fat_bpb->fsinfo.freecount++; |
| } |
| |
| LDEBUGF("update_fat_entry: %d free clusters\n", fat_bpb->fsinfo.freecount); |
| |
| sec[offset] = htole16(val); |
| } |
| else |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| { |
| long sector = entry / CLUSTERS_PER_FAT_SECTOR; |
| int offset = entry % CLUSTERS_PER_FAT_SECTOR; |
| unsigned long* sec; |
| |
| LDEBUGF("update_fat_entry(%lx,%lx)\n",entry,val); |
| |
| if (entry==val) |
| panicf("Creating FAT loop: %lx,%lx\n",entry,val); |
| |
| if ( entry < 2 ) |
| panicf("Updating reserved FAT entry %ld.\n",entry); |
| |
| sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, true); |
| if (!sec) |
| { |
| DEBUGF( "update_fat_entry() - Could not cache sector %ld\n", sector); |
| return -1; |
| } |
| |
| if ( val ) { |
| if (!(letoh32(sec[offset]) & 0x0fffffff) && |
| fat_bpb->fsinfo.freecount > 0) |
| fat_bpb->fsinfo.freecount--; |
| } |
| else { |
| if (letoh32(sec[offset]) & 0x0fffffff) |
| fat_bpb->fsinfo.freecount++; |
| } |
| |
| LDEBUGF("update_fat_entry: %ld free clusters\n", fat_bpb->fsinfo.freecount); |
| |
| /* don't change top 4 bits */ |
| sec[offset] &= htole32(0xf0000000); |
| sec[offset] |= htole32(val & 0x0fffffff); |
| } |
| |
| return 0; |
| } |
| |
| static long read_fat_entry(IF_MV2(struct bpb* fat_bpb,) unsigned long entry) |
| { |
| #ifdef HAVE_FAT16SUPPORT |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| if (fat_bpb->is_fat16) |
| { |
| int sector = entry / CLUSTERS_PER_FAT16_SECTOR; |
| int offset = entry % CLUSTERS_PER_FAT16_SECTOR; |
| unsigned short* sec; |
| |
| sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, false); |
| if (!sec) |
| { |
| DEBUGF( "read_fat_entry() - Could not cache sector %d\n", sector); |
| return -1; |
| } |
| |
| return letoh16(sec[offset]); |
| } |
| else |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| { |
| long sector = entry / CLUSTERS_PER_FAT_SECTOR; |
| int offset = entry % CLUSTERS_PER_FAT_SECTOR; |
| unsigned long* sec; |
| |
| sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, false); |
| if (!sec) |
| { |
| DEBUGF( "read_fat_entry() - Could not cache sector %ld\n", sector); |
| return -1; |
| } |
| |
| return letoh32(sec[offset]) & 0x0fffffff; |
| } |
| } |
| |
| static long get_next_cluster(IF_MV2(struct bpb* fat_bpb,) long cluster) |
| { |
| long next_cluster; |
| long eof_mark = FAT_EOF_MARK; |
| |
| #ifdef HAVE_FAT16SUPPORT |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| if (fat_bpb->is_fat16) |
| { |
| eof_mark &= 0xFFFF; /* only 16 bit */ |
| if (cluster < 0) /* FAT16 root dir */ |
| return cluster + 1; /* don't use the FAT */ |
| } |
| #endif |
| next_cluster = read_fat_entry(IF_MV2(fat_bpb,) cluster); |
| |
| /* is this last cluster in chain? */ |
| if ( next_cluster >= eof_mark ) |
| return 0; |
| else |
| return next_cluster; |
| } |
| |
| static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb)) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| unsigned char fsinfo[SECTOR_SIZE]; |
| unsigned long* intptr; |
| int rc; |
| |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| return 0; /* FAT16 has no FsInfo */ |
| #endif /* #ifdef HAVE_FAT16SUPPORT */ |
| |
| /* update fsinfo */ |
| rc = ata_read_sectors(IF_MV2(fat_bpb->drive,) |
| fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1,fsinfo); |
| if (rc < 0) |
| { |
| DEBUGF( "flush_fat() - Couldn't read FSInfo (error code %d)\n", rc); |
| return rc * 10 - 1; |
| } |
| intptr = (long*)&(fsinfo[FSINFO_FREECOUNT]); |
| *intptr = htole32(fat_bpb->fsinfo.freecount); |
| |
| intptr = (long*)&(fsinfo[FSINFO_NEXTFREE]); |
| *intptr = htole32(fat_bpb->fsinfo.nextfree); |
| |
| rc = ata_write_sectors(IF_MV2(fat_bpb->drive,) |
| fat_bpb->startsector + fat_bpb->bpb_fsinfo,1,fsinfo); |
| if (rc < 0) |
| { |
| DEBUGF( "flush_fat() - Couldn't write FSInfo (error code %d)\n", rc); |
| return rc * 10 - 2; |
| } |
| |
| return 0; |
| } |
| |
| static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb)) |
| { |
| int i; |
| int rc; |
| unsigned char *sec; |
| LDEBUGF("flush_fat()\n"); |
| |
| mutex_lock(&cache_mutex); |
| for(i = 0;i < FAT_CACHE_SIZE;i++) |
| { |
| struct fat_cache_entry *fce = &fat_cache[i]; |
| if(fce->inuse |
| #ifdef HAVE_MULTIVOLUME |
| && fce->fat_vol == fat_bpb |
| #endif |
| && fce->dirty) |
| { |
| sec = fat_cache_sectors[i]; |
| flush_fat_sector(fce, sec); |
| } |
| } |
| mutex_unlock(&cache_mutex); |
| |
| rc = update_fsinfo(IF_MV(fat_bpb)); |
| if (rc < 0) |
| return rc * 10 - 3; |
| |
| return 0; |
| } |
| |
| static void fat_time(unsigned short* date, |
| unsigned short* time, |
| unsigned short* tenth ) |
| { |
| #ifdef CONFIG_RTC |
| struct tm* tm = get_time(); |
| |
| if (date) |
| *date = ((tm->tm_year - 80) << 9) | |
| ((tm->tm_mon + 1) << 5) | |
| tm->tm_mday; |
| |
| if (time) |
| *time = (tm->tm_hour << 11) | |
| (tm->tm_min << 5) | |
| (tm->tm_sec >> 1); |
| |
| if (tenth) |
| *tenth = (tm->tm_sec & 1) * 100; |
| #else |
| /* non-RTC version returns an increment from the supplied time, or a |
| * fixed standard time/date if no time given as input */ |
| bool next_day = false; |
| |
| if (time) |
| { |
| if (0 == *time) |
| { |
| /* set to 00:15:00 */ |
| *time = (15 << 5); |
| } |
| else |
| { |
| unsigned short mins = (*time >> 5) & 0x003F; |
| unsigned short hours = (*time >> 11) & 0x001F; |
| if ((mins += 10) >= 60) |
| { |
| mins = 0; |
| hours++; |
| } |
| if ((++hours) >= 24) |
| { |
| hours = hours - 24; |
| next_day = true; |
| } |
| *time = (hours << 11) | (mins << 5); |
| } |
| } |
| |
| if (date) |
| { |
| if (0 == *date) |
| { |
| /* Macros to convert a 2-digit string to a decimal constant. |
| (YEAR), MONTH and DAY are set by the date command, which outputs |
| DAY as 00..31 and MONTH as 01..12. The leading zero would lead to |
| misinterpretation as an octal constant. */ |
| #define S100(x) 1 ## x |
| #define C2DIG2DEC(x) (S100(x)-100) |
| /* set to build date */ |
| *date = ((YEAR - 1980) << 9) | (C2DIG2DEC(MONTH) << 5) |
| | C2DIG2DEC(DAY); |
| } |
| else |
| { |
| unsigned short day = *date & 0x001F; |
| unsigned short month = (*date >> 5) & 0x000F; |
| unsigned short year = (*date >> 9) & 0x007F; |
| if (next_day) |
| { |
| /* do a very simple day increment - never go above 28 days */ |
| if (++day > 28) |
| { |
| day = 1; |
| if (++month > 12) |
| { |
| month = 1; |
| year++; |
| } |
| } |
| *date = (year << 9) | (month << 5) | day; |
| } |
| } |
| } |
| if (tenth) |
| *tenth = 0; |
| #endif /* CONFIG_RTC */ |
| } |
| |
| static int write_long_name(struct fat_file* file, |
| unsigned int firstentry, |
| unsigned int numentries, |
| const unsigned char* name, |
| const unsigned char* shortname, |
| bool is_directory) |
| { |
| unsigned char buf[SECTOR_SIZE]; |
| unsigned char* entry; |
| unsigned int idx = firstentry % DIR_ENTRIES_PER_SECTOR; |
| unsigned int sector = firstentry / DIR_ENTRIES_PER_SECTOR; |
| unsigned char chksum = 0; |
| unsigned int i, j=0; |
| unsigned int nameidx=0, namelen = utf8length(name); |
| int rc; |
| unsigned short name_utf16[namelen + 1]; |
| |
| LDEBUGF("write_long_name(file:%lx, first:%d, num:%d, name:%s)\n", |
| file->firstcluster, firstentry, numentries, name); |
| |
| rc = fat_seek(file, sector); |
| if (rc<0) |
| return rc * 10 - 1; |
| |
| rc = fat_readwrite(file, 1, buf, false); |
| if (rc<1) |
| return rc * 10 - 2; |
| |
| /* calculate shortname checksum */ |
| for (i=11; i>0; i--) |
| chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++]; |
| |
| /* calc position of last name segment */ |
| if ( namelen > NAME_BYTES_PER_ENTRY ) |
| for (nameidx=0; |
| nameidx < (namelen - NAME_BYTES_PER_ENTRY); |
| nameidx += NAME_BYTES_PER_ENTRY); |
| |
| /* we need to convert the name first */ |
| /* since it is written in reverse order */ |
| for (i = 0; i <= namelen; i++) |
| name = utf8decode(name, &name_utf16[i]); |
| |
| for (i=0; i < numentries; i++) { |
| /* new sector? */ |
| if ( idx >= DIR_ENTRIES_PER_SECTOR ) { |
| /* update current sector */ |
| rc = fat_seek(file, sector); |
| if (rc<0) |
| return rc * 10 - 3; |
| |
| rc = fat_readwrite(file, 1, buf, true); |
| if (rc<1) |
| return rc * 10 - 4; |
| |
| /* read next sector */ |
| rc = fat_readwrite(file, 1, buf, false); |
| if (rc<0) { |
| LDEBUGF("Failed writing new sector: %d\n",rc); |
| return rc * 10 - 5; |
| } |
| if (rc==0) |
| /* end of dir */ |
| memset(buf, 0, sizeof buf); |
| |
| sector++; |
| idx = 0; |
| } |
| |
| entry = buf + idx * DIR_ENTRY_SIZE; |
| |
| /* verify this entry is free */ |
| if (entry[0] && entry[0] != 0xe5 ) |
| panicf("Dir entry %d in sector %x is not free! " |
| "%02x %02x %02x %02x", |
| idx, sector, |
| entry[0], entry[1], entry[2], entry[3]); |
| |
| memset(entry, 0, DIR_ENTRY_SIZE); |
| if ( i+1 < numentries ) { |
| /* longname entry */ |
| unsigned int k, l = nameidx; |
| |
| entry[FATLONG_ORDER] = numentries-i-1; |
| if (i==0) { |
| /* mark this as last long entry */ |
| entry[FATLONG_ORDER] |= 0x40; |
| |
| /* pad name with 0xffff */ |
| for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE; |
| for (k=14; k<26; k++) entry[k] = FAT_LONGNAME_PAD_BYTE; |
| for (k=28; k<32; k++) entry[k] = FAT_LONGNAME_PAD_BYTE; |
| }; |
| /* set name */ |
| for (k=0; k<5 && l <= namelen; k++) { |
| entry[k*2 + 1] = (unsigned char)(name_utf16[l] & 0xff); |
| entry[k*2 + 2] = (unsigned char)(name_utf16[l++] >> 8); |
| } |
| for (k=0; k<6 && l <= namelen; k++) { |
| entry[k*2 + 14] = (unsigned char)(name_utf16[l] & 0xff); |
| entry[k*2 + 15] = (unsigned char)(name_utf16[l++] >> 8); |
| } |
| for (k=0; k<2 && l <= namelen; k++) { |
| entry[k*2 + 28] = (unsigned char)(name_utf16[l] & 0xff); |
| entry[k*2 + 29] = (unsigned char)(name_utf16[l++] >> 8); |
| } |
| |
| entry[FATDIR_ATTR] = FAT_ATTR_LONG_NAME; |
| entry[FATDIR_FSTCLUSLO] = 0; |
| entry[FATLONG_TYPE] = 0; |
| entry[FATLONG_CHKSUM] = chksum; |
| LDEBUGF("Longname entry %d: %s\n", idx, name+nameidx); |
| } |
| else { |
| /* shortname entry */ |
| unsigned short date=0, time=0, tenth=0; |
| LDEBUGF("Shortname entry: %s\n", shortname); |
| strncpy(entry + FATDIR_NAME, shortname, 11); |
| entry[FATDIR_ATTR] = is_directory?FAT_ATTR_DIRECTORY:0; |
| entry[FATDIR_NTRES] = 0; |
| |
| fat_time(&date, &time, &tenth); |
| entry[FATDIR_CRTTIMETENTH] = tenth; |
| *(unsigned short*)(entry + FATDIR_CRTTIME) = htole16(time); |
| *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time); |
| *(unsigned short*)(entry + FATDIR_CRTDATE) = htole16(date); |
| *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date); |
| *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date); |
| } |
| idx++; |
| nameidx -= NAME_BYTES_PER_ENTRY; |
| } |
| |
| /* update last sector */ |
| rc = fat_seek(file, sector); |
| if (rc<0) |
| return rc * 10 - 6; |
| |
| rc = fat_readwrite(file, 1, buf, true); |
| if (rc<1) |
| return rc * 10 - 7; |
| |
| return 0; |
| } |
| |
| static int fat_checkname(const unsigned char* newname) |
| { |
| /* More sanity checks are probably needed */ |
| if ( newname[strlen(newname) - 1] == '.' ) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int add_dir_entry(struct fat_dir* dir, |
| struct fat_file* file, |
| const char* name, |
| bool is_directory, |
| bool dotdir) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[dir->file.volume]; |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| unsigned char buf[SECTOR_SIZE]; |
| unsigned char shortname[12]; |
| int rc; |
| unsigned int sector; |
| bool done = false; |
| int entries_needed, entries_found = 0; |
| int firstentry; |
| |
| LDEBUGF( "add_dir_entry(%s,%lx)\n", |
| name, file->firstcluster); |
| |
| /* Don't check dotdirs name for validity */ |
| if (dotdir == false) { |
| rc = fat_checkname(name); |
| if (rc < 0) { |
| /* filename is invalid */ |
| return rc * 10 - 1; |
| } |
| } |
| |
| #ifdef HAVE_MULTIVOLUME |
| file->volume = dir->file.volume; /* inherit the volume, to make sure */ |
| #endif |
| |
| /* The "." and ".." directory entries must not be long names */ |
| if(dotdir) { |
| int i; |
| strncpy(shortname, name, 12); |
| for(i = strlen(shortname); i < 12; i++) |
| shortname[i] = ' '; |
| |
| entries_needed = 1; |
| } else { |
| create_dos_name(name, shortname); |
| |
| /* one dir entry needed for every 13 bytes of filename, |
| plus one entry for the short name */ |
| entries_needed = (utf8length(name) + (NAME_BYTES_PER_ENTRY-1)) |
| / NAME_BYTES_PER_ENTRY + 1; |
| } |
| |
| restart: |
| firstentry = -1; |
| |
| rc = fat_seek(&dir->file, 0); |
| if (rc < 0) |
| return rc * 10 - 2; |
| |
| /* step 1: search for free entries and check for duplicate shortname */ |
| for (sector = 0; !done; sector++) |
| { |
| unsigned int i; |
| |
| rc = fat_readwrite(&dir->file, 1, buf, false); |
| if (rc < 0) { |
| DEBUGF( "add_dir_entry() - Couldn't read dir" |
| " (error code %d)\n", rc); |
| return rc * 10 - 3; |
| } |
| |
| if (rc == 0) { /* current end of dir reached */ |
| LDEBUGF("End of dir on cluster boundary\n"); |
| break; |
| } |
| |
| /* look for free slots */ |
| for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++) |
| { |
| switch (buf[i * DIR_ENTRY_SIZE]) { |
| case 0: |
| entries_found += DIR_ENTRIES_PER_SECTOR - i; |
| LDEBUGF("Found end of dir %d\n", |
| sector * DIR_ENTRIES_PER_SECTOR + i); |
| i = DIR_ENTRIES_PER_SECTOR - 1; |
| done = true; |
| break; |
| |
| case 0xe5: |
| entries_found++; |
| LDEBUGF("Found free entry %d (%d/%d)\n", |
| sector * DIR_ENTRIES_PER_SECTOR + i, |
| entries_found, entries_needed); |
| break; |
| |
| default: |
| entries_found = 0; |
| |
| /* check that our intended shortname doesn't already exist */ |
| if (!strncmp(shortname, buf + i * DIR_ENTRY_SIZE, 11)) { |
| /* shortname exists already, make a new one */ |
| randomize_dos_name(shortname); |
| LDEBUGF("Duplicate shortname, changing to %s\n", |
| shortname); |
| |
| /* name has changed, we need to restart search */ |
| goto restart; |
| } |
| break; |
| } |
| if (firstentry < 0 && (entries_found >= entries_needed)) |
| firstentry = sector * DIR_ENTRIES_PER_SECTOR + i + 1 |
| - entries_found; |
| } |
| } |
| |
| /* step 2: extend the dir if necessary */ |
| if (firstentry < 0) |
| { |
| LDEBUGF("Adding new sector(s) to dir\n"); |
| rc = fat_seek(&dir->file, sector); |
| if (rc < 0) |
| return rc * 10 - 4; |
| memset(buf, 0, sizeof buf); |
| |
| /* we must clear whole clusters */ |
| for (; (entries_found < entries_needed) || |
| (dir->file.sectornum < (int)fat_bpb->bpb_secperclus); sector++) |
| { |
| if (sector >= (65536/DIR_ENTRIES_PER_SECTOR)) |
| return -5; /* dir too large -- FAT specification */ |
| |
| rc = fat_readwrite(&dir->file, 1, buf, true); |
| if (rc < 1) /* No more room or something went wrong */ |
| return rc * 10 - 6; |
| |
| entries_found += DIR_ENTRIES_PER_SECTOR; |
| } |
| |
| firstentry = sector * DIR_ENTRIES_PER_SECTOR - entries_found; |
| } |
| |
| /* step 3: add entry */ |
| sector = firstentry / DIR_ENTRIES_PER_SECTOR; |
| LDEBUGF("Adding longname to entry %d in sector %d\n", |
| firstentry, sector); |
| |
| rc = write_long_name(&dir->file, firstentry, |
| entries_needed, name, shortname, is_directory); |
| if (rc < 0) |
| return rc * 10 - 7; |
| |
| /* remember where the shortname dir entry is located */ |
| file->direntry = firstentry + entries_needed - 1; |
| file->direntries = entries_needed; |
| file->dircluster = dir->file.firstcluster; |
| LDEBUGF("Added new dir entry %d, using %d slots.\n", |
| file->direntry, file->direntries); |
| |
| return 0; |
| } |
| |
| unsigned char char2dos(unsigned char c) |
| { |
| switch(c) |
| { |
| case 0x22: |
| case 0x2a: |
| case 0x2b: |
| case 0x2c: |
| case 0x2e: |
| case 0x3a: |
| case 0x3b: |
| case 0x3c: |
| case 0x3d: |
| case 0x3e: |
| case 0x3f: |
| case 0x5b: |
| case 0x5c: |
| case 0x5d: |
| case 0x7c: |
| /* Illegal char, replace */ |
| c = '_'; |
| break; |
| |
| default: |
| if(c <= 0x20) |
| c = 0; /* Illegal char, remove */ |
| else |
| c = toupper(c); |
| break; |
| } |
| return c; |
| } |
| |
| static void create_dos_name(const unsigned char *name, unsigned char *newname) |
| { |
| int i; |
| unsigned char *ext; |
| |
| /* Find extension part */ |
| ext = strrchr(name, '.'); |
| if (ext == name) /* handle .dotnames */ |
| ext = NULL; |
| |
| /* Name part */ |
| for (i = 0; *name && (!ext || name < ext) && (i < 8); name++) |
| { |
| unsigned char c = char2dos(*name); |
| if (c) |
| newname[i++] = c; |
| } |
| |
| /* Pad both name and extension */ |
| while (i < 11) |
| newname[i++] = ' '; |
| |
| if (newname[0] == 0xe5) /* Special kanji character */ |
| newname[0] = 0x05; |
| |
| if (ext) |
| { /* Extension part */ |
| ext++; |
| for (i = 8; *ext && (i < 11); ext++) |
| { |
| unsigned char c = char2dos(*ext); |
| if (c) |
| newname[i++] = c; |
| } |
| } |
| } |
| |
| static void randomize_dos_name(unsigned char *name) |
| { |
| int i; |
| unsigned char buf[5]; |
| |
| snprintf(buf, sizeof buf, "%04X", (unsigned)rand() & 0xffff); |
| |
| for (i = 0; (i < 4) && (name[i] != ' '); i++); |
| /* account for possible shortname length < 4 */ |
| memcpy(&name[i], buf, 4); |
| } |
| |
| static int update_short_entry( struct fat_file* file, long size, int attr ) |
| { |
| unsigned char buf[SECTOR_SIZE]; |
| int sector = file->direntry / DIR_ENTRIES_PER_SECTOR; |
| unsigned char* entry = |
| buf + DIR_ENTRY_SIZE * (file->direntry % DIR_ENTRIES_PER_SECTOR); |
| unsigned long* sizeptr; |
| unsigned short* clusptr; |
| struct fat_file dir; |
| int rc; |
| |
| LDEBUGF("update_file_size(cluster:%lx entry:%d size:%ld)\n", |
| file->firstcluster, file->direntry, size); |
| |
| /* create a temporary file handle for the dir holding this file */ |
| rc = fat_open(IF_MV2(file->volume,) file->dircluster, &dir, NULL); |
| if (rc < 0) |
| return rc * 10 - 1; |
| |
| rc = fat_seek( &dir, sector ); |
| if (rc<0) |
| return rc * 10 - 2; |
| |
| rc = fat_readwrite(&dir, 1, buf, false); |
| if (rc < 1) |
| return rc * 10 - 3; |
| |
| if (!entry[0] || entry[0] == 0xe5) |
| panicf("Updating size on empty dir entry %d\n", file->direntry); |
| |
| entry[FATDIR_ATTR] = attr & 0xFF; |
| |
| clusptr = (short*)(entry + FATDIR_FSTCLUSHI); |
| *clusptr = htole16(file->firstcluster >> 16); |
| |
| clusptr = (short*)(entry + FATDIR_FSTCLUSLO); |
| *clusptr = htole16(file->firstcluster & 0xffff); |
| |
| sizeptr = (long*)(entry + FATDIR_FILESIZE); |
| *sizeptr = htole32(size); |
| |
| { |
| #ifdef CONFIG_RTC |
| unsigned short time = 0; |
| unsigned short date = 0; |
| #else |
| /* get old time to increment from */ |
| unsigned short time = htole16(*(unsigned short*)(entry + FATDIR_WRTTIME)); |
| unsigned short date = htole16(*(unsigned short*)(entry + FATDIR_WRTDATE)); |
| #endif |
| fat_time(&date, &time, NULL); |
| *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time); |
| *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date); |
| *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date); |
| } |
| |
| rc = fat_seek( &dir, sector ); |
| if (rc < 0) |
| return rc * 10 - 4; |
| |
| rc = fat_readwrite(&dir, 1, buf, true); |
| if (rc < 1) |
| return rc * 10 - 5; |
| |
| return 0; |
| } |
| |
| static int parse_direntry(struct fat_direntry *de, const unsigned char *buf) |
| { |
| int i=0,j=0; |
| unsigned char c; |
| bool lowercase; |
| |
| memset(de, 0, sizeof(struct fat_direntry)); |
| de->attr = buf[FATDIR_ATTR]; |
| de->crttimetenth = buf[FATDIR_CRTTIMETENTH]; |
| de->crtdate = BYTES2INT16(buf,FATDIR_CRTDATE); |
| de->crttime = BYTES2INT16(buf,FATDIR_CRTTIME); |
| de->wrtdate = BYTES2INT16(buf,FATDIR_WRTDATE); |
| de->wrttime = BYTES2INT16(buf,FATDIR_WRTTIME); |
| de->filesize = BYTES2INT32(buf,FATDIR_FILESIZE); |
| de->firstcluster = ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSLO)) | |
| ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSHI) << 16); |
| /* The double cast is to prevent a sign-extension to be done on CalmRISC16. |
| (the result of the shift is always considered signed) */ |
| |
| /* fix the name */ |
| lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_NAME); |
| c = buf[FATDIR_NAME]; |
| if (c == 0x05) /* special kanji char */ |
| c = 0xe5; |
| i = 0; |
| while (c != ' ') { |
| de->name[j++] = lowercase ? tolower(c) : c; |
| if (++i >= 8) |
| break; |
| c = buf[FATDIR_NAME+i]; |
| } |
| if (buf[FATDIR_NAME+8] != ' ') { |
| lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_EXT); |
| de->name[j++] = '.'; |
| for (i = 8; (i < 11) && ((c = buf[FATDIR_NAME+i]) != ' '); i++) |
| de->name[j++] = lowercase ? tolower(c) : c; |
| } |
| return 1; |
| } |
| |
| int fat_open(IF_MV2(int volume,) |
| long startcluster, |
| struct fat_file *file, |
| const struct fat_dir* dir) |
| { |
| file->firstcluster = startcluster; |
| file->lastcluster = startcluster; |
| file->lastsector = 0; |
| file->clusternum = 0; |
| file->sectornum = 0; |
| file->eof = false; |
| #ifdef HAVE_MULTIVOLUME |
| file->volume = volume; |
| /* fixme: remove error check when done */ |
| if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted) |
| { |
| LDEBUGF("fat_open() illegal volume %d\n", volume); |
| return -1; |
| } |
| #endif |
| |
| /* remember where the file's dir entry is located */ |
| if ( dir ) { |
| file->direntry = dir->entry - 1; |
| file->direntries = dir->entrycount; |
| file->dircluster = dir->file.firstcluster; |
| } |
| LDEBUGF("fat_open(%lx), entry %d\n",startcluster,file->direntry); |
| return 0; |
| } |
| |
| int fat_create_file(const char* name, |
| struct fat_file* file, |
| struct fat_dir* dir) |
| { |
| int rc; |
| |
| LDEBUGF("fat_create_file(\"%s\",%lx,%lx)\n",name,(long)file,(long)dir); |
| rc = add_dir_entry(dir, file, name, false, false); |
| if (!rc) { |
| file->firstcluster = 0; |
| file->lastcluster = 0; |
| file->lastsector = 0; |
| file->clusternum = 0; |
| file->sectornum = 0; |
| file->eof = false; |
| } |
| |
| return rc; |
| } |
| |
| int fat_create_dir(const char* name, |
| struct fat_dir* newdir, |
| struct fat_dir* dir) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[dir->file.volume]; |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| unsigned char buf[SECTOR_SIZE]; |
| int i; |
| long sector; |
| int rc; |
| struct fat_file dummyfile; |
| |
| LDEBUGF("fat_create_dir(\"%s\",%lx,%lx)\n",name,(long)newdir,(long)dir); |
| |
| memset(newdir, 0, sizeof(struct fat_dir)); |
| memset(&dummyfile, 0, sizeof(struct fat_file)); |
| |
| /* First, add the entry in the parent directory */ |
| rc = add_dir_entry(dir, &newdir->file, name, true, false); |
| if (rc < 0) |
| return rc * 10 - 1; |
| |
| /* Allocate a new cluster for the directory */ |
| newdir->file.firstcluster = find_free_cluster(IF_MV2(fat_bpb,) fat_bpb->fsinfo.nextfree); |
| if(newdir->file.firstcluster == 0) |
| return -1; |
| |
| update_fat_entry(IF_MV2(fat_bpb,) newdir->file.firstcluster, FAT_EOF_MARK); |
| |
| /* Clear the entire cluster */ |
| memset(buf, 0, sizeof buf); |
| sector = cluster2sec(IF_MV2(fat_bpb,) newdir->file.firstcluster); |
| for(i = 0;i < (int)fat_bpb->bpb_secperclus;i++) { |
| rc = transfer(IF_MV2(fat_bpb,) sector + i, 1, buf, true ); |
| if (rc < 0) |
| return rc * 10 - 2; |
| } |
| |
| /* Then add the "." entry */ |
| rc = add_dir_entry(newdir, &dummyfile, ".", true, true); |
| if (rc < 0) |
| return rc * 10 - 3; |
| dummyfile.firstcluster = newdir->file.firstcluster; |
| update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY); |
| |
| /* and the ".." entry */ |
| rc = add_dir_entry(newdir, &dummyfile, "..", true, true); |
| if (rc < 0) |
| return rc * 10 - 4; |
| |
| /* The root cluster is cluster 0 in the ".." entry */ |
| if(dir->file.firstcluster == fat_bpb->bpb_rootclus) |
| dummyfile.firstcluster = 0; |
| else |
| dummyfile.firstcluster = dir->file.firstcluster; |
| update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY); |
| |
| /* Set the firstcluster field in the direntry */ |
| update_short_entry(&newdir->file, 0, FAT_ATTR_DIRECTORY); |
| |
| rc = flush_fat(IF_MV(fat_bpb)); |
| if (rc < 0) |
| return rc * 10 - 5; |
| |
| return rc; |
| } |
| |
| int fat_truncate(const struct fat_file *file) |
| { |
| /* truncate trailing clusters */ |
| long next; |
| long last = file->lastcluster; |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #endif |
| |
| LDEBUGF("fat_truncate(%lx, %lx)\n", file->firstcluster, last); |
| |
| for ( last = get_next_cluster(IF_MV2(fat_bpb,) last); last; last = next ) { |
| next = get_next_cluster(IF_MV2(fat_bpb,) last); |
| update_fat_entry(IF_MV2(fat_bpb,) last,0); |
| } |
| if (file->lastcluster) |
| update_fat_entry(IF_MV2(fat_bpb,) file->lastcluster,FAT_EOF_MARK); |
| |
| return 0; |
| } |
| |
| int fat_closewrite(struct fat_file *file, long size, int attr) |
| { |
| int rc; |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #endif |
| LDEBUGF("fat_closewrite(size=%ld)\n",size); |
| |
| if (!size) { |
| /* empty file */ |
| if ( file->firstcluster ) { |
| update_fat_entry(IF_MV2(fat_bpb,) file->firstcluster, 0); |
| file->firstcluster = 0; |
| } |
| } |
| |
| if (file->dircluster) { |
| rc = update_short_entry(file, size, attr); |
| if (rc < 0) |
| return rc * 10 - 1; |
| } |
| |
| flush_fat(IF_MV(fat_bpb)); |
| |
| #ifdef TEST_FAT |
| if ( file->firstcluster ) { |
| /* debug */ |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| long count = 0; |
| long len; |
| long next; |
| for ( next = file->firstcluster; next; |
| next = get_next_cluster(IF_MV2(fat_bpb,) next) ) { |
| LDEBUGF("cluster %ld: %lx\n", count, next); |
| count++; |
| } |
| len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE; |
| LDEBUGF("File is %ld clusters (chainlen=%ld, size=%ld)\n", |
| count, len, size ); |
| if ( len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE) |
| panicf("Cluster chain is too long\n"); |
| if ( len < size ) |
| panicf("Cluster chain is too short\n"); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| static int free_direntries(struct fat_file* file) |
| { |
| unsigned char buf[SECTOR_SIZE]; |
| struct fat_file dir; |
| int numentries = file->direntries; |
| unsigned int entry = file->direntry - numentries + 1; |
| unsigned int sector = entry / DIR_ENTRIES_PER_SECTOR; |
| int i; |
| int rc; |
| |
| /* create a temporary file handle for the dir holding this file */ |
| rc = fat_open(IF_MV2(file->volume,) file->dircluster, &dir, NULL); |
| if (rc < 0) |
| return rc * 10 - 1; |
| |
| rc = fat_seek( &dir, sector ); |
| if (rc < 0) |
| return rc * 10 - 2; |
| |
| rc = fat_readwrite(&dir, 1, buf, false); |
| if (rc < 1) |
| return rc * 10 - 3; |
| |
| for (i=0; i < numentries; i++) { |
| LDEBUGF("Clearing dir entry %d (%d/%d)\n", |
| entry, i+1, numentries); |
| buf[(entry % DIR_ENTRIES_PER_SECTOR) * DIR_ENTRY_SIZE] = 0xe5; |
| entry++; |
| |
| if ( (entry % DIR_ENTRIES_PER_SECTOR) == 0 ) { |
| /* flush this sector */ |
| rc = fat_seek(&dir, sector); |
| if (rc < 0) |
| return rc * 10 - 4; |
| |
| rc = fat_readwrite(&dir, 1, buf, true); |
| if (rc < 1) |
| return rc * 10 - 5; |
| |
| if ( i+1 < numentries ) { |
| /* read next sector */ |
| rc = fat_readwrite(&dir, 1, buf, false); |
| if (rc < 1) |
| return rc * 10 - 6; |
| } |
| sector++; |
| } |
| } |
| |
| if ( entry % DIR_ENTRIES_PER_SECTOR ) { |
| /* flush this sector */ |
| rc = fat_seek(&dir, sector); |
| if (rc < 0) |
| return rc * 10 - 7; |
| |
| rc = fat_readwrite(&dir, 1, buf, true); |
| if (rc < 1) |
| return rc * 10 - 8; |
| } |
| |
| return 0; |
| } |
| |
| int fat_remove(struct fat_file* file) |
| { |
| long next, last = file->firstcluster; |
| int rc; |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #endif |
| |
| LDEBUGF("fat_remove(%lx)\n",last); |
| |
| while ( last ) { |
| next = get_next_cluster(IF_MV2(fat_bpb,) last); |
| update_fat_entry(IF_MV2(fat_bpb,) last,0); |
| last = next; |
| } |
| |
| if ( file->dircluster ) { |
| rc = free_direntries(file); |
| if (rc < 0) |
| return rc * 10 - 1; |
| } |
| |
| file->firstcluster = 0; |
| file->dircluster = 0; |
| |
| rc = flush_fat(IF_MV(fat_bpb)); |
| if (rc < 0) |
| return rc * 10 - 2; |
| |
| return 0; |
| } |
| |
| int fat_rename(struct fat_file* file, |
| struct fat_dir* dir, |
| const unsigned char* newname, |
| long size, |
| int attr) |
| { |
| int rc; |
| struct fat_dir olddir; |
| struct fat_file newfile = *file; |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| |
| if (file->volume != dir->file.volume) { |
| DEBUGF("No rename across volumes!\n"); |
| return -1; |
| } |
| #endif |
| |
| if ( !file->dircluster ) { |
| DEBUGF("File has no dir cluster!\n"); |
| return -2; |
| } |
| |
| /* create a temporary file handle */ |
| rc = fat_opendir(IF_MV2(file->volume,) &olddir, file->dircluster, NULL); |
| if (rc < 0) |
| return rc * 10 - 3; |
| |
| /* create new name */ |
| rc = add_dir_entry(dir, &newfile, newname, false, false); |
| if (rc < 0) |
| return rc * 10 - 4; |
| |
| /* write size and cluster link */ |
| rc = update_short_entry(&newfile, size, attr); |
| if (rc < 0) |
| return rc * 10 - 5; |
| |
| /* remove old name */ |
| rc = free_direntries(file); |
| if (rc < 0) |
| return rc * 10 - 6; |
| |
| rc = flush_fat(IF_MV(fat_bpb)); |
| if (rc < 0) |
| return rc * 10 - 7; |
| |
| return 0; |
| } |
| |
| static long next_write_cluster(struct fat_file* file, |
| long oldcluster, |
| long* newsector) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| long cluster = 0; |
| long sector; |
| |
| LDEBUGF("next_write_cluster(%lx,%lx)\n",file->firstcluster, oldcluster); |
| |
| if (oldcluster) |
| cluster = get_next_cluster(IF_MV2(fat_bpb,) oldcluster); |
| |
| if (!cluster) { |
| if (oldcluster > 0) |
| cluster = find_free_cluster(IF_MV2(fat_bpb,) oldcluster+1); |
| else if (oldcluster == 0) |
| cluster = find_free_cluster(IF_MV2(fat_bpb,) fat_bpb->fsinfo.nextfree); |
| #ifdef HAVE_FAT16SUPPORT |
| else /* negative, pseudo-cluster of the root dir */ |
| return 0; /* impossible to append something to the root */ |
| #endif |
| |
| if (cluster) { |
| if (oldcluster) |
| update_fat_entry(IF_MV2(fat_bpb,) oldcluster, cluster); |
| else |
| file->firstcluster = cluster; |
| update_fat_entry(IF_MV2(fat_bpb,) cluster, FAT_EOF_MARK); |
| } |
| else { |
| #ifdef TEST_FAT |
| if (fat_bpb->fsinfo.freecount>0) |
| panicf("There is free space, but find_free_cluster() " |
| "didn't find it!\n"); |
| #endif |
| DEBUGF("next_write_cluster(): Disk full!\n"); |
| return 0; |
| } |
| } |
| sector = cluster2sec(IF_MV2(fat_bpb,) cluster); |
| if (sector<0) |
| return 0; |
| |
| *newsector = sector; |
| return cluster; |
| } |
| |
| static int transfer(IF_MV2(struct bpb* fat_bpb,) |
| unsigned long start, long count, char* buf, bool write ) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| int rc; |
| |
| LDEBUGF("transfer(s=%lx, c=%lx, %s)\n", |
| start+ fat_bpb->startsector, count, write?"write":"read"); |
| if (write) { |
| unsigned long firstallowed; |
| #ifdef HAVE_FAT16SUPPORT |
| if (fat_bpb->is_fat16) |
| firstallowed = fat_bpb->rootdirsector; |
| else |
| #endif |
| firstallowed = fat_bpb->firstdatasector; |
| |
| if (start < firstallowed) |
| panicf("Write %ld before data\n", firstallowed - start); |
| if (start + count > fat_bpb->totalsectors) |
| panicf("Write %ld after data\n", |
| start + count - fat_bpb->totalsectors); |
| rc = ata_write_sectors(IF_MV2(fat_bpb->drive,) |
| start + fat_bpb->startsector, count, buf); |
| } |
| else |
| rc = ata_read_sectors(IF_MV2(fat_bpb->drive,) |
| start + fat_bpb->startsector, count, buf); |
| if (rc < 0) { |
| DEBUGF( "transfer() - Couldn't %s sector %lx" |
| " (error code %d)\n", |
| write ? "write":"read", start, rc); |
| return rc; |
| } |
| return 0; |
| } |
| |
| |
| long fat_readwrite( struct fat_file *file, long sectorcount, |
| void* buf, bool write ) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| long cluster = file->lastcluster; |
| long sector = file->lastsector; |
| long clusternum = file->clusternum; |
| long numsec = file->sectornum; |
| bool eof = file->eof; |
| long first=0, last=0; |
| long i; |
| int rc; |
| |
| LDEBUGF( "fat_readwrite(file:%lx,count:0x%lx,buf:%lx,%s)\n", |
| file->firstcluster,sectorcount,(long)buf,write?"write":"read"); |
| LDEBUGF( "fat_readwrite: sec=%lx numsec=%ld eof=%d\n", |
| sector,numsec, eof?1:0); |
| |
| if ( eof && !write) |
| return 0; |
| |
| /* find sequential sectors and write them all at once */ |
| for (i=0; (i < sectorcount) && (sector > -1); i++ ) { |
| numsec++; |
| if ( numsec > (long)fat_bpb->bpb_secperclus || !cluster ) { |
| long oldcluster = cluster; |
| if (write) |
| cluster = next_write_cluster(file, cluster, §or); |
| else { |
| cluster = get_next_cluster(IF_MV2(fat_bpb,) cluster); |
| sector = cluster2sec(IF_MV2(fat_bpb,) cluster); |
| } |
| |
| clusternum++; |
| numsec=1; |
| |
| if (!cluster) { |
| eof = true; |
| if ( write ) { |
| /* remember last cluster, in case |
| we want to append to the file */ |
| cluster = oldcluster; |
| clusternum--; |
| i = -1; /* Error code */ |
| break; |
| } |
| } |
| else |
| eof = false; |
| } |
| else { |
| if (sector) |
| sector++; |
| else { |
| /* look up first sector of file */ |
| sector = cluster2sec(IF_MV2(fat_bpb,) file->firstcluster); |
| numsec=1; |
| #ifdef HAVE_FAT16SUPPORT |
| if (file->firstcluster < 0) |
| { /* FAT16 root dir */ |
| sector += fat_bpb->rootdiroffset; |
| numsec += fat_bpb->rootdiroffset; |
| } |
| #endif |
| } |
| } |
| |
| if (!first) |
| first = sector; |
| |
| if ( ((sector != first) && (sector != last+1)) || /* not sequential */ |
| (last-first+1 == 256) ) { /* max 256 sectors per ata request */ |
| long count = last - first + 1; |
| rc = transfer(IF_MV2(fat_bpb,) first, count, buf, write ); |
| if (rc < 0) |
| return rc * 10 - 1; |
| |
| buf = (char *)buf + count * SECTOR_SIZE; |
| first = sector; |
| } |
| |
| if ((i == sectorcount-1) && /* last sector requested */ |
| (!eof)) |
| { |
| long count = sector - first + 1; |
| rc = transfer(IF_MV2(fat_bpb,) first, count, buf, write ); |
| if (rc < 0) |
| return rc * 10 - 2; |
| } |
| |
| last = sector; |
| } |
| |
| file->lastcluster = cluster; |
| file->lastsector = sector; |
| file->clusternum = clusternum; |
| file->sectornum = numsec; |
| file->eof = eof; |
| |
| /* if eof, don't report last block as read/written */ |
| if (eof) |
| i--; |
| |
| DEBUGF("Sectors written: %ld\n", i); |
| return i; |
| } |
| |
| int fat_seek(struct fat_file *file, unsigned long seeksector ) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[file->volume]; |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| long clusternum=0, numclusters=0, sectornum=0, sector=0; |
| long cluster = file->firstcluster; |
| long i; |
| |
| #ifdef HAVE_FAT16SUPPORT |
| if (cluster < 0) /* FAT16 root dir */ |
| seeksector += fat_bpb->rootdiroffset; |
| #endif |
| |
| file->eof = false; |
| if (seeksector) { |
| /* we need to find the sector BEFORE the requested, since |
| the file struct stores the last accessed sector */ |
| seeksector--; |
| numclusters = clusternum = seeksector / fat_bpb->bpb_secperclus; |
| sectornum = seeksector % fat_bpb->bpb_secperclus; |
| |
| if (file->clusternum && clusternum >= file->clusternum) |
| { |
| cluster = file->lastcluster; |
| numclusters -= file->clusternum; |
| } |
| |
| for (i=0; i<numclusters; i++) { |
| cluster = get_next_cluster(IF_MV2(fat_bpb,) cluster); |
| if (!cluster) { |
| DEBUGF("Seeking beyond the end of the file! " |
| "(sector %ld, cluster %ld)\n", seeksector, i); |
| return -1; |
| } |
| } |
| |
| sector = cluster2sec(IF_MV2(fat_bpb,) cluster) + sectornum; |
| } |
| else { |
| sectornum = -1; |
| } |
| |
| LDEBUGF("fat_seek(%lx, %lx) == %lx, %lx, %lx\n", |
| file->firstcluster, seeksector, cluster, sector, sectornum); |
| |
| file->lastcluster = cluster; |
| file->lastsector = sector; |
| file->clusternum = clusternum; |
| file->sectornum = sectornum + 1; |
| return 0; |
| } |
| |
| int fat_opendir(IF_MV2(int volume,) |
| struct fat_dir *dir, unsigned long startcluster, |
| const struct fat_dir *parent_dir) |
| { |
| #ifdef HAVE_MULTIVOLUME |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| /* fixme: remove error check when done */ |
| if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted) |
| { |
| LDEBUGF("fat_open() illegal volume %d\n", volume); |
| return -1; |
| } |
| #else |
| struct bpb* fat_bpb = &fat_bpbs[0]; |
| #endif |
| int rc; |
| |
| dir->entry = 0; |
| dir->sector = 0; |
| |
| if (startcluster == 0) |
| startcluster = fat_bpb->bpb_rootclus; |
| |
| rc = fat_open(IF_MV2(volume,) startcluster, &dir->file, parent_dir); |
| if(rc) |
| { |
| DEBUGF( "fat_opendir() - Couldn't open dir" |
| " (error code %d)\n", rc); |
| return rc * 10 - 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Copies a segment of long file name (UTF-16 LE encoded) to the |
| * destination buffer (UTF-8 encoded). Copying is stopped when |
| * either 0x0000 or 0xffff (FAT pad char) is encountered. |
| * Trailing \0 is also appended at the end of the UTF8-encoded |
| * string. |
| * |
| * utf16src utf16 (little endian) segment to copy |
| * utf16count max number of the utf16-characters to copy |
| * utf8dst where to write UTF8-encoded string to |
| * |
| * returns the number of UTF-16 characters actually copied |
| */ |
| static int fat_copy_long_name_segment(unsigned char *utf16src, |
| int utf16count, unsigned char *utf8dst) { |
| int cnt = 0; |
| while ((utf16count--) > 0) { |
| unsigned short ucs = utf16src[0] | (utf16src[1] << 8); |
| if ((ucs == 0) || (ucs == FAT_LONGNAME_PAD_UCS)) { |
| break; |
| } |
| utf8dst = utf8encode(ucs, utf8dst); |
| utf16src += 2; |
| cnt++; |
| } |
| *utf8dst = 0; |
| return cnt; |
| } |
| |
| int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) |
| { |
| bool done = false; |
| int i; |
| int rc; |
| unsigned char firstbyte; |
| /* Long file names are stored in special entries. Each entry holds |
| up to 13 characters. Names can be max 255 chars (not bytes!) long |
| hence max 20 entries are required. */ |
| int longarray[20]; |
| int longs=0; |
| int sectoridx=0; |
| unsigned char* cached_buf = dir->sectorcache[0]; |
| |
| dir->entrycount = 0; |
| |
| while(!done) |
| { |
| if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector ) |
| { |
| rc = fat_readwrite(&dir->file, 1, cached_buf, false); |
| if (rc == 0) { |
| /* eof */ |
| entry->name[0] = 0; |
| break; |
| } |
| if (rc < 0) { |
| DEBUGF( "fat_getnext() - Couldn't read dir" |
| " (error code %d)\n", rc); |
| return rc * 10 - 1; |
| } |
| dir->sector = dir->file.lastsector; |
| } |
| |
| for (i = dir->entry % DIR_ENTRIES_PER_SECTOR; |
| i < DIR_ENTRIES_PER_SECTOR; i++) |
| { |
| unsigned int entrypos = i * DIR_ENTRY_SIZE; |
| |
| firstbyte = cached_buf[entrypos]; |
| dir->entry++; |
| |
| if (firstbyte == 0xe5) { |
| /* free entry */ |
| sectoridx = 0; |
| dir->entrycount = 0; |
| continue; |
| } |
| |
| if (firstbyte == 0) { |
| /* last entry */ |
| entry->name[0] = 0; |
| dir->entrycount = 0; |
| return 0; |
| } |
| |
| dir->entrycount++; |
| |
| /* longname entry? */ |
| if ( ( cached_buf[entrypos + FATDIR_ATTR] & |
| FAT_ATTR_LONG_NAME_MASK ) == FAT_ATTR_LONG_NAME ) { |
| longarray[longs++] = entrypos + sectoridx; |
| } |
| else { |
| if ( parse_direntry(entry, |
| &cached_buf[entrypos]) ) { |
| |
| /* don't return volume id entry */ |
| if ( (entry->attr & |
| (FAT_ATTR_VOLUME_ID|FAT_ATTR_DIRECTORY)) |
| == FAT_ATTR_VOLUME_ID) |
| continue; |
| |
| /* replace shortname with longname? */ |
| if ( longs ) { |
| int j; |
| /* This should be enough to hold any name segment utf8-encoded */ |
| unsigned char shortname[13]; /* 8+3+dot+\0 */ |
| unsigned char longname_utf8segm[6*4 + 1]; /* Add 1 for trailing \0 */ |
| int longname_utf8len = 0; |
| |
| strcpy(shortname, entry->name); /* Temporarily store it */ |
| entry->name[0] = 0; |
| |
| /* iterate backwards through the dir entries */ |
| for (j=longs-1; j>=0; j--) { |
| unsigned char* ptr = cached_buf; |
| int index = longarray[j]; |
| /* current or cached sector? */ |
| if ( sectoridx >= SECTOR_SIZE ) { |
| if ( sectoridx >= SECTOR_SIZE*2 ) { |
| if ( ( index >= SECTOR_SIZE ) && |
| ( index < SECTOR_SIZE*2 )) |
| ptr = dir->sectorcache[1]; |
| else |
| ptr = dir->sectorcache[2]; |
| } |
| else { |
| if ( index < SECTOR_SIZE ) |
| ptr = dir->sectorcache[1]; |
| } |
| |
| index &= SECTOR_SIZE-1; |
| } |
| |
| /* Try to append each segment of the long name. Check if we'd |
| exceed the buffer. Also check for FAT padding characters 0xFFFF. */ |
| if (fat_copy_long_name_segment(ptr + index + 1, 5, |
| longname_utf8segm) == 0) break; |
| // logf("SG: %s, EN: %s", longname_utf8segm, entry->name); |
| longname_utf8len += strlen(longname_utf8segm); |
| if (longname_utf8len < FAT_FILENAME_BYTES) |
| strcat(entry->name, longname_utf8segm); |
| else |
| break; |
| |
| if (fat_copy_long_name_segment(ptr + index + 14, 6, |
| longname_utf8segm) == 0) break; |
| // logf("SG: %s, EN: %s", longname_utf8segm, entry->name); |
| longname_utf8len += strlen(longname_utf8segm); |
| if (longname_utf8len < FAT_FILENAME_BYTES) |
| strcat(entry->name, longname_utf8segm); |
| else |
| break; |
| |
| if (fat_copy_long_name_segment(ptr + index + 28, 2, |
| longname_utf8segm) == 0) break; |
| // logf("SG: %s, EN: %s", longname_utf8segm, entry->name); |
| longname_utf8len += strlen(longname_utf8segm); |
| if (longname_utf8len < FAT_FILENAME_BYTES) |
| strcat(entry->name, longname_utf8segm); |
| else |
| break; |
| } |
| |
| /* Does the utf8-encoded name fit into the entry? */ |
| if (longname_utf8len >= FAT_FILENAME_BYTES) { |
| /* Take the short DOS name. Need to utf8-encode it since |
| it may contain chars from the upper half of the OEM |
| code page which wouldn't be a valid utf8. Beware: this |
| file will be shown with strange glyphs in file browser |
| since unicode 0x80 to 0x9F are control characters. */ |
| logf("SN-DOS: %s", shortname); |
| unsigned char *utf8; |
| utf8 = iso_decode(shortname, entry->name, -1, strlen(shortname)); |
| *utf8 = 0; |
| logf("SN: %s", entry->name); |
| } else { |
| // logf("LN: %s", entry->name); |
| // logf("LNLen: %d (%c)", longname_utf8len, entry->name[0]); |
| } |
| } |
| done = true; |
| sectoridx = 0; |
| i++; |
| break; |
| } |
| } |
| } |
| |
| /* save this sector, for longname use */ |
| if ( sectoridx ) |
| memcpy( dir->sectorcache[2], dir->sectorcache[0], SECTOR_SIZE ); |
| else |
| memcpy( dir->sectorcache[1], dir->sectorcache[0], SECTOR_SIZE ); |
| sectoridx += SECTOR_SIZE; |
| |
| } |
| return 0; |
| } |
| |
| unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume)) |
| { |
| #ifndef HAVE_MULTIVOLUME |
| const int volume = 0; |
| #endif |
| struct bpb* fat_bpb = &fat_bpbs[volume]; |
| return fat_bpb->bpb_secperclus * SECTOR_SIZE; |
| } |
| |
| #ifdef HAVE_MULTIVOLUME |
| bool fat_ismounted(int volume) |
| { |
| return (volume<NUM_VOLUMES && fat_bpbs[volume].mounted); |
| } |
| #endif |