dfe22bc9e4caef33935db91a25bdd76bbf2ff119
[mw/micromonitor-lm32.git] / umon_main / target / common / dosfs.c
1 /*
2         DOSFS Embedded FAT-Compatible Filesystem
3         (C) 2005 Lewin A.R.W. Edwards (sysadm@zws.com)
4
5         You are permitted to modify and/or use this code in your own projects without
6         payment of royalty, regardless of the license(s) you choose for those projects.
7
8         You cannot re-copyright or restrict use of the code as released by Lewin Edwards.
9
10         Modifications: Aug 2006 (esutter/ghenderson)
11                 Initial integration into uMon source tree.
12                 Eliminated a few precedence warnings by adding parenthesis.
13                 Eliminated one "variable unused" warning.
14         Modifications: Sept 2006 (ghenderson)
15                 Added DFS_DirToCanonical.
16                 All structures in dosfs.h have been defined with the
17                         __attribute__ ((__packed__)).
18                 Added macros in dosfs.h to help in formating time,
19                         date, and filesize.
20         Modifications: Sept 25, 2006 (esutter)
21                 Updated to dosfs-1.02 (dated Sept 16, 2006).
22         Modifications: Oct 2, 2006 (esutter)
23                 Updated to dosfs-1.03 (dated Sept 30, 2006).
24                 Incorporate fix from Graham Henderson (see USE_GMHFIX)
25 */
26 #include "config.h"
27
28 #if INCLUDE_FATFS
29 #include <string.h>
30 #include <stdlib.h>
31
32 #include "dosfs.h"
33
34 /*
35         Get starting sector# of specified partition on drive #unit
36         NOTE: This code ASSUMES an MBR on the disk.
37         scratchsector should point to a SECTOR_SIZE scratch area
38         Returns 0xffffffff for any error.
39         If pactive is non-NULL, this function also returns the partition active flag.
40         If pptype is non-NULL, this function also returns the partition type.
41         If psize is non-NULL, this function also returns the partition size.
42 */
43 uint32_t DFS_GetPtnStart(uint8_t unit, uint8_t *scratchsector, uint8_t pnum, uint8_t *pactive, uint8_t *pptype, uint32_t *psize)
44 {
45         uint32_t result;
46         PMBR mbr = (PMBR) scratchsector;
47
48         // DOS ptable supports maximum 4 partitions
49         if (pnum > 3)
50                 return DFS_ERRMISC;
51
52         // Read MBR from target media
53         if (DFS_ReadSector(unit,scratchsector,0,1)) {
54                 return DFS_ERRMISC;
55         }
56
57         result = (uint32_t) mbr->ptable[pnum].start_0 |
58           (((uint32_t) mbr->ptable[pnum].start_1) << 8) |
59           (((uint32_t) mbr->ptable[pnum].start_2) << 16) |
60           (((uint32_t) mbr->ptable[pnum].start_3) << 24);
61
62         if (pactive)
63                 *pactive = mbr->ptable[pnum].active;
64
65         if (pptype)
66                 *pptype = mbr->ptable[pnum].type;
67
68         if (psize)
69                 *psize = (uint32_t) mbr->ptable[pnum].size_0 |
70                   (((uint32_t) mbr->ptable[pnum].size_1) << 8) |
71                   (((uint32_t) mbr->ptable[pnum].size_2) << 16) |
72                   (((uint32_t) mbr->ptable[pnum].size_3) << 24);
73
74         return result;
75 }
76
77
78 /*
79         Retrieve volume info from BPB and store it in a VOLINFO structure
80         You must provide the unit and starting sector of the filesystem, and
81         a pointer to a sector buffer for scratch
82         Attempts to read BPB and glean information about the FS from that.
83         Returns 0 OK, nonzero for any error.
84 */
85 uint32_t DFS_GetVolInfo(uint8_t unit, uint8_t *scratchsector, uint32_t startsector, PVOLINFO volinfo)
86 {
87         PLBR lbr = (PLBR) scratchsector;
88         volinfo->unit = unit;
89         volinfo->startsector = startsector;
90
91         if(DFS_ReadSector(unit,scratchsector,startsector,1))
92                 return DFS_ERRMISC;
93
94 // tag: OEMID, refer dosfs.h
95 //      strncpy(volinfo->oemid, lbr->oemid, 8);
96 //      volinfo->oemid[8] = 0;
97
98         volinfo->secperclus = lbr->bpb.secperclus;
99         volinfo->reservedsecs = (uint16_t) lbr->bpb.reserved_l |
100                   (((uint16_t) lbr->bpb.reserved_h) << 8);
101
102         volinfo->numsecs =  (uint16_t) lbr->bpb.sectors_s_l |
103                   (((uint16_t) lbr->bpb.sectors_s_h) << 8);
104
105         if (!volinfo->numsecs)
106                 volinfo->numsecs = (uint32_t) lbr->bpb.sectors_l_0 |
107                   (((uint32_t) lbr->bpb.sectors_l_1) << 8) |
108                   (((uint32_t) lbr->bpb.sectors_l_2) << 16) |
109                   (((uint32_t) lbr->bpb.sectors_l_3) << 24);
110
111         // If secperfat is 0, we must be in a FAT32 volume; get secperfat
112         // from the FAT32 EBPB. The volume label and system ID string are also
113         // in different locations for FAT12/16 vs FAT32.
114         volinfo->secperfat =  (uint16_t) lbr->bpb.secperfat_l |
115                   (((uint16_t) lbr->bpb.secperfat_h) << 8);
116         if (!volinfo->secperfat) {
117                 volinfo->secperfat = (uint32_t) lbr->ebpb.ebpb32.fatsize_0 |
118                   (((uint32_t) lbr->ebpb.ebpb32.fatsize_1) << 8) |
119                   (((uint32_t) lbr->ebpb.ebpb32.fatsize_2) << 16) |
120                   (((uint32_t) lbr->ebpb.ebpb32.fatsize_3) << 24);
121
122                 memcpy(volinfo->label, lbr->ebpb.ebpb32.label, 11);
123                 volinfo->label[11] = 0;
124         
125 // tag: OEMID, refer dosfs.h
126 //              memcpy(volinfo->system, lbr->ebpb.ebpb32.system, 8);
127 //              volinfo->system[8] = 0; 
128         }
129         else {
130                 memcpy(volinfo->label, lbr->ebpb.ebpb.label, 11);
131                 volinfo->label[11] = 0;
132         
133 // tag: OEMID, refer dosfs.h
134 //              memcpy(volinfo->system, lbr->ebpb.ebpb.system, 8);
135 //              volinfo->system[8] = 0; 
136         }
137
138         // note: if rootentries is 0, we must be in a FAT32 volume.
139         volinfo->rootentries =  (uint16_t) lbr->bpb.rootentries_l |
140                   (((uint16_t) lbr->bpb.rootentries_h) << 8);
141
142         // after extracting raw info we perform some useful precalculations
143         volinfo->fat1 = startsector + volinfo->reservedsecs;
144
145         // The calculation below is designed to round up the root directory size for FAT12/16
146         // and to simply ignore the root directory for FAT32, since it's a normal, expandable
147         // file in that situation.
148         if (volinfo->rootentries) {
149                 volinfo->rootdir = volinfo->fat1 + (volinfo->secperfat * 2);
150                 volinfo->dataarea = volinfo->rootdir + (((volinfo->rootentries * 32) + (SECTOR_SIZE - 1)) / SECTOR_SIZE);
151         }
152         else {
153                 volinfo->dataarea = volinfo->fat1 + (volinfo->secperfat * 2);
154                 volinfo->rootdir = (uint32_t) lbr->ebpb.ebpb32.root_0 |
155                   (((uint32_t) lbr->ebpb.ebpb32.root_1) << 8) |
156                   (((uint32_t) lbr->ebpb.ebpb32.root_2) << 16) |
157                   (((uint32_t) lbr->ebpb.ebpb32.root_3) << 24);
158         }
159
160         // Calculate number of clusters in data area and infer FAT type from this information.
161         volinfo->numclusters = (volinfo->numsecs - volinfo->dataarea) / volinfo->secperclus;
162         if (volinfo->numclusters < 4085)
163                 volinfo->filesystem = FAT12;
164         else if (volinfo->numclusters < 65525)
165                 volinfo->filesystem = FAT16;
166         else
167                 volinfo->filesystem = FAT32;
168
169         return DFS_OK;
170 }
171
172 /*
173         Fetch FAT entry for specified cluster number
174         You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
175         Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired
176         FAT entry.
177         scratchcache should point to a UINT32. This variable caches the physical sector number
178         last read into the scratch buffer for performance enhancement reasons.
179 */
180 uint32_t DFS_GetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster)
181 {
182         uint32_t offset, sector, result;
183
184         if (volinfo->filesystem == FAT12) {
185                 offset = cluster + (cluster / 2);
186         }
187         else if (volinfo->filesystem == FAT16) {
188                 offset = cluster * 2;
189         }
190         else if (volinfo->filesystem == FAT32) {
191                 offset = cluster * 4;
192         }
193         else
194                 return 0x0ffffff7;      // FAT32 bad cluster    
195
196         // at this point, offset is the BYTE offset of the desired sector from the start
197         // of the FAT. Calculate the physical sector containing this FAT entry.
198         sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1;
199
200         // If this is not the same sector we last read, then read it into RAM
201         if (sector != *scratchcache) {
202                 if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
203                         // avoid anyone assuming that this cache value is still valid, which
204                         // might cause disk corruption
205                         *scratchcache = 0;
206                         return 0x0ffffff7;      // FAT32 bad cluster    
207                 }
208                 *scratchcache = sector;
209         }
210
211         // At this point, we "merely" need to extract the relevant entry.
212         // This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
213         // may span a sector boundary. The normal way around this is always to read two
214         // FAT sectors, but that luxury is (by design intent) unavailable to DOSFS.
215         offset = ldiv(offset, SECTOR_SIZE).rem;
216
217         if (volinfo->filesystem == FAT12) {
218                 // Special case for sector boundary - Store last byte of current sector.
219                 // Then read in the next sector and put the first byte of that sector into
220                 // the high byte of result.
221                 if (offset == SECTOR_SIZE - 1) {
222                         result = (uint32_t) scratch[offset];
223                         sector++;
224                         if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
225                                 // avoid anyone assuming that this cache value is still valid, which
226                                 // might cause disk corruption
227                                 *scratchcache = 0;
228                                 return 0x0ffffff7;      // FAT32 bad cluster    
229                         }
230                         *scratchcache = sector;
231                         // Thanks to Claudio Leonel for pointing out this missing line.
232                         result |= ((uint32_t) scratch[0]) << 8;
233                 }
234                 else {
235                         result = (uint32_t) scratch[offset] |
236                           ((uint32_t) scratch[offset+1]) << 8;
237                 }
238                 if (cluster & 1)
239                         result = result >> 4;
240                 else
241                         result = result & 0xfff;
242         }
243         else if (volinfo->filesystem == FAT16) {
244                 result = (uint32_t) scratch[offset] |
245                   ((uint32_t) scratch[offset+1]) << 8;
246         }
247         else if (volinfo->filesystem == FAT32) {
248                 result = ((uint32_t) scratch[offset] |
249                   ((uint32_t) scratch[offset+1]) << 8 |
250                   ((uint32_t) scratch[offset+2]) << 16 |
251                   ((uint32_t) scratch[offset+3]) << 24) & 0x0fffffff;
252         }
253         else
254                 result = 0x0ffffff7;    // FAT32 bad cluster    
255         return result;
256 }
257
258
259 /*
260         Set FAT entry for specified cluster number
261         You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
262         Returns DFS_ERRMISC for any error, otherwise DFS_OK
263         scratchcache should point to a UINT32. This variable caches the physical sector number
264         last read into the scratch buffer for performance enhancement reasons.
265
266         NOTE: This code is HIGHLY WRITE-INEFFICIENT, particularly for flash media. Considerable
267         performance gains can be realized by caching the sector. However this is difficult to
268         achieve on FAT12 without requiring 2 sector buffers of scratch space, and it is a design
269         requirement of this code to operate on a single 512-byte scratch.
270
271         If you are operating DOSFS over flash, you are strongly advised to implement a writeback
272         cache in your physical I/O driver. This will speed up your code significantly and will
273         also conserve power and flash write life.
274 */
275 uint32_t DFS_SetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster, uint32_t new_contents)
276 {
277         uint32_t offset, sector, result;
278         if (volinfo->filesystem == FAT12) {
279                 offset = cluster + (cluster / 2);
280                 new_contents &=0xfff;
281         }
282         else if (volinfo->filesystem == FAT16) {
283                 offset = cluster * 2;
284                 new_contents &=0xffff;
285         }
286         else if (volinfo->filesystem == FAT32) {
287                 offset = cluster * 4;
288                 new_contents &=0x0fffffff;      // FAT32 is really "FAT28"
289         }
290         else
291                 return DFS_ERRMISC;     
292
293         // at this point, offset is the BYTE offset of the desired sector from the start
294         // of the FAT. Calculate the physical sector containing this FAT entry.
295         sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1;
296
297         // If this is not the same sector we last read, then read it into RAM
298         if (sector != *scratchcache) {
299                 if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
300                         // avoid anyone assuming that this cache value is still valid, which
301                         // might cause disk corruption
302                         *scratchcache = 0;
303                         return DFS_ERRMISC;
304                 }
305                 *scratchcache = sector;
306         }
307
308         // At this point, we "merely" need to extract the relevant entry.
309         // This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
310         // may span a sector boundary. The normal way around this is always to read two
311         // FAT sectors, but that luxury is (by design intent) unavailable to DOSFS.
312         offset = ldiv(offset, SECTOR_SIZE).rem;
313
314         if (volinfo->filesystem == FAT12) {
315
316                 // If this is an odd cluster, pre-shift the desired new contents 4 bits to
317                 // make the calculations below simpler
318                 if (cluster & 1)
319                         new_contents = new_contents << 4;
320
321                 // Special case for sector boundary
322                 if (offset == SECTOR_SIZE - 1) {
323
324                         // Odd cluster: High 12 bits being set
325                         if (cluster & 1) {
326                                 scratch[offset] = (scratch[offset] & 0x0f) | (new_contents & 0xf0);
327                         }
328                         // Even cluster: Low 12 bits being set
329                         else {
330                                 scratch[offset] = new_contents & 0xff;
331                         }
332                         result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
333                         // mirror the FAT into copy 2
334                         if (DFS_OK == result)
335                                 result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
336
337                         // If we wrote that sector OK, then read in the subsequent sector
338                         // and poke the first byte with the remainder of this FAT entry.
339                         if (DFS_OK == result) {
340 #if 0
341                                 /* ELSNOTE:
342                                  * Original line here is illegal (IMHO).  Didn't notice this
343                                  * till I used a version of GCC that warned me about it.
344                                  * I changed this to eliminate the warning, and *hopefully*
345                                  * generate the correct code.  Have not been able to test this.
346                                  */
347                                 *scratchcache++;
348 #else
349                                 (*scratchcache)++;
350 #endif
351                                 result = DFS_ReadSector(volinfo->unit, scratch, *scratchcache, 1);
352                                 if (DFS_OK == result) {
353                                         // Odd cluster: High 12 bits being set
354                                         if (cluster & 1) {
355                                                 scratch[0] = new_contents & 0xff00;
356                                         }
357                                         // Even cluster: Low 12 bits being set
358                                         else {
359                                                 scratch[0] = (scratch[0] & 0xf0) | (new_contents & 0x0f);
360                                         }
361                                         result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
362                                         // mirror the FAT into copy 2
363                                         if (DFS_OK == result)
364                                                 result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
365                                 }
366                                 else {
367                                         // avoid anyone assuming that this cache value is still valid, which
368                                         // might cause disk corruption
369                                         *scratchcache = 0;
370                                 }
371                         }
372                 } // if (offset == SECTOR_SIZE - 1)
373
374                 // Not a sector boundary. But we still have to worry about if it's an odd
375                 // or even cluster number.
376                 else {
377                         // Odd cluster: High 12 bits being set
378                         if (cluster & 1) {
379                                 scratch[offset] = (scratch[offset] & 0x0f) | (new_contents & 0xf0);
380                                 scratch[offset+1] = new_contents & 0xff00;
381                         }
382                         // Even cluster: Low 12 bits being set
383                         else {
384                                 scratch[offset] = new_contents & 0xff;
385                                 scratch[offset+1] = (scratch[offset+1] & 0xf0) | (new_contents & 0x0f);
386                         }
387                         result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
388                         // mirror the FAT into copy 2
389                         if (DFS_OK == result)
390                                 result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
391                 }
392         }
393         else if (volinfo->filesystem == FAT16) {
394                 scratch[offset] = (new_contents & 0xff);
395                 scratch[offset+1] = (new_contents & 0xff00) >> 8;
396                 result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
397                 // mirror the FAT into copy 2
398                 if (DFS_OK == result)
399                         result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
400         }
401         else if (volinfo->filesystem == FAT32) {
402                 scratch[offset] = (new_contents & 0xff);
403                 scratch[offset+1] = (new_contents & 0xff00) >> 8;
404                 scratch[offset+2] = (new_contents & 0xff0000) >> 16;
405                 scratch[offset+3] = (scratch[offset+3] & 0xf0) | ((new_contents & 0x0f000000) >> 24);
406                 // Note well from the above: Per Microsoft's guidelines we preserve the upper
407                 // 4 bits of the FAT32 cluster value. It's unclear what these bits will be used
408                 // for; in every example I've encountered they are always zero.
409                 result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
410                 // mirror the FAT into copy 2
411                 if (DFS_OK == result)
412                         result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
413         }
414         else
415                 result = DFS_ERRMISC;
416
417         return result;
418 }
419
420 /*
421         Convert a filename element from canonical (8.3) to directory entry (11) form
422         src must point to the first non-separator character.
423         dest must point to a 12-byte buffer.
424 */
425 uint8_t *DFS_CanonicalToDir(uint8_t *dest, uint8_t *src)
426 {
427         uint8_t *destptr = dest;
428
429         memset(dest, ' ', 11);
430         dest[11] = 0;
431
432         while (*src && (*src != DIR_SEPARATOR) && (destptr - dest < 11)) {
433                 if (*src >= 'a' && *src <='z') {
434                         *destptr++ = (*src - 'a') + 'A';
435                         src++;
436                 }
437                 else if (*src == '.') {
438                         src++;
439                         destptr = dest + 8;
440                 }
441                 else {
442                         *destptr++ = *src++;
443                 }
444         }
445
446         return dest;
447 }
448
449 /*
450         Find the first unused FAT entry
451         You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
452         Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired
453         FAT entry.
454         Returns FAT32 bad_sector (0x0ffffff7) if there is no free cluster available
455 */
456 uint32_t DFS_GetFreeFAT(PVOLINFO volinfo, uint8_t *scratch)
457 {
458         uint32_t i, result = 0xffffffff, scratchcache = 0;
459         
460         // Search starts at cluster 2, which is the first usable cluster
461         // NOTE: This search can't terminate at a bad cluster, because there might
462         // legitimately be bad clusters on the disk.
463         for (i=2; i < volinfo->numclusters; i++) {
464                 result = DFS_GetFAT(volinfo, scratch, &scratchcache, i);
465                 if (!result) {
466                         return i;
467                 }
468         }
469         return 0x0ffffff7;              // Can't find a free cluster
470 }
471
472
473 /*
474         Open a directory for enumeration by DFS_GetNextDirEnt
475         You must supply a populated VOLINFO (see DFS_GetVolInfo)
476         The empty string or a string containing only the directory separator are
477         considered to be the root directory.
478         Returns 0 OK, nonzero for any error.
479 */
480 uint32_t DFS_OpenDir(PVOLINFO volinfo, uint8_t *dirname, PDIRINFO dirinfo)
481 {
482         // Default behavior is a regular search for existing entries
483         dirinfo->flags = 0;
484
485         if (!strlen((char *) dirname) || (strlen((char *) dirname) == 1 && dirname[0] == DIR_SEPARATOR)) {
486                 if (volinfo->filesystem == FAT32) {
487                         dirinfo->currentcluster = volinfo->rootdir;
488                         dirinfo->currentsector = 0;
489                         dirinfo->currententry = 0;
490
491                         // read first sector of directory
492                         return DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((volinfo->rootdir - 2) * volinfo->secperclus), 1);
493                 }
494                 else {
495                         dirinfo->currentcluster = 0;
496                         dirinfo->currentsector = 0;
497                         dirinfo->currententry = 0;
498
499                         // read first sector of directory
500                         return DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir, 1);
501                 }
502         }
503
504         // This is not the root directory. We need to find the start of this subdirectory.
505         // We do this by devious means, using our own companion function DFS_GetNext.
506         else {
507                 uint8_t tmpfn[12];
508                 uint8_t *ptr = dirname;
509                 uint32_t result;
510                 DIRENT de;
511
512                 if (volinfo->filesystem == FAT32) {
513                         dirinfo->currentcluster = volinfo->rootdir;
514                         dirinfo->currentsector = 0;
515                         dirinfo->currententry = 0;
516
517                         // read first sector of directory
518                         if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((volinfo->rootdir - 2) * volinfo->secperclus), 1))
519                                 return DFS_ERRMISC;
520                 }
521                 else {
522                         dirinfo->currentcluster = 0;
523                         dirinfo->currentsector = 0;
524                         dirinfo->currententry = 0;
525
526                         // read first sector of directory
527                         if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir, 1))
528                                 return DFS_ERRMISC;
529                 }
530
531                 // skip leading path separators
532                 while (*ptr == DIR_SEPARATOR && *ptr)
533                         ptr++;
534
535                 // Scan the path from left to right, finding the start cluster of each entry
536                 // Observe that this code is inelegant, but obviates the need for recursion.
537                 while (*ptr) {
538                         DFS_CanonicalToDir(tmpfn, ptr);
539
540                         de.name[0] = 0;
541
542                         do {
543                                 result = DFS_GetNext(volinfo, dirinfo, &de);
544                         } while (!result && memcmp(de.name, tmpfn, 11));
545
546                         if (!memcmp(de.name, tmpfn, 11) && ((de.attr & ATTR_DIRECTORY) == ATTR_DIRECTORY)) {
547                                 if (volinfo->filesystem == FAT32) {
548                                         dirinfo->currentcluster = (uint32_t) de.startclus_l_l |
549                                           ((uint32_t) de.startclus_l_h) << 8 |
550                                           ((uint32_t) de.startclus_h_l) << 16 |
551                                           ((uint32_t) de.startclus_h_h) << 24;
552                                 }
553                                 else {
554                                         dirinfo->currentcluster = (uint32_t) de.startclus_l_l |
555                                           ((uint32_t) de.startclus_l_h) << 8;
556                                 }
557                                 dirinfo->currentsector = 0;
558                                 dirinfo->currententry = 0;
559
560                                 if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((dirinfo->currentcluster - 2) * volinfo->secperclus), 1))
561                                         return DFS_ERRMISC;
562                         }
563                         else if (!memcmp(de.name, tmpfn, 11) && !(de.attr & ATTR_DIRECTORY))
564                                 return DFS_NOTFOUND;
565
566                         // seek to next item in list
567                         while (*ptr != DIR_SEPARATOR && *ptr)
568                                 ptr++;
569                         if (*ptr == DIR_SEPARATOR)
570                                 ptr++;
571                 }
572
573                 if (!dirinfo->currentcluster)
574                         return DFS_NOTFOUND;
575         }
576         return DFS_OK;
577 }
578
579 /*
580         Get next entry in opened directory structure. Copies fields into the dirent
581         structure, updates dirinfo. Note that it is the _caller's_ responsibility to
582         handle the '.' and '..' entries.
583         A deleted file will be returned as a NULL entry (first char of filename=0)
584         by this code. Filenames beginning with 0x05 will be translated to 0xE5
585         automatically. Long file name entries will be returned as NULL.
586         returns DFS_EOF if there are no more entries, DFS_OK if this entry is valid,
587         or DFS_ERRMISC for a media error
588 */
589 uint32_t DFS_GetNext(PVOLINFO volinfo, PDIRINFO dirinfo, PDIRENT dirent)
590 {
591         uint32_t tempint;       // required by DFS_GetFAT
592
593         // Do we need to read the next sector of the directory?
594         if (dirinfo->currententry >= SECTOR_SIZE / sizeof(DIRENT)) {
595                 dirinfo->currententry = 0;
596                 dirinfo->currentsector++;
597
598                 // Root directory; special case handling 
599                 // Note that currentcluster will only ever be zero if both:
600                 // (a) this is the root directory, and
601                 // (b) we are on a FAT12/16 volume, where the root dir can't be expanded
602                 if (dirinfo->currentcluster == 0) {
603                         // Trying to read past end of root directory?
604                         if (dirinfo->currentsector * (SECTOR_SIZE / sizeof(DIRENT)) >= volinfo->rootentries)
605                                 return DFS_EOF;
606
607                         // Otherwise try to read the next sector
608                         if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir + dirinfo->currentsector, 1))
609                                 return DFS_ERRMISC;
610                 }
611
612                 // Normal handling
613                 else {
614                         if (dirinfo->currentsector >= volinfo->secperclus) {
615                                 dirinfo->currentsector = 0;
616                                 if ((dirinfo->currentcluster >= 0xff7 &&  volinfo->filesystem == FAT12) ||
617                                   (dirinfo->currentcluster >= 0xfff7 &&  volinfo->filesystem == FAT16) ||
618                                   (dirinfo->currentcluster >= 0x0ffffff7 &&  volinfo->filesystem == FAT32)) {
619                                   
620                                         // We are at the end of the directory chain. If this is a normal
621                                         // find operation, we should indicate that there is nothing more
622                                         // to see.
623                                         if (!(dirinfo->flags & DFS_DI_BLANKENT))
624                                                 return DFS_EOF;
625                                         
626                                         // On the other hand, if this is a "find free entry" search,
627                                         // we need to tell the caller to allocate a new cluster
628                                         else
629                                                 return DFS_ALLOCNEW;
630                                 }
631                                 dirinfo->currentcluster = DFS_GetFAT(volinfo, dirinfo->scratch, &tempint, dirinfo->currentcluster);
632                         }
633                         if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((dirinfo->currentcluster - 2) * volinfo->secperclus) + dirinfo->currentsector, 1))
634                                 return DFS_ERRMISC;
635                 }
636         }
637
638         memcpy(dirent, &(((PDIRENT) dirinfo->scratch)[dirinfo->currententry]), sizeof(DIRENT));
639
640         if (dirent->name[0] == 0) {             // no more files in this directory
641                 // If this is a "find blank" then we can reuse this name.
642                 if (dirinfo->flags & DFS_DI_BLANKENT)
643                         return DFS_OK;
644                 else
645                         return DFS_EOF;
646         }
647
648         if (dirent->name[0] == 0xe5)    // handle deleted file entries
649                 dirent->name[0] = 0;
650         else if ((dirent->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME)
651                 dirent->name[0] = 0;
652         else if (dirent->name[0] == 0x05)       // handle kanji filenames beginning with 0xE5
653                 dirent->name[0] = 0xe5;
654
655         dirinfo->currententry++;
656
657         return DFS_OK;
658 }
659
660 /*
661         INTERNAL
662         Find a free directory entry in the directory specified by path
663         This function MAY cause a disk write if it is necessary to extend the directory
664         size.
665         Note - di.scratch must be preinitialized to point to a sector scratch buffer
666         de is a scratch structure
667         Returns DFS_ERRMISC if a new entry could not be located or created
668         de is updated with the same return information you would expect from DFS_GetNext
669 */
670 uint32_t DFS_GetFreeDirEnt(PVOLINFO volinfo, uint8_t *path, PDIRINFO di, PDIRENT de)
671 {
672         uint32_t tempclus,i;
673
674         if (DFS_OpenDir(volinfo, path, di))
675                 return DFS_NOTFOUND;
676
677         // Set "search for empty" flag so DFS_GetNext knows what we're doing
678         di->flags |= DFS_DI_BLANKENT;
679
680         // We seek through the directory looking for an empty entry
681         // Note we are reusing tempclus as a temporary result holder.
682         tempclus = 0;   
683         do {
684                 tempclus = DFS_GetNext(volinfo, di, de);
685
686                 // Empty entry found
687                 if (tempclus == DFS_OK && (!de->name[0])) {
688                         return DFS_OK;
689                 }
690
691                 // End of root directory reached
692                 else if (tempclus == DFS_EOF)
693                         return DFS_ERRMISC;
694                         
695                 else if (tempclus == DFS_ALLOCNEW) {
696                         tempclus = DFS_GetFreeFAT(volinfo, di->scratch);
697                         if (tempclus == 0x0ffffff7)
698                                 return DFS_ERRMISC;
699
700                         // write out zeroed sectors to the new cluster
701                         memset(di->scratch, 0, SECTOR_SIZE);
702                         for (i=0;i<volinfo->secperclus;i++) {
703                                 if (DFS_WriteSector(volinfo->unit, di->scratch, volinfo->dataarea + ((tempclus - 2) * volinfo->secperclus) + i, 1))
704                                         return DFS_ERRMISC;
705                         }
706                         // Point old end cluster to newly allocated cluster
707                         i = 0;
708                         DFS_SetFAT(volinfo, di->scratch, &i, di->currentcluster, tempclus);
709
710                         // Update DIRINFO so caller knows where to place the new file                   
711                         di->currentcluster = tempclus;
712                         di->currentsector = 0;
713                         di->currententry = 1;   // since the code coming after this expects to subtract 1
714                         
715                         // Mark newly allocated cluster as end of chain                 
716                         switch(volinfo->filesystem) {
717                                 case FAT12:             tempclus = 0xff8;       break;
718                                 case FAT16:             tempclus = 0xfff8;      break;
719                                 case FAT32:             tempclus = 0x0ffffff8;  break;
720                                 default:                return DFS_ERRMISC;
721                         }
722                         DFS_SetFAT(volinfo, di->scratch, &i, di->currentcluster, tempclus);
723                 }
724         } while (!tempclus);
725
726         // We shouldn't get here
727         return DFS_ERRMISC;
728 }
729
730 /*
731         Open a file for reading or writing. You supply populated VOLINFO, a path to the file,
732         mode (DFS_READ or DFS_WRITE) and an empty fileinfo structure. You also need to
733         provide a pointer to a sector-sized scratch buffer.
734         Returns various DFS_* error states. If the result is DFS_OK, fileinfo can be used
735         to access the file from this point on.
736 */
737 uint32_t DFS_OpenFile(PVOLINFO volinfo, uint8_t *path, uint8_t mode, uint8_t *scratch, PFILEINFO fileinfo)
738 {
739         uint8_t tmppath[MAX_PATH];
740         uint8_t filename[12];
741         uint8_t *p;
742         DIRINFO di;
743         DIRENT de;
744
745         // larwe 2006-09-16 +1 zero out file structure
746         memset(fileinfo, 0, sizeof(FILEINFO));
747
748         // save access mode
749         fileinfo->mode = mode;
750
751         // Get a local copy of the path. If it's longer than MAX_PATH, abort.
752         strncpy((char *) tmppath, (char *) path, MAX_PATH);
753         tmppath[MAX_PATH - 1] = 0;
754         if (strcmp((char *) path,(char *) tmppath)) {
755                 return DFS_PATHLEN;
756         }
757
758         // strip leading path separators
759         while (tmppath[0] == DIR_SEPARATOR)
760                 strcpy((char *) tmppath, (char *) tmppath + 1);
761
762         // Parse filename off the end of the supplied path
763         p = tmppath;
764         while (*(p++));
765
766         p--;
767         while (p > tmppath && *p != DIR_SEPARATOR) // larwe 9/16/06 ">=" to ">" bugfix
768                 p--;
769         if (*p == DIR_SEPARATOR)
770                 p++;
771
772         DFS_CanonicalToDir(filename, p);
773
774         if (p > tmppath)
775                 p--;
776         if (*p == DIR_SEPARATOR || p == tmppath) // larwe 9/16/06 +"|| p == tmppath" bugfix
777                 *p = 0;
778
779         // At this point, if our path was MYDIR/MYDIR2/FILE.EXT, filename = "FILE    EXT" and
780         // tmppath = "MYDIR/MYDIR2".
781         di.scratch = scratch;
782         if (DFS_OpenDir(volinfo, tmppath, &di))
783                 return DFS_NOTFOUND;
784
785         while (!DFS_GetNext(volinfo, &di, &de)) {
786                 if (!memcmp(de.name, filename, 11)) {
787                         // You can't use this function call to open a directory.
788                         if (de.attr & ATTR_DIRECTORY)
789                                 return DFS_NOTFOUND;
790
791                         fileinfo->volinfo = volinfo;
792                         fileinfo->pointer = 0;
793                         // The reason we store this extra info about the file is so that we can
794                         // speedily update the file size, modification date, etc. on a file that is
795                         // opened for writing.
796                         if (di.currentcluster == 0)
797                                 fileinfo->dirsector = volinfo->rootdir + di.currentsector;
798                         else
799                                 fileinfo->dirsector = volinfo->dataarea + ((di.currentcluster - 2) * volinfo->secperclus) + di.currentsector;
800                         fileinfo->diroffset = di.currententry - 1;
801                         if (volinfo->filesystem == FAT32) {
802                                 fileinfo->cluster = (uint32_t) de.startclus_l_l |
803                                   ((uint32_t) de.startclus_l_h) << 8 |
804                                   ((uint32_t) de.startclus_h_l) << 16 |
805                                   ((uint32_t) de.startclus_h_h) << 24;
806                         }
807                         else {
808                                 fileinfo->cluster = (uint32_t) de.startclus_l_l |
809                                   ((uint32_t) de.startclus_l_h) << 8;
810                         }
811                         fileinfo->firstcluster = fileinfo->cluster;
812                         fileinfo->filelen = (uint32_t) de.filesize_0 |
813                           ((uint32_t) de.filesize_1) << 8 |
814                           ((uint32_t) de.filesize_2) << 16 |
815                           ((uint32_t) de.filesize_3) << 24;
816
817                         return DFS_OK;
818                 }
819         }
820
821         // At this point, we KNOW the file does not exist. If the file was opened
822         // with write access, we can create it.
823         if (mode & DFS_WRITE) {
824                 uint32_t cluster, temp;
825
826                 // Locate or create a directory entry for this file
827                 if (DFS_OK != DFS_GetFreeDirEnt(volinfo, tmppath, &di, &de))
828                         return DFS_ERRMISC;
829
830                 // put sane values in the directory entry
831                 memset(&de, 0, sizeof(de));
832                 memcpy(de.name, filename, 11);
833                 de.crttime_l = 0x20;    // 01:01:00am, Jan 1, 2006.
834                 de.crttime_h = 0x08;
835                 de.crtdate_l = 0x11;
836                 de.crtdate_h = 0x34;
837                 de.lstaccdate_l = 0x11;
838                 de.lstaccdate_h = 0x34;
839                 de.wrttime_l = 0x20;
840                 de.wrttime_h = 0x08;
841                 de.wrtdate_l = 0x11;
842                 de.wrtdate_h = 0x34;
843
844                 // allocate a starting cluster for the directory entry
845                 cluster = DFS_GetFreeFAT(volinfo, scratch);
846
847                 de.startclus_l_l = cluster & 0xff;
848                 de.startclus_l_h = (cluster & 0xff00) >> 8;
849                 de.startclus_h_l = (cluster & 0xff0000) >> 16;
850                 de.startclus_h_h = (cluster & 0xff000000) >> 24;
851
852                 // update FILEINFO for our caller's sake
853                 fileinfo->volinfo = volinfo;
854                 fileinfo->pointer = 0;
855                 // The reason we store this extra info about the file is so that we can
856                 // speedily update the file size, modification date, etc. on a file that is
857                 // opened for writing.
858                 if (di.currentcluster == 0)
859                         fileinfo->dirsector = volinfo->rootdir + di.currentsector;
860                 else
861                         fileinfo->dirsector = volinfo->dataarea + ((di.currentcluster - 2) * volinfo->secperclus) + di.currentsector;
862 #ifdef USE_GMHFIX
863                 fileinfo->diroffset = di.currententry;
864 #else
865                 fileinfo->diroffset = di.currententry - 1;
866 #endif
867                 fileinfo->cluster = cluster;
868                 fileinfo->firstcluster = cluster;
869                 fileinfo->filelen = 0;
870                 
871                 // write the directory entry
872                 // note that we no longer have the sector containing the directory entry,
873                 // tragically, so we have to re-read it
874                 if (DFS_ReadSector(volinfo->unit, scratch, fileinfo->dirsector, 1))
875                         return DFS_ERRMISC;
876 #ifdef USE_GMHFIX
877                 memcpy(&(((PDIRENT) scratch)[di.currententry]), &de, sizeof(DIRENT));
878 #else
879                 memcpy(&(((PDIRENT) scratch)[di.currententry-1]), &de, sizeof(DIRENT));
880 #endif
881                 if (DFS_WriteSector(volinfo->unit, scratch, fileinfo->dirsector, 1))
882                         return DFS_ERRMISC;
883
884                 // Mark newly allocated cluster as end of chain                 
885                 switch(volinfo->filesystem) {
886                         case FAT12:             cluster = 0xff8;        break;
887                         case FAT16:             cluster = 0xfff8;       break;
888                         case FAT32:             cluster = 0x0ffffff8;   break;
889                         default:                return DFS_ERRMISC;
890                 }
891                 temp = 0;
892                 DFS_SetFAT(volinfo, scratch, &temp, fileinfo->cluster, cluster);
893
894                 return DFS_OK;
895         }
896
897         return DFS_NOTFOUND;
898 }
899
900 /*
901         Read an open file
902         You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a
903         pointer to a SECTOR_SIZE scratch buffer.
904         Note that returning DFS_EOF is not an error condition. This function updates the
905         successcount field with the number of bytes actually read.
906 */
907 uint32_t DFS_ReadFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len)
908 {
909         uint32_t remain;
910         uint32_t result = DFS_OK;
911         uint32_t sector;
912         uint32_t bytesread;
913
914         // Don't try to read past EOF
915         if (len > fileinfo->filelen - fileinfo->pointer)
916                 len = fileinfo->filelen - fileinfo->pointer;
917
918         remain = len;
919         *successcount = 0;
920
921         while (remain && result == DFS_OK) {
922                 // This is a bit complicated. The sector we want to read is addressed at a cluster
923                 // granularity by the fileinfo->cluster member. The file pointer tells us how many
924                 // extra sectors to add to that number.
925                 sector = fileinfo->volinfo->dataarea +
926                   ((fileinfo->cluster - 2) * fileinfo->volinfo->secperclus) +
927                   div(div(fileinfo->pointer,fileinfo->volinfo->secperclus * SECTOR_SIZE).rem, SECTOR_SIZE).quot;
928
929                 // Case 1 - File pointer is not on a sector boundary
930                 if (div(fileinfo->pointer, SECTOR_SIZE).rem) {
931                         uint16_t tempreadsize;
932
933                         // We always have to go through scratch in this case
934                         result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
935
936                         // This is the number of bytes that we actually care about in the sector
937                         // just read.
938                         tempreadsize = SECTOR_SIZE - (div(fileinfo->pointer, SECTOR_SIZE).rem);
939                                         
940                         // Case 1A - We want the entire remainder of the sector. After this
941                         // point, all passes through the read loop will be aligned on a sector
942                         // boundary, which allows us to go through the optimal path 2A below.
943                         if (remain >= tempreadsize) {
944                                 memcpy(buffer, scratch + (SECTOR_SIZE - tempreadsize), tempreadsize);
945                                 bytesread = tempreadsize;
946                                 buffer += tempreadsize;
947                                 fileinfo->pointer += tempreadsize;
948                                 remain -= tempreadsize;
949                         }
950                         // Case 1B - This read concludes the file read operation
951                         else {
952                                 memcpy(buffer, scratch + (SECTOR_SIZE - tempreadsize), remain);
953
954                                 buffer += remain;
955                                 fileinfo->pointer += remain;
956                                 bytesread = remain;
957                                 remain = 0;
958                         }
959                 }
960                 // Case 2 - File pointer is on sector boundary
961                 else {
962                         // Case 2A - We have at least one more full sector to read and don't have
963                         // to go through the scratch buffer. You could insert optimizations here to
964                         // read multiple sectors at a time, if you were thus inclined (note that
965                         // the maximum multi-read you could perform is a single cluster, so it would
966                         // be advantageous to have code similar to case 1A above that would round the
967                         // pointer to a cluster boundary the first pass through, so all subsequent
968                         // [large] read requests would be able to go a cluster at a time).
969                         if (remain >= SECTOR_SIZE) {
970                                 result = DFS_ReadSector(fileinfo->volinfo->unit, buffer, sector, 1);
971                                 remain -= SECTOR_SIZE;
972                                 buffer += SECTOR_SIZE;
973                                 fileinfo->pointer += SECTOR_SIZE;
974                                 bytesread = SECTOR_SIZE;
975                         }
976                         // Case 2B - We are only reading a partial sector
977                         else {
978                                 result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
979                                 memcpy(buffer, scratch, remain);
980                                 buffer += remain;
981                                 fileinfo->pointer += remain;
982                                 bytesread = remain;
983                                 remain = 0;
984                         }
985                 }
986
987                 *successcount += bytesread;
988
989                 // check to see if we stepped over a cluster boundary
990                 if (div(fileinfo->pointer - bytesread, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot !=
991                   div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
992                         // An act of minor evil - we use bytesread as a scratch integer, knowing that
993                         // its value is not used after updating *successcount above
994                         bytesread = 0;
995                         if (((fileinfo->volinfo->filesystem == FAT12) && (fileinfo->cluster >= 0xff8)) ||
996                           ((fileinfo->volinfo->filesystem == FAT16) && (fileinfo->cluster >= 0xfff8)) ||
997                           ((fileinfo->volinfo->filesystem == FAT32) && (fileinfo->cluster >= 0x0ffffff8)))
998                                 result = DFS_EOF;
999                         else
1000                                 fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &bytesread, fileinfo->cluster);
1001                 }
1002         }
1003         
1004         return result;
1005 }
1006
1007 /*
1008         Seek file pointer to a given position
1009         This function does not return status - refer to the fileinfo->pointer value
1010         to see where the pointer wound up.
1011         Requires a SECTOR_SIZE scratch buffer
1012 */
1013 void DFS_Seek(PFILEINFO fileinfo, uint32_t offset, uint8_t *scratch)
1014 {
1015         uint32_t tempint;
1016
1017         // larwe 9/16/06 bugfix split case 0a/0b and changed fallthrough handling
1018         // Case 0a - Return immediately for degenerate case
1019         if (offset == fileinfo->pointer) {
1020                 return;
1021         }
1022         
1023         // Case 0b - Don't allow the user to seek past the end of the file
1024         if (offset > fileinfo->filelen) {
1025                 offset = fileinfo->filelen;
1026                 // NOTE NO RETURN HERE!
1027         }
1028
1029         // Case 1 - Simple rewind to start
1030         // Note _intentional_ fallthrough from Case 0b above
1031         if (offset == 0) {
1032                 fileinfo->cluster = fileinfo->firstcluster;
1033                 fileinfo->pointer = 0;
1034                 return;         // larwe 9/16/06 +1 bugfix
1035         }
1036         // Case 2 - Seeking backwards. Need to reset and seek forwards
1037         else if (offset < fileinfo->pointer) {
1038                 fileinfo->cluster = fileinfo->firstcluster;
1039                 fileinfo->pointer = 0;
1040                 // NOTE NO RETURN HERE!
1041         }
1042
1043         // Case 3 - Seeking forwards
1044         // Note _intentional_ fallthrough from Case 2 above
1045
1046         // Case 3a - Seek size does not cross cluster boundary - 
1047         // very simple case
1048         // larwe 9/16/06 changed .rem to .quot in both div calls, bugfix
1049         if (div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot ==
1050           div(fileinfo->pointer + offset, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
1051                 fileinfo->pointer = offset;
1052         }
1053         // Case 3b - Seeking across cluster boundary(ies)
1054         else {
1055                 // round file pointer down to cluster boundary
1056                 fileinfo->pointer = div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot *
1057                   fileinfo->volinfo->secperclus * SECTOR_SIZE;
1058
1059                 // seek by clusters
1060                 // larwe 9/30/06 bugfix changed .rem to .quot in both div calls
1061                 while (div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot !=
1062                   div(fileinfo->pointer + offset, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
1063
1064                         fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &tempint, fileinfo->cluster);
1065                         // Abort if there was an error
1066                         if (fileinfo->cluster == 0x0ffffff7) {
1067                                 fileinfo->pointer = 0;
1068                                 fileinfo->cluster = fileinfo->firstcluster;
1069                                 return;
1070                         }
1071                         fileinfo->pointer += SECTOR_SIZE * fileinfo->volinfo->secperclus;
1072                 }
1073
1074                 // since we know the cluster is right, we have no more work to do
1075                 fileinfo->pointer = offset;
1076         }
1077 }
1078
1079 /*
1080         Delete a file
1081         scratch must point to a sector-sized buffer
1082 */
1083 uint32_t DFS_UnlinkFile(PVOLINFO volinfo, uint8_t *path, uint8_t *scratch)
1084 {
1085         FILEINFO fi;
1086         uint32_t cache = 0;
1087         uint32_t tempclus;
1088
1089         // DFS_OpenFile gives us all the information we need to delete it
1090         if (DFS_OK != DFS_OpenFile(volinfo, path, DFS_READ, scratch, &fi))
1091                 return DFS_NOTFOUND;
1092
1093         // First, read the directory sector and delete that entry
1094         if (DFS_ReadSector(volinfo->unit, scratch, fi.dirsector, 1))
1095                 return DFS_ERRMISC;
1096         ((PDIRENT) scratch)[fi.diroffset].name[0] = 0xe5;
1097         if (DFS_WriteSector(volinfo->unit, scratch, fi.dirsector, 1))
1098                 return DFS_ERRMISC;
1099
1100         // Now follow the cluster chain to free the file space
1101         while (!((volinfo->filesystem == FAT12 && fi.firstcluster >= 0x0ff7) ||
1102           (volinfo->filesystem == FAT16 && fi.firstcluster >= 0xfff7) ||
1103           (volinfo->filesystem == FAT32 && fi.firstcluster >= 0x0ffffff7))) {
1104                 tempclus = fi.firstcluster;
1105
1106                 fi.firstcluster = DFS_GetFAT(volinfo, scratch, &cache, fi.firstcluster);
1107                 DFS_SetFAT(volinfo, scratch, &cache, tempclus, 0);
1108
1109         }
1110         return DFS_OK;
1111 }
1112
1113
1114 /*
1115         Write an open file
1116         You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a
1117         pointer to a SECTOR_SIZE scratch buffer.
1118         This function updates the successcount field with the number of bytes actually written.
1119 */
1120 uint32_t DFS_WriteFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len)
1121 {
1122         uint32_t remain;
1123         uint32_t result = DFS_OK;
1124         uint32_t sector;
1125         uint32_t byteswritten;
1126
1127         // Don't allow writes to a file that's open as readonly
1128         if (!(fileinfo->mode & DFS_WRITE))
1129                 return DFS_ERRMISC;
1130
1131         remain = len;
1132         *successcount = 0;
1133
1134         while (remain && result == DFS_OK) {
1135                 // This is a bit complicated. The sector we want to read is addressed at a cluster
1136                 // granularity by the fileinfo->cluster member. The file pointer tells us how many
1137                 // extra sectors to add to that number.
1138                 sector = fileinfo->volinfo->dataarea +
1139                   ((fileinfo->cluster - 2) * fileinfo->volinfo->secperclus) +
1140                   div(div(fileinfo->pointer,fileinfo->volinfo->secperclus * SECTOR_SIZE).rem, SECTOR_SIZE).quot;
1141
1142                 // Case 1 - File pointer is not on a sector boundary
1143                 if (div(fileinfo->pointer, SECTOR_SIZE).rem) {
1144                         uint16_t tempsize;
1145
1146                         // We always have to go through scratch in this case
1147                         result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
1148
1149                         // This is the number of bytes that we don't want to molest in the
1150                         // scratch sector just read.
1151                         tempsize = div(fileinfo->pointer, SECTOR_SIZE).rem;
1152                                         
1153                         // Case 1A - We are writing the entire remainder of the sector. After
1154                         // this point, all passes through the read loop will be aligned on a
1155                         // sector boundary, which allows us to go through the optimal path
1156                         // 2A below.
1157                         if (remain >= SECTOR_SIZE - tempsize) {
1158                                 memcpy(scratch + tempsize, buffer, SECTOR_SIZE - tempsize);
1159                                 if (!result)
1160                                         result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1);
1161
1162                                 byteswritten = SECTOR_SIZE - tempsize;
1163                                 buffer += SECTOR_SIZE - tempsize;
1164                                 fileinfo->pointer += SECTOR_SIZE - tempsize;
1165                                 if (fileinfo->filelen < fileinfo->pointer) {
1166                                         fileinfo->filelen = fileinfo->pointer;
1167                                 }
1168                                 remain -= SECTOR_SIZE - tempsize;
1169                         }
1170                         // Case 1B - This concludes the file write operation
1171                         else {
1172                                 memcpy(scratch + tempsize, buffer, remain);
1173                                 if (!result)
1174                                         result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1);
1175
1176                                 buffer += remain;
1177                                 fileinfo->pointer += remain;
1178                                 if (fileinfo->filelen < fileinfo->pointer) {
1179                                         fileinfo->filelen = fileinfo->pointer;
1180                                 }
1181                                 byteswritten = remain;
1182                                 remain = 0;
1183                         }
1184                 } // case 1
1185                 // Case 2 - File pointer is on sector boundary
1186                 else {
1187                         // Case 2A - We have at least one more full sector to write and don't have
1188                         // to go through the scratch buffer. You could insert optimizations here to
1189                         // write multiple sectors at a time, if you were thus inclined. Refer to
1190                         // similar notes in DFS_ReadFile.
1191                         if (remain >= SECTOR_SIZE) {
1192                                 result = DFS_WriteSector(fileinfo->volinfo->unit, buffer, sector, 1);
1193                                 remain -= SECTOR_SIZE;
1194                                 buffer += SECTOR_SIZE;
1195                                 fileinfo->pointer += SECTOR_SIZE;
1196                                 if (fileinfo->filelen < fileinfo->pointer) {
1197                                         fileinfo->filelen = fileinfo->pointer;
1198                                 }
1199                                 byteswritten = SECTOR_SIZE;
1200                         }
1201                         // Case 2B - We are only writing a partial sector and potentially need to
1202                         // go through the scratch buffer.
1203                         else {
1204                                 // If the current file pointer is not yet at or beyond the file
1205                                 // length, we are writing somewhere in the middle of the file and
1206                                 // need to load the original sector to do a read-modify-write.
1207                                 if (fileinfo->pointer < fileinfo->filelen) {
1208                                         result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
1209                                         if (!result) {
1210                                                 memcpy(scratch, buffer, remain);
1211                                                 result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1);
1212                                         }
1213                                 }
1214                                 else {
1215                                         result = DFS_WriteSector(fileinfo->volinfo->unit, buffer, sector, 1);
1216                                 }
1217
1218                                 buffer += remain;
1219                                 fileinfo->pointer += remain;
1220                                 if (fileinfo->filelen < fileinfo->pointer) {
1221                                         fileinfo->filelen = fileinfo->pointer;
1222                                 }
1223                                 byteswritten = remain;
1224                                 remain = 0;
1225                         }
1226                 }
1227
1228                 *successcount += byteswritten;
1229
1230                 // check to see if we stepped over a cluster boundary
1231                 if (div(fileinfo->pointer - byteswritten, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot !=
1232                   div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
1233                         uint32_t lastcluster;
1234
1235                         // We've transgressed into another cluster. If we were already at EOF,
1236                         // we need to allocate a new cluster.
1237                         // An act of minor evil - we use byteswritten as a scratch integer, knowing
1238                         // that its value is not used after updating *successcount above
1239                         byteswritten = 0;
1240
1241                         lastcluster = fileinfo->cluster;
1242                         fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &byteswritten, fileinfo->cluster);
1243                         
1244                         // Allocate a new cluster?
1245                         if (((fileinfo->volinfo->filesystem == FAT12) && (fileinfo->cluster >= 0xff8)) ||
1246                           ((fileinfo->volinfo->filesystem == FAT16) && (fileinfo->cluster >= 0xfff8)) ||
1247                           ((fileinfo->volinfo->filesystem == FAT32) && (fileinfo->cluster >= 0x0ffffff8))) {
1248                                 uint32_t tempclus;
1249
1250                                 tempclus = DFS_GetFreeFAT(fileinfo->volinfo, scratch);
1251                                 byteswritten = 0; // invalidate cache
1252                                 if (tempclus == 0x0ffffff7)
1253                                         return DFS_ERRMISC;
1254
1255                                 // Link new cluster onto file
1256                                 DFS_SetFAT(fileinfo->volinfo, scratch, &byteswritten, lastcluster, tempclus);
1257                                 fileinfo->cluster = tempclus;
1258
1259                                 // Mark newly allocated cluster as end of chain                 
1260                                 switch(fileinfo->volinfo->filesystem) {
1261                                         case FAT12:             tempclus = 0xff8;       break;
1262                                         case FAT16:             tempclus = 0xfff8;      break;
1263                                         case FAT32:             tempclus = 0x0ffffff8;  break;
1264                                         default:                return DFS_ERRMISC;
1265                                 }
1266                                 DFS_SetFAT(fileinfo->volinfo, scratch, &byteswritten, fileinfo->cluster, tempclus);
1267
1268                                 result = DFS_OK;
1269                         }
1270                         // No else clause is required.
1271                 }
1272         }
1273         
1274         // Update directory entry
1275                 if (DFS_ReadSector(fileinfo->volinfo->unit, scratch, fileinfo->dirsector, 1))
1276                         return DFS_ERRMISC;
1277                 ((PDIRENT) scratch)[fileinfo->diroffset].filesize_0 = fileinfo->filelen & 0xff;
1278                 ((PDIRENT) scratch)[fileinfo->diroffset].filesize_1 = (fileinfo->filelen & 0xff00) >> 8;
1279                 ((PDIRENT) scratch)[fileinfo->diroffset].filesize_2 = (fileinfo->filelen & 0xff0000) >> 16;
1280                 ((PDIRENT) scratch)[fileinfo->diroffset].filesize_3 = (fileinfo->filelen & 0xff000000) >> 24;
1281                 if (DFS_WriteSector(fileinfo->volinfo->unit, scratch, fileinfo->dirsector, 1))
1282                         return DFS_ERRMISC;
1283         return result;
1284 }
1285
1286 /*************************************************************************
1287  * 
1288  * The remainder of this file has been added as a result of the dosfs
1289  * distribution being used in MicroMonitor.
1290  * This is not part of the original dosfs.c as distributed by Lewin Edwards.
1291  *
1292  *************************************************************************
1293  */
1294
1295 /*
1296  * Convert a filename element from directory entry (11) to canonical (8.3) form
1297  *      dest must point to the first non-separator character.
1298  *      sec must point to a 12-byte buffer.
1299  */
1300 uint8_t *DFS_DirToCanonical(uint8_t *dest, uint8_t *src)
1301 {
1302         uint8_t *srcptr = src;
1303
1304    // copy filename until spaces
1305    while(( srcptr -  src) <= 11)
1306    {
1307           if (*srcptr == 0) break;
1308
1309       // name.ext seperator
1310       if(( srcptr - src) == 8) *dest++ = '.';
1311       if( *srcptr != ' ') *dest++ = *srcptr;
1312
1313       srcptr++;
1314    }
1315
1316    // null terminate and rewind one char
1317    *dest-- = 0;
1318
1319    // if name has no extension - kill the seperator
1320    if( *dest == '.') *dest = 0;
1321
1322         return dest;
1323 }
1324 #endif