initial commit
This commit is contained in:
@@ -0,0 +1,488 @@
|
||||
/* Arduino FatReader Library
|
||||
* Copyright (C) 2009 by William Greiman
|
||||
*
|
||||
* This file is part of the Arduino FatReader 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 FatReader Library. If not, see
|
||||
* <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <string.h>
|
||||
#if ARDUINO < 100
|
||||
#include <WProgram.h>
|
||||
#else // ARDUINO
|
||||
#include <Arduino.h>
|
||||
#endif // ARDUINO
|
||||
#include <FatReader.h>
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Format the name field of the dir_t struct \a dir into the 13 byte array
|
||||
* \a name in the standard 8.3 short name format.
|
||||
*/
|
||||
void dirName(dir_t &dir, char name[]) {
|
||||
uint8_t j = 0;
|
||||
for (uint8_t i = 0; i < 11; i++) {
|
||||
if (dir.name[i] == ' ')
|
||||
continue;
|
||||
if (i == 8)
|
||||
name[j++] = '.';
|
||||
name[j++] = dir.name[i];
|
||||
}
|
||||
name[j] = 0;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Print the name field of a dir_t structure in 8.3 format.
|
||||
* Append a '/' if it is a subdirectory.
|
||||
*
|
||||
*/
|
||||
void printEntryName(dir_t &dir) {
|
||||
for (uint8_t i = 0; i < 11; i++) {
|
||||
if (dir.name[i] == ' ')
|
||||
continue;
|
||||
if (i == 8)
|
||||
Serial.write('.');
|
||||
Serial.write(dir.name[i]);
|
||||
}
|
||||
if (DIR_IS_SUBDIR(dir)) {
|
||||
// indicate subdirectory
|
||||
Serial.write('/');
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief list file in a directory
|
||||
@param flags file flags
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void FatReader::ls(uint8_t flags) {
|
||||
dir_t d;
|
||||
if (isDir())
|
||||
lsR(d, flags, 0);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// recursive part of ls()
|
||||
void FatReader::lsR(dir_t &d, uint8_t flags, uint8_t indent) {
|
||||
while (readDir(d) > 0) {
|
||||
|
||||
// print any indent spaces
|
||||
for (int8_t i = 0; i < indent; i++) {
|
||||
Serial.write(' ');
|
||||
}
|
||||
printEntryName(d);
|
||||
|
||||
if (DIR_IS_SUBDIR(d)) {
|
||||
Serial.println();
|
||||
// recursive call if LS_R
|
||||
if (flags & LS_R) {
|
||||
FatReader s;
|
||||
if (s.open(*vol_, d)) {
|
||||
s.lsR(d, flags, indent + 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (flags & LS_FLAG_FRAGMENTED) {
|
||||
uint32_t c = (uint32_t)d.firstClusterHigh << 16;
|
||||
c |= d.firstClusterLow;
|
||||
|
||||
// fragmented if has clusters and not contiguous
|
||||
char f = c && !vol_->chainIsContiguous(c) ? '*' : ' ';
|
||||
Serial.write(' ');
|
||||
Serial.write(f);
|
||||
}
|
||||
if (flags & LS_SIZE) {
|
||||
Serial.write(' ');
|
||||
Serial.print(d.fileSize);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/** return the next cluster in a chain */
|
||||
uint32_t FatVolume::nextCluster(uint32_t cluster) {
|
||||
if (!validCluster(cluster)) {
|
||||
return 0;
|
||||
}
|
||||
if (fatType_ == 32) {
|
||||
uint32_t next;
|
||||
uint32_t block = fatStartBlock_ + (cluster >> 7);
|
||||
uint16_t offset = 0X1FF & (cluster << 2);
|
||||
if (!rawRead(block, offset, (uint8_t *)&next, 4)) {
|
||||
return 0;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
if (fatType_ == 16) {
|
||||
uint16_t next;
|
||||
uint32_t block = fatStartBlock_ + (cluster >> 8);
|
||||
uint16_t offset = 0X1FF & (cluster << 1);
|
||||
if (!rawRead(block, offset, (uint8_t *)&next, 2)) {
|
||||
return 0;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Open a file or subdirectory by index.
|
||||
*
|
||||
* \param[in] dir An open FatReader instance for the directory.
|
||||
*
|
||||
* \param[in] index The \a index for a file or subdirectory in the
|
||||
* directory \a dir. \a index is the byte offset divided by 32 of
|
||||
* the directory entry for the file or subdirectory.
|
||||
*
|
||||
* To determine the index for a file open it by name. The index for the
|
||||
* file is then is: (dir.readPosition()/32 -1)
|
||||
*
|
||||
*
|
||||
* \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 dir
|
||||
* is not a directory, \a name is invalid, the file or subdirectory does not
|
||||
* exist, or an I/O error occurred.
|
||||
*/
|
||||
uint8_t FatReader::open(FatReader &dir, uint16_t index) {
|
||||
dir_t d;
|
||||
|
||||
// position directory file to entry
|
||||
if (!dir.seekSet(32UL * index))
|
||||
return false;
|
||||
|
||||
// read entry
|
||||
if (dir.read(&d, 32) != 32)
|
||||
return false;
|
||||
|
||||
// must be a real file or directory
|
||||
if (!DIR_IS_FILE_OR_SUBDIR(d) || d.name[0] == DIR_NAME_FREE ||
|
||||
d.name[0] == DIR_NAME_DELETED) {
|
||||
return false;
|
||||
}
|
||||
return open(*dir.volume(), d);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Open a file or subdirectory by name.
|
||||
*
|
||||
* \note The file or subdirectory, \a name, must be in the specified
|
||||
* directory, \a dir, and must have a DOS 8.3 name.
|
||||
*
|
||||
* \param[in] dir An open FatReader instance for the directory.
|
||||
*
|
||||
* \param[in] name A valid 8.3 DOS name for a file or subdirectory in the
|
||||
* directory \a dir.
|
||||
*
|
||||
* \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 dir
|
||||
* is not a directory, \a name is invalid, the file or subdirectory does not
|
||||
* exist, or an I/O error occurred.
|
||||
*/
|
||||
uint8_t FatReader::open(FatReader &dir, char *name) {
|
||||
dir_t entry;
|
||||
char dname[13];
|
||||
|
||||
dir.rewind();
|
||||
while (dir.readDir(entry) > 0) {
|
||||
dirName(entry, dname);
|
||||
if (strcasecmp(dname, name))
|
||||
continue;
|
||||
return open(*(dir.vol_), entry);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Open a file or subdirectory by directory structure.
|
||||
*
|
||||
* \param[in] vol The FAT volume that contains the file or subdirectory.
|
||||
*
|
||||
* \param[in] dir The directory structure describing the file or subdirectory.
|
||||
*
|
||||
* \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, \a vol, has not been initialized,
|
||||
* \a vol is a FAT12 volume or \a dir is not a valid directory entry.
|
||||
*/
|
||||
uint8_t FatReader::open(FatVolume &vol, dir_t &dir) {
|
||||
if (vol.fatType() < 16)
|
||||
return false;
|
||||
if (dir.name[0] == 0 || dir.name[0] == DIR_NAME_DELETED) {
|
||||
return false;
|
||||
}
|
||||
firstCluster_ = (uint32_t)dir.firstClusterHigh << 16;
|
||||
firstCluster_ |= dir.firstClusterLow;
|
||||
if (DIR_IS_FILE(dir)) {
|
||||
type_ = FILE_TYPE_NORMAL;
|
||||
fileSize_ = dir.fileSize;
|
||||
} else if (DIR_IS_SUBDIR(dir)) {
|
||||
type_ = FILE_TYPE_SUBDIR;
|
||||
fileSize_ = vol.chainSize(firstCluster_);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
vol_ = &vol;
|
||||
rewind();
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Open a volume's root directory.
|
||||
*
|
||||
* \param[in] vol The FAT volume containing the root directory to be opened.
|
||||
*
|
||||
* \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
|
||||
* or it a FAT12 volume.
|
||||
*/
|
||||
uint8_t FatReader::openRoot(FatVolume &vol) {
|
||||
if (vol.fatType() == 16) {
|
||||
type_ = FILE_TYPE_ROOT16;
|
||||
firstCluster_ = 0;
|
||||
fileSize_ = 32 * vol.rootDirEntryCount();
|
||||
} else if (vol.fatType() == 32) {
|
||||
type_ = FILE_TYPE_ROOT32;
|
||||
firstCluster_ = vol.rootDirStart();
|
||||
fileSize_ = vol.chainSize(firstCluster_);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
vol_ = &vol;
|
||||
rewind();
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Check for a contiguous file and enable optimized reads if the
|
||||
* file is contiguous.
|
||||
*/
|
||||
void FatReader::optimizeContiguous(void) {
|
||||
if (isOpen() && firstCluster_) {
|
||||
if (vol_->chainIsContiguous(firstCluster_)) {
|
||||
type_ |= FILE_IS_CONTIGUOUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Read data from a file at starting at the current read position.
|
||||
*
|
||||
* \param[out] buf 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, will 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 an I/O error occurred.
|
||||
*/
|
||||
int16_t FatReader::read(void *buf, uint16_t count) {
|
||||
uint8_t *dst = (uint8_t *)buf;
|
||||
uint16_t nr = 0;
|
||||
int16_t n = 0;
|
||||
while (nr < count && (n = readBlockData(dst, count - nr)) > 0) {
|
||||
if (!seekCur(n))
|
||||
return -1;
|
||||
dst += n;
|
||||
nr += n;
|
||||
}
|
||||
return n < 0 ? -1 : nr;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// read maximum amount possible from current physical block
|
||||
int16_t FatReader::readBlockData(uint8_t *dst, uint16_t count) {
|
||||
uint32_t block;
|
||||
uint16_t offset = readPosition_ & 0X1FF;
|
||||
if (count > (512 - offset))
|
||||
count = 512 - offset;
|
||||
if (count > (fileSize_ - readPosition_))
|
||||
count = fileSize_ - readPosition_;
|
||||
if (fileType() == FILE_TYPE_ROOT16) {
|
||||
block = vol_->rootDirStart() + (readPosition_ >> 9);
|
||||
} else {
|
||||
uint8_t bpc = vol_->blocksPerCluster();
|
||||
block = vol_->dataStartBlock() + (readCluster_ - 2) * bpc +
|
||||
((readPosition_ >> 9) & (bpc - 1));
|
||||
}
|
||||
return vol_->rawRead(block, offset, dst, count) ? count : -1;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Read the next directory entry from a directory file.
|
||||
*
|
||||
* \param[out] dir The dir_t struct that will receive the data.
|
||||
*
|
||||
* \return For success readDir() returns the number of bytes read.
|
||||
* A value of zero will be returned if end of file is reached.
|
||||
* If an error occurs, readDir() returns -1. Possible errors include
|
||||
* readDir() called before a directory has been opened, this is not
|
||||
* a directory file or an I/O error occurred.
|
||||
*/
|
||||
int8_t FatReader::readDir(dir_t &dir) {
|
||||
int8_t n;
|
||||
// if not a directory file return an error
|
||||
if (!isDir())
|
||||
return -1;
|
||||
while ((n = read((uint8_t *)&dir, sizeof(dir_t))) == sizeof(dir_t) &&
|
||||
dir.name[0] != DIR_NAME_FREE) {
|
||||
if (dir.name[0] == DIR_NAME_DELETED || dir.name[0] == '.')
|
||||
continue;
|
||||
if (DIR_IS_FILE(dir) || DIR_IS_SUBDIR(dir))
|
||||
return n;
|
||||
}
|
||||
return n < 0 ? n : 0;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/** Set read position to start of file */
|
||||
void FatReader::rewind(void) {
|
||||
readCluster_ = firstCluster_;
|
||||
readPosition_ = 0;
|
||||
}
|
||||
/**
|
||||
* Set the read position for a file or directory to the current position plus
|
||||
* \a offset.
|
||||
*
|
||||
* \param[in] offset The amount to advance the read position.
|
||||
*
|
||||
* \return The value one, true, is returned for success and
|
||||
* the value zero, false, is returned for failure.
|
||||
*/
|
||||
uint8_t FatReader::seekCur(uint32_t offset) {
|
||||
|
||||
uint32_t newPos = readPosition_ + offset;
|
||||
|
||||
// can't position beyond end of file
|
||||
if (newPos > fileSize_)
|
||||
return false;
|
||||
|
||||
// number of clusters forward
|
||||
uint32_t nc = (newPos >> 9) / vol_->blocksPerCluster() -
|
||||
(readPosition_ >> 9) / vol_->blocksPerCluster();
|
||||
|
||||
// set new position - only corrupt file system can cause error now
|
||||
readPosition_ = newPos;
|
||||
|
||||
// no clusters if FAT16 root
|
||||
if (fileType() == FILE_TYPE_ROOT16)
|
||||
return true;
|
||||
|
||||
// don't need to read FAT if contiguous
|
||||
if (isContiguous()) {
|
||||
readCluster_ += nc;
|
||||
return true;
|
||||
}
|
||||
|
||||
// read FAT chain while nc != 0
|
||||
while (nc-- != 0) {
|
||||
if (!(readCluster_ = vol_->nextCluster(readCluster_))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/** check for contiguous chain */
|
||||
uint8_t FatVolume::chainIsContiguous(uint32_t cluster) {
|
||||
uint32_t next;
|
||||
while ((next = nextCluster(cluster))) {
|
||||
if (next != (cluster + 1)) {
|
||||
return isEOC(next);
|
||||
}
|
||||
cluster = next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/** return the number of bytes in a cluster chain */
|
||||
uint32_t FatVolume::chainSize(uint32_t cluster) {
|
||||
uint32_t size = 0;
|
||||
while ((cluster = nextCluster(cluster))) {
|
||||
size += 512 * blocksPerCluster_;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
/**
|
||||
* Initialize a FAT volume.
|
||||
*
|
||||
* \param[in] dev The SD card 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 partition, not finding a valid
|
||||
* FAT file system in the specified partition or an I/O error.
|
||||
*/
|
||||
uint8_t FatVolume::init(SdReader &dev, uint8_t part) {
|
||||
uint8_t buf[BPB_COUNT];
|
||||
uint32_t volumeStartBlock = 0;
|
||||
rawDevice_ = &dev;
|
||||
// if part == 0 assume super floppy with FAT boot sector in block zero
|
||||
// if part > 0 assume mbr volume with partition table
|
||||
if (part) {
|
||||
if (part > 4)
|
||||
return false;
|
||||
|
||||
if (!rawRead(volumeStartBlock, PART_OFFSET + 16 * (part - 1), buf, 16)) {
|
||||
return false;
|
||||
}
|
||||
part_t *part = (part_t *)buf;
|
||||
if ((part->boot & 0X7F) != 0 || part->totalSectors < 100 ||
|
||||
part->firstSector == 0) {
|
||||
// not a valid partition
|
||||
return false;
|
||||
}
|
||||
volumeStartBlock = part->firstSector;
|
||||
}
|
||||
if (!rawRead(volumeStartBlock, BPB_OFFSET, buf, BPB_COUNT)) {
|
||||
return false;
|
||||
}
|
||||
bpb_t *bpb = (bpb_t *)buf;
|
||||
if (bpb->bytesPerSector != 512 || bpb->fatCount == 0 ||
|
||||
bpb->reservedSectorCount == 0 || bpb->sectorsPerCluster == 0 ||
|
||||
(bpb->sectorsPerCluster & (bpb->sectorsPerCluster - 1)) != 0) {
|
||||
// not valid FAT volume
|
||||
return false;
|
||||
}
|
||||
fatCount_ = bpb->fatCount;
|
||||
blocksPerCluster_ = bpb->sectorsPerCluster;
|
||||
blocksPerFat_ =
|
||||
bpb->sectorsPerFat16 ? bpb->sectorsPerFat16 : bpb->sectorsPerFat32;
|
||||
rootDirEntryCount_ = bpb->rootDirEntryCount;
|
||||
fatStartBlock_ = volumeStartBlock + bpb->reservedSectorCount;
|
||||
rootDirStart_ = fatStartBlock_ + bpb->fatCount * blocksPerFat_;
|
||||
dataStartBlock_ = rootDirStart_ + ((32 * bpb->rootDirEntryCount + 511) / 512);
|
||||
totalBlocks_ =
|
||||
bpb->totalSectors16 ? bpb->totalSectors16 : bpb->totalSectors32;
|
||||
clusterCount_ = (totalBlocks_ - (dataStartBlock_ - volumeStartBlock)) /
|
||||
bpb->sectorsPerCluster;
|
||||
if (clusterCount_ < 4085) {
|
||||
fatType_ = 12;
|
||||
} else if (clusterCount_ < 65525) {
|
||||
fatType_ = 16;
|
||||
} else {
|
||||
rootDirStart_ = bpb->fat32RootCluster;
|
||||
fatType_ = 32;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user