diff --git a/firmware/drivers/fat.c b/firmware/drivers/fat.c index 13719fa..47312d6 100644 --- a/firmware/drivers/fat.c +++ b/firmware/drivers/fat.c @@ -31,6 +31,7 @@ #include "timefuncs.h" #include "kernel.h" #include "rbunicode.h" +/*#define LOGF_ENABLE*/ #include "logf.h" #define BYTES2INT16(array,pos) \ @@ -110,6 +111,12 @@ #define FATLONG_ORDER 0 #define FATLONG_TYPE 12 #define FATLONG_CHKSUM 13 +#define FATLONG_LAST_LONG_ENTRY 0x40 +#define FATLONG_NAME_BYTES_PER_ENTRY 26 + +#define FATLONG_NAME_CHUNKS 3 +static unsigned char FATLONG_NAME_POS[FATLONG_NAME_CHUNKS] = {1, 14, 28}; +static unsigned char FATLONG_NAME_SIZE[FATLONG_NAME_CHUNKS] = {10, 12, 4}; #define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4) #define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2) @@ -1173,7 +1180,7 @@ static int write_long_name(struct fat_file* file, entry[FATLONG_ORDER] = numentries-i-1; if (i==0) { /* mark this as last long entry */ - entry[FATLONG_ORDER] |= 0x40; + entry[FATLONG_ORDER] |= FATLONG_LAST_LONG_ENTRY; /* pad name with 0xffff */ for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE; @@ -2323,47 +2330,20 @@ int fat_opendir(IF_MV2(int volume,) 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 i, j; int rc; + int order; 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]; + up to 13 characters. Names can be max 255 chars (not bytes!) long */ + /* The number of long entries in the long name can be retrieve from the first long entry + * because there are stored in reverse order and have an ordinal */ + int nb_longs = 0; + /* The long entries are expected to be in order, so remember the last ordinal */ + int last_long_ord = 0; dir->entrycount = 0; @@ -2371,7 +2351,7 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) { if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector ) { - rc = fat_readwrite(&dir->file, 1, cached_buf, false); + rc = fat_readwrite(&dir->file, 1, dir->sectorcache, false); if (rc == 0) { /* eof */ entry->name[0] = 0; @@ -2386,16 +2366,14 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) } for (i = dir->entry % DIR_ENTRIES_PER_SECTOR; - i < DIR_ENTRIES_PER_SECTOR; i++) - { + i < DIR_ENTRIES_PER_SECTOR; i++) { unsigned int entrypos = i * DIR_ENTRY_SIZE; - firstbyte = cached_buf[entrypos]; + firstbyte = dir->sectorcache[entrypos]; dir->entry++; if (firstbyte == 0xe5) { /* free entry */ - sectoridx = 0; dir->entrycount = 0; continue; } @@ -2410,13 +2388,44 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) dir->entrycount++; /* longname entry? */ - if ( ( cached_buf[entrypos + FATDIR_ATTR] & + if ( ( dir->sectorcache[entrypos + FATDIR_ATTR] & FAT_ATTR_LONG_NAME_MASK ) == FAT_ATTR_LONG_NAME ) { - longarray[longs++] = entrypos + sectoridx; + /* is this entry the first long entry ? (first in order but containing last part) */ + order = dir->sectorcache[entrypos + FATLONG_ORDER] & ~FATLONG_LAST_LONG_ENTRY; + + if(dir->sectorcache[entrypos + FATLONG_ORDER] & FATLONG_LAST_LONG_ENTRY) { + nb_longs = order; + last_long_ord = order; + } + else { + /* check orphan entry */ + if(nb_longs == 0) { + logf("fat warning: orphan LFN entry"); + /* ignore */ + continue; + } + + /* check order */ + if(order != (last_long_ord - 1)) { + logf("fat warning: wrong LFN ordinal"); + /* ignore the whole LFN, will trigger lots of warnings */ + nb_longs = 0; + } + + last_long_ord = order; + } + + /* copy part, reuse [order] for another purpose :) */ + order = (order - 1) * FATLONG_NAME_BYTES_PER_ENTRY; + for(j = 0; j < FATLONG_NAME_CHUNKS; j++) { + memcpy(dir->longname + order, + dir->sectorcache + entrypos + FATLONG_NAME_POS[j], + FATLONG_NAME_SIZE[j]); + order += FATLONG_NAME_SIZE[j]; + } } else { - if ( parse_direntry(entry, - &cached_buf[entrypos]) ) { + if ( parse_direntry(entry, dir->sectorcache + entrypos) ) { /* don't return volume id entry */ if ( (entry->attr & @@ -2425,71 +2434,42 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) continue; /* replace shortname with longname? */ - if ( longs ) { - int j; + /* also check that the long name is complete */ + if ( nb_longs != 0 && last_long_ord == 1) { + /* This should be enough to hold any name segment utf8-encoded */ unsigned char shortname[13]; /* 8+3+dot+\0 */ - /* Add 1 for trailing \0 */ - unsigned char longname_utf8segm[6*4 + 1]; int longname_utf8len = 0; - /* Temporarily store it */ + /* One character at a time, add 1 for trailing \0 */ + unsigned char longname_utf8segm[4 + 1]; + unsigned short ucs; + int segm_utf8len; + /* Temporarily store short name */ strcpy(shortname, entry->name); 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 + /* convert the FAT name to a utf8-encoded one */ + for(j = 0; j < nb_longs * FATLONG_NAME_BYTES_PER_ENTRY; j += 2) + { + ucs = dir->longname[j] | (dir->longname[j + 1] << 8); + if(ucs == 0 || ucs == FAT_LONGNAME_PAD_UCS) 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); + /* ugly */ + segm_utf8len = utf8encode(ucs, longname_utf8segm) - longname_utf8segm; + + /* + 1 for trailing 0 */ + if((longname_utf8len + segm_utf8len + 1) >= FAT_FILENAME_BYTES) + { + longname_utf8len = FAT_FILENAME_BYTES; + break; /* fallback later */ + } else - break; + { + longname_utf8segm[segm_utf8len] = 0; + strcat(entry->name + longname_utf8len, longname_utf8segm); + longname_utf8len += segm_utf8len; + } } /* Does the utf8-encoded name fit into the entry? */ @@ -2504,29 +2484,19 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) unsigned char *utf8; utf8 = iso_decode(shortname, entry->name, -1, strlen(shortname)); - *utf8 = 0; + *utf8 = 0; logf("SN: %s", entry->name); } else { - /* logf("LN: %s", entry->name); - logf("LNLen: %d (%c)", longname_utf8len, - entry->name[0]); */ + logf("LN: %s", entry->name); + logf("LNLen: %d", longname_utf8len); } } 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; } diff --git a/firmware/export/fat.h b/firmware/export/fat.h index 638a659..82d6168 100644 --- a/firmware/export/fat.h +++ b/firmware/export/fat.h @@ -32,6 +32,9 @@ #define SECTOR_SIZE 512 #endif +/* The number of allowed character (not bytes !) in a FAT long name entry */ +#define FAT_MAX_FILENAME_CHARACTERS 255 + /* Number of bytes reserved for a file name (including the trailing \0). Since names are stored in the entry as UTF-8, we won't be able to store all names allowed by FAT. In FAT, a name can have max 255 @@ -85,7 +88,8 @@ struct fat_dir unsigned int entrycount; long sector; struct fat_file file; - unsigned char sectorcache[3][SECTOR_SIZE]; + unsigned char sectorcache[SECTOR_SIZE]; + unsigned char longname[FAT_MAX_FILENAME_CHARACTERS * 2]; /* 2-bytes per character */ }; #ifdef HAVE_HOTSWAP