aboutsummaryrefslogtreecommitdiff
path: root/libraries/Robot_Control/Fat16.cpp
blob: aa8f585e95cca0c7a4baea30ae3405b01e5fbb0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
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
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
/* 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
 * <http://www.gnu.org/licenses/>.
 */
#include <avr/pgmspace.h>
#if ARDUINO < 100
#include <WProgram.h>
#else  // ARDUINO
#include <Arduino.h>
#endif  // ARDUINO
#include <Fat16.h>
//-----------------------------------------------------------------------------
// 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
fat_t    Fat16::blocksPerFat_;           // number of blocks in one FAT
fat_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
SdCard *Fat16::rawDev_ = 0;             // class for block read and write
uint32_t Fat16::cacheBlockNumber_ = 0XFFFFFFFF;  // init to invalid block number
cache16_t  Fat16::cacheBuffer_;         // 512 byte cache for SdCard
uint8_t  Fat16::cacheDirty_ = 0;        // cacheFlush() will write block if true
uint32_t Fat16::cacheMirrorBlock_ = 0;  // mirror  block for second FAT
//------------------------------------------------------------------------------
// callback function for date/time
void (*Fat16::dateTime_)(uint16_t* date, uint16_t* time) = NULL;

#if ALLOW_DEPRECATED_FUNCTIONS
void (*Fat16::oldDateTime_)(uint16_t& date, uint16_t& time) = NULL;  // NOLINT
#endif  // ALLOW_DEPRECATED_FUNCTIONS
//------------------------------------------------------------------------------
// format 8.3 name for directory entry
static uint8_t make83Name(const char* str, uint8_t* name) {
  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 ((c = *str++) != '\0') {
    if (c == '.') {
      if (n == 10) return false;  // 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) return false;
      // check length and only allow ASCII printable characters
      if (i > n || c < 0X21 || c > 0X7E) return false;
      // 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 is optional
  return name[0] != ' ';
}
//==============================================================================
// Fat16 member functions
//------------------------------------------------------------------------------
uint8_t Fat16::addCluster(void) {
  // start search after last cluster of file or at cluster two in FAT
  fat_t freeCluster = curCluster_ ? curCluster_ : 1;
  for (fat_t i = 0; ; i++) {
    // return no free clusters
    if (i >= clusterCount_) return false;
    // Fat has clusterCount + 2 entries
    if (freeCluster > clusterCount_) freeCluster = 1;
    freeCluster++;
    fat_t value;
    if (!fatGet(freeCluster, &value)) return false;
    if (value == 0) break;
  }
  // mark cluster allocated
  if (!fatPut(freeCluster, FAT16EOC)) return false;

  if (curCluster_ != 0) {
    // link cluster to chain
    if (!fatPut(curCluster_, freeCluster)) return false;
  } else {
    // first cluster of file so update directory entry
    flags_ |= F_FILE_DIR_DIRTY;
    firstCluster_ = freeCluster;
  }
  curCluster_ = freeCluster;
  return true;
}
//------------------------------------------------------------------------------
//
dir_t* Fat16::cacheDirEntry(uint16_t index, uint8_t action) {
  if (index >= rootDirEntryCount_) return NULL;
  if (!cacheRawBlock(rootDirStartBlock_ + (index >> 4), action)) return NULL;
  return &cacheBuffer_.dir[index & 0XF];
}
//------------------------------------------------------------------------------
//
uint8_t Fat16::cacheFlush(void) {
  if (cacheDirty_) {
    if (!rawDev_->writeBlock(cacheBlockNumber_, cacheBuffer_.data)) {
      return false;
    }
    // mirror FAT tables
    if (cacheMirrorBlock_) {
      if (!rawDev_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data)) {
        return false;
      }
      cacheMirrorBlock_ = 0;
    }
    cacheDirty_ = 0;
  }
  return true;
}
//------------------------------------------------------------------------------
//
uint8_t Fat16::cacheRawBlock(uint32_t blockNumber, uint8_t action) {
  if (cacheBlockNumber_ != blockNumber) {
    if (!cacheFlush()) return false;
    if (!rawDev_->readBlock(blockNumber, cacheBuffer_.data)) return false;
    cacheBlockNumber_ = blockNumber;
  }
  cacheDirty_ |= action;
  return true;
}
//------------------------------------------------------------------------------
/**
 *  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.
 */
uint8_t Fat16::close(void) {
  if (!sync()) return false;
  flags_ = 0;
  return true;
}
//------------------------------------------------------------------------------
/**
 * Return a files directory entry
 *
 * \param[out] dir Location for return of the files directory entry.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Fat16::dirEntry(dir_t* dir) {
  if (!sync()) return false;
  dir_t* p = cacheDirEntry(dirEntryIndex_, CACHE_FOR_WRITE);
  if (!p) return false;
  memcpy(dir, p, sizeof(dir_t));
  return true;
}
//------------------------------------------------------------------------------
uint8_t Fat16::fatGet(fat_t cluster, fat_t* value) {
  if (cluster > (clusterCount_ + 1)) return false;
  uint32_t lba = fatStartBlock_ + (cluster >> 8);
  if (lba != cacheBlockNumber_) {
    if (!cacheRawBlock(lba)) return false;
  }
  *value = cacheBuffer_.fat[cluster & 0XFF];
  return true;
}
//------------------------------------------------------------------------------
uint8_t Fat16::fatPut(fat_t cluster, fat_t value) {
  if (cluster < 2) return false;
  if (cluster > (clusterCount_ + 1)) return false;
  uint32_t lba = fatStartBlock_ + (cluster >> 8);
  if (lba != cacheBlockNumber_) {
    if (!cacheRawBlock(lba)) return false;
  }
  cacheBuffer_.fat[cluster & 0XFF] = value;
  cacheSetDirty();
  // mirror second FAT
  if (fatCount_ > 1) cacheMirrorBlock_ = lba + blocksPerFat_;
  return true;
}
//------------------------------------------------------------------------------
// free a cluster chain
uint8_t Fat16::freeChain(fat_t cluster) {
  while (1) {
    fat_t next;
    if (!fatGet(cluster, &next)) return false;
    if (!fatPut(cluster, 0)) return false;
    if (isEOC(next)) return true;
    cluster = next;
  }
}
//------------------------------------------------------------------------------
/**
 *  Initialize a FAT16 volume.
 *
 * \param[in] dev The SdCard 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(SdCard* dev, uint8_t part) {
  // error if invalid partition
  if (part > 4) return false;
  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 false;
    volumeStartBlock = cacheBuffer_.mbr.part[part - 1].firstSector;
  }
  if (!cacheRawBlock(volumeStartBlock)) return false;
  // check boot block signature
  if (cacheBuffer_.data[510] != BOOTSIG0 ||
      cacheBuffer_.data[511] != BOOTSIG1) return false;
  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
        // power of 2 test
     || bpb->sectorsPerCluster & (bpb->sectorsPerCluster - 1)) {
    // not a usable FAT16 bpb
    return false;
  }
  volumeInitialized_ = 1;
  return true;
}
//------------------------------------------------------------------------------
/** List directory contents to Serial.
 *
 * \param[in] flags The inclusive OR of
 *
 * LS_DATE - %Print file modification date
 *
 * LS_SIZE - %Print file size.
 */
void Fat16::ls(uint8_t flags) {
  dir_t d;
  for (uint16_t index = 0; readDir(&d, &index, DIR_ATT_VOLUME_ID); index++) {
    // print file name with possible blank fill
    printDirName(d, flags & (LS_DATE | LS_SIZE) ? 14 : 0);

    // print modify date/time if requested
    if (flags & LS_DATE) {
       printFatDate(d.lastWriteDate);
       Serial.write(' ');
       printFatTime(d.lastWriteTime);
    }

    // print size if requested
    if (DIR_IS_FILE(&d) && (flags & LS_SIZE)) {
      Serial.write(' ');
      Serial.print(d.fileSize);
    }
    Serial.println();
  }
}
//------------------------------------------------------------------------------
/**
 * Open a file by file name.
 *
 * \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.
 *
 * \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_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 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.
 *
 * \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,
 * is a directory, or can't be opened in the access mode specified by oflag.
 */
uint8_t Fat16::open(const char* fileName, uint8_t oflag) {
  uint8_t dname[11];   // name formated for dir entry
  int16_t empty = -1;  // index of empty slot
  dir_t* p;            // pointer to cached dir entry

  if (!volumeInitialized_ || isOpen()) return false;

  // error if invalid name
  if (!make83Name(fileName, dname)) return false;

  for (uint16_t index = 0; index < rootDirEntryCount_; index++) {
    if (!(p = cacheDirEntry(index))) return false;
    if (p->name[0] == DIR_NAME_FREE || p->name[0] == DIR_NAME_DELETED) {
      // remember first empty slot
      if (empty < 0) empty = index;
      // done if no entries follow
      if (p->name[0] == DIR_NAME_FREE) break;
    } else if (!memcmp(dname, p->name, 11)) {
      // don't open existing file if O_CREAT and O_EXCL
      if ((oflag & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)) return false;

      // open existing file
      return open(index, oflag);
    }
  }
  // error if directory is full
  if (empty < 0) return false;

  // only create file if O_CREAT and O_WRITE
  if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false;

  if (!(p = cacheDirEntry(empty, CACHE_FOR_WRITE))) return false;

  // initialize as empty file
  memset(p, 0, sizeof(dir_t));
  memcpy(p->name, dname, 11);

  // set timestamps
  if (dateTime_) {
    // call user 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;

  // insure created directory entry will be written to storage device
  if (!cacheFlush()) return false;

  // open entry
  return open(empty, oflag);
}
//------------------------------------------------------------------------------
/**
 * Open a file by file index.
 *
 * \param[in] index The root directory index of the file to be opened.  See \link
 *  Fat16::readDir() readDir()\endlink.
 *
 * \param[in] oflag  See \link Fat16::open(const char*, uint8_t)\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 or the file cannot be opened in the access mode specified by oflag.
 */
uint8_t Fat16::open(uint16_t index, uint8_t oflag) {
  if (!volumeInitialized_ || isOpen()) return false;
  if ((oflag & O_TRUNC) && !(oflag & O_WRITE)) return false;
  dir_t* d = cacheDirEntry(index);
  // if bad file index or I/O error
  if (!d) return false;

  // error if unused entry
  if (d->name[0] == DIR_NAME_FREE || d->name[0] == DIR_NAME_DELETED) {
    return false;
  }
  // error if long name, volume label or subdirectory
  if ((d->attributes & (DIR_ATT_VOLUME_ID | DIR_ATT_DIRECTORY)) != 0) {
    return false;
  }
  // don't allow write or truncate if read-only
  if (d->attributes & DIR_ATT_READ_ONLY) {
    if (oflag & (O_WRITE | O_TRUNC)) return false;
  }

  curCluster_ = 0;
  curPosition_ = 0;
  dirEntryIndex_ = index;
  fileSize_ = d->fileSize;
  firstCluster_ = d->firstClusterLow;
  flags_ = oflag & (O_ACCMODE | O_SYNC | O_APPEND);

  if (oflag & O_TRUNC ) return truncate(0);
  return true;
}
//------------------------------------------------------------------------------
/** %Print the name field of a directory entry in 8.3 format to Serial.
 *
 * \param[in] dir The directory structure containing the name.
 * \param[in] width Blank fill name if length is less than \a width.
 */
void Fat16::printDirName(const dir_t& dir, uint8_t width) {
  uint8_t w = 0;
  for (uint8_t i = 0; i < 11; i++) {
    if (dir.name[i] == ' ') continue;
    if (i == 8) {
      Serial.write('.');
      w++;
    }
    Serial.write(dir.name[i]);
    w++;
  }
  if (DIR_IS_SUBDIR(&dir)) {
    Serial.write('/');
    w++;
  }
  while (w < width) {
    Serial.write(' ');
    w++;
  }
}
//------------------------------------------------------------------------------
/** %Print a directory date field to Serial.
 *
 *  Format is yyyy-mm-dd.
 *
 * \param[in] fatDate The date field from a directory entry.
 */
void Fat16::printFatDate(uint16_t fatDate) {
  Serial.print(FAT_YEAR(fatDate));
  Serial.write('-');
  printTwoDigits(FAT_MONTH(fatDate));
  Serial.write('-');
  printTwoDigits(FAT_DAY(fatDate));
}
//------------------------------------------------------------------------------
/** %Print a directory time field to Serial.
 *
 * Format is hh:mm:ss.
 *
 * \param[in] fatTime The time field from a directory entry.
 */
void Fat16::printFatTime(uint16_t fatTime) {
  printTwoDigits(FAT_HOUR(fatTime));
  Serial.write(':');
  printTwoDigits(FAT_MINUTE(fatTime));
  Serial.write(':');
  printTwoDigits(FAT_SECOND(fatTime));
}

//------------------------------------------------------------------------------
/** %Print a value as two digits to Serial.
 *
 * \param[in] v Value to be printed, 0 <= \a v <= 99
 */
void Fat16::printTwoDigits(uint8_t v) {
  char str[3];
  str[0] = '0' + v/10;
  str[1] = '0' + v % 10;
  str[2] = 0;
  Serial.print(str);
}
//------------------------------------------------------------------------------
/**
 * 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 file position.
 *
 * \param[out] buf Pointer to the location that will receive the data.
 *
 * \param[in] nbyte Maximum number of bytes to read.
 *
 * \return For success read returns the number of bytes read.
 * A value less than \a nbyte, 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, the file has not been opened in
 * read mode, a corrupt file system, or an I/O error.
 */
int16_t Fat16::read(void* buf, uint16_t nbyte) {
  // convert void pointer to uin8_t pointer
  uint8_t* dst = reinterpret_cast<uint8_t*>(buf);

  // error if not open for read
  if (!(flags_ & O_READ)) return -1;

  // don't read beyond end of file
  if ((curPosition_ + nbyte) > fileSize_) nbyte = fileSize_ - curPosition_;

  // bytes left to read in loop
  uint16_t nToRead = nbyte;
  while (nToRead > 0) {
    uint8_t blkOfCluster = blockOfCluster(curPosition_);
    uint16_t blockOffset = cacheDataOffset(curPosition_);
    if (blkOfCluster == 0 && blockOffset == 0) {
      // start next cluster
      if (curCluster_ == 0) {
        curCluster_ = firstCluster_;
      } else {
        if (!fatGet(curCluster_, &curCluster_)) return -1;
      }
      // return error if bad cluster chain
      if (curCluster_ < 2 || isEOC(curCluster_)) return -1;
    }
    // cache data block
    if (!cacheRawBlock(dataBlockLba(curCluster_, blkOfCluster))) return -1;

    // location of data in cache
    uint8_t* src = cacheBuffer_.data + blockOffset;

    // max number of byte available in block
    uint16_t n = 512 - blockOffset;

    // lesser of available and amount to read
    if (n > nToRead) n = nToRead;

    // copy data to caller
    memcpy(dst, src, n);

    curPosition_ += n;
    dst += n;
    nToRead -= n;
  }
  return nbyte;
}
//------------------------------------------------------------------------------
/**
 *  Read the next short, 8.3, directory entry.
 *
 *  Unused entries and entries for long names are skipped.
 *
 * \param[out] dir Location that will receive the entry.
 *
 * \param[in,out] index The search starts at \a index and \a index is
 * updated with the root directory index of the found directory entry.
 * If the entry is a file, it may be opened by calling
 * \link Fat16::open(uint16_t, uint8_t) \endlink.
 *
 * \param[in] skip Skip entries that have these attributes. If \a skip
 * is not specified, the default is to skip the volume label and directories.
 *
 * \return The value one, true, is returned for success and the value zero,
 * false, is returned if an error occurs or the end of the root directory is
 * reached.  On success, \a entry is set to the index of the found directory
 * entry.
 */
uint8_t Fat16::readDir(dir_t* dir, uint16_t* index, uint8_t skip) {
  dir_t* p;
  for (uint16_t i = *index; ; i++) {
    if (i >= rootDirEntryCount_) return false;
    if (!(p = cacheDirEntry(i))) return false;

    // done if beyond last used entry
    if (p->name[0] == DIR_NAME_FREE) return false;

    // skip deleted entry
    if (p->name[0] == DIR_NAME_DELETED) continue;

    // skip long names
    if ((p->attributes & DIR_ATT_LONG_NAME_MASK) == DIR_ATT_LONG_NAME) continue;

    // skip if attribute match
    if (p->attributes & skip) continue;

    // return found index
    *index = i;
    break;
  }
  memcpy(dir, p, sizeof(dir_t));
  return true;
}
//------------------------------------------------------------------------------
/**
 * Remove a file.  The directory entry and all data for the file are deleted.
 *
 * \note This function should not be used to delete the 8.3 version of a
 * file that has a long name. For example if a file has the long name
 * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
 *
 * \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 not open for write
 * or an I/O error occurred.
 */
uint8_t Fat16::remove(void) {
  // error if file is not open for write
  if (!(flags_ & O_WRITE)) return false;
  if (firstCluster_) {
    if (!freeChain(firstCluster_)) return false;
  }
  dir_t* d = cacheDirEntry(dirEntryIndex_, CACHE_FOR_WRITE);
  if (!d) return false;
  d->name[0] = DIR_NAME_DELETED;
  flags_ = 0;
  return cacheFlush();
}
//------------------------------------------------------------------------------
/**
 * Remove a file.
 *
 * The directory entry and all data for the file are deleted.
 *
 * \param[in] fileName The name of the file to be removed.
 *
 * \note This function should not be used to delete the 8.3 version of a
 * file that has a long name. For example if a file has the long name
 * "New Text Document.txt" you should not delete the 8.3 name "NEWTEX~1.TXT".
 *
 * \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 read only, \a fileName is not found
 * or an I/O error occurred.
 */
uint8_t Fat16::remove(const char* fileName) {
  Fat16 file;
  if (!file.open(fileName, O_WRITE)) return false;
  return file.remove();
}
//------------------------------------------------------------------------------
/**
 * Sets the file's read/write position.
 *
 * \param[in] pos The new 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::seekSet(uint32_t pos) {
  // error if file not open or seek past end of file
  if (!isOpen() || pos > fileSize_) return false;
  if (pos == 0) {
    // set position to start of file
    curCluster_ = 0;
    curPosition_ = 0;
    return true;
  }
  fat_t n = ((pos - 1) >> 9)/blocksPerCluster_;
  if (pos < curPosition_ || curPosition_ == 0) {
    // must follow chain from first cluster
    curCluster_ = firstCluster_;
  } else {
    // advance from curPosition
    n -= ((curPosition_ - 1) >> 9)/blocksPerCluster_;
  }
  while (n--) {
    if (!fatGet(curCluster_, &curCluster_)) return false;
  }
  curPosition_ = pos;
  return true;
}
//------------------------------------------------------------------------------
/**
 *  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) {
  if (flags_ & F_FILE_DIR_DIRTY) {
    // cache directory entry
    dir_t* d = cacheDirEntry(dirEntryIndex_, CACHE_FOR_WRITE);
    if (!d) return false;

    // update file size and first cluster
    d->fileSize = fileSize_;
    d->firstClusterLow = firstCluster_;

    // set modify time if user supplied a callback date/time function
    if (dateTime_) {
      dateTime_(&d->lastWriteDate, &d->lastWriteTime);
      d->lastAccessDate = d->lastWriteDate;
    }
    flags_ &= ~F_FILE_DIR_DIRTY;
  }
  return cacheFlush();
}
//------------------------------------------------------------------------------
/**
 * The timestamp() call sets a file's timestamps in its directory entry.
 *
 * \param[in] flags Values for \a flags are constructed by a bitwise-inclusive
 * OR of flags from the following list
 *
 * T_ACCESS - Set the file's last access date.
 *
 * T_CREATE - Set the file's creation date and time.
 *
 * T_WRITE - Set the file's last write/modification date and time.
 *
 * \param[in] year Valid range 1980 - 2107 inclusive.
 *
 * \param[in] month Valid range 1 - 12 inclusive.
 *
 * \param[in] day Valid range 1 - 31 inclusive.
 *
 * \param[in] hour Valid range 0 - 23 inclusive.
 *
 * \param[in] minute Valid range 0 - 59 inclusive.
 *
 * \param[in] second Valid range 0 - 59 inclusive
 *
 * \note It is possible to set an invalid date since there is no check for
 * the number of days in a month.
 *
 * \return The value one, true, is returned for success and
 * the value zero, false, is returned for failure.
 */
uint8_t Fat16::timestamp(uint8_t flags, uint16_t year, uint8_t month,
         uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
  if (!isOpen()
    || year < 1980
    || year > 2107
    || month < 1
    || month > 12
    || day < 1
    || day > 31
    || hour > 23
    || minute > 59
    || second > 59) {
      return false;
  }
  dir_t* d = cacheDirEntry(dirEntryIndex_, CACHE_FOR_WRITE);
  if (!d) return false;
  uint16_t dirDate = FAT_DATE(year, month, day);
  uint16_t dirTime = FAT_TIME(hour, minute, second);
  if (flags & T_ACCESS) {
    d->lastAccessDate = dirDate;
  }
  if (flags & T_CREATE) {
    d->creationDate = dirDate;
    d->creationTime = dirTime;
    // seems to be units of 1/100 second not 1/10 as Microsoft standard states
    d->creationTimeTenths = second & 1 ? 100 : 0;
  }
  if (flags & T_WRITE) {
    d->lastWriteDate = dirDate;
    d->lastWriteTime = dirTime;
  }
  cacheSetDirty();
  return sync();
}
//------------------------------------------------------------------------------
/**
 * Truncate a file to a specified length.  The current file position
 * will be maintained if it is less than or equal to \a length otherwise
 * it will be set to end of file.
 *
 * \param[in] length The desired length 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 read only, file is a directory,
 * \a length is greater than the current file size or an I/O error occurs.
 */
uint8_t Fat16::truncate(uint32_t length) {
  // error if file is not open for write
  if (!(flags_ & O_WRITE)) return false;

  if (length > fileSize_) return false;

  // fileSize and length are zero - nothing to do
  if (fileSize_ == 0) return true;
  uint32_t newPos = curPosition_ > length ? length : curPosition_;
  if (length == 0) {
    // free all clusters
    if (!freeChain(firstCluster_)) return false;
    curCluster_ = firstCluster_ = 0;
  } else {
    fat_t toFree;
    if (!seekSet(length)) return false;
    if (!fatGet(curCluster_, &toFree)) return false;
    if (!isEOC(toFree)) {
      // free extra clusters
      if (!fatPut(curCluster_, FAT16EOC)) return false;
      if (!freeChain(toFree)) return false;
    }
  }
  fileSize_ = length;
  flags_ |= F_FILE_DIR_DIRTY;
  if (!sync()) return false;
  return seekSet(newPos);
}
//------------------------------------------------------------------------------
/**
 * Write data at the current position 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] buf Pointer to the location of the data to be written.
 *
 * \param[in] nbyte Number of bytes to write.
 *
 * \return For success write() returns the number of bytes written, always
 * \a nbyte.  If an error occurs, write() returns -1.  Possible errors include
 * write() is called before a file has been opened, the file has not been opened
 * for write, device is full, a corrupt file system or an I/O error.
 *
 */
int16_t Fat16::write(const void* buf, uint16_t nbyte) {
  uint16_t nToWrite = nbyte;
  const uint8_t* src = reinterpret_cast<const uint8_t*>(buf);

  // error if file is not open for write
  if (!(flags_ & O_WRITE)) goto writeErrorReturn;

  // go to end of file if O_APPEND
  if ((flags_ & O_APPEND) && curPosition_ != fileSize_) {
    if (!seekEnd()) goto writeErrorReturn;
  }
  while (nToWrite > 0) {
    uint8_t blkOfCluster = blockOfCluster(curPosition_);
    uint16_t blockOffset = cacheDataOffset(curPosition_);
    if (blkOfCluster == 0 && blockOffset == 0) {
      // start of new cluster
      if (curCluster_ == 0) {
        if (firstCluster_ == 0) {
          // allocate first cluster of file
          if (!addCluster()) goto writeErrorReturn;
        } else {
          curCluster_ = firstCluster_;
        }
      } else {
        fat_t next;
        if (!fatGet(curCluster_, &next)) goto writeErrorReturn;
        if (isEOC(next)) {
          // add cluster if at end of chain
          if (!addCluster()) goto writeErrorReturn;
        } else {
          curCluster_ = next;
        }
      }
    }
    uint32_t lba = dataBlockLba(curCluster_, blkOfCluster);
    if (blockOffset == 0 && curPosition_ >= fileSize_) {
      // start of new block don't need to read into cache
      if (!cacheFlush()) goto writeErrorReturn;
      cacheBlockNumber_ = lba;
      cacheSetDirty();
    } else {
      // rewrite part of block
      if (!cacheRawBlock(lba, CACHE_FOR_WRITE)) return -1;
    }
    uint8_t* dst = cacheBuffer_.data + blockOffset;

    // max space in block
    uint16_t n = 512 - blockOffset;

    // lesser of space and amount to write
    if (n > nToWrite) n = nToWrite;

    // copy data to cache
    memcpy(dst, src, n);

    curPosition_ += n;
    nToWrite -= n;
    src += n;
  }
  if (curPosition_ > fileSize_) {
    // update fileSize and insure sync will update dir entry
    fileSize_ = curPosition_;
    flags_ |= F_FILE_DIR_DIRTY;
  } else if (dateTime_ && nbyte) {
    // insure sync will update modified date and time
    flags_ |= F_FILE_DIR_DIRTY;
  }

  if (flags_ & O_SYNC) {
    if (!sync()) goto writeErrorReturn;
  }
  return nbyte;

 writeErrorReturn:
  writeError = true;
  return -1;
}
//------------------------------------------------------------------------------
/**
 * Write a byte to a file. Required by the Arduino Print class.
 *
 *  Use Fat16::writeError to check for errors.
 */
#if ARDUINO < 100
void Fat16::write(uint8_t b) {
  write(&b, 1);
}
#else  // ARDUINO < 100
size_t Fat16::write(uint8_t b) {
  return write(&b, 1) == 1 ? 1 : 0;
}
#endif  // ARDUINO < 100
//------------------------------------------------------------------------------
/**
 * Write a string to a file. Used by the Arduino Print class.
 *
 * Use Fat16::writeError to check for errors.
 */
#if ARDUINO < 100
void Fat16::write(const char* str) {
  write(str, strlen(str));
}
#else  // ARDUINO < 100
int16_t Fat16::write(const char* str) {
  return write(str, strlen(str));
}
#endif  // ARDUINO < 100
//------------------------------------------------------------------------------
/**
 * Write a PROGMEM string to a file.
 *
 * Use Fat16::writeError to check for errors.
 */
void Fat16::write_P(PGM_P str) {
  for (uint8_t c; (c = pgm_read_byte(str)); str++) write(c);
}
//------------------------------------------------------------------------------
/**
 * Write a PROGMEM string followed by CR/LF to a file.
 *
 * Use Fat16::writeError to check for errors.
 */
void Fat16::writeln_P(PGM_P str) {
  write_P(str);
  println();
}