/* Arduino FAT16 Library * Copyright (C) 2008 by William Greiman * * This file is part of the Arduino FAT16 Library * * This Library is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with the Arduino Fat16 Library. If not, see * . */ #include #include "Fat16Config.h" #include "Fat16.h" static uint8_t make83Name(char *str, uint8_t *name); // block device driver BlockDevice *Fat16::rawDev_ = 0; //class for block read and write // volume info uint8_t Fat16::volumeInitialized_ = 0; //true if FAT16 volume is valid uint8_t Fat16::fatCount_; //number of file allocation tables uint8_t Fat16::blocksPerCluster_; //must be power of 2 uint16_t Fat16::rootDirEntryCount_; //should be 512 for FAT16 uint16_t Fat16::blocksPerFat_; //number of blocks in one FAT uint16_t Fat16::clusterCount_; //total clusters in volume uint32_t Fat16::fatStartBlock_; //start of first FAT uint32_t Fat16::rootDirStartBlock_; //start of root dir uint32_t Fat16::dataStartBlock_; //start of data clusters //raw block cache uint32_t Fat16::cacheBlockNumber_ = 0XFFFFFFFF; //init to invalid block number cache_t Fat16::cacheBuffer_; //512 byte cache for BlockDevice uint8_t Fat16::cacheDirty_ = 0; //cacheFlush() will write block if true // form 8.3 name for directory entry uint8_t make83Name(char *str, uint8_t *name) { uint8_t c; uint8_t n = 7; uint8_t i = 0; //blank fill name and extension while (i < 11)name[i++] = ' '; i = 0; while ((c = *str++) != '\0') { if (c == '.') { if (n == 10) return 0;// only one dot allowed n = 10; i = 8; } else { #if FAT16_SAVE_RAM // using PSTR gives incorrect warning in C++ files for Arduino V12 // illegal FAT16 characters char b, *p = (char *)PSTR("|<>^+=?/[];,*\"\\"); while ((b = pgm_read_byte(p++))) if (b == c) return 0; #else //FAT16_SAVE_RAM // illegal FAT16 characters const char *p = "|<>^+=?/[];,*\"\\"; while (*p) if (*p++ == c) return 0; #endif //FAT16_SAVE_RAM // check length and only allow ASCII printable characters if (i > n || c < 0X21 || c > 0X7E)return 0; //only upper case allowed in 8.3 names - convert lower to upper name[i++] = c < 'a' || c > 'z' ? c : c + ('A' - 'a'); } } //must have a file name, extension optional if (name[0] == ' ') return 0; return 1; } #if FAT16_WRITE_SUPPORT uint8_t Fat16::addCluster(void) { fat_t *f; //start search after last cluster of file or at cluster two in FAT fat_t freeCluster = writeCluster_ != 0 ? writeCluster_ : 1; for (uint16_t i = 0; ; i++) { // return no free clusters if (i >= clusterCount_) return 0; // Fat has clusterCount + 2 entries if (freeCluster > clusterCount_) freeCluster = 1; freeCluster++; if (!(f = cacheFatEntry(freeCluster))) return 0; if (*f == 0) break; } if (writeCluster_ == 0) { // first cluster of file so link to directory entry dir_t *d = cacheDirEntry(dirEntryIndex_, CACHE_FOR_WRITE); if (!d) return 0; d->firstClusterLow = freeCluster; firstCluster_ = freeCluster; } //link freeCluster to chain and mark it as allocated in each FAT for (uint8_t i = 0; i < fatCount_; i++) { if (writeCluster_ != 0) { // link cluster to chain if (!(f = cacheFatEntry(writeCluster_, CACHE_FOR_WRITE, i))) return 0; *f = freeCluster; } // mark cluster allocated if (!(f = cacheFatEntry(freeCluster, CACHE_FOR_WRITE, i))) return 0; *f = FAT_EOC; } writeCluster_ = freeCluster; return 1; } #endif //FAT16_WRITE_SUPPORT uint8_t Fat16::cacheDataBlock(fat_t cluster, uint8_t blockOfCluster, uint8_t action) { uint32_t lba = dataStartBlock_ + (cluster - 2)*blocksPerCluster_ + blockOfCluster; return cacheRawBlock(lba, action); } dir_t *Fat16::cacheDirEntry(uint16_t index, uint8_t action) { if (index >= rootDirEntryCount_) return 0; if (!cacheRawBlock(rootDirStartBlock_ + (index >> 4), action)) return 0; return &cacheBuffer_.dir[index & 0XF]; } fat_t *Fat16::cacheFatEntry(fat_t cluster, uint8_t action, uint8_t table) { if (cluster > (clusterCount_ + 1)) return 0; uint32_t lba = fatStartBlock_ + (cluster >> 8) + table*blocksPerFat_ ; if (!cacheRawBlock(lba, action)) return 0; return &cacheBuffer_.fat[cluster & 0XFF]; } uint8_t Fat16::cacheFlush(void) { if (cacheDirty_) { #if FAT16_WRITE_SUPPORT if (!rawDev_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) return 0; cacheDirty_ = 0; #else //FAT16_WRITE_SUPPORT //error if dirty with no write support return 0; #endif //FAT16_WRITE_SUPPORT } return 1; } uint8_t Fat16::cacheRawBlock(uint32_t blockNumber, uint8_t action) { if (action == CACHE_ZERO_BLOCK) { if (!cacheFlush()) return 0; for (uint16_t i = 0; i < sizeof(cacheBuffer_); i++) { cacheBuffer_.data[i] = 0; } cacheBlockNumber_ = blockNumber; } else if (cacheBlockNumber_ != blockNumber) { if (!cacheFlush()) return 0; if (!rawDev_->readBlock(blockNumber, cacheBuffer_.data)) return 0; cacheBlockNumber_ = blockNumber; } cacheDirty_ |= action; return 1; } /** * Closes a file and forces cached data and directory information * to be written to the storage device. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. * Reasons for failure include no file is open or an I/O error. */ uint8_t Fat16::close(void) { #if FAT16_WRITE_SUPPORT if (!sync())return 0; #else //FAT16_WRITE_SUPPORT if (!isOpen()) return 0; #endif //FAT16_WRITE_SUPPORT attributes_ = 0; return 1; } #if FAT16_WRITE_SUPPORT /** * Create and open a new file. * * \note This function only creates files in the root directory and * only supports short DOS 8.3 names. See open() for more information. * * \param[in] fileName a valid DOS 8.3 file name. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. * Reasons for failure include \a fileName contains * an invalid DOS 8.3 file name, the FAT volume has not been initialized, * a file is already open, the file already exists, the root * directory is full or an I/O error. * */ uint8_t Fat16::create(char *fileName) { uint8_t name[11]; uint16_t index = 0; if (isOpen())return 0; //error if bad file name if (!make83Name(fileName, name)) return 0; //error if name exists if (findDirEntry(index, name)) return 0; index = 0; //find unused directory entry uint8_t *p = (uint8_t *)findDirEntry(index, 0); //error if directory full if(!p) return 0; //insure created directory entry will be written to storage device cacheSetDirty(); //initialize as empty file for (uint8_t i = 0; i < sizeof(dir_t); i++) { p[i] = i < sizeof(name) ? name[i] : 0; } // open entry return open(index); } #endif //FAT16_WRITE_SUPPORT // dir_t *Fat16::findDirEntry(uint16_t &entry, uint8_t *name, uint8_t skip) { if(!volumeInitialized_) return 0; uint16_t index = entry; dir_t *d; for(; ; index++) { if (index >= rootDirEntryCount_) return 0; if(!(d = cacheDirEntry(index))) return 0; if (name == 0) { // done if unused entry if (d->name[0] == DIR_NAME_FREE || d->name[0] == DIR_NAME_DELETED) break; } else { // done if beyond last used entry if (d->name[0] == DIR_NAME_FREE) return 0; // skip deleted entry if (d->name[0] == DIR_NAME_DELETED) continue; // skip long names if ((d->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME) continue; // skip if attribute match if (d->attributes & skip) continue; // done if no name if (name[0] == '\0') break; //check for match of name and extension uint8_t i; for (i = 10; d->name[i] == name[i] && i > 0; i--); if(i == 0)break; } } entry = index; return d; } /** * Initialize a FAT16 volume. * * \param[in] dev The BlockDevice where the volume is located. * * \param[in] part The partition to be used. Legal values for \a part are * 1-4 to use the corresponding partition on a device formatted with * a MBR, Master Boot Record, or zero if the device is formatted as * a super floppy with the FAT boot sector in block zero. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. reasons for * failure include not finding a valid FAT16 file system in the * specified partition, a call to init() after a volume has * been successful initialized or an I/O error. * */ uint8_t Fat16::init(BlockDevice &dev, uint8_t part) { //error if volume already open or invalid partition if (volumeInitialized_ || part > 4) return 0; rawDev_ = &dev; uint32_t volumeStartBlock = 0; // if part == 0 assume super floppy with FAT16 boot sector in block zero // if part > 0 assume mbr volume with partition table if (part) { if (!cacheRawBlock(volumeStartBlock)) return 0; volumeStartBlock = cacheBuffer_.mbr.part[part - 1].firstSector; } if (!cacheRawBlock(volumeStartBlock)) return 0; //check boot block signature if (cacheBuffer_.data[510] != BOOTSIG0 || cacheBuffer_.data[511] != BOOTSIG1) return 0; bpb_t *bpb = &cacheBuffer_.fbs.bpb; fatCount_ = bpb->fatCount; blocksPerCluster_ = bpb->sectorsPerCluster; blocksPerFat_ = bpb->sectorsPerFat16; rootDirEntryCount_ = bpb->rootDirEntryCount; fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount; rootDirStartBlock_ = fatStartBlock_ + bpb->fatCount*bpb->sectorsPerFat16; dataStartBlock_ = rootDirStartBlock_ + ((32*bpb->rootDirEntryCount + 511)/512); uint32_t totalBlocks = bpb->totalSectors16 ? bpb->totalSectors16 : bpb->totalSectors32; clusterCount_ = (totalBlocks - (dataStartBlock_ - volumeStartBlock)) /bpb->sectorsPerCluster; //verify valid FAT16 volume if (bpb->bytesPerSector != 512 //only allow 512 byte blocks || bpb->sectorsPerFat16 == 0 //zero for FAT32 || clusterCount_ < 4085 //FAT12 if true || totalBlocks > 0X800000 //Max size for FAT16 volume || bpb->reservedSectorCount == 0 //invalid volume || bpb->fatCount == 0 //invalid volume || bpb->sectorsPerFat16 < (clusterCount_ >> 8) //invalid volume || bpb->sectorsPerCluster == 0 //invalid volume || bpb->sectorsPerCluster & (bpb->sectorsPerCluster - 1)) {//power of 2 test //not a usable FAT16 bpb return 0; } volumeInitialized_ = 1; return 1; } /** * Open a file for read and write by file name. Two file positions are * maintained. The write position is at the end of the file. Data is * appended to the file. The read position starts at the beginning of the file. * *\note The file must be in the root directory and must have a DOS * 8.3 name. * * \param[in] fileName A valid 8.3 DOS name for a file in the root directory. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. * Reasons for failure include the FAT volume has not been initialized, * a file is already open, \a fileName is invalid, the file does not * exist or it is a directory. */ uint8_t Fat16::open(char *fileName) { uint8_t name[11]; // error if invalid name if (!make83Name(fileName, name)) return 0; // start search at start of root directory uint16_t index = 0; // error if name not found if(!findDirEntry(index, name)) return 0; // open found entry return open(index); } /** * Open a file for read and write by file index. Two file positions are * maintained. The write position is at the end of the file. Data is * appended to the file. The read position starts at the beginning of the file. * * \param[in] index The root directory index of the file to be opened. See \link * Fat16::readDir() readDir()\endlink. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. * Reasons for failure include the FAT volume has not been initialized, * a file is already open, \a index is invalid or is not the index of a * file. */ uint8_t Fat16::open(uint16_t index) { if (isOpen() || !volumeInitialized_ )return 0; dir_t *d = cacheDirEntry(index); // if bad file index or I/O error if (!d) return 0; // error if unused entry if (d->name[0] == DIR_NAME_FREE || d->name[0] == DIR_NAME_DELETED) return 0; //error if long name, volume label or subdirectory if ((d->attributes & (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY)) != 0) return 0; attributes_ = d->attributes & ATTR_DIR_MASK; firstCluster_ = d->firstClusterLow; fileSize_ = d->fileSize; #if FAT16_READ_SUPPORT readCluster_ = 0; readPosition_ = 0; #endif //FAT16_READ_SUPPORT #if FAT16_WRITE_SUPPORT if (fileSize_ == 0) { writeCluster_ = 0; } else { // find cluster for end of file writeCluster_ = firstCluster_; //assumes 512 byte blocks uint16_t n = fileSize_/(blocksPerCluster_ << 9); while (n-- > 0) { writeCluster_ = nextCluster(writeCluster_); // return error if bad cluster chain if (writeCluster_ < 2 || isEOC(writeCluster_)) return 0; } } #endif //FAT16_WRITE_SUPPORT // set file open attributes_ |= ATTR_IS_OPEN; dirEntryIndex_ = index; #if FAT16_WRITE_SUPPORT return sync(); #else //FAT16_WRITE_SUPPORT return 1; #endif //FAT16_WRITE_SUPPORT } //get next cluster in chain fat_t Fat16::nextCluster(fat_t cluster) { fat_t *f; if (!(f = cacheFatEntry(cluster))) return 0; return *f; } #if FAT16_READ_SUPPORT /** * Read the next byte from a file. * * \return For success read returns the next byte in the file as an int. * If an error occurs or end of file is reached -1 is returned. */ int16_t Fat16::read(void) { uint8_t b; return read(&b, 1) == 1 ? b : -1; } /** * Read data from a file at starting at the current read position. * * \param[out] dst Pointer to the location that will receive the data. * * \param[in] count Maximum number of bytes to read. * * \return For success read returns the number of bytes read. * A value less than \a count, including zero, may be returned * if end of file is reached. * If an error occurs, read returns -1. Possible errors include * read called before a file has been opened, corrupt file system * or I/O error. */ int16_t Fat16::read(uint8_t *dst, uint16_t count) { if (!isOpen()) return -1; // don't read beyond end of file if (readPosition_ + count > fileSize_) count = fileSize_ - readPosition_; uint16_t nToRead = count; while (nToRead > 0) { uint8_t blkOfCluster = blockOfCluster(readPosition_); uint16_t blockOffset = cacheDataOffset(readPosition_); if (blkOfCluster == 0 && blockOffset == 0) { // start next cluster readCluster_ = readCluster_ ? nextCluster(readCluster_) : firstCluster_; // return error if bad cluster chain if (readCluster_ < 2 || isEOC(readCluster_)) return -1; } if (!cacheDataBlock(readCluster_, blkOfCluster)) return -1; // max number of byte available in block uint16_t n = 512 - blockOffset; //lesser of available and amount to read if(n > nToRead) n = nToRead; uint8_t *src = cacheBuffer_.data + blockOffset; uint8_t *end = src + n; while (src != end) *dst++ = *src++; nToRead -= n; readPosition_ += n; } return count; } #endif //FAT16_READ_SUPPORT #if FAT16_SEEK_SUPPORT & FAT16_READ_SUPPORT /** * Sets the file's read position. * * \param[in] pos The new read position in bytes from the beginning of the file. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. */ uint8_t Fat16::seek(uint32_t pos) { // error if file not open or seek past end of file if (!isOpen() || pos > fileSize_) return 0; if (pos == 0) { //set position to start of file readCluster_ = 0; } else { // find cluster for new read position fat_t tmpCluster = firstCluster_; uint16_t n = pos/(blocksPerCluster_ << 9); while (n-- > 0) { tmpCluster = nextCluster(tmpCluster); // return error if bad cluster chain if (tmpCluster < 2 || isEOC(tmpCluster)) return 0; } readCluster_ = tmpCluster; } readPosition_ = pos; return 1; } #endif //FAT16_SEEK_SUPPORT & FAT16_READ_SUPPORT #if FAT16_WRITE_SUPPORT /** * The sync() call causes all modified data and directory fields * to be written to the storage device. * * \return The value one, true, is returned for success and * the value zero, false, is returned for failure. * Reasons for failure include a call to sync() before a file has been * opened or an I/O error. */ uint8_t Fat16::sync(void) { dir_t *d; if (!isOpen()) return 0; if (attributes_ & ATTR_FILE_SIZE_DIRTY) { // update file size if(!(d = cacheDirEntry(dirEntryIndex_, CACHE_FOR_WRITE))) return 0; d->fileSize = fileSize_; attributes_ &= ~ATTR_FILE_SIZE_DIRTY; } return cacheFlush(); } /** * Append one byte to a file. This function is called by Arduino's Print class. * * \note The byte is moved to the cache but may not be written to the * storage device until sync() is called. * * \param[in] b The byte to be written. */ void Fat16::write(uint8_t b) { write(&b, 1); } /** * Write data at the end of an open file. * * \note Data is moved to the cache but may not be written to the * storage device until sync() is called. * * \param[in] src Pointer to the location of the data to be written. * * \param[in] count Number of bytes to write. * * \return For success write() returns the number of bytes written, always * \a count. If an error occurs, write() returns -1. Possible errors * include write() is called before a file has been opened, write is called * for a read-only file, device is full, a corrupt file system or an I/O error. * */ int16_t Fat16::write(uint8_t *src, uint16_t count) { if (!isOpen()) return -1; //error if file is read only if (attributes_ & DIR_ATT_READ_ONLY) return -1; uint16_t nToWrite = count; while (nToWrite > 0) { uint8_t blkOfCluster = blockOfCluster(fileSize_); uint16_t blockOffset = cacheDataOffset(fileSize_); if (blkOfCluster == 0 && blockOffset == 0) { //start of new cluster if (writeCluster_ == 0) { if (firstCluster_ == 0) { // allocate first cluster of file if (!addCluster()) return -1; } else { // only happens if empty file has allocated clusters writeCluster_ = firstCluster_; } } else { uint16_t next = nextCluster(writeCluster_); if (isEOC(next)) { // add cluster if at end of chain if (!addCluster()) return -1; } else { // error in cluster chain if(next < 2) return -1; // only happens if file has extra allocated clusters writeCluster_ = next; } } } // just zero cache if start of new block otherwise read block into cache uint8_t action = blockOffset == 0 ? CACHE_ZERO_BLOCK : CACHE_FOR_WRITE; if (!cacheDataBlock(writeCluster_, blkOfCluster, action)) return -1; // max space in block uint16_t n = 512 - blockOffset; // lesser of space and amount to write if(n > nToWrite) n = nToWrite; uint8_t *dst = cacheBuffer_.data + blockOffset; uint8_t *end = dst + n; while (dst != end) *dst++ = *src++; nToWrite -= n; // update fileSize and insure sync will update dir entry fileSize_ += n; attributes_ |= ATTR_FILE_SIZE_DIRTY; } return count - nToWrite; } #endif //FAT16_WRITE_SUPPORT