Index: main/common/dosfs.c
===================================================================
--- main/common/dosfs.c	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
+++ main/common/dosfs.c	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
@@ -0,0 +1,1326 @@
+/*
+	DOSFS Embedded FAT-Compatible Filesystem
+	(C) 2005 Lewin A.R.W. Edwards (sysadm@zws.com)
+
+	You are permitted to modify and/or use this code in your own projects without
+	payment of royalty, regardless of the license(s) you choose for those projects.
+
+	You cannot re-copyright or restrict use of the code as released by Lewin Edwards.
+
+	Modifications: Aug 2006 (esutter/ghenderson)
+		Initial integration into uMon source tree.
+		Eliminated a few precedence warnings by adding parenthesis.
+		Eliminated one "variable unused" warning.
+	Modifications: Sept 2006 (ghenderson)
+		Added DFS_DirToCanonical.
+		All structures in dosfs.h have been defined with the
+			__attribute__ ((__packed__)).
+		Added macros in dosfs.h to help in formating time,
+			date, and filesize.
+	Modifications: Sept 25, 2006 (esutter)
+		Updated to dosfs-1.02 (dated Sept 16, 2006).
+	Modifications: Oct 2, 2006 (esutter)
+		Updated to dosfs-1.03 (dated Sept 30, 2006).
+		Incorporate fix from Graham Henderson (see USE_GMHFIX)
+*/
+#include "config.h"
+
+#if INCLUDE_FATFS
+#include "genlib.h"
+#include "dosfs.h"
+
+#define memcpy(a,b,c) memcpy((char *)a, (char *)b,(int)c)
+#define memset(a,b,c) memset((char *)a, (char)b,(int)c)
+#define memcmp(a,b,c) memcmp((char *)a, (char *)b,(int)c)
+
+/*
+	Get starting sector# of specified partition on drive #unit
+	NOTE: This code ASSUMES an MBR on the disk.
+	scratchsector should point to a SECTOR_SIZE scratch area
+	Returns 0xffffffff for any error.
+	If pactive is non-NULL, this function also returns the partition active flag.
+	If pptype is non-NULL, this function also returns the partition type.
+	If psize is non-NULL, this function also returns the partition size.
+*/
+uint32_t DFS_GetPtnStart(uint8_t unit, uint8_t *scratchsector, uint8_t pnum, uint8_t *pactive, uint8_t *pptype, uint32_t *psize)
+{
+	uint32_t result;
+	PMBR mbr = (PMBR) scratchsector;
+
+	// DOS ptable supports maximum 4 partitions
+	if (pnum > 3)
+		return DFS_ERRMISC;
+
+	// Read MBR from target media
+	if (DFS_ReadSector(unit,scratchsector,0,1)) {
+		return DFS_ERRMISC;
+	}
+
+	result = (uint32_t) mbr->ptable[pnum].start_0 |
+	  (((uint32_t) mbr->ptable[pnum].start_1) << 8) |
+	  (((uint32_t) mbr->ptable[pnum].start_2) << 16) |
+	  (((uint32_t) mbr->ptable[pnum].start_3) << 24);
+
+	if (pactive)
+		*pactive = mbr->ptable[pnum].active;
+
+	if (pptype)
+		*pptype = mbr->ptable[pnum].type;
+
+	if (psize)
+		*psize = (uint32_t) mbr->ptable[pnum].size_0 |
+		  (((uint32_t) mbr->ptable[pnum].size_1) << 8) |
+		  (((uint32_t) mbr->ptable[pnum].size_2) << 16) |
+		  (((uint32_t) mbr->ptable[pnum].size_3) << 24);
+
+	return result;
+}
+
+
+/*
+	Retrieve volume info from BPB and store it in a VOLINFO structure
+	You must provide the unit and starting sector of the filesystem, and
+	a pointer to a sector buffer for scratch
+	Attempts to read BPB and glean information about the FS from that.
+	Returns 0 OK, nonzero for any error.
+*/
+uint32_t DFS_GetVolInfo(uint8_t unit, uint8_t *scratchsector, uint32_t startsector, PVOLINFO volinfo)
+{
+	PLBR lbr = (PLBR) scratchsector;
+	volinfo->unit = unit;
+	volinfo->startsector = startsector;
+
+	if(DFS_ReadSector(unit,scratchsector,startsector,1))
+		return DFS_ERRMISC;
+
+// tag: OEMID, refer dosfs.h
+//	strncpy(volinfo->oemid, lbr->oemid, 8);
+//	volinfo->oemid[8] = 0;
+
+	volinfo->secperclus = lbr->bpb.secperclus;
+	volinfo->reservedsecs = (uint16_t) lbr->bpb.reserved_l |
+		  (((uint16_t) lbr->bpb.reserved_h) << 8);
+
+	volinfo->numsecs =  (uint16_t) lbr->bpb.sectors_s_l |
+		  (((uint16_t) lbr->bpb.sectors_s_h) << 8);
+
+	if (!volinfo->numsecs)
+		volinfo->numsecs = (uint32_t) lbr->bpb.sectors_l_0 |
+		  (((uint32_t) lbr->bpb.sectors_l_1) << 8) |
+		  (((uint32_t) lbr->bpb.sectors_l_2) << 16) |
+		  (((uint32_t) lbr->bpb.sectors_l_3) << 24);
+
+	// If secperfat is 0, we must be in a FAT32 volume; get secperfat
+	// from the FAT32 EBPB. The volume label and system ID string are also
+	// in different locations for FAT12/16 vs FAT32.
+	volinfo->secperfat =  (uint16_t) lbr->bpb.secperfat_l |
+		  (((uint16_t) lbr->bpb.secperfat_h) << 8);
+	if (!volinfo->secperfat) {
+		volinfo->secperfat = (uint32_t) lbr->ebpb.ebpb32.fatsize_0 |
+		  (((uint32_t) lbr->ebpb.ebpb32.fatsize_1) << 8) |
+		  (((uint32_t) lbr->ebpb.ebpb32.fatsize_2) << 16) |
+		  (((uint32_t) lbr->ebpb.ebpb32.fatsize_3) << 24);
+
+		memcpy(volinfo->label, lbr->ebpb.ebpb32.label, 11);
+		volinfo->label[11] = 0;
+	
+// tag: OEMID, refer dosfs.h
+//		memcpy(volinfo->system, lbr->ebpb.ebpb32.system, 8);
+//		volinfo->system[8] = 0; 
+	}
+	else {
+		memcpy(volinfo->label, lbr->ebpb.ebpb.label, 11);
+		volinfo->label[11] = 0;
+	
+// tag: OEMID, refer dosfs.h
+//		memcpy(volinfo->system, lbr->ebpb.ebpb.system, 8);
+//		volinfo->system[8] = 0; 
+	}
+
+	// note: if rootentries is 0, we must be in a FAT32 volume.
+	volinfo->rootentries =  (uint16_t) lbr->bpb.rootentries_l |
+		  (((uint16_t) lbr->bpb.rootentries_h) << 8);
+
+	// after extracting raw info we perform some useful precalculations
+	volinfo->fat1 = startsector + volinfo->reservedsecs;
+
+	// The calculation below is designed to round up the root directory size for FAT12/16
+	// and to simply ignore the root directory for FAT32, since it's a normal, expandable
+	// file in that situation.
+	if (volinfo->rootentries) {
+		volinfo->rootdir = volinfo->fat1 + (volinfo->secperfat * 2);
+		volinfo->dataarea = volinfo->rootdir + (((volinfo->rootentries * 32) + (SECTOR_SIZE - 1)) / SECTOR_SIZE);
+	}
+	else {
+		volinfo->dataarea = volinfo->fat1 + (volinfo->secperfat * 2);
+		volinfo->rootdir = (uint32_t) lbr->ebpb.ebpb32.root_0 |
+		  (((uint32_t) lbr->ebpb.ebpb32.root_1) << 8) |
+		  (((uint32_t) lbr->ebpb.ebpb32.root_2) << 16) |
+		  (((uint32_t) lbr->ebpb.ebpb32.root_3) << 24);
+	}
+
+	// Calculate number of clusters in data area and infer FAT type from this information.
+	volinfo->numclusters = (volinfo->numsecs - volinfo->dataarea) / volinfo->secperclus;
+	if (volinfo->numclusters < 4085)
+		volinfo->filesystem = FAT12;
+	else if (volinfo->numclusters < 65525)
+		volinfo->filesystem = FAT16;
+	else
+		volinfo->filesystem = FAT32;
+
+	return DFS_OK;
+}
+
+/*
+	Fetch FAT entry for specified cluster number
+	You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
+	Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired
+	FAT entry.
+	scratchcache should point to a UINT32. This variable caches the physical sector number
+	last read into the scratch buffer for performance enhancement reasons.
+*/
+uint32_t DFS_GetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster)
+{
+	uint32_t offset, sector, result;
+
+	if (volinfo->filesystem == FAT12) {
+		offset = cluster + (cluster / 2);
+	}
+	else if (volinfo->filesystem == FAT16) {
+		offset = cluster * 2;
+	}
+	else if (volinfo->filesystem == FAT32) {
+		offset = cluster * 4;
+	}
+	else
+		return 0x0ffffff7;	// FAT32 bad cluster	
+
+	// at this point, offset is the BYTE offset of the desired sector from the start
+	// of the FAT. Calculate the physical sector containing this FAT entry.
+	sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1;
+
+	// If this is not the same sector we last read, then read it into RAM
+	if (sector != *scratchcache) {
+		if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
+			// avoid anyone assuming that this cache value is still valid, which
+			// might cause disk corruption
+			*scratchcache = 0;
+			return 0x0ffffff7;	// FAT32 bad cluster	
+		}
+		*scratchcache = sector;
+	}
+
+	// At this point, we "merely" need to extract the relevant entry.
+	// This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
+	// may span a sector boundary. The normal way around this is always to read two
+	// FAT sectors, but that luxury is (by design intent) unavailable to DOSFS.
+	offset = ldiv(offset, SECTOR_SIZE).rem;
+
+	if (volinfo->filesystem == FAT12) {
+		// Special case for sector boundary - Store last byte of current sector.
+		// Then read in the next sector and put the first byte of that sector into
+		// the high byte of result.
+		if (offset == SECTOR_SIZE - 1) {
+			result = (uint32_t) scratch[offset];
+			sector++;
+			if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
+				// avoid anyone assuming that this cache value is still valid, which
+				// might cause disk corruption
+				*scratchcache = 0;
+				return 0x0ffffff7;	// FAT32 bad cluster	
+			}
+			*scratchcache = sector;
+			// Thanks to Claudio Leonel for pointing out this missing line.
+			result |= ((uint32_t) scratch[0]) << 8;
+		}
+		else {
+			result = (uint32_t) scratch[offset] |
+			  ((uint32_t) scratch[offset+1]) << 8;
+		}
+		if (cluster & 1)
+			result = result >> 4;
+		else
+			result = result & 0xfff;
+	}
+	else if (volinfo->filesystem == FAT16) {
+		result = (uint32_t) scratch[offset] |
+		  ((uint32_t) scratch[offset+1]) << 8;
+	}
+	else if (volinfo->filesystem == FAT32) {
+		result = ((uint32_t) scratch[offset] |
+		  ((uint32_t) scratch[offset+1]) << 8 |
+		  ((uint32_t) scratch[offset+2]) << 16 |
+		  ((uint32_t) scratch[offset+3]) << 24) & 0x0fffffff;
+	}
+	else
+		result = 0x0ffffff7;	// FAT32 bad cluster	
+	return result;
+}
+
+
+/*
+	Set FAT entry for specified cluster number
+	You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
+	Returns DFS_ERRMISC for any error, otherwise DFS_OK
+	scratchcache should point to a UINT32. This variable caches the physical sector number
+	last read into the scratch buffer for performance enhancement reasons.
+
+	NOTE: This code is HIGHLY WRITE-INEFFICIENT, particularly for flash media. Considerable
+	performance gains can be realized by caching the sector. However this is difficult to
+	achieve on FAT12 without requiring 2 sector buffers of scratch space, and it is a design
+	requirement of this code to operate on a single 512-byte scratch.
+
+	If you are operating DOSFS over flash, you are strongly advised to implement a writeback
+	cache in your physical I/O driver. This will speed up your code significantly and will
+	also conserve power and flash write life.
+*/
+uint32_t DFS_SetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster, uint32_t new_contents)
+{
+	uint32_t offset, sector, result;
+	if (volinfo->filesystem == FAT12) {
+		offset = cluster + (cluster / 2);
+		new_contents &=0xfff;
+	}
+	else if (volinfo->filesystem == FAT16) {
+		offset = cluster * 2;
+		new_contents &=0xffff;
+	}
+	else if (volinfo->filesystem == FAT32) {
+		offset = cluster * 4;
+		new_contents &=0x0fffffff;	// FAT32 is really "FAT28"
+	}
+	else
+		return DFS_ERRMISC;	
+
+	// at this point, offset is the BYTE offset of the desired sector from the start
+	// of the FAT. Calculate the physical sector containing this FAT entry.
+	sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1;
+
+	// If this is not the same sector we last read, then read it into RAM
+	if (sector != *scratchcache) {
+		if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) {
+			// avoid anyone assuming that this cache value is still valid, which
+			// might cause disk corruption
+			*scratchcache = 0;
+			return DFS_ERRMISC;
+		}
+		*scratchcache = sector;
+	}
+
+	// At this point, we "merely" need to extract the relevant entry.
+	// This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry
+	// may span a sector boundary. The normal way around this is always to read two
+	// FAT sectors, but that luxury is (by design intent) unavailable to DOSFS.
+	offset = ldiv(offset, SECTOR_SIZE).rem;
+
+	if (volinfo->filesystem == FAT12) {
+
+		// If this is an odd cluster, pre-shift the desired new contents 4 bits to
+		// make the calculations below simpler
+		if (cluster & 1)
+			new_contents = new_contents << 4;
+
+		// Special case for sector boundary
+		if (offset == SECTOR_SIZE - 1) {
+
+			// Odd cluster: High 12 bits being set
+			if (cluster & 1) {
+				scratch[offset] = (scratch[offset] & 0x0f) | (new_contents & 0xf0);
+			}
+			// Even cluster: Low 12 bits being set
+			else {
+				scratch[offset] = new_contents & 0xff;
+			}
+			result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
+			// mirror the FAT into copy 2
+			if (DFS_OK == result)
+				result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
+
+			// If we wrote that sector OK, then read in the subsequent sector
+			// and poke the first byte with the remainder of this FAT entry.
+			if (DFS_OK == result) {
+#if 0
+				/* ELSNOTE:
+				 * Original line here is illegal (IMHO).  Didn't notice this
+				 * till I used a version of GCC that warned me about it.
+				 * I changed this to eliminate the warning, and *hopefully*
+				 * generate the correct code.  Have not been able to test this.
+				 */
+				*scratchcache++;
+#else
+				(*scratchcache)++;
+#endif
+				result = DFS_ReadSector(volinfo->unit, scratch, *scratchcache, 1);
+				if (DFS_OK == result) {
+					// Odd cluster: High 12 bits being set
+					if (cluster & 1) {
+						scratch[0] = new_contents & 0xff00;
+					}
+					// Even cluster: Low 12 bits being set
+					else {
+						scratch[0] = (scratch[0] & 0xf0) | (new_contents & 0x0f);
+					}
+					result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
+					// mirror the FAT into copy 2
+					if (DFS_OK == result)
+						result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
+				}
+				else {
+					// avoid anyone assuming that this cache value is still valid, which
+					// might cause disk corruption
+					*scratchcache = 0;
+				}
+			}
+		} // if (offset == SECTOR_SIZE - 1)
+
+		// Not a sector boundary. But we still have to worry about if it's an odd
+		// or even cluster number.
+		else {
+			// Odd cluster: High 12 bits being set
+			if (cluster & 1) {
+				scratch[offset] = (scratch[offset] & 0x0f) | (new_contents & 0xf0);
+				scratch[offset+1] = new_contents & 0xff00;
+			}
+			// Even cluster: Low 12 bits being set
+			else {
+				scratch[offset] = new_contents & 0xff;
+				scratch[offset+1] = (scratch[offset+1] & 0xf0) | (new_contents & 0x0f);
+			}
+			result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
+			// mirror the FAT into copy 2
+			if (DFS_OK == result)
+				result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
+		}
+	}
+	else if (volinfo->filesystem == FAT16) {
+		scratch[offset] = (new_contents & 0xff);
+		scratch[offset+1] = (new_contents & 0xff00) >> 8;
+		result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
+		// mirror the FAT into copy 2
+		if (DFS_OK == result)
+			result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
+	}
+	else if (volinfo->filesystem == FAT32) {
+		scratch[offset] = (new_contents & 0xff);
+		scratch[offset+1] = (new_contents & 0xff00) >> 8;
+		scratch[offset+2] = (new_contents & 0xff0000) >> 16;
+		scratch[offset+3] = (scratch[offset+3] & 0xf0) | ((new_contents & 0x0f000000) >> 24);
+		// Note well from the above: Per Microsoft's guidelines we preserve the upper
+		// 4 bits of the FAT32 cluster value. It's unclear what these bits will be used
+		// for; in every example I've encountered they are always zero.
+		result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1);
+		// mirror the FAT into copy 2
+		if (DFS_OK == result)
+			result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1);
+	}
+	else
+		result = DFS_ERRMISC;
+
+	return result;
+}
+
+/*
+	Convert a filename element from canonical (8.3) to directory entry (11) form
+	src must point to the first non-separator character.
+	dest must point to a 12-byte buffer.
+*/
+uint8_t *DFS_CanonicalToDir(uint8_t *dest, uint8_t *src)
+{
+	uint8_t *destptr = dest;
+
+	memset(dest, ' ', 11);
+	dest[11] = 0;
+
+	while (*src && (*src != DIR_SEPARATOR) && (destptr - dest < 11)) {
+		if (*src >= 'a' && *src <='z') {
+			*destptr++ = (*src - 'a') + 'A';
+			src++;
+		}
+		else if (*src == '.') {
+			src++;
+			destptr = dest + 8;
+		}
+		else {
+			*destptr++ = *src++;
+		}
+	}
+
+	return dest;
+}
+
+/*
+	Find the first unused FAT entry
+	You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO
+	Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired
+	FAT entry.
+	Returns FAT32 bad_sector (0x0ffffff7) if there is no free cluster available
+*/
+uint32_t DFS_GetFreeFAT(PVOLINFO volinfo, uint8_t *scratch)
+{
+	uint32_t i, result = 0xffffffff, scratchcache = 0;
+	
+	// Search starts at cluster 2, which is the first usable cluster
+	// NOTE: This search can't terminate at a bad cluster, because there might
+	// legitimately be bad clusters on the disk.
+	for (i=2; i < volinfo->numclusters; i++) {
+		result = DFS_GetFAT(volinfo, scratch, &scratchcache, i);
+		if (!result) {
+			return i;
+		}
+	}
+	return 0x0ffffff7;		// Can't find a free cluster
+}
+
+
+/*
+	Open a directory for enumeration by DFS_GetNextDirEnt
+	You must supply a populated VOLINFO (see DFS_GetVolInfo)
+	The empty string or a string containing only the directory separator are
+	considered to be the root directory.
+	Returns 0 OK, nonzero for any error.
+*/
+uint32_t DFS_OpenDir(PVOLINFO volinfo, uint8_t *dirname, PDIRINFO dirinfo)
+{
+	// Default behavior is a regular search for existing entries
+	dirinfo->flags = 0;
+
+	if (!strlen((char *) dirname) || (strlen((char *) dirname) == 1 && dirname[0] == DIR_SEPARATOR)) {
+		if (volinfo->filesystem == FAT32) {
+			dirinfo->currentcluster = volinfo->rootdir;
+			dirinfo->currentsector = 0;
+			dirinfo->currententry = 0;
+
+			// read first sector of directory
+			return DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((volinfo->rootdir - 2) * volinfo->secperclus), 1);
+		}
+		else {
+			dirinfo->currentcluster = 0;
+			dirinfo->currentsector = 0;
+			dirinfo->currententry = 0;
+
+			// read first sector of directory
+			return DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir, 1);
+		}
+	}
+
+	// This is not the root directory. We need to find the start of this subdirectory.
+	// We do this by devious means, using our own companion function DFS_GetNext.
+	else {
+		uint8_t tmpfn[12];
+		uint8_t *ptr = dirname;
+		uint32_t result;
+		DIRENT de;
+
+		if (volinfo->filesystem == FAT32) {
+			dirinfo->currentcluster = volinfo->rootdir;
+			dirinfo->currentsector = 0;
+			dirinfo->currententry = 0;
+
+			// read first sector of directory
+			if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((volinfo->rootdir - 2) * volinfo->secperclus), 1))
+				return DFS_ERRMISC;
+		}
+		else {
+			dirinfo->currentcluster = 0;
+			dirinfo->currentsector = 0;
+			dirinfo->currententry = 0;
+
+			// read first sector of directory
+			if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir, 1))
+				return DFS_ERRMISC;
+		}
+
+		// skip leading path separators
+		while (*ptr == DIR_SEPARATOR && *ptr)
+			ptr++;
+
+		// Scan the path from left to right, finding the start cluster of each entry
+		// Observe that this code is inelegant, but obviates the need for recursion.
+		while (*ptr) {
+			DFS_CanonicalToDir(tmpfn, ptr);
+
+			de.name[0] = 0;
+
+			do {
+				result = DFS_GetNext(volinfo, dirinfo, &de);
+			} while (!result && memcmp(de.name, tmpfn, 11));
+
+			if (!memcmp(de.name, tmpfn, 11) && ((de.attr & ATTR_DIRECTORY) == ATTR_DIRECTORY)) {
+				if (volinfo->filesystem == FAT32) {
+					dirinfo->currentcluster = (uint32_t) de.startclus_l_l |
+					  ((uint32_t) de.startclus_l_h) << 8 |
+					  ((uint32_t) de.startclus_h_l) << 16 |
+					  ((uint32_t) de.startclus_h_h) << 24;
+				}
+				else {
+					dirinfo->currentcluster = (uint32_t) de.startclus_l_l |
+					  ((uint32_t) de.startclus_l_h) << 8;
+				}
+				dirinfo->currentsector = 0;
+				dirinfo->currententry = 0;
+
+				if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((dirinfo->currentcluster - 2) * volinfo->secperclus), 1))
+					return DFS_ERRMISC;
+			}
+			else if (!memcmp(de.name, tmpfn, 11) && !(de.attr & ATTR_DIRECTORY))
+				return DFS_NOTFOUND;
+
+			// seek to next item in list
+			while (*ptr != DIR_SEPARATOR && *ptr)
+				ptr++;
+			if (*ptr == DIR_SEPARATOR)
+				ptr++;
+		}
+
+		if (!dirinfo->currentcluster)
+			return DFS_NOTFOUND;
+	}
+	return DFS_OK;
+}
+
+/*
+	Get next entry in opened directory structure. Copies fields into the dirent
+	structure, updates dirinfo. Note that it is the _caller's_ responsibility to
+	handle the '.' and '..' entries.
+	A deleted file will be returned as a NULL entry (first char of filename=0)
+	by this code. Filenames beginning with 0x05 will be translated to 0xE5
+	automatically. Long file name entries will be returned as NULL.
+	returns DFS_EOF if there are no more entries, DFS_OK if this entry is valid,
+	or DFS_ERRMISC for a media error
+*/
+uint32_t DFS_GetNext(PVOLINFO volinfo, PDIRINFO dirinfo, PDIRENT dirent)
+{
+	uint32_t tempint;	// required by DFS_GetFAT
+
+	// Do we need to read the next sector of the directory?
+	if (dirinfo->currententry >= SECTOR_SIZE / sizeof(DIRENT)) {
+		dirinfo->currententry = 0;
+		dirinfo->currentsector++;
+
+		// Root directory; special case handling 
+		// Note that currentcluster will only ever be zero if both:
+		// (a) this is the root directory, and
+		// (b) we are on a FAT12/16 volume, where the root dir can't be expanded
+		if (dirinfo->currentcluster == 0) {
+			// Trying to read past end of root directory?
+			if (dirinfo->currentsector * (SECTOR_SIZE / sizeof(DIRENT)) >= volinfo->rootentries)
+				return DFS_EOF;
+
+			// Otherwise try to read the next sector
+			if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir + dirinfo->currentsector, 1))
+				return DFS_ERRMISC;
+		}
+
+		// Normal handling
+		else {
+			if (dirinfo->currentsector >= volinfo->secperclus) {
+				dirinfo->currentsector = 0;
+				if ((dirinfo->currentcluster >= 0xff7 &&  volinfo->filesystem == FAT12) ||
+				  (dirinfo->currentcluster >= 0xfff7 &&  volinfo->filesystem == FAT16) ||
+				  (dirinfo->currentcluster >= 0x0ffffff7 &&  volinfo->filesystem == FAT32)) {
+				  
+				  	// We are at the end of the directory chain. If this is a normal
+				  	// find operation, we should indicate that there is nothing more
+				  	// to see.
+				  	if (!(dirinfo->flags & DFS_DI_BLANKENT))
+						return DFS_EOF;
+					
+					// On the other hand, if this is a "find free entry" search,
+					// we need to tell the caller to allocate a new cluster
+					else
+						return DFS_ALLOCNEW;
+				}
+				dirinfo->currentcluster = DFS_GetFAT(volinfo, dirinfo->scratch, &tempint, dirinfo->currentcluster);
+			}
+			if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((dirinfo->currentcluster - 2) * volinfo->secperclus) + dirinfo->currentsector, 1))
+				return DFS_ERRMISC;
+		}
+	}
+
+	memcpy(dirent, &(((PDIRENT) dirinfo->scratch)[dirinfo->currententry]), sizeof(DIRENT));
+
+	if (dirent->name[0] == 0) {		// no more files in this directory
+		// If this is a "find blank" then we can reuse this name.
+		if (dirinfo->flags & DFS_DI_BLANKENT)
+			return DFS_OK;
+		else
+			return DFS_EOF;
+	}
+
+	if (dirent->name[0] == 0xe5)	// handle deleted file entries
+		dirent->name[0] = 0;
+	else if ((dirent->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME)
+		dirent->name[0] = 0;
+	else if (dirent->name[0] == 0x05)	// handle kanji filenames beginning with 0xE5
+		dirent->name[0] = 0xe5;
+
+	dirinfo->currententry++;
+
+	return DFS_OK;
+}
+
+/*
+	INTERNAL
+	Find a free directory entry in the directory specified by path
+	This function MAY cause a disk write if it is necessary to extend the directory
+	size.
+	Note - di.scratch must be preinitialized to point to a sector scratch buffer
+	de is a scratch structure
+	Returns DFS_ERRMISC if a new entry could not be located or created
+	de is updated with the same return information you would expect from DFS_GetNext
+*/
+uint32_t DFS_GetFreeDirEnt(PVOLINFO volinfo, uint8_t *path, PDIRINFO di, PDIRENT de)
+{
+	uint32_t tempclus,i;
+
+	if (DFS_OpenDir(volinfo, path, di))
+		return DFS_NOTFOUND;
+
+	// Set "search for empty" flag so DFS_GetNext knows what we're doing
+	di->flags |= DFS_DI_BLANKENT;
+
+	// We seek through the directory looking for an empty entry
+	// Note we are reusing tempclus as a temporary result holder.
+	tempclus = 0;	
+	do {
+		tempclus = DFS_GetNext(volinfo, di, de);
+
+		// Empty entry found
+		if (tempclus == DFS_OK && (!de->name[0])) {
+			return DFS_OK;
+		}
+
+		// End of root directory reached
+		else if (tempclus == DFS_EOF)
+			return DFS_ERRMISC;
+			
+		else if (tempclus == DFS_ALLOCNEW) {
+			tempclus = DFS_GetFreeFAT(volinfo, di->scratch);
+			if (tempclus == 0x0ffffff7)
+				return DFS_ERRMISC;
+
+			// write out zeroed sectors to the new cluster
+			memset(di->scratch, 0, SECTOR_SIZE);
+			for (i=0;i<volinfo->secperclus;i++) {
+				if (DFS_WriteSector(volinfo->unit, di->scratch, volinfo->dataarea + ((tempclus - 2) * volinfo->secperclus) + i, 1))
+					return DFS_ERRMISC;
+			}
+			// Point old end cluster to newly allocated cluster
+			i = 0;
+			DFS_SetFAT(volinfo, di->scratch, &i, di->currentcluster, tempclus);
+
+			// Update DIRINFO so caller knows where to place the new file			
+			di->currentcluster = tempclus;
+			di->currentsector = 0;
+			di->currententry = 1;	// since the code coming after this expects to subtract 1
+			
+			// Mark newly allocated cluster as end of chain			
+			switch(volinfo->filesystem) {
+				case FAT12:		tempclus = 0xff8;	break;
+				case FAT16:		tempclus = 0xfff8;	break;
+				case FAT32:		tempclus = 0x0ffffff8;	break;
+				default:		return DFS_ERRMISC;
+			}
+			DFS_SetFAT(volinfo, di->scratch, &i, di->currentcluster, tempclus);
+		}
+	} while (!tempclus);
+
+	// We shouldn't get here
+	return DFS_ERRMISC;
+}
+
+/*
+	Open a file for reading or writing. You supply populated VOLINFO, a path to the file,
+	mode (DFS_READ or DFS_WRITE) and an empty fileinfo structure. You also need to
+	provide a pointer to a sector-sized scratch buffer.
+	Returns various DFS_* error states. If the result is DFS_OK, fileinfo can be used
+	to access the file from this point on.
+*/
+uint32_t DFS_OpenFile(PVOLINFO volinfo, uint8_t *path, uint8_t mode, uint8_t *scratch, PFILEINFO fileinfo)
+{
+	uint8_t tmppath[MAX_PATH];
+	uint8_t filename[12];
+	uint8_t *p;
+	DIRINFO di;
+	DIRENT de;
+
+	// larwe 2006-09-16 +1 zero out file structure
+	memset(fileinfo, 0, sizeof(FILEINFO));
+
+	// save access mode
+	fileinfo->mode = mode;
+
+	// Get a local copy of the path. If it's longer than MAX_PATH, abort.
+	strncpy((char *) tmppath, (char *) path, MAX_PATH);
+	tmppath[MAX_PATH - 1] = 0;
+	if (strcmp((char *) path,(char *) tmppath)) {
+		return DFS_PATHLEN;
+	}
+
+	// strip leading path separators
+	while (tmppath[0] == DIR_SEPARATOR)
+		strcpy((char *) tmppath, (char *) tmppath + 1);
+
+	// Parse filename off the end of the supplied path
+	p = tmppath;
+	while (*(p++));
+
+	p--;
+	while (p > tmppath && *p != DIR_SEPARATOR) // larwe 9/16/06 ">=" to ">" bugfix
+		p--;
+	if (*p == DIR_SEPARATOR)
+		p++;
+
+	DFS_CanonicalToDir(filename, p);
+
+	if (p > tmppath)
+		p--;
+	if (*p == DIR_SEPARATOR || p == tmppath) // larwe 9/16/06 +"|| p == tmppath" bugfix
+		*p = 0;
+
+	// At this point, if our path was MYDIR/MYDIR2/FILE.EXT, filename = "FILE    EXT" and
+	// tmppath = "MYDIR/MYDIR2".
+	di.scratch = scratch;
+	if (DFS_OpenDir(volinfo, tmppath, &di))
+		return DFS_NOTFOUND;
+
+	while (!DFS_GetNext(volinfo, &di, &de)) {
+		if (!memcmp(de.name, filename, 11)) {
+			// You can't use this function call to open a directory.
+			if (de.attr & ATTR_DIRECTORY)
+				return DFS_NOTFOUND;
+
+			fileinfo->volinfo = volinfo;
+			fileinfo->pointer = 0;
+			// The reason we store this extra info about the file is so that we can
+			// speedily update the file size, modification date, etc. on a file that is
+			// opened for writing.
+			if (di.currentcluster == 0)
+				fileinfo->dirsector = volinfo->rootdir + di.currentsector;
+			else
+				fileinfo->dirsector = volinfo->dataarea + ((di.currentcluster - 2) * volinfo->secperclus) + di.currentsector;
+			fileinfo->diroffset = di.currententry - 1;
+			if (volinfo->filesystem == FAT32) {
+				fileinfo->cluster = (uint32_t) de.startclus_l_l |
+				  ((uint32_t) de.startclus_l_h) << 8 |
+				  ((uint32_t) de.startclus_h_l) << 16 |
+				  ((uint32_t) de.startclus_h_h) << 24;
+			}
+			else {
+				fileinfo->cluster = (uint32_t) de.startclus_l_l |
+				  ((uint32_t) de.startclus_l_h) << 8;
+			}
+			fileinfo->firstcluster = fileinfo->cluster;
+			fileinfo->filelen = (uint32_t) de.filesize_0 |
+			  ((uint32_t) de.filesize_1) << 8 |
+			  ((uint32_t) de.filesize_2) << 16 |
+			  ((uint32_t) de.filesize_3) << 24;
+
+			return DFS_OK;
+		}
+	}
+
+	// At this point, we KNOW the file does not exist. If the file was opened
+	// with write access, we can create it.
+	if (mode & DFS_WRITE) {
+		uint32_t cluster, temp;
+
+		// Locate or create a directory entry for this file
+		if (DFS_OK != DFS_GetFreeDirEnt(volinfo, tmppath, &di, &de))
+			return DFS_ERRMISC;
+
+		// put sane values in the directory entry
+		memset(&de, 0, sizeof(de));
+		memcpy(de.name, filename, 11);
+		de.crttime_l = 0x20;	// 01:01:00am, Jan 1, 2006.
+		de.crttime_h = 0x08;
+		de.crtdate_l = 0x11;
+		de.crtdate_h = 0x34;
+		de.lstaccdate_l = 0x11;
+		de.lstaccdate_h = 0x34;
+		de.wrttime_l = 0x20;
+		de.wrttime_h = 0x08;
+		de.wrtdate_l = 0x11;
+		de.wrtdate_h = 0x34;
+
+		// allocate a starting cluster for the directory entry
+		cluster = DFS_GetFreeFAT(volinfo, scratch);
+
+		de.startclus_l_l = cluster & 0xff;
+		de.startclus_l_h = (cluster & 0xff00) >> 8;
+		de.startclus_h_l = (cluster & 0xff0000) >> 16;
+		de.startclus_h_h = (cluster & 0xff000000) >> 24;
+
+		// update FILEINFO for our caller's sake
+		fileinfo->volinfo = volinfo;
+		fileinfo->pointer = 0;
+		// The reason we store this extra info about the file is so that we can
+		// speedily update the file size, modification date, etc. on a file that is
+		// opened for writing.
+		if (di.currentcluster == 0)
+			fileinfo->dirsector = volinfo->rootdir + di.currentsector;
+		else
+			fileinfo->dirsector = volinfo->dataarea + ((di.currentcluster - 2) * volinfo->secperclus) + di.currentsector;
+#ifdef USE_GMHFIX
+		fileinfo->diroffset = di.currententry;
+#else
+		fileinfo->diroffset = di.currententry - 1;
+#endif
+		fileinfo->cluster = cluster;
+		fileinfo->firstcluster = cluster;
+		fileinfo->filelen = 0;
+		
+		// write the directory entry
+		// note that we no longer have the sector containing the directory entry,
+		// tragically, so we have to re-read it
+		if (DFS_ReadSector(volinfo->unit, scratch, fileinfo->dirsector, 1))
+			return DFS_ERRMISC;
+#ifdef USE_GMHFIX
+		memcpy(&(((PDIRENT) scratch)[di.currententry]), &de, sizeof(DIRENT));
+#else
+		memcpy(&(((PDIRENT) scratch)[di.currententry-1]), &de, sizeof(DIRENT));
+#endif
+		if (DFS_WriteSector(volinfo->unit, scratch, fileinfo->dirsector, 1))
+			return DFS_ERRMISC;
+
+		// Mark newly allocated cluster as end of chain			
+		switch(volinfo->filesystem) {
+			case FAT12:		cluster = 0xff8;	break;
+			case FAT16:		cluster = 0xfff8;	break;
+			case FAT32:		cluster = 0x0ffffff8;	break;
+			default:		return DFS_ERRMISC;
+		}
+		temp = 0;
+		DFS_SetFAT(volinfo, scratch, &temp, fileinfo->cluster, cluster);
+
+		return DFS_OK;
+	}
+
+	return DFS_NOTFOUND;
+}
+
+/*
+	Read an open file
+	You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a
+	pointer to a SECTOR_SIZE scratch buffer.
+	Note that returning DFS_EOF is not an error condition. This function updates the
+	successcount field with the number of bytes actually read.
+*/
+uint32_t DFS_ReadFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len)
+{
+	uint32_t remain;
+	uint32_t result = DFS_OK;
+	uint32_t sector;
+	uint32_t bytesread;
+
+	// Don't try to read past EOF
+	if (len > fileinfo->filelen - fileinfo->pointer)
+		len = fileinfo->filelen - fileinfo->pointer;
+
+	remain = len;
+	*successcount = 0;
+
+	while (remain && result == DFS_OK) {
+		// This is a bit complicated. The sector we want to read is addressed at a cluster
+		// granularity by the fileinfo->cluster member. The file pointer tells us how many
+		// extra sectors to add to that number.
+		sector = fileinfo->volinfo->dataarea +
+		  ((fileinfo->cluster - 2) * fileinfo->volinfo->secperclus) +
+		  div(div(fileinfo->pointer,fileinfo->volinfo->secperclus * SECTOR_SIZE).rem, SECTOR_SIZE).quot;
+
+		// Case 1 - File pointer is not on a sector boundary
+		if (div(fileinfo->pointer, SECTOR_SIZE).rem) {
+			uint16_t tempreadsize;
+
+			// We always have to go through scratch in this case
+			result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
+
+			// This is the number of bytes that we actually care about in the sector
+			// just read.
+			tempreadsize = SECTOR_SIZE - (div(fileinfo->pointer, SECTOR_SIZE).rem);
+					
+			// Case 1A - We want the entire remainder of the sector. After this
+			// point, all passes through the read loop will be aligned on a sector
+			// boundary, which allows us to go through the optimal path 2A below.
+		   	if (remain >= tempreadsize) {
+				memcpy(buffer, scratch + (SECTOR_SIZE - tempreadsize), tempreadsize);
+				bytesread = tempreadsize;
+				buffer += tempreadsize;
+				fileinfo->pointer += tempreadsize;
+				remain -= tempreadsize;
+			}
+			// Case 1B - This read concludes the file read operation
+			else {
+				memcpy(buffer, scratch + (SECTOR_SIZE - tempreadsize), remain);
+
+				buffer += remain;
+				fileinfo->pointer += remain;
+				bytesread = remain;
+				remain = 0;
+			}
+		}
+		// Case 2 - File pointer is on sector boundary
+		else {
+			// Case 2A - We have at least one more full sector to read and don't have
+			// to go through the scratch buffer. You could insert optimizations here to
+			// read multiple sectors at a time, if you were thus inclined (note that
+			// the maximum multi-read you could perform is a single cluster, so it would
+			// be advantageous to have code similar to case 1A above that would round the
+			// pointer to a cluster boundary the first pass through, so all subsequent
+			// [large] read requests would be able to go a cluster at a time).
+			if (remain >= SECTOR_SIZE) {
+				result = DFS_ReadSector(fileinfo->volinfo->unit, buffer, sector, 1);
+				remain -= SECTOR_SIZE;
+				buffer += SECTOR_SIZE;
+				fileinfo->pointer += SECTOR_SIZE;
+				bytesread = SECTOR_SIZE;
+			}
+			// Case 2B - We are only reading a partial sector
+			else {
+				result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
+				memcpy(buffer, scratch, remain);
+				buffer += remain;
+				fileinfo->pointer += remain;
+				bytesread = remain;
+				remain = 0;
+			}
+		}
+
+		*successcount += bytesread;
+
+		// check to see if we stepped over a cluster boundary
+		if (div(fileinfo->pointer - bytesread, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot !=
+		  div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
+			// An act of minor evil - we use bytesread as a scratch integer, knowing that
+			// its value is not used after updating *successcount above
+			bytesread = 0;
+			if (((fileinfo->volinfo->filesystem == FAT12) && (fileinfo->cluster >= 0xff8)) ||
+			  ((fileinfo->volinfo->filesystem == FAT16) && (fileinfo->cluster >= 0xfff8)) ||
+			  ((fileinfo->volinfo->filesystem == FAT32) && (fileinfo->cluster >= 0x0ffffff8)))
+				result = DFS_EOF;
+			else
+				fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &bytesread, fileinfo->cluster);
+		}
+	}
+	
+	return result;
+}
+
+/*
+	Seek file pointer to a given position
+	This function does not return status - refer to the fileinfo->pointer value
+	to see where the pointer wound up.
+	Requires a SECTOR_SIZE scratch buffer
+*/
+void DFS_Seek(PFILEINFO fileinfo, uint32_t offset, uint8_t *scratch)
+{
+	uint32_t tempint;
+
+	// larwe 9/16/06 bugfix split case 0a/0b and changed fallthrough handling
+	// Case 0a - Return immediately for degenerate case
+	if (offset == fileinfo->pointer) {
+		return;
+	}
+	
+	// Case 0b - Don't allow the user to seek past the end of the file
+	if (offset > fileinfo->filelen) {
+		offset = fileinfo->filelen;
+		// NOTE NO RETURN HERE!
+	}
+
+	// Case 1 - Simple rewind to start
+	// Note _intentional_ fallthrough from Case 0b above
+	if (offset == 0) {
+		fileinfo->cluster = fileinfo->firstcluster;
+		fileinfo->pointer = 0;
+		return;		// larwe 9/16/06 +1 bugfix
+	}
+	// Case 2 - Seeking backwards. Need to reset and seek forwards
+	else if (offset < fileinfo->pointer) {
+		fileinfo->cluster = fileinfo->firstcluster;
+		fileinfo->pointer = 0;
+		// NOTE NO RETURN HERE!
+	}
+
+	// Case 3 - Seeking forwards
+	// Note _intentional_ fallthrough from Case 2 above
+
+	// Case 3a - Seek size does not cross cluster boundary - 
+	// very simple case
+	// larwe 9/16/06 changed .rem to .quot in both div calls, bugfix
+	if (div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot ==
+	  div(fileinfo->pointer + offset, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
+		fileinfo->pointer = offset;
+	}
+	// Case 3b - Seeking across cluster boundary(ies)
+	else {
+		// round file pointer down to cluster boundary
+		fileinfo->pointer = div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot *
+		  fileinfo->volinfo->secperclus * SECTOR_SIZE;
+
+		// seek by clusters
+		// larwe 9/30/06 bugfix changed .rem to .quot in both div calls
+		while (div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot !=
+		  div(fileinfo->pointer + offset, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
+
+			fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &tempint, fileinfo->cluster);
+			// Abort if there was an error
+			if (fileinfo->cluster == 0x0ffffff7) {
+				fileinfo->pointer = 0;
+				fileinfo->cluster = fileinfo->firstcluster;
+				return;
+			}
+			fileinfo->pointer += SECTOR_SIZE * fileinfo->volinfo->secperclus;
+		}
+
+		// since we know the cluster is right, we have no more work to do
+		fileinfo->pointer = offset;
+	}
+}
+
+/*
+	Delete a file
+	scratch must point to a sector-sized buffer
+*/
+uint32_t DFS_UnlinkFile(PVOLINFO volinfo, uint8_t *path, uint8_t *scratch)
+{
+	FILEINFO fi;
+	uint32_t cache = 0;
+	uint32_t tempclus;
+
+	// DFS_OpenFile gives us all the information we need to delete it
+	if (DFS_OK != DFS_OpenFile(volinfo, path, DFS_READ, scratch, &fi))
+		return DFS_NOTFOUND;
+
+	// First, read the directory sector and delete that entry
+	if (DFS_ReadSector(volinfo->unit, scratch, fi.dirsector, 1))
+		return DFS_ERRMISC;
+	((PDIRENT) scratch)[fi.diroffset].name[0] = 0xe5;
+	if (DFS_WriteSector(volinfo->unit, scratch, fi.dirsector, 1))
+		return DFS_ERRMISC;
+
+	// Now follow the cluster chain to free the file space
+	while (!((volinfo->filesystem == FAT12 && fi.firstcluster >= 0x0ff7) ||
+	  (volinfo->filesystem == FAT16 && fi.firstcluster >= 0xfff7) ||
+	  (volinfo->filesystem == FAT32 && fi.firstcluster >= 0x0ffffff7))) {
+		tempclus = fi.firstcluster;
+
+		fi.firstcluster = DFS_GetFAT(volinfo, scratch, &cache, fi.firstcluster);
+		DFS_SetFAT(volinfo, scratch, &cache, tempclus, 0);
+
+	}
+	return DFS_OK;
+}
+
+
+/*
+	Write an open file
+	You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a
+	pointer to a SECTOR_SIZE scratch buffer.
+	This function updates the successcount field with the number of bytes actually written.
+*/
+uint32_t DFS_WriteFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len)
+{
+	uint32_t remain;
+	uint32_t result = DFS_OK;
+	uint32_t sector;
+	uint32_t byteswritten;
+
+	// Don't allow writes to a file that's open as readonly
+	if (!(fileinfo->mode & DFS_WRITE))
+		return DFS_ERRMISC;
+
+	remain = len;
+	*successcount = 0;
+
+	while (remain && result == DFS_OK) {
+		// This is a bit complicated. The sector we want to read is addressed at a cluster
+		// granularity by the fileinfo->cluster member. The file pointer tells us how many
+		// extra sectors to add to that number.
+		sector = fileinfo->volinfo->dataarea +
+		  ((fileinfo->cluster - 2) * fileinfo->volinfo->secperclus) +
+		  div(div(fileinfo->pointer,fileinfo->volinfo->secperclus * SECTOR_SIZE).rem, SECTOR_SIZE).quot;
+
+		// Case 1 - File pointer is not on a sector boundary
+		if (div(fileinfo->pointer, SECTOR_SIZE).rem) {
+			uint16_t tempsize;
+
+			// We always have to go through scratch in this case
+			result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
+
+			// This is the number of bytes that we don't want to molest in the
+			// scratch sector just read.
+			tempsize = div(fileinfo->pointer, SECTOR_SIZE).rem;
+					
+			// Case 1A - We are writing the entire remainder of the sector. After
+			// this point, all passes through the read loop will be aligned on a
+			// sector boundary, which allows us to go through the optimal path
+			// 2A below.
+		   	if (remain >= SECTOR_SIZE - tempsize) {
+				memcpy(scratch + tempsize, buffer, SECTOR_SIZE - tempsize);
+				if (!result)
+					result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1);
+
+				byteswritten = SECTOR_SIZE - tempsize;
+				buffer += SECTOR_SIZE - tempsize;
+				fileinfo->pointer += SECTOR_SIZE - tempsize;
+				if (fileinfo->filelen < fileinfo->pointer) {
+					fileinfo->filelen = fileinfo->pointer;
+				}
+				remain -= SECTOR_SIZE - tempsize;
+			}
+			// Case 1B - This concludes the file write operation
+			else {
+				memcpy(scratch + tempsize, buffer, remain);
+				if (!result)
+					result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1);
+
+				buffer += remain;
+				fileinfo->pointer += remain;
+				if (fileinfo->filelen < fileinfo->pointer) {
+					fileinfo->filelen = fileinfo->pointer;
+				}
+				byteswritten = remain;
+				remain = 0;
+			}
+		} // case 1
+		// Case 2 - File pointer is on sector boundary
+		else {
+			// Case 2A - We have at least one more full sector to write and don't have
+			// to go through the scratch buffer. You could insert optimizations here to
+			// write multiple sectors at a time, if you were thus inclined. Refer to
+			// similar notes in DFS_ReadFile.
+			if (remain >= SECTOR_SIZE) {
+				result = DFS_WriteSector(fileinfo->volinfo->unit, buffer, sector, 1);
+				remain -= SECTOR_SIZE;
+				buffer += SECTOR_SIZE;
+				fileinfo->pointer += SECTOR_SIZE;
+				if (fileinfo->filelen < fileinfo->pointer) {
+					fileinfo->filelen = fileinfo->pointer;
+				}
+				byteswritten = SECTOR_SIZE;
+			}
+			// Case 2B - We are only writing a partial sector and potentially need to
+			// go through the scratch buffer.
+			else {
+				// If the current file pointer is not yet at or beyond the file
+				// length, we are writing somewhere in the middle of the file and
+				// need to load the original sector to do a read-modify-write.
+				if (fileinfo->pointer < fileinfo->filelen) {
+					result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1);
+					if (!result) {
+						memcpy(scratch, buffer, remain);
+						result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1);
+					}
+				}
+				else {
+					result = DFS_WriteSector(fileinfo->volinfo->unit, buffer, sector, 1);
+				}
+
+				buffer += remain;
+				fileinfo->pointer += remain;
+				if (fileinfo->filelen < fileinfo->pointer) {
+					fileinfo->filelen = fileinfo->pointer;
+				}
+				byteswritten = remain;
+				remain = 0;
+			}
+		}
+
+		*successcount += byteswritten;
+
+		// check to see if we stepped over a cluster boundary
+		if (div(fileinfo->pointer - byteswritten, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot !=
+		  div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) {
+		  	uint32_t lastcluster;
+
+		  	// We've transgressed into another cluster. If we were already at EOF,
+		  	// we need to allocate a new cluster.
+			// An act of minor evil - we use byteswritten as a scratch integer, knowing
+			// that its value is not used after updating *successcount above
+			byteswritten = 0;
+
+			lastcluster = fileinfo->cluster;
+			fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &byteswritten, fileinfo->cluster);
+			
+			// Allocate a new cluster?
+			if (((fileinfo->volinfo->filesystem == FAT12) && (fileinfo->cluster >= 0xff8)) ||
+			  ((fileinfo->volinfo->filesystem == FAT16) && (fileinfo->cluster >= 0xfff8)) ||
+			  ((fileinfo->volinfo->filesystem == FAT32) && (fileinfo->cluster >= 0x0ffffff8))) {
+			  	uint32_t tempclus;
+
+				tempclus = DFS_GetFreeFAT(fileinfo->volinfo, scratch);
+				byteswritten = 0; // invalidate cache
+				if (tempclus == 0x0ffffff7)
+					return DFS_ERRMISC;
+
+				// Link new cluster onto file
+				DFS_SetFAT(fileinfo->volinfo, scratch, &byteswritten, lastcluster, tempclus);
+				fileinfo->cluster = tempclus;
+
+				// Mark newly allocated cluster as end of chain			
+				switch(fileinfo->volinfo->filesystem) {
+					case FAT12:		tempclus = 0xff8;	break;
+					case FAT16:		tempclus = 0xfff8;	break;
+					case FAT32:		tempclus = 0x0ffffff8;	break;
+					default:		return DFS_ERRMISC;
+				}
+				DFS_SetFAT(fileinfo->volinfo, scratch, &byteswritten, fileinfo->cluster, tempclus);
+
+				result = DFS_OK;
+			}
+			// No else clause is required.
+		}
+	}
+	
+	// Update directory entry
+		if (DFS_ReadSector(fileinfo->volinfo->unit, scratch, fileinfo->dirsector, 1))
+			return DFS_ERRMISC;
+		((PDIRENT) scratch)[fileinfo->diroffset].filesize_0 = fileinfo->filelen & 0xff;
+		((PDIRENT) scratch)[fileinfo->diroffset].filesize_1 = (fileinfo->filelen & 0xff00) >> 8;
+		((PDIRENT) scratch)[fileinfo->diroffset].filesize_2 = (fileinfo->filelen & 0xff0000) >> 16;
+		((PDIRENT) scratch)[fileinfo->diroffset].filesize_3 = (fileinfo->filelen & 0xff000000) >> 24;
+		if (DFS_WriteSector(fileinfo->volinfo->unit, scratch, fileinfo->dirsector, 1))
+			return DFS_ERRMISC;
+	return result;
+}
+
+/*************************************************************************
+ * 
+ * The remainder of this file has been added as a result of the dosfs
+ * distribution being used in MicroMonitor.
+ * This is not part of the original dosfs.c as distributed by Lewin Edwards.
+ *
+ *************************************************************************
+ */
+
+/*
+ * Convert a filename element from directory entry (11) to canonical (8.3) form
+ * 	dest must point to the first non-separator character.
+ * 	sec must point to a 12-byte buffer.
+ */
+uint8_t *DFS_DirToCanonical(uint8_t *dest, uint8_t *src)
+{
+	uint8_t *srcptr = src;
+
+   // copy filename until spaces
+   while(( srcptr -  src) <= 11)
+   {
+	  if (*srcptr == 0) break;
+
+      // name.ext seperator
+      if(( srcptr - src) == 8) *dest++ = '.';
+      if( *srcptr != ' ') *dest++ = *srcptr;
+
+      srcptr++;
+   }
+
+   // null terminate and rewind one char
+   *dest-- = 0;
+
+   // if name has no extension - kill the seperator
+   if( *dest == '.') *dest = 0;
+
+	return dest;
+}
+#endif
Index: main/common/dosfs.h
===================================================================
--- main/common/dosfs.h	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
+++ main/common/dosfs.h	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
@@ -0,0 +1,393 @@
+/*
+	DOSFS Embedded FAT-Compatible Filesystem
+	(C) 2005 Lewin A.R.W. Edwards (sysadm@zws.com)
+*/
+
+#ifndef _DOSFS_H
+#define _DOSFS_H
+
+#include <stddefs.h>
+
+//#define USE_GMHFIX	// Defined to pull in dosfs fixes from Graham Henderson
+
+
+//===================================================================
+// User-supplied functions
+uint32_t DFS_ReadSector(uint8_t unit, uint8_t *buffer, uint32_t sector, uint32_t count);
+uint32_t DFS_WriteSector(uint8_t unit, uint8_t *buffer, uint32_t sector, uint32_t count);
+
+
+//===================================================================
+// Configurable items
+#define MAX_PATH		64		// Maximum path length (increasing this will
+								// GREATLY increase stack requirements!)
+#define DIR_SEPARATOR	'/'		// character separating directory components
+
+// End of configurable items
+//===================================================================
+
+//===================================================================
+// 32-bit error codes
+#define DFS_OK			0			// no error
+#define DFS_EOF			1			// end of file (not an error)
+#define DFS_WRITEPROT	2			// volume is write protected
+#define DFS_NOTFOUND	3			// path or file not found
+#define DFS_PATHLEN		4			// path too long
+#define DFS_ALLOCNEW	5			// must allocate new directory cluster
+#define DFS_ERRMISC		0xffffffff	// generic error
+
+//===================================================================
+// File access modes
+#define DFS_READ		1			// read-only
+#define DFS_WRITE		2			// write-only
+
+//===================================================================
+// Miscellaneous constants
+#define SECTOR_SIZE		512		// sector size in bytes
+
+//===================================================================
+// Internal subformat identifiers
+#define FAT12			0
+#define FAT16			1
+#define FAT32			2
+
+//===================================================================
+// DOS attribute bits
+#define ATTR_READ_ONLY	0x01
+#define ATTR_HIDDEN		0x02
+#define ATTR_SYSTEM		0x04
+#define ATTR_VOLUME_ID	0x08
+#define ATTR_DIRECTORY	0x10
+#define ATTR_ARCHIVE	0x20
+#define ATTR_LONG_NAME	(ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)
+
+
+/*
+	Directory entry structure
+	note: if name[0] == 0xe5, this is a free dir entry
+	      if name[0] == 0x00, this is a free entry and all subsequent entries are free
+		  if name[0] == 0x05, the first character of the name is 0xe5 [a kanji nicety]
+
+	Date format: bit 0-4  = day of month (1-31)
+	             bit 5-8  = month, 1=Jan..12=Dec
+				 bit 9-15 =	count of years since 1980 (0-127)
+	Time format: bit 0-4  = 2-second count, (0-29)
+	             bit 5-10 = minutes (0-59)
+				 bit 11-15= hours (0-23)
+*/
+
+// some macros to aid in formatting file date, times. and file sizes
+#define CREATE_DAY(  _x_)  ((_x_.crtdate_l)      & 0x1f)
+#define CREATE_MON(  _x_) (((_x_.crtdate_l >> 5) & 0x07) | ((_x_.crtdate_h & 1) << 3))
+#define CREATE_YEAR( _x_)  ((_x_.crtdate_h >> 1) + 1980)
+
+#define CREATE_SEC( _x_)  (((_x_.crttime_l)      & 0x1f)  * 2)
+#define CREATE_MIN( _x_)  (((_x_.crttime_l >> 5) & 0x07) | ((_x_.crttime_h & 0x07) << 3))
+#define CREATE_HR(  _x_)   ((_x_.crttime_h >> 3))
+
+#define FILESIZE(_x_)      (int)( ( _x_.filesize_3 << 24)| ( _x_.filesize_2 << 16) | ( _x_.filesize_1 << 8) | _x_.filesize_0)
+
+
+typedef struct __attribute__ ((__packed__)) _tagDIRENT {
+	uint8_t name[11];			// filename
+	uint8_t attr;				// attributes (see ATTR_* constant definitions)
+	uint8_t reserved;			// reserved, must be 0
+	uint8_t crttimetenth;		// create time, 10ths of a second (0-199 are valid)
+	uint8_t crttime_l;			// creation time low byte
+	uint8_t crttime_h;			// creation time high byte
+	uint8_t crtdate_l;			// creation date low byte
+	uint8_t crtdate_h;			// creation date high byte
+	uint8_t lstaccdate_l;		// last access date low byte
+	uint8_t lstaccdate_h;		// last access date high byte
+	uint8_t startclus_h_l;		// high word of first cluster, low byte (FAT32)
+	uint8_t startclus_h_h;		// high word of first cluster, high byte (FAT32)
+	uint8_t wrttime_l;			// last write time low byte
+	uint8_t wrttime_h;			// last write time high byte
+	uint8_t wrtdate_l;			// last write date low byte
+	uint8_t wrtdate_h;			// last write date high byte
+	uint8_t startclus_l_l;		// low word of first cluster, low byte
+	uint8_t startclus_l_h;		// low word of first cluster, high byte
+	uint8_t filesize_0;			// file size, low byte
+	uint8_t filesize_1;			//
+	uint8_t filesize_2;			//
+	uint8_t filesize_3;			// file size, high byte
+} DIRENT, *PDIRENT;
+
+/*
+	Partition table entry structure
+*/
+typedef struct __attribute__ ((__packed__)) _tagPTINFO {
+	uint8_t		active;			// 0x80 if partition active
+	uint8_t		start_h;		// starting head
+	uint8_t		start_cs_l;		// starting cylinder and sector (low byte)
+	uint8_t		start_cs_h;		// starting cylinder and sector (high byte)
+	uint8_t		type;			// type ID byte
+	uint8_t		end_h;			// ending head
+	uint8_t		end_cs_l;		// ending cylinder and sector (low byte)
+	uint8_t		end_cs_h;		// ending cylinder and sector (high byte)
+	uint8_t		start_0;		// starting sector# (low byte)
+	uint8_t		start_1;		//
+	uint8_t		start_2;		//
+	uint8_t		start_3;		// starting sector# (high byte)
+	uint8_t		size_0;			// size of partition (low byte)
+	uint8_t		size_1;			//
+	uint8_t		size_2;			//
+	uint8_t		size_3;			// size of partition (high byte)
+} PTINFO, *PPTINFO;
+
+/*
+	Master Boot Record structure
+*/
+typedef struct __attribute__ ((__packed__)) _tagMBR {
+	uint8_t bootcode[0x1be];	// boot sector
+	PTINFO ptable[4];			// four partition table structures
+	uint8_t sig_55;				// 0x55 signature byte
+	uint8_t sig_aa;				// 0xaa signature byte
+} MBR, *PMBR;
+
+/*
+	BIOS Parameter Block structure (FAT12/16)
+*/
+typedef struct __attribute__ ((__packed__)) _tagBPB {
+	uint8_t bytepersec_l;		// bytes per sector low byte (0x00)
+	uint8_t bytepersec_h;		// bytes per sector high byte (0x02)
+	uint8_t	secperclus;			// sectors per cluster (1,2,4,8,16,32,64,128 are valid)
+	uint8_t reserved_l;			// reserved sectors low byte
+	uint8_t reserved_h;			// reserved sectors high byte
+	uint8_t numfats;			// number of FAT copies (2)
+	uint8_t rootentries_l;		// number of root dir entries low byte (0x00 normally)
+	uint8_t rootentries_h;		// number of root dir entries high byte (0x02 normally)
+	uint8_t sectors_s_l;		// small num sectors low byte
+	uint8_t sectors_s_h;		// small num sectors high byte
+	uint8_t mediatype;			// media descriptor byte
+	uint8_t secperfat_l;		// sectors per FAT low byte
+	uint8_t secperfat_h;		// sectors per FAT high byte
+	uint8_t secpertrk_l;		// sectors per track low byte
+	uint8_t secpertrk_h;		// sectors per track high byte
+	uint8_t heads_l;			// heads low byte
+	uint8_t heads_h;			// heads high byte
+	uint8_t hidden_0;			// hidden sectors low byte
+	uint8_t hidden_1;			// (note - this is the number of MEDIA sectors before
+	uint8_t hidden_2;			// first sector of VOLUME - we rely on the MBR instead)
+	uint8_t hidden_3;			// hidden sectors high byte
+	uint8_t sectors_l_0;		// large num sectors low byte
+	uint8_t sectors_l_1;		//
+	uint8_t sectors_l_2;		//
+	uint8_t sectors_l_3;		// large num sectors high byte
+} BPB, *PBPB;
+
+/*
+	Extended BIOS Parameter Block structure (FAT12/16)
+*/
+typedef struct __attribute__ ((__packed__)) _tagEBPB {
+	uint8_t unit;				// int 13h drive#
+	uint8_t head;				// archaic, used by Windows NT-class OSes for flags
+	uint8_t signature;			// 0x28 or 0x29
+	uint8_t serial_0;			// serial#
+	uint8_t serial_1;			// serial#
+	uint8_t serial_2;			// serial#
+	uint8_t serial_3;			// serial#
+	uint8_t label[11];			// volume label
+	uint8_t system[8];			// filesystem ID
+} EBPB, *PEBPB;
+
+/*
+	Extended BIOS Parameter Block structure (FAT32)
+*/
+typedef struct __attribute__ ((__packed__)) _tagEBPB32 {
+	uint8_t fatsize_0;			// big FAT size in sectors low byte
+	uint8_t fatsize_1;			//
+	uint8_t fatsize_2;			//
+	uint8_t fatsize_3;			// big FAT size in sectors high byte
+	uint8_t extflags_l;			// extended flags low byte
+	uint8_t extflags_h;			// extended flags high byte
+	uint8_t fsver_l;			// filesystem version (0x00) low byte
+	uint8_t fsver_h;			// filesystem version (0x00) high byte
+	uint8_t root_0;				// cluster of root dir, low byte
+	uint8_t root_1;				//
+	uint8_t root_2;				//
+	uint8_t root_3;				// cluster of root dir, high byte
+	uint8_t fsinfo_l;			// sector pointer to FSINFO within reserved area, low byte (2)
+	uint8_t fsinfo_h;			// sector pointer to FSINFO within reserved area, high byte (0)
+	uint8_t bkboot_l;			// sector pointer to backup boot sector within reserved area, low byte (6)
+	uint8_t bkboot_h;			// sector pointer to backup boot sector within reserved area, high byte (0)
+	uint8_t reserved[12];		// reserved, should be 0
+
+	uint8_t unit;				// int 13h drive#
+	uint8_t head;				// archaic, used by Windows NT-class OSes for flags
+	uint8_t signature;			// 0x28 or 0x29
+	uint8_t serial_0;			// serial#
+	uint8_t serial_1;			// serial#
+	uint8_t serial_2;			// serial#
+	uint8_t serial_3;			// serial#
+	uint8_t label[11];			// volume label
+	uint8_t system[8];			// filesystem ID
+} EBPB32, *PEBPB32;
+
+/*
+	Logical Boot Record structure (volume boot sector)
+*/
+typedef struct  __attribute__ ((__packed__)) _tagLBR {
+	uint8_t jump[3];			// JMP instruction
+	uint8_t oemid[8];			// OEM ID, space-padded
+	BPB bpb;					// BIOS Parameter Block
+	union {
+		EBPB ebpb;				// FAT12/16 Extended BIOS Parameter Block
+		EBPB32 ebpb32;			// FAT32 Extended BIOS Parameter Block
+	} ebpb;
+	uint8_t code[420];			// boot sector code
+	uint8_t sig_55;				// 0x55 signature byte
+	uint8_t sig_aa;				// 0xaa signature byte
+} LBR, *PLBR;
+
+/*
+	Volume information structure (Internal to DOSFS)
+*/
+typedef struct  __attribute__ ((__packed__)) _tagVOLINFO {
+	uint8_t unit;				// unit on which this volume resides
+	uint8_t filesystem;			// formatted filesystem
+
+// These two fields aren't very useful, so support for them has been commented out to
+// save memory. (Note that the "system" tag is not actually used by DOS to determine
+// filesystem type - that decision is made entirely on the basis of how many clusters
+// the drive contains. DOSFS works the same way).
+// See tag: OEMID in dosfs.c
+//	uint8_t oemid[9];			// OEM ID ASCIIZ
+//	uint8_t system[9];			// system ID ASCIIZ
+	uint8_t label[12];			// volume label ASCIIZ
+	uint32_t startsector;		// starting sector of filesystem
+	uint8_t secperclus;			// sectors per cluster
+	uint16_t reservedsecs;		// reserved sectors
+	uint32_t numsecs;			// number of sectors in volume
+	uint32_t secperfat;			// sectors per FAT
+	uint16_t rootentries;		// number of root dir entries
+
+	uint32_t numclusters;		// number of clusters on drive
+
+	// The fields below are PHYSICAL SECTOR NUMBERS.
+	uint32_t fat1;				// starting sector# of FAT copy 1
+	uint32_t rootdir;			// starting sector# of root directory (FAT12/FAT16) or cluster (FAT32)
+	uint32_t dataarea;			// starting sector# of data area (cluster #2)
+} VOLINFO, *PVOLINFO;
+
+/*
+	Flags in DIRINFO.flags
+*/
+#define DFS_DI_BLANKENT		0x01	// Searching for blank entry
+
+/*
+	Directory search structure (Internal to DOSFS)
+*/
+typedef struct  __attribute__ ((__packed__)) _tagDIRINFO {
+	uint32_t currentcluster;	// current cluster in dir
+	uint8_t currentsector;		// current sector in cluster
+	uint8_t currententry;		// current dir entry in sector
+	uint8_t *scratch;			// ptr to user-supplied scratch buffer (one sector)
+	uint8_t flags;				// internal DOSFS flags
+} DIRINFO, *PDIRINFO;
+
+/*
+	File handle structure (Internal to DOSFS)
+*/
+typedef struct  __attribute__ ((__packed__)) _tagFILEINFO {
+	PVOLINFO volinfo;			// VOLINFO used to open this file
+	uint32_t dirsector;			// physical sector containing dir entry of this file
+	uint8_t diroffset;			// # of this entry within the dir sector
+	uint8_t mode;				// mode in which this file was opened
+	uint32_t firstcluster;		// first cluster of file
+	uint32_t filelen;			// byte length of file
+
+	uint32_t cluster;			// current cluster
+	uint32_t pointer;			// current (BYTE) pointer
+} FILEINFO, *PFILEINFO;
+
+/*
+	Get starting sector# of specified partition on drive #unit
+	NOTE: This code ASSUMES an MBR on the disk.
+	scratchsector should point to a SECTOR_SIZE scratch area
+	Returns 0xffffffff for any error.
+	If pactive is non-NULL, this function also returns the partition active flag.
+	If pptype is non-NULL, this function also returns the partition type.
+	If psize is non-NULL, this function also returns the partition size.
+*/
+uint32_t DFS_GetPtnStart(uint8_t unit, uint8_t *scratchsector, uint8_t pnum, uint8_t *pactive, uint8_t *pptype, uint32_t *psize);
+
+/*
+	Retrieve volume info from BPB and store it in a VOLINFO structure
+	You must provide the unit and starting sector of the filesystem, and
+	a pointer to a sector buffer for scratch
+	Attempts to read BPB and glean information about the FS from that.
+	Returns 0 OK, nonzero for any error.
+*/
+uint32_t DFS_GetVolInfo(uint8_t unit, uint8_t *scratchsector, uint32_t startsector, PVOLINFO volinfo);
+
+/*
+	Open a directory for enumeration by DFS_GetNextDirEnt
+	You must supply a populated VOLINFO (see DFS_GetVolInfo)
+	The empty string or a string containing only the directory separator are
+	considered to be the root directory.
+	Returns 0 OK, nonzero for any error.
+*/
+uint32_t DFS_OpenDir(PVOLINFO volinfo, uint8_t *dirname, PDIRINFO dirinfo);
+
+/*
+	Get next entry in opened directory structure. Copies fields into the dirent
+	structure, updates dirinfo. Note that it is the _caller's_ responsibility to
+	handle the '.' and '..' entries.
+	A deleted file will be returned as a NULL entry (first char of filename=0)
+	by this code. Filenames beginning with 0x05 will be translated to 0xE5
+	automatically. Long file name entries will be returned as NULL.
+	returns DFS_EOF if there are no more entries, DFS_OK if this entry is valid,
+	or DFS_ERRMISC for a media error
+*/
+uint32_t DFS_GetNext(PVOLINFO volinfo, PDIRINFO dirinfo, PDIRENT dirent);
+
+/*
+	Open a file for reading or writing. You supply populated VOLINFO, a path to the file,
+	mode (DFS_READ or DFS_WRITE) and an empty fileinfo structure. You also need to
+	provide a pointer to a sector-sized scratch buffer.
+	Returns various DFS_* error states. If the result is DFS_OK, fileinfo can be used
+	to access the file from this point on.
+*/
+uint32_t DFS_OpenFile(PVOLINFO volinfo, uint8_t *path, uint8_t mode, uint8_t *scratch, PFILEINFO fileinfo);
+
+/*
+	Read an open file
+	You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a
+	pointer to a SECTOR_SIZE scratch buffer.
+	Note that returning DFS_EOF is not an error condition. This function updates the
+	successcount field with the number of bytes actually read.
+*/
+uint32_t DFS_ReadFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len);
+
+/*
+	Write an open file
+	You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a
+	pointer to a SECTOR_SIZE scratch buffer.
+	This function updates the successcount field with the number of bytes actually written.
+*/
+uint32_t DFS_WriteFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len);
+
+/*
+	Seek file pointer to a given position
+	This function does not return status - refer to the fileinfo->pointer value
+	to see where the pointer wound up.
+	Requires a SECTOR_SIZE scratch buffer
+*/
+void DFS_Seek(PFILEINFO fileinfo, uint32_t offset, uint8_t *scratch);
+
+/*
+	Delete a file
+	scratch must point to a sector-sized buffer
+*/
+uint32_t DFS_UnlinkFile(PVOLINFO volinfo, uint8_t *path, uint8_t *scratch);
+
+uint32_t DFS_GetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster);
+uint8_t *DFS_DirToCanonical(uint8_t *dest, uint8_t *src);
+
+// If we are building a host-emulation version, include host support
+#ifdef HOSTVER
+#include "hostemu.h"
+#endif
+
+#endif // _DOSFS_H
Index: main/common/fatfs.c
===================================================================
--- main/common/fatfs.c	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
+++ main/common/fatfs.c	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
@@ -0,0 +1,696 @@
+/* vi: set ts=4: */
+/* fatfs.c:
+ * This is a front end (i.e. the CLI portion) of the dosfs.c source code.
+ * The dosfs.c file is code from Lewin Edwards.  The website I got this
+ * from originally was: http://www.zws.com/products/dosfs/index.html .
+ *
+ * The dosfs.c dosfs.h files in uMon common (i.e. here) are taken directly
+ * from that website, with minimal modification (so as to remain compatible
+ * with any DOSFS updates).  Much of this fatfs code is also shamelessly
+ * cut-and-pasted from Lewin's source file main.c (part of the dosfs
+ * tarball) used to demonstrate the dosfs interface.
+ *
+ * Discussion on uMon's philosophy of file-systems other than TFS...
+ * The intent under the scope of uMon for support of file systems other than
+ * TFS is to support two commands: a file-system-specific command and an
+ * interface-specific command.  The goal is that members of the two different
+ * command sets can be mixed and matched as needed in a particular design.
+ * For example, if we have commands for compact flash (CF), SPI and I2C 
+ * interfaces the "fatfs" command could use anyone of them to retrieve files
+ * from that interface (assuming of course, that the interface has an FAT
+ * FS on the other side).
+ *
+ * The way this works is simple, the only thing the fatfs command needs is
+ * a set of interface-specific functions: readblock and writeblock. As
+ * long as the CF/SPI/I2C commands support this, and also support the
+ * ability to share those interface functions through the CLI, it simply
+ * becomes a matter of assigning the function pointers in a startup script
+ * when uMon boots up (probably in monrc).
+ *
+ * That being said, here's the model...
+ *
+ * File-system-specific Command:
+ * Each command will support the ability to establish its read and write
+ * pointers through the command line.  The -r and -w options do this in
+ * the fatfs command, and it would be nice if other similar commands could
+ * follow the same model.  The command should support (at minimum) the
+ * ability to list the files in the FS (ls), query for the presence of a
+ * specific file within the FS (qry), copy a file from the FS to TFS or
+ * ram (get), and also dump the content of an ASCII file from the FS to the
+ * console (cat).  If the command supports modification of the FS, then,
+ * it should also support the ability remove (rm) and copy from ram or TFS
+ * to the FS (put).  The command also supports default shell variables that
+ * may contain the read write functions.
+ *
+ * Interface-specific Command:
+ * Each command will support the potential of multiple interfaces of the 
+ * same type.  The command must be able to initialize (with optional
+ * verbosity) said interface and populate a defined set of shell variables
+ * with the address of the read and write functions applicable to that
+ * interface.  Ideally the interface command should also support the ability
+ * to do raw reads (FS to memory) and writes (memory to FS) on the interface.
+ *
+ * With the above model we then have the ability to connect the "FS" command
+ * with any "Interface" command; so as file system support in uMon grows,
+ * it immediately becomes useful on a variety of interfaces, as long as
+ * they adhere to the above simple model.  As of this writing the "cf" and
+ * "fatfs" commands comply to this model and can be used as a template for
+ * developing new commands that fit into this category.
+ * 
+ * Here is an example of their usage:
+ *
+ * uMON> cf init FATFS    # Initialize the compact flash interface and
+ *                        # laod FATFS_RD and FATFS_WR with the read and
+ *                        # write function pointers. 
+ * uMON> fatfs cat file1  # Using the FATFS_RD and FATFS_WR shell variables
+ *                        # previously set up, dump the content of the file 
+ *                        # named 'file1'.
+ *
+ *	General notice:
+ *	This code is part of a boot-monitor package developed as a generic base
+ *	platform for embedded system designs.  As such, it is likely to be
+ *	distributed to various projects beyond the control of the original
+ *	author.  Please notify the author of any enhancements made or bugs found
+ *	so that all may benefit from the changes.  In addition, notification back
+ *	to the author will allow the new user to pick up changes that may have
+ *	been made by other users after this version of the code was distributed.
+ *
+ *	Note1: the majority of this code was edited with 4-space tabs.
+ *	Note2: as more and more contributions are accepted, the term "author"
+ *		   is becoming a mis-representation of credit.
+ *
+ *	Original author:	Ed Sutter			esutter@lucent.com 
+ *	Contributor(s):		Graham Henderson
+ */
+ 
+#include "config.h"
+#if INCLUDE_FATFS
+#include "stddefs.h"
+#include "genlib.h"
+#include "cli.h"
+#include "tfs.h"
+#include "tfsprivate.h"
+#include "dosfs.h"
+
+static int	(*fatfsRead)(int interface, char *buf, int blknum, int count);
+static int	(*fatfsWrite)(int interface, char *buf, int blknum, int count);
+
+static char fatfs_initialized;
+static char *fatfs_tmp_space;
+static VOLINFO vi __attribute__((aligned (32)));
+static uint8_t sector[SECTOR_SIZE]  __attribute__ ((aligned (32)));
+static uint8_t sector2[SECTOR_SIZE] __attribute__ ((aligned (32)));
+
+/* Support skipping LFN dir entries...
+ */
+#define ATTR_LONG_NAME  \
+	(ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID)
+
+#define FATFS_FNAME_STR		"FATFSFNAME"
+#define FATFS_FTOT_STR		"FATFSFTOT"
+#define FATFS_FSIZE_STR		"FATFSFSIZE"
+
+/* fatfsInit():
+ * Start up the fs by reading in volume information.
+ * This function will return if already initialized, unless force
+ * is nonzero.
+ */
+static int
+fatfsInit(int force, int verbose)
+{
+	uint8_t pactive, ptype;
+	uint32_t pstart, psize;
+
+	if ((fatfs_initialized == 1) && (force == 0))
+		return(0);
+
+	/* Obtain pointer to first partition on first (only) unit.
+	 */
+	pstart = DFS_GetPtnStart(0, sector, 0, &pactive, &ptype, &psize);
+	if (pstart == 0xffffffff) {
+		printf("Cannot find first partition\n");
+		return(-1);
+	}
+
+	if (verbose) {
+		printf("Partition 0...\n");
+		printf("  Start sector 0x%08lx\n",pstart);
+		printf("  Active %d\n",pactive);
+		printf("  Type 0x%x\n",ptype);
+		printf("  Size 0x%lx\n",psize);
+	}
+
+	if (DFS_GetVolInfo(0, sector, pstart, &vi)) {
+		printf("Error getting volume information\n");
+		return(-1);
+	}
+
+	if (verbose) {
+		printf("Volume label '%-11s'\n", vi.label);
+		printf("  %d sector(s) per cluster\n",vi.secperclus);
+		printf("  %d reserved sector(s)\n",vi.reservedsecs);
+		printf("  %d total volume sector(s)\n", vi.numsecs);
+		printf("  %d sectors per FAT\n",vi.secperfat);
+		printf("  first FAT at sector #%d\n",vi.fat1);
+		printf("  root dir at #%d\n",vi.rootdir);
+
+		printf("For.. FAT32 rootdir is CLUSTER #,\n");
+		printf("      FAT12/16 rootdir is a SECTOR #.\n");
+		printf("%d root dir entries, data area commences at sector #%d.\n",
+			vi.rootentries,vi.dataarea);
+		printf("%d clusters (%d bytes) in data area, filesystem IDd as ",
+			vi.numclusters, vi.numclusters * vi.secperclus * SECTOR_SIZE);
+
+		if (vi.filesystem == FAT12)
+			printf("FAT12.\n");
+		else if (vi.filesystem == FAT16)
+			printf("FAT16.\n");
+		else if (vi.filesystem == FAT32)
+			printf("FAT32.\n");
+		else
+			printf("[unknown]\n");
+	}
+	fatfs_initialized = 1;
+	return(0);
+}
+
+/* fatfsCat():
+ * Dump the content of the specified fatfs file to the console.
+ * This function assumes the data is ASCII.
+ */
+static int
+fatfsCat(char *filepath)
+{
+	uint32_t i, j;
+	FILEINFO fi;
+	uint8_t *p;
+
+	if (DFS_OpenFile(&vi, (uint8_t *)filepath, DFS_READ, sector, &fi)) {
+		printf("error opening '%s'\n",filepath);
+		return(-1);
+	}
+	p = (uint8_t *)fatfs_tmp_space;
+	DFS_ReadFile(&fi, sector, p, &i, fi.filelen);
+	for(j=0;j<fi.filelen;j++)
+		putchar(*p++);
+	putchar('\n');
+	return(0);
+}
+
+/* fatfsGet():
+ * Retreive a file from the FATFS and place it in the specified destination.
+ * The destination may be either a TFS filename or a hex address
+ * (indicated by a leading "0x").
+ *
+ * Input:
+ * char *fatfspath
+ *	Pointer to FATFS file and path.
+ * char *dest
+ *	Pointer to TFS filename or a addr,len string.
+ *	The expected "addr,len" syntax is 0xADDR,LEN or 0xADDR,0xLEN.
+ */
+static int
+fatfsGet(char *fatfile, char *dest)
+{
+	FILEINFO fi;
+	char *p, tfs;
+	uint32_t i;
+
+	if (DFS_OpenFile(&vi, (uint8_t *)fatfile, DFS_READ, sector, &fi)) {
+		printf("error opening file\n");
+		return(-1);
+	}
+	if (strncmp(dest,"0x",2) == 0) {
+		tfs = 0;
+		p = (char *)strtoul(dest,0,0);
+	}
+	else {
+		tfs = 1;
+		p = fatfs_tmp_space;
+	}
+
+	DFS_ReadFile(&fi, sector, (uint8_t *)p, &i, fi.filelen);
+	if (i != fi.filelen) {
+		printf("read %d, expected %d\n",i,fi.filelen);
+		return(-1);
+	}
+	if (tfs) {
+#if INCLUDE_TFS
+		int	tfserr;
+		char *flags, *info;
+
+		flags = info = (char *)0;
+		if ((flags = strchr(dest,','))) {
+			*flags++ = 0;
+			if ((info = strchr(flags,',')))
+				*info++ = 0;
+		}
+		tfserr = tfsadd(dest,info,flags,(uchar *)p,fi.filelen);
+		if (tfserr != TFS_OKAY) {
+			printf("TFS error: %s\n",tfserrmsg(tfserr));
+			return(-1);
+		}
+#else
+		printf("TFS not built in\n");
+		return(-1);
+#endif
+	}
+	return(0);
+}
+
+/* fatfsPut():
+ * Copy data or file to a FATFS file.
+ *
+ * Input:
+ * char *fatfspath
+ *	Pointer to FATFS file and path.
+ * char *src
+ *	Pointer to TFS filename or a addr,len string.
+ *	The expected "addr,len" syntax is 0xADDR,LEN or 0xADDR,0xLEN.
+ */
+static int
+fatfsPut(char *src, char *fatfspath)
+{
+	FILEINFO fi;
+	char tfs, *p, *cp;
+	uint32_t successcount, len, size, rc;
+
+	if (strncmp(src,"0x",2) == 0) {
+		tfs = 0;
+		p = (char *)strtoul(src,&cp,0);
+		cp++;
+		len = strtoul(cp,0,0);
+	}
+	else {
+#if INCLUDE_TFS
+		TFILE *tfp;
+
+		tfs = 1;
+		if ((tfp = tfsstat(src)) == 0) {
+			printf("Can't find file '%s' in TFS\n",src);
+			return(CMD_FAILURE);
+		}
+		p = TFS_BASE(tfp);
+		len = TFS_SIZE(tfp);
+#else
+		printf("TFS not built in\n");
+		return(-1);
+#endif
+	}
+
+	/* Copy 'len' bytes from 'p' to DOSFS file 'fatfspath'...
+	 */
+	if (DFS_OpenFile(&vi, (uint8_t *)fatfspath, DFS_WRITE, sector, &fi)) {
+		printf("error opening '%s'\n",fatfspath);
+		return(-1);
+	}
+
+	while(len > 0) {
+		if (len >= SECTOR_SIZE)
+			size = SECTOR_SIZE;
+		else
+			size = len;
+
+		rc = DFS_WriteFile(&fi, sector2, (uint8_t *)p, &successcount, size);
+		if ((rc != DFS_OK) || (successcount != size)) {
+			printf("error writing '%s'\n",fatfspath);
+			return(-1);
+		}
+		p += size;
+		len -= size;
+	}
+	return(0);
+}
+
+/* fatfsRm():
+ * Remove the specified file from the FATFS.
+ * If the path is a directory, then don't remove anything.
+ */
+static int
+fatfsRm(char *fatfspath)
+{
+	DIRINFO di;
+
+	di.scratch = sector;
+	if (DFS_OpenDir(&vi, (uint8_t *)fatfspath, &di) == 0) {
+		printf("can't remove directory '%s'\n",fatfspath);
+		return(-1);
+	}
+	if (DFS_UnlinkFile(&vi, (uint8_t *)fatfspath, sector)) {
+		printf("error unlinking file %s\n",fatfspath);
+		return(-1);
+	}
+	return(0);
+}
+
+/* fatfsLs():
+ * The incoming string is assumed to be some directory name.
+ * If NULL, then it is assumed to be the CF's top-most level.
+ */ 
+static int
+fatfsLs(char *dirname)
+{
+	int	ftot;
+	DIRENT de;
+	DIRINFO di;
+	char *dir;
+
+	if (dirname)
+		dir = dirname;
+	else
+		dir = "";
+
+	di.scratch = sector;
+	if (DFS_OpenDir(&vi, (uint8_t *)dir, &di)) {
+		printf("Can't open dir: <%s>\n",dirname);
+		return(CMD_FAILURE);
+	}
+
+	ftot = 0;
+	while (!DFS_GetNext(&vi, &di, &de)) {
+		char tmp[32]; 
+
+		if (de.name[0] && ((de.attr & ATTR_LONG_NAME) != ATTR_LONG_NAME)) {
+			if ((de.attr & ATTR_VOLUME_ID) > 0) {
+				de.name[8] = 0;
+				printf("Volume:     %s\n",de.name);
+				continue;
+            }
+			ftot++;
+
+			printf("%02d/%02d/%4d  %02d:%02d   ",
+				CREATE_MON(de), CREATE_DAY(de), CREATE_YEAR(de),
+				CREATE_HR(de), CREATE_MIN(de));
+
+			if ((de.attr & ATTR_DIRECTORY) > 0) {
+				// truncate the extension
+				de.name[8] = 0;
+
+				printf("<DIR>            %-8s",de.name);
+			}
+			else {
+				DFS_DirToCanonical((uint8_t *)tmp,de.name);
+				printf("       %8d  %-12s",FILESIZE(de), tmp);
+			}
+
+			printf("  %c%c%c%c%c\n",
+				(( de.attr & ATTR_VOLUME_ID) ? 'V' : ' '),
+				(( de.attr & ATTR_READ_ONLY) ? 'R' : ' '),
+				(( de.attr & ATTR_ARCHIVE  ) ? 'A' : ' '),
+				(( de.attr & ATTR_SYSTEM   ) ? 'S' : ' '),
+				(( de.attr & ATTR_HIDDEN   ) ? 'H' : ' '));
+		}
+	}
+	shell_sprintf(FATFS_FTOT_STR,"%d",ftot);
+	return(0);
+}
+
+static int
+fatfsQry(char *fatfspath, int verbose)
+{
+	DIRENT de;
+	DIRINFO di;
+	char *lastslash, *fname, *dir, pathcopy[80], matchname[80];
+	int flen, flen1, fsize, pathlen, match, ftot;
+
+	if (!fatfspath)
+		return(-1);
+
+	/* Prior to each 'ls', clear the content of the name and
+	 * size shell variables...
+	 */
+	setenv(FATFS_FNAME_STR,0);
+	setenv(FATFS_FSIZE_STR,0);
+	setenv(FATFS_FTOT_STR,0);
+
+	pathlen = strlen(fatfspath);
+	if (pathlen > sizeof(pathcopy)) {
+		printf("path too big\n");
+		return(-1);
+	}
+	strcpy(pathcopy, fatfspath);
+	lastslash = strrchr(pathcopy,DIR_SEPARATOR);
+
+	if (lastslash == 0) {
+		dir = "";
+		fname = pathcopy;
+	}
+	else {
+		*lastslash = 0;
+		dir = pathcopy;
+		fname = lastslash+1;
+	}
+
+	flen = strlen(fname);
+	if (verbose > 1)
+		printf("Qry opening dir <%s>, fname <%s>...\n",dir,fname);
+
+	di.scratch = sector;
+	if (DFS_OpenDir(&vi, (uint8_t *)dir, &di)) {
+		printf("error opening subdirectory\n");
+		return(CMD_FAILURE);
+	}
+
+	match = fsize = ftot = flen1 = 0;
+	while (!DFS_GetNext(&vi, &di, &de)) {
+		int i;
+		char dosname[16];
+
+		memset(dosname,0,sizeof(dosname));
+		if (de.name[0] && ((de.attr & ATTR_LONG_NAME) != ATTR_LONG_NAME)) {
+			if ((de.attr & ATTR_VOLUME_ID) || (de.attr & ATTR_DIRECTORY)) {
+				for(i=0;i<8;i++) {
+					if (de.name[i] != ' ') {
+						dosname[i] = de.name[i];
+					}
+					else {
+						dosname[i] = 0;
+						break;
+					}
+				}
+				dosname[8] = 0;
+			}
+			else
+				DFS_DirToCanonical((uint8_t *)dosname,de.name);
+	
+			flen1 = strlen(dosname);
+
+			if ((fname[0] == '*') && (fname[flen-1] == '*')) {
+				fname[flen-1] = 0;
+				if (strstr(dosname,fname+1))
+					match = 1;
+				fname[flen-1] = '*';
+			}
+			else if (fname[0] == '*') {
+				if (!strcmp(dosname+(flen1-flen+1),fname+1))
+					match = 1;
+			}
+			else if (fname[flen-1] == '*') {
+				fname[flen-1] = 0;
+				if (!strncmp(dosname,fname,flen-1))
+					match = 1;
+				fname[flen-1] = '*';
+			}
+			else if (!strcmp(dosname,fname)) {
+				match = 1;
+			}
+			if (match) {
+				strcpy(matchname,dosname);
+				fsize = FILESIZE(de);
+				ftot++;
+				match = 0;
+				if (verbose)
+					printf("  %s (%d)\n",dosname,fsize);
+			}
+		}
+	}
+	shell_sprintf(FATFS_FTOT_STR,"%d",ftot);
+	if (ftot) {
+		shell_sprintf(FATFS_FSIZE_STR,"%d",fsize);
+		setenv(FATFS_FNAME_STR,matchname);
+	}
+	return(0);
+}
+
+
+char *FatfsHelp[] = {
+	"Fat 12|16|32 File System Support",
+	"[r:s:w:v] {operation} [args]...",
+#if INCLUDE_VERBOSEHELP
+	"",
+	"Options:",
+	" -r {addr}   set read-block func ptr (default: FATFS_RD)",
+	" -s {addr}   base of ram scratch space used by fatfs",
+	"             (default is APPRAMBASE)",
+	" -w {addr}   set write-block func ptr (default: FATFS_WR)",
+	" -v          additive verbosity",
+	"",
+	"Operations:",
+	" cat {fname}",
+	" get {fat_file} {tfs_file | addr}",
+	"              (get from fatfs)",
+	" init         initialize internal structures",
+	" put {tfs_file | addr,size} {fat_file}",
+	"              (put to fatfs)",
+	" ls  [dir]    list files within specified directory (or top)",
+	" qry {fltr}   qry for file or directory presence",
+	"              - fltr format: sss, *sss, sss*, *sss* ('sss' is any string)",
+	"              - loads " FATFS_FNAME_STR " & " FATFS_FSIZE_STR " based on most recent match",
+	"              - loads " FATFS_FTOT_STR " with number of files listed",
+	" rm  {fname}",
+#endif
+	0
+};
+
+/* FatfsCmd():
+ * This command provides a front end (command line interface) to the
+ * dosfs.c code.  The intent is that this be a template for other
+ * "fs-ish" commands within uMon (refer to discussion at the top of this
+ * file).
+ */
+int
+FatfsCmd(int argc, char *argv[])
+{
+	uint32_t i, j;
+	int		opt, verbose;
+	char	*cmd, *env, *arg1, *arg2, *arg3;
+
+	/* Establish default read/write functions to access underlying
+	 * storage media.  These defaults can be overridden by the 
+	 * command line options -r & -w below.
+	 */
+	if ((env = getenv("FATFS_RD")) != 0)
+		fatfsRead = (int(*)(int,char*,int,int))strtol(env,0,0);
+
+	if ((env = getenv("FATFS_WR")) != 0)
+		fatfsWrite = (int(*)(int,char*,int,int))strtol(env,0,0);
+
+	verbose = 0;
+	fatfs_tmp_space = (char *)0xffffffff;
+	while ((opt=getopt(argc,argv,"r:s:vw:")) != -1) {
+		switch(opt) {
+			case 'r':
+				fatfsRead = (int(*)(int,char*,int,int))strtoul(optarg,0,0);
+				break;
+			case 's':
+				fatfs_tmp_space = (char *)strtoul(optarg,0,0);
+				break;
+			case 'w':
+				fatfsWrite = (int(*)(int,char*,int,int))strtoul(optarg,0,0);
+				break;
+			case 'v':
+				verbose++;
+				break;
+			default:
+				return(CMD_PARAM_ERROR);
+		}
+	}
+
+	if ((fatfsRead == 0) || (fatfsWrite == 0)) {
+		printf("FATFS access pointers not initialized\n");
+		return(CMD_FAILURE);
+	}
+
+	if (argc < optind + 1)
+		return(CMD_PARAM_ERROR);
+
+	if (fatfs_tmp_space == (char *)0xffffffff)
+		fatfs_tmp_space = (char *)getAppRamStart();
+
+	cmd = argv[optind];
+	arg1 = argv[optind+1];
+	arg2 = argv[optind+2];
+	arg3 = argv[optind+3];
+
+	if (strcmp(cmd,"init") == 0) {
+		if (fatfsInit(1,verbose) < 0)
+			return(CMD_FAILURE);
+	}
+	else if (strcmp(cmd,"cat") == 0) {
+		if (argc != optind + 2) 
+			return(CMD_PARAM_ERROR);
+
+		if (fatfsInit(0,0) < 0)
+			return(CMD_FAILURE);
+
+		for(i=optind+1;i<argc;i++) {
+			if (fatfsCat(argv[i]) < 0)
+				return(CMD_FAILURE);
+		}
+	}
+	else if (strcmp(cmd,"get") == 0) {
+		if (argc != optind + 3) 
+			return(CMD_PARAM_ERROR);
+
+		if (fatfsInit(0,0) < 0)
+			return(CMD_FAILURE);
+
+		if (fatfsGet(arg1,arg2) < 0)
+			return(CMD_FAILURE);
+
+	}
+	else if (strcmp(cmd,"put") == 0) {
+		if (argc != optind + 3) 
+			return(CMD_PARAM_ERROR);
+
+		if (fatfsInit(0,0) < 0)
+			return(CMD_FAILURE);
+
+		if (fatfsPut(arg1,arg2) < 0)
+			return(CMD_FAILURE);
+	}
+	else if (strcmp(cmd,"qry") == 0) {
+		if (argc != optind + 2) 
+			return(CMD_PARAM_ERROR);
+
+		if (fatfsInit(0,0) < 0)
+			return(CMD_FAILURE);
+
+		if (fatfsQry(arg1,verbose) < 0)
+			return(CMD_FAILURE);
+	}
+	else if (strcmp(cmd,"ls") == 0) {
+		if (fatfsInit(0,0) < 0)
+			return(CMD_FAILURE);
+
+		if (fatfsLs(arg1) < 0)
+			return(CMD_FAILURE);
+	}
+	else if (strcmp(cmd,"rm") == 0) {
+		if (fatfsInit(0,0) < 0)
+			return(CMD_FAILURE);
+
+		for(j=optind+1;j<argc;j++) {
+			if (fatfsRm(argv[j]) < 0)
+				return(CMD_FAILURE);
+		}
+	}
+	else {
+		printf("fatfs op <%s> not found\n",cmd);
+		return(CMD_FAILURE);
+	}
+
+	return(CMD_SUCCESS);
+}
+
+/* Hookup between DOSFS and this command...
+ * The dosfs.c code requires that the user provide two functions
+ * (DFS_ReadSector() and DFS_WriteSector()) to hook it up to an
+ * external interface.  These two functions are configured by
+ * the fatfs command to point to whatever driver is to be established
+ * as the FATFS physical interface.  For more information refer to the
+ * discussion at the top of this file.
+ */
+uint32_t
+DFS_ReadSector(uint8_t unit, uint8_t *data, uint32_t sec,uint32_t count)
+{
+	return((uint32_t)fatfsRead((int)unit, (char *)data, sec, count));
+}
+
+uint32_t
+DFS_WriteSector(uint8_t unit, uint8_t *data, uint32_t sec,uint32_t count)
+{
+	return((uint32_t)fatfsWrite((int)unit, (char *)data, sec, count));
+}
+
+#endif
Index: ports/beagleboneblack/Makefile
===================================================================
--- ports/beagleboneblack/Makefile	(revision ecb72ce1ea8d80e69244a1c29b8229f969d20dc0)
+++ ports/beagleboneblack/Makefile	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
@@ -40,5 +40,5 @@
 LIBABIDIR       = -L $(ABIDIR)
 COMMON_AFLAGS	= -c -D PLATFORM_$(PLATFORM)=1 -D ASSEMBLY_ONLY 
-CUSTOM_CFLAGS	= -mcpu=cortex-a8 -O2 -isystem $(ABIDIR)/include -Wno-char-subscripts
+CUSTOM_CFLAGS	= -mcpu=cortex-a8 -O2 -isystem $(ABIDIR)/include -Wno-char-subscripts -mno-unaligned-access
 
 
@@ -49,5 +49,5 @@
 # 
 BOOTROMBASE=0x402F0400
-BOOTROMLEN=0x010000
+BOOTROMLEN=0x01b000
 BOOTRAMBASE=0x80000000
 BOOTRAMLEN=0x010000
@@ -76,5 +76,5 @@
 			  start.c struct.c symtbl.c syslog.c tcpstuff.c tfs.c tfsapi.c \
 			  tfsclean1.c tfscli.c tfsloader.c tfslog.c tftp.c timestuff.c \
-			  tsi.c xmodem.c
+			  tsi.c xmodem.c dosfs.c fatfs.c
 CPUCSRC		= except_arm.c misc_arm.c strace_arm.c 
 IODEVSRC	= uart16550.c
Index: ports/beagleboneblack/config.h
===================================================================
--- ports/beagleboneblack/config.h	(revision ecb72ce1ea8d80e69244a1c29b8229f969d20dc0)
+++ ports/beagleboneblack/config.h	(revision 696ee75469e4e21afa570f8471c81ebd291f243a)
@@ -177,5 +177,5 @@
 #define INCLUDE_BBC             0
 #define INCLUDE_STOREMAC        0
-#define INCLUDE_SHELLVARS       0
+#define INCLUDE_SHELLVARS       1
 #define INCLUDE_MALLOC          0
 #define INCLUDE_PORTCMD         0
@@ -192,4 +192,5 @@
 #define INCLUDE_DNS             0
 #define INCLUDE_BLINKLED        1
+#define INCLUDE_FATFS		1
 #define TARGET_BLINKLED         target_blinkled
 
