diff options
-rw-r--r-- | firmware/drivers/fat.c | 211 | ||||
-rw-r--r-- | firmware/export/fat.h | 7 |
2 files changed, 99 insertions, 119 deletions
diff --git a/firmware/drivers/fat.c b/firmware/drivers/fat.c index 13719fa874..ff2bebce92 100644 --- a/firmware/drivers/fat.c +++ b/firmware/drivers/fat.c | |||
@@ -31,6 +31,7 @@ | |||
31 | #include "timefuncs.h" | 31 | #include "timefuncs.h" |
32 | #include "kernel.h" | 32 | #include "kernel.h" |
33 | #include "rbunicode.h" | 33 | #include "rbunicode.h" |
34 | /*#define LOGF_ENABLE*/ | ||
34 | #include "logf.h" | 35 | #include "logf.h" |
35 | 36 | ||
36 | #define BYTES2INT16(array,pos) \ | 37 | #define BYTES2INT16(array,pos) \ |
@@ -110,6 +111,14 @@ | |||
110 | #define FATLONG_ORDER 0 | 111 | #define FATLONG_ORDER 0 |
111 | #define FATLONG_TYPE 12 | 112 | #define FATLONG_TYPE 12 |
112 | #define FATLONG_CHKSUM 13 | 113 | #define FATLONG_CHKSUM 13 |
114 | #define FATLONG_LAST_LONG_ENTRY 0x40 | ||
115 | #define FATLONG_NAME_BYTES_PER_ENTRY 26 | ||
116 | /* at most 20 LFN entries, keep coherent with fat_dir->longname size ! */ | ||
117 | #define FATLONG_MAX_ORDER 20 | ||
118 | |||
119 | #define FATLONG_NAME_CHUNKS 3 | ||
120 | static unsigned char FATLONG_NAME_POS[FATLONG_NAME_CHUNKS] = {1, 14, 28}; | ||
121 | static unsigned char FATLONG_NAME_SIZE[FATLONG_NAME_CHUNKS] = {10, 12, 4}; | ||
113 | 122 | ||
114 | #define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4) | 123 | #define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4) |
115 | #define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2) | 124 | #define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2) |
@@ -1173,7 +1182,7 @@ static int write_long_name(struct fat_file* file, | |||
1173 | entry[FATLONG_ORDER] = numentries-i-1; | 1182 | entry[FATLONG_ORDER] = numentries-i-1; |
1174 | if (i==0) { | 1183 | if (i==0) { |
1175 | /* mark this as last long entry */ | 1184 | /* mark this as last long entry */ |
1176 | entry[FATLONG_ORDER] |= 0x40; | 1185 | entry[FATLONG_ORDER] |= FATLONG_LAST_LONG_ENTRY; |
1177 | 1186 | ||
1178 | /* pad name with 0xffff */ | 1187 | /* pad name with 0xffff */ |
1179 | for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE; | 1188 | for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE; |
@@ -2323,47 +2332,20 @@ int fat_opendir(IF_MV2(int volume,) | |||
2323 | return 0; | 2332 | return 0; |
2324 | } | 2333 | } |
2325 | 2334 | ||
2326 | /* Copies a segment of long file name (UTF-16 LE encoded) to the | ||
2327 | * destination buffer (UTF-8 encoded). Copying is stopped when | ||
2328 | * either 0x0000 or 0xffff (FAT pad char) is encountered. | ||
2329 | * Trailing \0 is also appended at the end of the UTF8-encoded | ||
2330 | * string. | ||
2331 | * | ||
2332 | * utf16src utf16 (little endian) segment to copy | ||
2333 | * utf16count max number of the utf16-characters to copy | ||
2334 | * utf8dst where to write UTF8-encoded string to | ||
2335 | * | ||
2336 | * returns the number of UTF-16 characters actually copied | ||
2337 | */ | ||
2338 | static int fat_copy_long_name_segment(unsigned char *utf16src, | ||
2339 | int utf16count, unsigned char *utf8dst) { | ||
2340 | int cnt = 0; | ||
2341 | while ((utf16count--) > 0) { | ||
2342 | unsigned short ucs = utf16src[0] | (utf16src[1] << 8); | ||
2343 | if ((ucs == 0) || (ucs == FAT_LONGNAME_PAD_UCS)) { | ||
2344 | break; | ||
2345 | } | ||
2346 | utf8dst = utf8encode(ucs, utf8dst); | ||
2347 | utf16src += 2; | ||
2348 | cnt++; | ||
2349 | } | ||
2350 | *utf8dst = 0; | ||
2351 | return cnt; | ||
2352 | } | ||
2353 | |||
2354 | int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) | 2335 | int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) |
2355 | { | 2336 | { |
2356 | bool done = false; | 2337 | bool done = false; |
2357 | int i; | 2338 | int i, j; |
2358 | int rc; | 2339 | int rc; |
2340 | int order; | ||
2359 | unsigned char firstbyte; | 2341 | unsigned char firstbyte; |
2360 | /* Long file names are stored in special entries. Each entry holds | 2342 | /* Long file names are stored in special entries. Each entry holds |
2361 | up to 13 characters. Names can be max 255 chars (not bytes!) long | 2343 | up to 13 characters. Names can be max 255 chars (not bytes!) long */ |
2362 | hence max 20 entries are required. */ | 2344 | /* The number of long entries in the long name can be retrieve from the first |
2363 | int longarray[20]; | 2345 | * long entry because there are stored in reverse order and have an ordinal */ |
2364 | int longs=0; | 2346 | int nb_longs = 0; |
2365 | int sectoridx=0; | 2347 | /* The long entries are expected to be in order, so remember the last ordinal */ |
2366 | unsigned char* cached_buf = dir->sectorcache[0]; | 2348 | int last_long_ord = 0; |
2367 | 2349 | ||
2368 | dir->entrycount = 0; | 2350 | dir->entrycount = 0; |
2369 | 2351 | ||
@@ -2371,7 +2353,7 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) | |||
2371 | { | 2353 | { |
2372 | if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector ) | 2354 | if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector ) |
2373 | { | 2355 | { |
2374 | rc = fat_readwrite(&dir->file, 1, cached_buf, false); | 2356 | rc = fat_readwrite(&dir->file, 1, dir->sectorcache, false); |
2375 | if (rc == 0) { | 2357 | if (rc == 0) { |
2376 | /* eof */ | 2358 | /* eof */ |
2377 | entry->name[0] = 0; | 2359 | entry->name[0] = 0; |
@@ -2386,16 +2368,14 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) | |||
2386 | } | 2368 | } |
2387 | 2369 | ||
2388 | for (i = dir->entry % DIR_ENTRIES_PER_SECTOR; | 2370 | for (i = dir->entry % DIR_ENTRIES_PER_SECTOR; |
2389 | i < DIR_ENTRIES_PER_SECTOR; i++) | 2371 | i < DIR_ENTRIES_PER_SECTOR; i++) { |
2390 | { | ||
2391 | unsigned int entrypos = i * DIR_ENTRY_SIZE; | 2372 | unsigned int entrypos = i * DIR_ENTRY_SIZE; |
2392 | 2373 | ||
2393 | firstbyte = cached_buf[entrypos]; | 2374 | firstbyte = dir->sectorcache[entrypos]; |
2394 | dir->entry++; | 2375 | dir->entry++; |
2395 | 2376 | ||
2396 | if (firstbyte == 0xe5) { | 2377 | if (firstbyte == 0xe5) { |
2397 | /* free entry */ | 2378 | /* free entry */ |
2398 | sectoridx = 0; | ||
2399 | dir->entrycount = 0; | 2379 | dir->entrycount = 0; |
2400 | continue; | 2380 | continue; |
2401 | } | 2381 | } |
@@ -2409,14 +2389,48 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) | |||
2409 | 2389 | ||
2410 | dir->entrycount++; | 2390 | dir->entrycount++; |
2411 | 2391 | ||
2412 | /* longname entry? */ | 2392 | /* LFN entry? */ |
2413 | if ( ( cached_buf[entrypos + FATDIR_ATTR] & | 2393 | if ( ( dir->sectorcache[entrypos + FATDIR_ATTR] & |
2414 | FAT_ATTR_LONG_NAME_MASK ) == FAT_ATTR_LONG_NAME ) { | 2394 | FAT_ATTR_LONG_NAME_MASK ) == FAT_ATTR_LONG_NAME ) { |
2415 | longarray[longs++] = entrypos + sectoridx; | 2395 | /* extract ordinal */ |
2396 | order = dir->sectorcache[entrypos + FATLONG_ORDER] & ~FATLONG_LAST_LONG_ENTRY; | ||
2397 | /* is this entry the first long entry ? (first in order but containing last part) */ | ||
2398 | if (dir->sectorcache[entrypos + FATLONG_ORDER] & FATLONG_LAST_LONG_ENTRY) { | ||
2399 | /* check that order is not too big ! (and non-zero) */ | ||
2400 | if(order <= 0 || order > FATLONG_MAX_ORDER) | ||
2401 | continue; /* ignore the whole LFN, will trigger lots of warnings */ | ||
2402 | nb_longs = order; | ||
2403 | last_long_ord = order; | ||
2404 | } | ||
2405 | else { | ||
2406 | /* check orphan entry */ | ||
2407 | if (nb_longs == 0) { | ||
2408 | logf("fat warning: orphan LFN entry"); | ||
2409 | /* ignore */ | ||
2410 | continue; | ||
2411 | } | ||
2412 | |||
2413 | /* check order */ | ||
2414 | if (order != (last_long_ord - 1)) { | ||
2415 | logf("fat warning: wrong LFN ordinal"); | ||
2416 | /* ignore the whole LFN, will trigger lots of warnings */ | ||
2417 | nb_longs = 0; | ||
2418 | } | ||
2419 | |||
2420 | last_long_ord = order; | ||
2421 | } | ||
2422 | |||
2423 | /* copy part, reuse [order] for another purpose :) */ | ||
2424 | order = (order - 1) * FATLONG_NAME_BYTES_PER_ENTRY; | ||
2425 | for(j = 0; j < FATLONG_NAME_CHUNKS; j++) { | ||
2426 | memcpy(dir->longname + order, | ||
2427 | dir->sectorcache + entrypos + FATLONG_NAME_POS[j], | ||
2428 | FATLONG_NAME_SIZE[j]); | ||
2429 | order += FATLONG_NAME_SIZE[j]; | ||
2430 | } | ||
2416 | } | 2431 | } |
2417 | else { | 2432 | else { |
2418 | if ( parse_direntry(entry, | 2433 | if ( parse_direntry(entry, dir->sectorcache + entrypos) ) { |
2419 | &cached_buf[entrypos]) ) { | ||
2420 | 2434 | ||
2421 | /* don't return volume id entry */ | 2435 | /* don't return volume id entry */ |
2422 | if ( (entry->attr & | 2436 | if ( (entry->attr & |
@@ -2425,74 +2439,45 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) | |||
2425 | continue; | 2439 | continue; |
2426 | 2440 | ||
2427 | /* replace shortname with longname? */ | 2441 | /* replace shortname with longname? */ |
2428 | if ( longs ) { | 2442 | /* check that the long name is complete */ |
2429 | int j; | 2443 | if (nb_longs != 0 && last_long_ord == 1) { |
2430 | /* This should be enough to hold any name segment | 2444 | /* hold a copy of the shortname in case the long one is too long */ |
2431 | utf8-encoded */ | ||
2432 | unsigned char shortname[13]; /* 8+3+dot+\0 */ | 2445 | unsigned char shortname[13]; /* 8+3+dot+\0 */ |
2433 | /* Add 1 for trailing \0 */ | ||
2434 | unsigned char longname_utf8segm[6*4 + 1]; | ||
2435 | int longname_utf8len = 0; | 2446 | int longname_utf8len = 0; |
2436 | /* Temporarily store it */ | 2447 | /* One character at a time, add 1 for trailing \0, 4 is the maximum size |
2448 | * of a UTF8 encoded character in rockbox */ | ||
2449 | unsigned char longname_utf8segm[4 + 1]; | ||
2450 | unsigned short ucs; | ||
2451 | int segm_utf8len; | ||
2452 | /* Temporarily store short name */ | ||
2437 | strcpy(shortname, entry->name); | 2453 | strcpy(shortname, entry->name); |
2438 | entry->name[0] = 0; | 2454 | entry->name[0] = 0; |
2439 | 2455 | ||
2440 | /* iterate backwards through the dir entries */ | 2456 | /* Convert the FAT name to a utf8-encoded one. |
2441 | for (j=longs-1; j>=0; j--) { | 2457 | * The name is not necessary NUL-terminated ! */ |
2442 | unsigned char* ptr = cached_buf; | 2458 | for (j = 0; j < nb_longs * FATLONG_NAME_BYTES_PER_ENTRY; j += 2) { |
2443 | int index = longarray[j]; | 2459 | ucs = dir->longname[j] | (dir->longname[j + 1] << 8); |
2444 | /* current or cached sector? */ | 2460 | if(ucs == 0 || ucs == FAT_LONGNAME_PAD_UCS) |
2445 | if ( sectoridx >= SECTOR_SIZE ) { | ||
2446 | if ( sectoridx >= SECTOR_SIZE*2 ) { | ||
2447 | if ( ( index >= SECTOR_SIZE ) && | ||
2448 | ( index < SECTOR_SIZE*2 )) | ||
2449 | ptr = dir->sectorcache[1]; | ||
2450 | else | ||
2451 | ptr = dir->sectorcache[2]; | ||
2452 | } | ||
2453 | else { | ||
2454 | if ( index < SECTOR_SIZE ) | ||
2455 | ptr = dir->sectorcache[1]; | ||
2456 | } | ||
2457 | |||
2458 | index &= SECTOR_SIZE-1; | ||
2459 | } | ||
2460 | |||
2461 | /* Try to append each segment of the long name. | ||
2462 | Check if we'd exceed the buffer. | ||
2463 | Also check for FAT padding characters 0xFFFF. */ | ||
2464 | if (fat_copy_long_name_segment(ptr + index + 1, 5, | ||
2465 | longname_utf8segm) == 0) break; | ||
2466 | /* logf("SG: %s, EN: %s", longname_utf8segm, | ||
2467 | entry->name); */ | ||
2468 | longname_utf8len += strlen(longname_utf8segm); | ||
2469 | if (longname_utf8len < FAT_FILENAME_BYTES) | ||
2470 | strcat(entry->name, longname_utf8segm); | ||
2471 | else | ||
2472 | break; | ||
2473 | |||
2474 | if (fat_copy_long_name_segment(ptr + index + 14, 6, | ||
2475 | longname_utf8segm) == 0) break; | ||
2476 | /* logf("SG: %s, EN: %s", longname_utf8segm, | ||
2477 | entry->name); */ | ||
2478 | longname_utf8len += strlen(longname_utf8segm); | ||
2479 | if (longname_utf8len < FAT_FILENAME_BYTES) | ||
2480 | strcat(entry->name, longname_utf8segm); | ||
2481 | else | ||
2482 | break; | ||
2483 | |||
2484 | if (fat_copy_long_name_segment(ptr + index + 28, 2, | ||
2485 | longname_utf8segm) == 0) break; | ||
2486 | /* logf("SG: %s, EN: %s", longname_utf8segm, | ||
2487 | entry->name); */ | ||
2488 | longname_utf8len += strlen(longname_utf8segm); | ||
2489 | if (longname_utf8len < FAT_FILENAME_BYTES) | ||
2490 | strcat(entry->name, longname_utf8segm); | ||
2491 | else | ||
2492 | break; | 2461 | break; |
2462 | /* utf8encode will return a pointer after the converted | ||
2463 | * string, subtract the pointer to the start to get the length of it */ | ||
2464 | segm_utf8len = utf8encode(ucs, longname_utf8segm) - longname_utf8segm; | ||
2465 | |||
2466 | /* warn the trailing zero ! (FAT_FILENAME_BYTES includes it) */ | ||
2467 | if (longname_utf8len + segm_utf8len >= FAT_FILENAME_BYTES) { | ||
2468 | /* force use of short name */ | ||
2469 | longname_utf8len = FAT_FILENAME_BYTES + 1; | ||
2470 | break; /* fallback later */ | ||
2471 | } | ||
2472 | else { | ||
2473 | longname_utf8segm[segm_utf8len] = 0; | ||
2474 | strcat(entry->name + longname_utf8len, longname_utf8segm); | ||
2475 | longname_utf8len += segm_utf8len; | ||
2476 | } | ||
2493 | } | 2477 | } |
2494 | 2478 | ||
2495 | /* Does the utf8-encoded name fit into the entry? */ | 2479 | /* Does the utf8-encoded name fit into the entry? */ |
2480 | /* warn the trailing zero ! (FAT_FILENAME_BYTES includes it) */ | ||
2496 | if (longname_utf8len >= FAT_FILENAME_BYTES) { | 2481 | if (longname_utf8len >= FAT_FILENAME_BYTES) { |
2497 | /* Take the short DOS name. Need to utf8-encode it | 2482 | /* Take the short DOS name. Need to utf8-encode it |
2498 | since it may contain chars from the upper half of | 2483 | since it may contain chars from the upper half of |
@@ -2504,29 +2489,19 @@ int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) | |||
2504 | unsigned char *utf8; | 2489 | unsigned char *utf8; |
2505 | utf8 = iso_decode(shortname, entry->name, -1, | 2490 | utf8 = iso_decode(shortname, entry->name, -1, |
2506 | strlen(shortname)); | 2491 | strlen(shortname)); |
2507 | *utf8 = 0; | 2492 | *utf8 = 0; |
2508 | logf("SN: %s", entry->name); | 2493 | logf("SN: %s", entry->name); |
2509 | } else { | 2494 | } else { |
2510 | /* logf("LN: %s", entry->name); | 2495 | logf("LN: %s", entry->name); |
2511 | logf("LNLen: %d (%c)", longname_utf8len, | 2496 | logf("LNLen: %d", longname_utf8len); |
2512 | entry->name[0]); */ | ||
2513 | } | 2497 | } |
2514 | } | 2498 | } |
2515 | done = true; | 2499 | done = true; |
2516 | sectoridx = 0; | ||
2517 | i++; | 2500 | i++; |
2518 | break; | 2501 | break; |
2519 | } | 2502 | } |
2520 | } | 2503 | } |
2521 | } | 2504 | } |
2522 | |||
2523 | /* save this sector, for longname use */ | ||
2524 | if ( sectoridx ) | ||
2525 | memcpy( dir->sectorcache[2], dir->sectorcache[0], SECTOR_SIZE ); | ||
2526 | else | ||
2527 | memcpy( dir->sectorcache[1], dir->sectorcache[0], SECTOR_SIZE ); | ||
2528 | sectoridx += SECTOR_SIZE; | ||
2529 | |||
2530 | } | 2505 | } |
2531 | return 0; | 2506 | return 0; |
2532 | } | 2507 | } |
diff --git a/firmware/export/fat.h b/firmware/export/fat.h index 638a659f7a..5df5dc4b91 100644 --- a/firmware/export/fat.h +++ b/firmware/export/fat.h | |||
@@ -85,7 +85,12 @@ struct fat_dir | |||
85 | unsigned int entrycount; | 85 | unsigned int entrycount; |
86 | long sector; | 86 | long sector; |
87 | struct fat_file file; | 87 | struct fat_file file; |
88 | unsigned char sectorcache[3][SECTOR_SIZE]; | 88 | unsigned char sectorcache[SECTOR_SIZE]; |
89 | /* There are 2-bytes per characters. We don't want to bother too much, as LFN entries are | ||
90 | * at much 255 characters longs, that's at most 20 LFN entries. Each entry hold at most | ||
91 | * 13 characters, that a total of 260 characters. So we keep a buffer of that size. | ||
92 | * Keep coherent with fat.c code. */ | ||
93 | unsigned char longname[260 * 2]; | ||
89 | }; | 94 | }; |
90 | 95 | ||
91 | #ifdef HAVE_HOTSWAP | 96 | #ifdef HAVE_HOTSWAP |