README.TXT (C) Copyright 2006 DOSFS Level 1 Version 1.02 Lewin A.R.W. Edwards (sysadm@zws.com) ===================================================================== Abstract ======== DOSFS is a FAT-compatible filesystem intended for fairly low-end embedded applications. It is not the leanest possible implementation (the leanest FAT implementations operate in << 512 bytes of RAM, with heavy restrictions). This code strikes a good balance between size and functionality, with an emphasis on RAM footprint. Intended target systems would be in the ballpark of 1K RAM, 4K ROM or more. Features: * Supports FAT12, FAT16 and FAT32 volumes * Supports storage devices up to 2048Gbytes in size (LBA32) * Supports devices with or without MBRs (hard disks vs. floppy disks or ZIP drives formatted as "big floppies") * Supports multiple partitions on disks with MBRs * Supports subdirectories * Can be operated with a single global 512-byte sector buffer * Fully reentrant code (assuming the underlying physical device driver is reentrant and global sector buffers are not used). There are no global variables in the filesystem * Does not perform any memory allocation * Partial support for random-access files Applications: * Firmware upgrades * Failsafe IPL * Media playback * Data logging * Configuration storage There is no technical support for this free product; however, if you have questions or suggestions, you are encouraged to email Lewin Edwards at sysadm@zws.com. If you need custom additions to the code, or if you have other projects for which you need engineering assistance, please feel free to email or call (646) 549-3715. License ======= The license for DOSFS is very simple but verbose to state. 1. DOSFS is (C) Copyright 2006 by Lewin A.R.W. Edwards ("Author"). All rights not explicitly granted herein are reserved. The DOSFS code is the permanent property of the Author and no transfer of ownership is implied by this license. 2. DOSFS is an educational project, provided as-is. No guarantee of performance or suitability for any application is stated or implied. You use this product entirely at your own risk. Use of this product in any manner automatically waives any right to seek compensation or damages of any sort from the Author. Since the products you might make are entirely out of the Author's control, use of this product also constitutes an agreement by you to take full responsibility for and indemnify the Author against any action for any loss or damage (including economic loss of any type, and specifically including patent litigation) that arises from a product made by you that incorporates any portion of the DOSFS code. 3. If you live under the jurisdiction of any legislation that would prohibit or limit any condition in this license, you cannot be licensed to use this product. 4. If you do not fall into the excluded category in point 3, you are hereby licensed to use the DOSFS code in any application that you see fit. You are not required to pay any fee or notify the Author that you are using DOSFS. Any modifications made by you to the DOSFS code are your property and you may distribute the modified version in any manner that you wish. You are not required to disclose sourcecode to such modifications, either to the Author or to any third party. Any such disclosure made to the Author will irrevocably become the property of the Author in the absence of a formal agreement to the contrary, established prior to such disclosure being made. To summarize the intent of the above: DOSFS is free. You can do what you want with it. Anything that happens as a result is entirely your responsibility. You can't take ownership of my code and stop me from doing whatever I want with it. If you do something nifty with DOSFS and send me the sourcecode, I may include your changes in the next distribution and it will be released to the world as free software. If someone sues you because your DOSFS-containing product causes any sort of legal, financial or other problem, it's your lawsuit, not mine, and you'll exclude me from the proceedings. User-Supplied Functions ======================= You must provide functions to read sectors into memory and write them back to the target media. The demo suite includes an emulation module that reads/writes a disk image file (#define HOSTVER pulls in hostemu.h which wraps the prototypes for these functions). There are various tools for UNIX, DOS, Windows et al, to create images from storage media; my preferred utility is dd. The functions you must supply in your embedded app are: DFS_ReadSector(unit,buffer,sector,count) DFS_WriteSector(unit,buffer,sector,count) These two functions read and write, respectively, "count" sectors of size SECTOR_SIZE (512 bytes; see below) from/to physical sector #"sector" of device "unit", to/from the scratch buffer "buffer". They should return 0 for success or nonzero for failure. In the current implementation of DOSFS, count will always be 1. The "unit" argument is designed to permit implementation of multiple storage devices, for example multiple media slots on a single device, or to differentiate between master and slave devices on an ATAPI bus. This code is designed for 512-byte sectors. Although the sector size is a #define, you should not tinker with it because the vast majority of FAT filesystems use 512-byte sectors, and the DOSFS code doesn't support runtime determination of sector size. This will not affect the vast majority of users. Example Code ============ Refer to the tests in main.c to see how to call DOSFS functions. (These tests are all commented out). Note that the only two files you need to add to your project are dosfs.c and dosfs.h. Mounting Volumes ================ --If the device has a partition table (practically all removable flash media are formatted this way), call DFS_GetPtnStart to get the starting sector# of the desired partition. You can optionally also retrieve the active state, partition type byte and partition size in this step. The reason this step is broken out separately is so you can support devices that are formatted like a floppy disk, i.e. the volume starts directly at physical sector 0 of the media. --Call DFS_GetVolInfo to read filesystem info into a VOLINFO structure. DFS_GetVolInfo needs to know the unit number and partition starting sector (as returned by DFS_GetPtnStart, or 0 if this is a "floppy- format" volume without an MBR). From this point on, the VOLINFO structure is all you'll need - you can forget the unit and partition start sector numbers. Enumerating Directory Contents ============================== --Call DFS_Opendir and supply a path, populated VOLINFO and a DIRINFO structure to receive the results. Note - you must PREPOPULATE the DIRINFO.scratch field with a pointer to a sector scratch buffer. This buffer must remain unmolested while you have the directory open for searching. --Call DFS_GetNext to receive the DIRENT contents for the next directory item. This function returns DFS_OK for no error, and DFS_EOF if there are no more entries in the directory being searched. Before using the DIRENT, check the first character of the name. If it is NULL, then this is an unusable entry - call DFS_GetNext again to keep searching. LFN directory entries are automatically tagged this way so your application will not be pestered by them. Note: A designed side-effect of this code is that when you locate the file of interest, the DIRINFO.currentcluster, DIRINFO.currentsector and DIRINFO.currententry-1 fields will identify the directory entry of interest. Reading a File ============== --Call DFS_OpenFile with mode = DFS_READ and supply a path and the relevant VOLINFO structure. DFS_OpenFile will populate a FILEINFO that can be used to refer to the file. --Optionally call DFS_Seek to set the file pointer. If you attempt to set the file pointer past the end of file, the file will NOT be extended. Check the FILEINFO.pointer value after DFS_Seek to verify that the pointer is where you expect it to be. --Observe that functionality similar to the "whence" parameter of fseek() can be obtained by using simple arithmetic on the FILEINFO.pointer and FILEINFO.filelen members. --Call DFS_ReadFile with the FILEINFO you obtained from OpenFile, and a pointer to a buffer plus the desired number of bytes to read, and a pointer to a sector-sized scratch buffer. The reason a scratch sector is required is because the underlying sector read function doesn't know about partial reads. --Note that a file opened for reading cannot be written. If you need r/w access, open with mode = DFS_WRITE (see below). Writing a file ============== --Call DFS_OpenFile with mode = DFS_WRITE and supply a path and the relevant VOLINFO structure. DFS_OpenFile will populate a FILEINFO that can be used to refer to the file. --Optionally call DFS_Seek to set the file pointer. Refer to the notes on this topic in the section on reading files, above. --Call DFS_WriteFile with the FILEINFO you obtained from OpenFile, and a pointer to the source buffer, and a pointer to a sector-sized scratch buffer. --Note that a file open for writing can also be read. --Files are created automatically if they do not exist. Subdirectories are NOT automatically created. --If you open an existing file for writing, the file pointer will start at the beginning of the data; if you want to append, seek to the end before writing new data. --If you perform random-access writes to a file, the length will NOT change unless you exceed the file's original length. There is currently no function to truncate a file at the current pointer position. --On-disk consistency is guaranteed when DFS_WriteFile exits, unless your physical layer has a writeback cache in it. Deleting a file =============== --Call DFS_UnlinkFile --WARNING: This call will delete a subdirectory (correctly) but will NOT first recurse the directory to delete the contents - so you will end up with lost clusters. Notes ===== Some platforms may require explicit pragmas or attributes to the structures and unions. For example, arm-gcc will require __attribute__ ((__packed__)) otherwise it will try to be "smart" and place the uint8_t members on 4-byte boundaries. There is no truly elegant compiler-independent method to get around this sort of problem. The code assumes either a von Neumann architecture, or a compiler that is smart enough to understand where your pointers are aimed and emit the right kind of memory read and write instructions. The implications of this statement depend on your target processor and the compiler you are using. Be very careful not to straddle bank boundaries on bank- switched memory systems. Physical 32-bit sector numbers are used throughout. Therefore, the CHS geometry (if any) of the storage media is not known to DOSFS. Your sector r/w functions may need to query the CHS geometry and perform mapping. File timestamps set by DOSFS are always 1:01:00am on Jan 1, 2006. If your system has a concept of real time, you can enhance this. FILEINFO structures contain a pointer to the corresponding VOLINFO used to open the file, mainly in order to avoid mixups but also to obviate the need for an extra parameter to every file read/write. DOSFS assumes that the VOLINFO won't move around. If you need to move or destroy VOLINFOs pertaining to open files, you'll have to fix up the pointer in the FILEINFO structure yourself. The subdirectory delimiter is a forward slash ( '/' ) by default. The reason for this is to avoid the common programming error of forgetting that backslash is an escape character in C strings; i.e. "\MYDIR\FILE" is NOT what you want; "\\MYDIR\\FILE" is what you wanted to type. If you are porting DOS code into an embedded environment, feel free to change this #define. DOSFS does not have a concept of "current directory". A current directory is owned by a process, and a process is an operating system concept. DOSFS is a filesystem library, not an operating system. Therefore, any path you provide to a DOSFS call is assumed to be relative to the root of the volume. There is no call to close a file or directory that is open for reading or writing. You can simply destroy or reuse the data structures allocated for that operation; there is no internal state in DOSFS so no cleanup is necessary. Similarly, there is no call to close a file that is open for writing. (Observe that dosfs.c has no global variables. All state information is stored in data structures provided by the caller). MAX_PATH is defined as 64. MS-type DOS filesystems support 128 characters or more in paths. You can increase this define, but it may GREATLY increase memory requirements. VFAT long filenames are not supported. There is a certain amount of patent controversy about them, but more importantly they don't really belong in the scope of a "minimalist embedded filesystem". Improving Performance ===================== Read performance is fairly good, but can be improved by implementing read caching on the FAT (see below) and, depending on your hardware platform, possibly by implementing multi-sector reads. Write performance may benefit ENORMOUSLY from platform-specific optimization, especially if you are working with a flash media type that has a large erase block size. While it is not possible to offer detailed platform-independent advice, my general advice is to implement writeback caching on the FAT area. One method for doing this would be to have a cache system that lives in the DFS_ReadSector/WriteSector functions (on top of the physical sector r/w functions) and is initially switched off. Once you have called DFS_GetVolInfo, you then extract the VOLINFO.fat1 and VOLINFO.rootdir parameters and pass them to your caching layer. Sectors >= fat1 and < rootdir should be cached. The cache strategy is determined by the physical storage medium underlying the filesystem. CACHING HINT: Observe that there will be numerous read-modify-write operations in the region from VOLINFO.fat1 through VOLINFO.fat1+VOLINFO.secperfat-1, but in the region from VOLINFO.fat1+VOLINFO.secperfat through VOLINFO.rootdir there will ONLY be write operations. Platform Compatibility ====================== DOSFS was derived from code originally written for ARM7TDMI but designed to be portable. It has been tested on AVR (using avrgcc), MSP430 (using Rowley's CrossWorks) and PPC603e (using gcc); the host test suite has also been validated on x86 using gcc under both Cygwin and 32-bit Fedora Core 4 Linux. TODO list ========= * Add function to create subdirectory * Make DFS_UnlinkFile recognize non-empty subdirectories * Support "fast write" files where the FAT is not updated, for logging applications where latency is important. Test cases for V1.02 ==================== Version 1.02 has NOT been through full regression testing. However the bugs fixed in this version are important, and people have been asking about them. Test cases for V1.01 ==================== See below. Test cases for V1.00 ==================== These are the test cases that were used to validate the correct functionality of the DOSFS suite. Each test was performed on FAT12, FAT16 and FAT32 volumes. P=Pass, F=Fail. Case F12 F16 F32 --------------------------------------------------------------------- Get volume information P P P Open root directory P P P List contents of root directory (fully populated) P P P Open subdirectory P P P List contents of subdirectory (<= 1 cluster) P P P List contents of large subdirectory (> 1 cluster) P P P Open 5-level nested subdirectory P P P Open existing file for reading P P P Open nonexistent file for reading P P P Seek past EOF, file open for reading P P P Seek to cluster boundary P P P Seek past cluster boundary P P P Seek backwards to nonzero offset, pointer > cluster size P P P Block-read entire file >1 cluster in size, odd size P P P Seek to odd location in file P P P Perform <1 sector reads from random file locations P P P Open nonexistent file for writing in root dir P P P Open nonexistent file for writing in subdir P P P Repeat prev. 2 tests on volume with 0 free clusters P P P Seek past EOF, file open for writing P P P Open existing file for writing in root dir P P P Write random-length records to file, 20 clusters total P P P MS-DOS 6.0 SCANDISK cross-check P P P Revision History ================ Jan-06-2005 larwe Initial release (1.0) Jan-29-2006 larwe Bugfix release (1.01) - Fixed error in FAT12 FAT read on boundary of sector - Improved compilability under avrgcc Sep-16-2006 larwe Bugfix release (1.02) - DFS_Seek would not correctly rewind to start of file - DFS_Seek would not correctly seek to a position not on a cluster boundary - DFS_OpenFile fencepost error caused memory access at [start of string-1] with a local variable - DFS_OpenFile could not open a file in the root directory