Skip to content
Snippets Groups Projects
SdBaseFile.cpp 54.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* Arduino SdFat Library
     * Copyright (C) 2009 by William Greiman
     *
     * This file is part of the Arduino SdFat 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 SdFat Library.  If not, see
     * <http://www.gnu.org/licenses/>.
     */
    
    
    #include "Marlin.h"
    
    #if ENABLED(SDSUPPORT)
    
    #include "SdBaseFile.h"
    //------------------------------------------------------------------------------
    // pointer to cwd directory
    SdBaseFile* SdBaseFile::cwd_ = 0;
    // callback function for date/time
    void (*SdBaseFile::dateTime_)(uint16_t* date, uint16_t* time) = 0;
    //------------------------------------------------------------------------------
    // add a cluster to a file
    bool SdBaseFile::addCluster() {
      if (!vol_->allocContiguous(1, &curCluster_)) goto fail;
    
      // if first cluster of file link to directory entry
      if (firstCluster_ == 0) {
        firstCluster_ = curCluster_;
        flags_ |= F_FILE_DIR_DIRTY;
      }
      return true;
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    // Add a cluster to a directory file and zero the cluster.
    // return with first block of cluster in the cache
    bool SdBaseFile::addDirCluster() {
      uint32_t block;
      // max folder size
      if (fileSize_/sizeof(dir_t) >= 0XFFFF) goto fail;
    
      if (!addCluster()) goto fail;
      if (!vol_->cacheFlush()) goto fail;
    
      block = vol_->clusterStartBlock(curCluster_);
    
      // set cache to first block of cluster
      vol_->cacheSetBlockNumber(block, true);
    
      // zero first block of cluster
      memset(vol_->cacheBuffer_.data, 0, 512);
    
      // zero rest of cluster
      for (uint8_t i = 1; i < vol_->blocksPerCluster_; i++) {
        if (!vol_->writeBlock(block + i, vol_->cacheBuffer_.data)) goto fail;
      }
      // Increase directory file size by cluster size
      fileSize_ += 512UL << vol_->clusterSizeShift_;
      return true;
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    // cache a file's directory entry
    // return pointer to cached entry or null for failure
    dir_t* SdBaseFile::cacheDirEntry(uint8_t action) {
      if (!vol_->cacheRawBlock(dirBlock_, action)) goto fail;
      return vol_->cache()->dir + dirIndex_;
    
     fail:
      return 0;
    }
    //------------------------------------------------------------------------------
    /** Close a file and force 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.
     */
    bool SdBaseFile::close() {
      bool rtn = sync();
      type_ = FAT_FILE_TYPE_CLOSED;
      return rtn;
    }
    //------------------------------------------------------------------------------
    /** Check for contiguous file and return its raw block range.
     *
     * \param[out] bgnBlock the first block address for the file.
     * \param[out] endBlock the last  block address for the file.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     * Reasons for failure include file is not contiguous, file has zero length
     * or an I/O error occurred.
     */
    bool SdBaseFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) {
      // error if no blocks
      if (firstCluster_ == 0) goto fail;
    
      for (uint32_t c = firstCluster_; ; c++) {
        uint32_t next;
        if (!vol_->fatGet(c, &next)) goto fail;
    
        // check for contiguous
        if (next != (c + 1)) {
          // error if not end of chain
          if (!vol_->isEOC(next)) goto fail;
          *bgnBlock = vol_->clusterStartBlock(firstCluster_);
          *endBlock = vol_->clusterStartBlock(c)
                      + vol_->blocksPerCluster_ - 1;
          return true;
        }
      }
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Create and open a new contiguous file of a specified size.
     *
     * \note This function only supports short DOS 8.3 names.
     * See open() for more information.
     *
     * \param[in] dirFile The directory where the file will be created.
     * \param[in] path A path with a valid DOS 8.3 file name.
     * \param[in] size The desired file size.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     * Reasons for failure include \a path 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.
     *
     */
    bool SdBaseFile::createContiguous(SdBaseFile* dirFile,
            const char* path, uint32_t size) {
      uint32_t count;
      // don't allow zero length file
      if (size == 0) goto fail;
      if (!open(dirFile, path, O_CREAT | O_EXCL | O_RDWR)) goto fail;
    
      // calculate number of clusters needed
      count = ((size - 1) >> (vol_->clusterSizeShift_ + 9)) + 1;
    
      // allocate clusters
      if (!vol_->allocContiguous(count, &firstCluster_)) {
        remove();
        goto fail;
      }
      fileSize_ = size;
    
      // insure sync() will update dir entry
      flags_ |= F_FILE_DIR_DIRTY;
    
      return sync();
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Return a file's directory entry.
     *
     * \param[out] dir Location for return of the file's directory entry.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     */
    bool SdBaseFile::dirEntry(dir_t* dir) {
      dir_t* p;
      // make sure fields on SD are correct
      if (!sync()) goto fail;
    
      // read entry
      p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
      if (!p) goto fail;
    
      // copy to caller's struct
      memcpy(dir, p, sizeof(dir_t));
      return true;
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Format the name field of \a dir into the 13 byte array
     * \a name in standard 8.3 short name format.
     *
     * \param[in] dir The directory structure containing the name.
     * \param[out] name A 13 byte char array for the formatted name.
     */
    void SdBaseFile::dirName(const 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;
    }
    //------------------------------------------------------------------------------
    /** Test for the existence of a file in a directory
     *
     * \param[in] name Name of the file to be tested for.
     *
     * The calling instance must be an open directory file.
     *
     * dirFile.exists("TOFIND.TXT") searches for "TOFIND.TXT" in  the directory
     * dirFile.
     *
     * \return true if the file exists else false.
     */
    bool SdBaseFile::exists(const char* name) {
      SdBaseFile file;
      return file.open(this, name, O_READ);
    }
    //------------------------------------------------------------------------------
    /**
     * Get a string from a file.
     *
     * fgets() reads bytes from a file into the array pointed to by \a str, until
     * \a num - 1 bytes are read, or a delimiter is read and transferred to \a str,
     * or end-of-file is encountered. The string is then terminated
     * with a null byte.
     *
     * fgets() deletes CR, '\\r', from the string.  This insures only a '\\n'
     * terminates the string for Windows text files which use CRLF for newline.
     *
     * \param[out] str Pointer to the array where the string is stored.
     * \param[in] num Maximum number of characters to be read
     * (including the final null byte). Usually the length
     * of the array \a str is used.
     * \param[in] delim Optional set of delimiters. The default is "\n".
     *
     * \return For success fgets() returns the length of the string in \a str.
     * If no data is read, fgets() returns zero for EOF or -1 if an error occurred.
     **/
    int16_t SdBaseFile::fgets(char* str, int16_t num, char* delim) {
      char ch;
      int16_t n = 0;
      int16_t r = -1;
      while ((n + 1) < num && (r = read(&ch, 1)) == 1) {
        // delete CR
        if (ch == '\r') continue;
        str[n++] = ch;
        if (!delim) {
          if (ch == '\n') break;
        } else {
          if (strchr(delim, ch)) break;
        }
      }
      if (r < 0) {
        // read error
        return -1;
      }
      str[n] = '\0';
      return n;
    }
    //------------------------------------------------------------------------------
    /** Get a file's name
     *
     * \param[out] name An array of 13 characters for the file's name.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     */
    bool SdBaseFile::getFilename(char* name) {
      if (!isOpen()) return false;
    
      if (isRoot()) {
        name[0] = '/';
        name[1] = '\0';
        return true;
      }
      // cache entry
      dir_t* p = cacheDirEntry(SdVolume::CACHE_FOR_READ);
      if (!p) return false;
    
      // format name
      dirName(*p, name);
      return true;
    }
    //------------------------------------------------------------------------------
    void SdBaseFile::getpos(fpos_t* pos) {
      pos->position = curPosition_;
      pos->cluster = curCluster_;
    }
    
    //------------------------------------------------------------------------------
    /** List directory contents.
     *
     * \param[in] pr Print stream for list.
     *
     * \param[in] flags The inclusive OR of
     *
     * LS_DATE - %Print file modification date
     *
     * LS_SIZE - %Print file size.
     *
     * LS_R - Recursive list of subdirectories.
     *
     * \param[in] indent Amount of space before file name. Used for recursive
     * list to indicate subdirectory level.
     */
    
    void SdBaseFile::ls(uint8_t flags, uint8_t indent) {
    
      rewind();
      int8_t status;
    
      while ((status = lsPrintNext( flags, indent))) {
    
        if (status > 1 && (flags & LS_R)) {
          uint16_t index = curPosition()/32 - 1;
          SdBaseFile s;
    
          if (s.open(this, index, O_READ)) s.ls( flags, indent + 2);
    
          seekSet(32 * (index + 1));
        }
      }
    }
    //------------------------------------------------------------------------------
    // saves 32 bytes on stack for ls recursion
    // return 0 - EOF, 1 - normal file, or 2 - directory
    
    int8_t SdBaseFile::lsPrintNext( uint8_t flags, uint8_t indent) {
    
      dir_t dir;
      uint8_t w = 0;
    
      while (1) {
        if (read(&dir, sizeof(dir)) != sizeof(dir)) return 0;
        if (dir.name[0] == DIR_NAME_FREE) return 0;
    
        // skip deleted entry and entries for . and  ..
        if (dir.name[0] != DIR_NAME_DELETED && dir.name[0] != '.'
          && DIR_IS_FILE_OR_SUBDIR(&dir)) break;
      }
      // indent for dir level
    
      for (uint8_t i = 0; i < indent; i++) MYSERIAL.write(' ');
    
    
      // print name
      for (uint8_t i = 0; i < 11; i++) {
        if (dir.name[i] == ' ')continue;
        if (i == 8) {
    
        w++;
      }
      if (DIR_IS_SUBDIR(&dir)) {
    
        w++;
      }
      if (flags & (LS_DATE | LS_SIZE)) {
    
        while (w++ < 14) MYSERIAL.write(' ');
    
      }
      // print modify date/time if requested
      if (flags & LS_DATE) {
    
        printFatDate( dir.lastWriteDate);
    
        printFatTime( dir.lastWriteTime);
    
      }
      // print size if requested
      if (!DIR_IS_SUBDIR(&dir) && (flags & LS_SIZE)) {
    
        MYSERIAL.write(' ');
        MYSERIAL.print(dir.fileSize);
    
      return DIR_IS_FILE(&dir) ? 1 : 2;
    }
    //------------------------------------------------------------------------------
    // format directory name field from a 8.3 name string
    bool SdBaseFile::make83Name(const char* str, uint8_t* name, const char** ptr) {
      uint8_t c;
      uint8_t n = 7;  // max index for part before dot
      uint8_t i = 0;
      // blank fill name and extension
      while (i < 11) name[i++] = ' ';
      i = 0;
      while (*str != '\0' && *str != '/') {
        c = *str++;
        if (c == '.') {
          if (n == 10) goto fail;  // only one dot allowed
          n = 10;  // max index for full 8.3 name
          i = 8;   // place for extension
        } else {
          // illegal FAT characters
          PGM_P p = PSTR("|<>^+=?/[];,*\"\\");
          uint8_t b;
          while ((b = pgm_read_byte(p++))) if (b == c) goto fail;
          // check size and only allow ASCII printable characters
          if (i > n || c < 0X21 || c > 0X7E)goto fail;
          // only upper case allowed in 8.3 names - convert lower to upper
    
          name[i++] = (c < 'a' || c > 'z') ?  (c) : (c + ('A' - 'a'));
    
    404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
        }
      }
      *ptr = str;
      // must have a file name, extension is optional
      return name[0] != ' ';
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Make a new directory.
     *
     * \param[in] parent An open SdFat instance for the directory that will contain
     * the new directory.
     *
     * \param[in] path A path with a valid 8.3 DOS name for the new directory.
     *
     * \param[in] pFlag Create missing parent directories if true.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     * Reasons for failure include this file is already open, \a parent is not a
     * directory, \a path is invalid or already exists in \a parent.
     */
    bool SdBaseFile::mkdir(SdBaseFile* parent, const char* path, bool pFlag) {
      uint8_t dname[11];
      SdBaseFile dir1, dir2;
      SdBaseFile* sub = &dir1;
      SdBaseFile* start = parent;
    
      if (!parent || isOpen()) goto fail;
    
      if (*path == '/') {
        while (*path == '/') path++;
        if (!parent->isRoot()) {
          if (!dir2.openRoot(parent->vol_)) goto fail;
          parent = &dir2;
        }
      }
      while (1) {
        if (!make83Name(path, dname, &path)) goto fail;
        while (*path == '/') path++;
        if (!*path) break;
        if (!sub->open(parent, dname, O_READ)) {
          if (!pFlag || !sub->mkdir(parent, dname)) {
            goto fail;
          }
        }
        if (parent != start) parent->close();
        parent = sub;
        sub = parent != &dir1 ? &dir1 : &dir2;
      }
      return mkdir(parent, dname);
    
      fail:
      return false;
    }
    //------------------------------------------------------------------------------
    bool SdBaseFile::mkdir(SdBaseFile* parent, const uint8_t dname[11]) {
      uint32_t block;
      dir_t d;
      dir_t* p;
    
      if (!parent->isDir()) goto fail;
    
      // create a normal file
      if (!open(parent, dname, O_CREAT | O_EXCL | O_RDWR)) goto fail;
    
      // convert file to directory
      flags_ = O_READ;
      type_ = FAT_FILE_TYPE_SUBDIR;
    
      // allocate and zero first cluster
      if (!addDirCluster())goto fail;
    
      // force entry to SD
      if (!sync()) goto fail;
    
      // cache entry - should already be in cache due to sync() call
      p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
      if (!p) goto fail;
    
      // change directory entry  attribute
      p->attributes = DIR_ATT_DIRECTORY;
    
      // make entry for '.'
      memcpy(&d, p, sizeof(d));
      d.name[0] = '.';
      for (uint8_t i = 1; i < 11; i++) d.name[i] = ' ';
    
      // cache block for '.'  and '..'
      block = vol_->clusterStartBlock(firstCluster_);
      if (!vol_->cacheRawBlock(block, SdVolume::CACHE_FOR_WRITE)) goto fail;
    
      // copy '.' to block
      memcpy(&vol_->cache()->dir[0], &d, sizeof(d));
    
      // make entry for '..'
      d.name[1] = '.';
      if (parent->isRoot()) {
        d.firstClusterLow = 0;
        d.firstClusterHigh = 0;
      } else {
        d.firstClusterLow = parent->firstCluster_ & 0XFFFF;
        d.firstClusterHigh = parent->firstCluster_ >> 16;
      }
      // copy '..' to block
      memcpy(&vol_->cache()->dir[1], &d, sizeof(d));
    
      // write first block
      return vol_->cacheFlush();
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
     /** Open a file in the current working directory.
      *
      * \param[in] path A path with a valid 8.3 DOS name for a file to be opened.
      *
      * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
      * OR of open flags. see SdBaseFile::open(SdBaseFile*, const char*, uint8_t).
      *
      * \return The value one, true, is returned for success and
      * the value zero, false, is returned for failure.
      */
      bool SdBaseFile::open(const char* path, uint8_t oflag) {
        return open(cwd_, path, oflag);
      }
    //------------------------------------------------------------------------------
    /** Open a file or directory by name.
     *
     * \param[in] dirFile An open SdFat instance for the directory containing the
     * file to be opened.
     *
     * \param[in] path A path with a valid 8.3 DOS name for a file to be opened.
     *
     * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
     * OR of flags from the following list
     *
     * O_READ - Open for reading.
     *
     * O_RDONLY - Same as O_READ.
     *
     * O_WRITE - Open for writing.
     *
     * O_WRONLY - Same as O_WRITE.
     *
     * O_RDWR - Open for reading and writing.
     *
     * O_APPEND - If set, the file offset shall be set to the end of the
     * file prior to each write.
     *
     * O_AT_END - Set the initial position at the end of the file.
     *
     * O_CREAT - If the file exists, this flag has no effect except as noted
     * under O_EXCL below. Otherwise, the file shall be created
     *
     * O_EXCL - If O_CREAT and O_EXCL are set, open() shall fail if the file exists.
     *
     * O_SYNC - Call sync() after each write.  This flag should not be used with
     * write(uint8_t), write_P(PGM_P), writeln_P(PGM_P), or the Arduino Print class.
     * These functions do character at a time writes so sync() will be called
     * after each byte.
     *
     * O_TRUNC - If the file exists and is a regular file, and the file is
     * successfully opened and is not read only, its length shall be truncated to 0.
     *
     * WARNING: A given file must not be opened by more than one SdBaseFile object
     * of file corruption may occur.
     *
     * \note Directory files must be opened read only.  Write and truncation is
     * not allowed for directory files.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     * Reasons for failure include this file is already open, \a dirFile is not
     * a directory, \a path is invalid, the file does not exist
     * or can't be opened in the access mode specified by oflag.
     */
    bool SdBaseFile::open(SdBaseFile* dirFile, const char* path, uint8_t oflag) {
      uint8_t dname[11];
      SdBaseFile dir1, dir2;
      SdBaseFile *parent = dirFile;
      SdBaseFile *sub = &dir1;
    
      if (!dirFile) goto fail;
    
      // error if already open
      if (isOpen()) goto fail;
    
      if (*path == '/') {
        while (*path == '/') path++;
        if (!dirFile->isRoot()) {
          if (!dir2.openRoot(dirFile->vol_)) goto fail;
          parent = &dir2;
        }
      }
      while (1) {
        if (!make83Name(path, dname, &path)) goto fail;
        while (*path == '/') path++;
        if (!*path) break;
        if (!sub->open(parent, dname, O_READ)) goto fail;
        if (parent != dirFile) parent->close();
        parent = sub;
        sub = parent != &dir1 ? &dir1 : &dir2;
      }
      return open(parent, dname, oflag);
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    // open with filename in dname
    bool SdBaseFile::open(SdBaseFile* dirFile,
      const uint8_t dname[11], uint8_t oflag) {
      bool emptyFound = false;
      bool fileFound = false;
      uint8_t index;
      dir_t* p;
    
      vol_ = dirFile->vol_;
    
      dirFile->rewind();
      // search for file
    
      while (dirFile->curPosition_ < dirFile->fileSize_) {
        index = 0XF & (dirFile->curPosition_ >> 5);
        p = dirFile->readDirCache();
        if (!p) goto fail;
    
        if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
          // remember first empty slot
          if (!emptyFound) {
            dirBlock_ = dirFile->vol_->cacheBlockNumber();
            dirIndex_ = index;
            emptyFound = true;
          }
          // done if no entries follow
          if (p->name[0] == DIR_NAME_FREE) break;
        } else if (!memcmp(dname, p->name, 11)) {
          fileFound = true;
          break;
        }
      }
      if (fileFound) {
        // don't open existing file if O_EXCL
        if (oflag & O_EXCL) goto fail;
      } else {
        // don't create unless O_CREAT and O_WRITE
        if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) goto fail;
        if (emptyFound) {
          index = dirIndex_;
          p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
          if (!p) goto fail;
        } else {
          if (dirFile->type_ == FAT_FILE_TYPE_ROOT_FIXED) goto fail;
    
          // add and zero cluster for dirFile - first cluster is in cache for write
          if (!dirFile->addDirCluster()) goto fail;
    
          // use first entry in cluster
          p = dirFile->vol_->cache()->dir;
          index = 0;
        }
        // initialize as empty file
        memset(p, 0, sizeof(dir_t));
        memcpy(p->name, dname, 11);
    
        // set timestamps
        if (dateTime_) {
          // call user date/time function
          dateTime_(&p->creationDate, &p->creationTime);
        } else {
          // use default date/time
          p->creationDate = FAT_DEFAULT_DATE;
          p->creationTime = FAT_DEFAULT_TIME;
        }
        p->lastAccessDate = p->creationDate;
        p->lastWriteDate = p->creationDate;
        p->lastWriteTime = p->creationTime;
    
        // write entry to SD
        if (!dirFile->vol_->cacheFlush()) goto fail;
      }
      // open entry in cache
      return openCachedEntry(index, oflag);
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Open a file by index.
     *
     * \param[in] dirFile An open SdFat instance for the directory.
     *
     * \param[in] index The \a index of the directory entry for the file to be
     * opened.  The value for \a index is (directory file position)/32.
     *
     * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
     * OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
     *
     * See open() by path for definition of flags.
     * \return true for success or false for failure.
     */
    bool SdBaseFile::open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag) {
      dir_t* p;
    
      vol_ = dirFile->vol_;
    
      // error if already open
      if (isOpen() || !dirFile) goto fail;
    
      // don't open existing file if O_EXCL - user call error
      if (oflag & O_EXCL) goto fail;
    
      // seek to location of entry
      if (!dirFile->seekSet(32 * index)) goto fail;
    
      // read entry into cache
      p = dirFile->readDirCache();
      if (!p) goto fail;
    
      // error if empty slot or '.' or '..'
      if (p->name[0] == DIR_NAME_FREE ||
          p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') {
        goto fail;
      }
      // open cached entry
      return openCachedEntry(index & 0XF, oflag);
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    // open a cached directory entry. Assumes vol_ is initialized
    bool SdBaseFile::openCachedEntry(uint8_t dirIndex, uint8_t oflag) {
      // location of entry in cache
      dir_t* p = &vol_->cache()->dir[dirIndex];
    
      // write or truncate is an error for a directory or read-only file
      if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) {
        if (oflag & (O_WRITE | O_TRUNC)) goto fail;
      }
      // remember location of directory entry on SD
      dirBlock_ = vol_->cacheBlockNumber();
      dirIndex_ = dirIndex;
    
      // copy first cluster number for directory fields
      firstCluster_ = (uint32_t)p->firstClusterHigh << 16;
      firstCluster_ |= p->firstClusterLow;
    
      // make sure it is a normal file or subdirectory
      if (DIR_IS_FILE(p)) {
        fileSize_ = p->fileSize;
        type_ = FAT_FILE_TYPE_NORMAL;
      } else if (DIR_IS_SUBDIR(p)) {
        if (!vol_->chainSize(firstCluster_, &fileSize_)) goto fail;
        type_ = FAT_FILE_TYPE_SUBDIR;
      } else {
        goto fail;
      }
      // save open flags for read/write
      flags_ = oflag & F_OFLAG;
    
      // set to start of file
      curCluster_ = 0;
      curPosition_ = 0;
      if ((oflag & O_TRUNC) && !truncate(0)) return false;
      return oflag & O_AT_END ? seekEnd(0) : true;
    
     fail:
      type_ = FAT_FILE_TYPE_CLOSED;
      return false;
    }
    //------------------------------------------------------------------------------
    /** Open the next file or subdirectory in a directory.
     *
     * \param[in] dirFile An open SdFat instance for the directory containing the
     * file to be opened.
     *
     * \param[in] oflag Values for \a oflag are constructed by a bitwise-inclusive
     * OR of flags O_READ, O_WRITE, O_TRUNC, and O_SYNC.
     *
     * See open() by path for definition of flags.
     * \return true for success or false for failure.
     */
    bool SdBaseFile::openNext(SdBaseFile* dirFile, uint8_t oflag) {
      dir_t* p;
      uint8_t index;
    
      if (!dirFile) goto fail;
    
      // error if already open
      if (isOpen()) goto fail;
    
      vol_ = dirFile->vol_;
    
      while (1) {
        index = 0XF & (dirFile->curPosition_ >> 5);
    
        // read entry into cache
        p = dirFile->readDirCache();
        if (!p) goto fail;
    
        // done if last entry
        if (p->name[0] == DIR_NAME_FREE) goto fail;
    
        // skip empty slot or '.' or '..'
        if (p->name[0] == DIR_NAME_DELETED || p->name[0] == '.') {
          continue;
        }
        // must be file or dir
        if (DIR_IS_FILE_OR_SUBDIR(p)) {
          return openCachedEntry(index, oflag);
        }
      }
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Open a directory's parent directory.
     *
     * \param[in] dir Parent of this directory will be opened.  Must not be root.
     *
     * \return The value one, true, is returned for success and
     * the value zero, false, is returned for failure.
     */
    bool SdBaseFile::openParent(SdBaseFile* dir) {
      dir_t entry;
      dir_t* p;
      SdBaseFile file;
      uint32_t c;
      uint32_t cluster;
      uint32_t lbn;
      // error if already open or dir is root or dir is not a directory
      if (isOpen() || !dir || dir->isRoot() || !dir->isDir()) goto fail;
      vol_ = dir->vol_;
      // position to '..'
      if (!dir->seekSet(32)) goto fail;
      // read '..' entry
      if (dir->read(&entry, sizeof(entry)) != 32) goto fail;
      // verify it is '..'
      if (entry.name[0] != '.' || entry.name[1] != '.') goto fail;
      // start cluster for '..'
      cluster = entry.firstClusterLow;
      cluster |= (uint32_t)entry.firstClusterHigh << 16;
      if (cluster == 0) return openRoot(vol_);
      // start block for '..'
      lbn = vol_->clusterStartBlock(cluster);
      // first block of parent dir
      if (!vol_->cacheRawBlock(lbn, SdVolume::CACHE_FOR_READ)) {
        goto fail;
      }
      p = &vol_->cacheBuffer_.dir[1];
      // verify name for '../..'
      if (p->name[0] != '.' || p->name[1] != '.') goto fail;
      // '..' is pointer to first cluster of parent. open '../..' to find parent
      if (p->firstClusterHigh == 0 && p->firstClusterLow == 0) {
        if (!file.openRoot(dir->volume())) goto fail;
      } else {
        if (!file.openCachedEntry(1, O_READ)) goto fail;
      }
      // search for parent in '../..'
      do {
    
    daid's avatar
    daid committed
        if (file.readDir(&entry, NULL) != 32) goto fail;
    
        c = entry.firstClusterLow;
        c |= (uint32_t)entry.firstClusterHigh << 16;
      } while (c != cluster);
      // open parent
      return open(&file, file.curPosition()/32 - 1, O_READ);
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** 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 file is already open, the FAT volume has
     * not been initialized or it a FAT12 volume.
     */
    bool SdBaseFile::openRoot(SdVolume* vol) {
      // error if file is already open
      if (isOpen()) goto fail;
    
      if (vol->fatType() == 16 || (FAT12_SUPPORT && vol->fatType() == 12)) {
        type_ = FAT_FILE_TYPE_ROOT_FIXED;
        firstCluster_ = 0;
        fileSize_ = 32 * vol->rootDirEntryCount();
      } else if (vol->fatType() == 32) {
        type_ = FAT_FILE_TYPE_ROOT32;
        firstCluster_ = vol->rootDirStart();
        if (!vol->chainSize(firstCluster_, &fileSize_)) goto fail;
      } else {
        // volume is not initialized, invalid, or FAT12 without support
        return false;
      }
      vol_ = vol;
      // read only
      flags_ = O_READ;
    
      // set to start of file
      curCluster_ = 0;
      curPosition_ = 0;
    
      // root has no directory entry
      dirBlock_ = 0;
      dirIndex_ = 0;
      return true;
    
     fail:
      return false;
    }
    //------------------------------------------------------------------------------
    /** Return the next available byte without consuming it.
     *
     * \return The byte if no error and not at eof else -1;
     */
    int SdBaseFile::peek() {
      fpos_t pos;
      getpos(&pos);
      int c = read();
      if (c >= 0) setpos(&pos);
      return c;
    }
    
    //------------------------------------------------------------------------------
    /** %Print the name field of a directory entry in 8.3 format.
     * \param[in] pr Print stream for output.
     * \param[in] dir The directory structure containing the name.
     * \param[in] width Blank fill name if length is less than \a width.
     * \param[in] printSlash Print '/' after directory names if true.
     */
    
    void SdBaseFile::printDirName(const dir_t& dir,
      uint8_t width, bool printSlash) {
    
      uint8_t w = 0;
      for (uint8_t i = 0; i < 11; i++) {
        if (dir.name[i] == ' ')continue;
        if (i == 8) {
    
        w++;
      }
      if (DIR_IS_SUBDIR(&dir) && printSlash) {
    
        w++;
      }
      while (w < width) {
    
        w++;
      }
    }
    //------------------------------------------------------------------------------
    // print uint8_t with width 2
    
    static void print2u( uint8_t v) {
    
      if (v < 10) MYSERIAL.write('0');
      MYSERIAL.print(v, DEC);
    
    }
    //------------------------------------------------------------------------------
    /** %Print a directory date field to Serial.
     *
     *  Format is yyyy-mm-dd.
     *
     * \param[in] fatDate The date field from a directory entry.
     */
    
    //------------------------------------------------------------------------------
    /** %Print a directory date field.
     *
     *  Format is yyyy-mm-dd.
     *
     * \param[in] pr Print stream for output.
     * \param[in] fatDate The date field from a directory entry.
     */
    
    void SdBaseFile::printFatDate(uint16_t fatDate) {
    
      MYSERIAL.print(FAT_YEAR(fatDate));
      MYSERIAL.write('-');
    
      print2u( FAT_MONTH(fatDate));
    
      print2u( FAT_DAY(fatDate));
    
    //------------------------------------------------------------------------------
    /** %Print a directory time field.
     *
     * Format is hh:mm:ss.
     *
     * \param[in] pr Print stream for output.
     * \param[in] fatTime The time field from a directory entry.
     */