/* 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 * . */ #include #if ARDUINO < 100 #include #else // ARDUINO #include #endif // ARDUINO #include //------------------------------------------------------------------------------ /** * 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; }