altos: seek forward on FAT cluster chain instead of restarting
[fw/altos] / src / drivers / ao_fat.c
1 /*
2  * Copyright © 2013 Keith Packard <keithp@keithp.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 2 of the License.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License along
14  * with this program; if not, write to the Free Software Foundation, Inc.,
15  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16  */
17
18 #ifndef AO_FAT_TEST
19 #include "ao.h"
20 #endif
21
22 #include "ao_fat.h"
23 #include "ao_bufio.h"
24
25 /* Partition information, sector numbers */
26
27 static uint8_t partition_type;
28 static uint32_t partition_start, partition_end;
29
30 #define SECTOR_SIZE     512
31 #define SECTOR_MASK     (SECTOR_SIZE - 1)
32 #define SECTOR_SHIFT    9
33
34 #define DIRENT_SIZE     32
35
36 /* File system parameters */
37 static uint8_t  sectors_per_cluster;
38 static uint32_t bytes_per_cluster;
39 static uint16_t reserved_sector_count;
40 static uint8_t  number_fat;
41 static uint16_t root_entries;
42 static uint16_t sectors_per_fat;
43 static uint16_t number_cluster;
44 static uint32_t fat_start;
45 static uint32_t root_start;
46 static uint32_t data_start;
47 static uint16_t first_free_cluster;
48
49 /*
50  * Deal with LSB FAT data structures
51  */
52 static uint32_t
53 get_u32(uint8_t *base)
54 {
55         return ((uint32_t) base[0] |
56                 ((uint32_t) base[1] << 8) |
57                 ((uint32_t) base[2] << 16) |
58                 ((uint32_t) base[3] << 24));
59 }
60
61 static void
62 put_u32(uint8_t *base, uint32_t value)
63 {
64         base[0] = value;
65         base[1] = value >> 8;
66         base[2] = value >> 16;
67         base[3] = value >> 24;
68 }
69
70 static uint16_t
71 get_u16(uint8_t *base)
72 {
73         return ((uint16_t) base[0] | ((uint16_t) base[1] << 8));
74 }
75
76 static void
77 put_u16(uint8_t *base, uint16_t value)
78 {
79         base[0] = value;
80         base[1] = value >> 8;
81 }
82
83 static uint8_t
84 ao_fat_cluster_valid(uint16_t cluster)
85 {
86         return (2 <= cluster && cluster < number_cluster);
87 }
88
89 /* Start using a sector */
90 static uint8_t *
91 ao_fat_sector_get(uint32_t sector)
92 {
93         sector += partition_start;
94         if (sector >= partition_end)
95                 return NULL;
96         return ao_bufio_get(sector);
97 }
98
99 /* Finish using a sector, 'w' is 1 if modified */
100 #define ao_fat_sector_put(b,w) ao_bufio_put(b,w)
101
102 /* Start using a root directory entry */
103 static uint8_t *
104 ao_fat_root_get(uint16_t e)
105 {
106         uint32_t        byte = e * DIRENT_SIZE;
107         uint32_t        sector = byte >> SECTOR_SHIFT;
108         uint16_t        offset = byte & SECTOR_MASK;
109         uint8_t         *buf;
110
111         buf = ao_fat_sector_get(root_start + sector);
112         if (!buf)
113                 return NULL;
114         return buf + offset;
115 }
116
117 /* Finish using a root directory entry, 'w' is 1 if modified */
118 static void
119 ao_fat_root_put(uint8_t *root, uint16_t e, uint8_t write)
120 {
121         uint16_t        offset = ((e * DIRENT_SIZE) & SECTOR_MASK);
122         uint8_t         *buf = root - offset;
123
124         ao_fat_sector_put(buf, write);
125 }
126
127 /* Get the next cluster entry in the chain */
128 static uint16_t
129 ao_fat_entry_read(uint16_t cluster)
130 {
131         uint32_t        sector;
132         uint16_t        offset;
133         uint8_t         *buf;
134         uint16_t        ret;
135
136         if (!ao_fat_cluster_valid(cluster))
137                 return 0xfff7;
138
139         sector = cluster >> (SECTOR_SHIFT - 1);
140         offset = (cluster << 1) & SECTOR_MASK;
141         buf = ao_fat_sector_get(fat_start + sector);
142         if (!buf)
143                 return 0;
144         ret = get_u16(buf + offset);
145         ao_fat_sector_put(buf, 0);
146         return ret;
147 }
148
149 /* Replace the referenced cluster entry in the chain with
150  * 'new_value'. Return the previous value.
151  */
152 static uint16_t
153 ao_fat_entry_replace(uint16_t  cluster, uint16_t new_value)
154 {
155         uint32_t        sector;
156         uint16_t        offset;
157         uint8_t         *buf;
158         uint16_t        ret;
159         uint8_t         other_fats;
160
161         if (!ao_fat_cluster_valid(cluster))
162                 return 0;
163
164         sector = cluster >> (SECTOR_SHIFT - 1);
165         offset = (cluster << 1) & SECTOR_MASK;
166         buf = ao_fat_sector_get(fat_start + sector);
167         if (!buf)
168                 return 0;
169         ret = get_u16(buf + offset);
170         put_u16(buf + offset, new_value);
171         ao_fat_sector_put(buf, 1);
172
173         /*
174          * Keep the other FATs in sync
175          */
176         for (other_fats = 1; other_fats < number_fat; other_fats++) {
177                 buf = ao_fat_sector_get(fat_start + other_fats * sectors_per_fat + sector);
178                 if (buf) {
179                         put_u16(buf + offset, new_value);
180                         ao_fat_sector_put(buf, 1);
181                 }
182         }
183         return ret;
184         
185 }
186
187 /*
188  * Walk a cluster chain and mark
189  * all of them as free
190  */
191 static void
192 ao_fat_free_cluster_chain(uint16_t cluster)
193 {
194         while (ao_fat_cluster_valid(cluster)) {
195                 if (cluster < first_free_cluster)
196                         first_free_cluster = cluster;
197                 cluster = ao_fat_entry_replace(cluster, 0x0000);
198         }
199 }
200
201 /*
202  * ao_fat_cluster_seek
203  * 
204  * The basic file system operation -- map a file cluster index to a
205  * partition cluster number. Done by computing the cluster number and
206  * then walking that many clusters from the first cluster. Returns
207  * 0xffff if we walk off the end of the file or the cluster chain
208  * is damaged somehow
209  */
210 static uint16_t
211 ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
212 {
213         while (distance) {
214                 cluster = ao_fat_entry_read(cluster);
215                 if (!ao_fat_cluster_valid(cluster))
216                         break;
217                 distance--;
218         }
219         return cluster;
220 }
221
222 /*
223  * ao_fat_setup_partition
224  * 
225  * Load the boot block and find the first partition
226  */
227 static uint8_t
228 ao_fat_setup_partition(void)
229 {
230         uint8_t *mbr;
231         uint8_t *partition;
232         uint32_t partition_size;
233
234         mbr = ao_bufio_get(0);
235         if (!mbr)
236                 return 0;
237
238         /* Check the signature */
239         if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) {
240                 printf ("Invalid MBR signature %02x %02x\n",
241                         mbr[0x1fe], mbr[0x1ff]);
242                 ao_bufio_put(mbr, 0);
243                 return 0;
244         }
245
246         /* Check to see if it's actually a boot block, in which
247          * case it's presumably not a paritioned device
248          */
249
250         if (mbr[0] == 0xeb) {
251                 partition_start = 0;
252                 partition_size = get_u16(mbr + 0x13);
253                 if (partition_size == 0)
254                         partition_size = get_u32(mbr + 0x20);
255         } else {
256                 /* Just use the first partition */
257                 partition = &mbr[0x1be];
258         
259                 partition_type = partition[4];
260                 switch (partition_type) {
261                 case 4:         /* FAT16 up to 32M */
262                 case 6:         /* FAT16 over 32M */
263                         break;
264                 case 0x0b:      /* FAT32 up to 2047GB */
265                 case 0x0c:      /* FAT32 LBA */
266                         break;
267                 default:
268                         printf ("Invalid partition type %02x\n", partition_type);
269                         ao_bufio_put(mbr, 0);
270                         return 0;
271                 }
272
273                 partition_start = get_u32(partition+8);
274                 partition_size = get_u32(partition+12);
275                 if (partition_size == 0) {
276                         printf ("Zero-sized partition\n");
277                         ao_bufio_put(mbr, 0);
278                         return 0;
279                 }
280         }
281         partition_end = partition_start + partition_size;
282         printf ("Partition type %02x start %08x end %08x\n",
283                 partition_type, partition_start, partition_end);
284         ao_bufio_put(mbr, 0);
285         return 1;
286 }
287         
288 static uint8_t
289 ao_fat_setup_fs(void)
290 {
291         uint8_t         *boot = ao_fat_sector_get(0);
292         uint32_t        data_sectors;
293
294         if (!boot)
295                 return 0;
296
297         /* Check the signature */
298         if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) {
299                 printf ("Invalid BOOT signature %02x %02x\n",
300                         boot[0x1fe], boot[0x1ff]);
301                 ao_fat_sector_put(boot, 0);
302                 return 0;
303         }
304
305         /* Check the sector size */
306         if (get_u16(boot + 0xb) != SECTOR_SIZE) {
307                 printf ("Invalid sector size %d\n",
308                         get_u16(boot + 0xb));
309                 ao_fat_sector_put(boot, 0);
310                 return 0;
311         }
312
313         sectors_per_cluster = boot[0xd];
314         bytes_per_cluster = sectors_per_cluster << SECTOR_SHIFT;
315         reserved_sector_count = get_u16(boot+0xe);
316         number_fat = boot[0x10];
317         root_entries = get_u16(boot + 0x11);
318         sectors_per_fat = get_u16(boot+0x16);
319
320         fat_start = reserved_sector_count;;
321         root_start = fat_start + number_fat * sectors_per_fat;
322         data_start = root_start + ((root_entries * DIRENT_SIZE + SECTOR_MASK) >> SECTOR_SHIFT);
323
324         data_sectors = (partition_end - partition_start) - data_start;
325
326         number_cluster = data_sectors / sectors_per_cluster;
327
328         printf ("sectors per cluster %d\n", sectors_per_cluster);
329         printf ("reserved sectors %d\n", reserved_sector_count);
330         printf ("number of FATs %d\n", number_fat);
331         printf ("root entries %d\n", root_entries);
332         printf ("sectors per fat %d\n", sectors_per_fat);
333
334         printf ("fat  start %d\n", fat_start);
335         printf ("root start %d\n", root_start);
336         printf ("data start %d\n", data_start);
337
338         ao_fat_sector_put(boot, 0);
339
340         return 1;
341 }
342
343 static uint8_t
344 ao_fat_setup(void)
345 {
346         if (!ao_fat_setup_partition())
347                 return 0;
348         check_bufio("partition setup");
349         if (!ao_fat_setup_fs())
350                 return 0;
351         check_bufio("fs setup");
352         return 1;
353 }
354
355 /*
356  * Basic file operations
357  */
358
359 static struct ao_fat_dirent     ao_file_dirent;
360 static uint32_t                 ao_file_offset;
361 static uint32_t                 ao_file_cluster_offset;
362 static uint16_t                 ao_file_cluster;
363 static uint8_t                  ao_file_opened;
364
365 static uint32_t
366 ao_fat_current_sector(void)
367 {
368         uint16_t        cluster_offset;
369         uint32_t        sector_offset;
370         uint16_t        sector_index;
371         uint16_t        cluster;
372
373         if (ao_file_offset > ao_file_dirent.size)
374                 return 0xffffffff;
375
376         sector_offset = ao_file_offset >> SECTOR_SHIFT;
377
378         if (!ao_file_cluster || ao_file_offset < ao_file_cluster_offset) {
379                 ao_file_cluster = ao_file_dirent.cluster;
380                 ao_file_cluster_offset = 0;
381         }
382
383         if (ao_file_cluster_offset + bytes_per_cluster <= ao_file_offset) {
384                 uint16_t        cluster_distance;
385
386                 cluster_offset = sector_offset / sectors_per_cluster;
387
388                 cluster_distance = cluster_offset - ao_file_cluster_offset / bytes_per_cluster;
389
390                 cluster = ao_fat_cluster_seek(ao_file_cluster, cluster_distance);
391
392                 if (!ao_fat_cluster_valid(cluster))
393                         return 0xffffffff;
394                 ao_file_cluster = cluster;
395                 ao_file_cluster_offset = cluster_offset * bytes_per_cluster;
396         }
397
398         sector_index = sector_offset % sectors_per_cluster;
399         return data_start + (uint32_t) (ao_file_cluster-2) * sectors_per_cluster + sector_index;
400 }
401
402 static void
403 ao_fat_set_offset(uint32_t offset)
404 {
405         ao_file_offset = offset;
406 }
407
408 /*
409  * ao_fat_set_size
410  *
411  * Set the size of the current file, truncating or extending
412  * the cluster chain as needed
413  */
414 static int8_t
415 ao_fat_set_size(uint32_t size)
416 {
417         uint16_t        clear_cluster = 0;
418         uint8_t         *dent;
419         uint16_t        first_cluster;
420
421         first_cluster = ao_file_dirent.cluster;
422         if (size == ao_file_dirent.size)
423                 return AO_FAT_SUCCESS;
424         if (size == 0) {
425                 clear_cluster = ao_file_dirent.cluster;
426                 first_cluster = 0;
427         } else {
428                 uint16_t        new_num;
429                 uint16_t        old_num;
430
431                 new_num = (size + bytes_per_cluster - 1) / bytes_per_cluster;
432                 old_num = (ao_file_dirent.size + bytes_per_cluster - 1) / bytes_per_cluster;
433                 if (new_num < old_num) {
434                         uint16_t last_cluster;
435
436                         /* Go find the last cluster we want to preserve in the file */
437                         last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, new_num - 1);
438
439                         /* Rewrite that cluster entry with 0xffff to mark the end of the chain */
440                         clear_cluster = ao_fat_entry_replace(last_cluster, 0xffff);
441                 } else if (new_num > old_num) {
442                         uint16_t        need;
443                         uint16_t        free;
444                         uint16_t        last_cluster;
445                         uint16_t        highest_allocated = 0;
446
447                         if (old_num)
448                                 last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, old_num - 1);
449                         else
450                                 last_cluster = 0;
451
452                         if (first_free_cluster < 2 || number_cluster <= first_free_cluster)
453                                 first_free_cluster = 2;
454
455                         /* See if there are enough free clusters in the file system */
456                         need = new_num - old_num;
457
458 #define loop_cluster    for (free = first_free_cluster; need > 0;)
459 #define next_cluster                                    \
460                         if (++free == number_cluster)   \
461                                 free = 2;               \
462                         if (free == first_free_cluster) \
463                                 break;                  \
464
465                         loop_cluster {
466                                 if (!ao_fat_entry_read(free))
467                                         need--;
468                                 next_cluster;
469                         }
470                         /* Still need some, tell the user that we've failed */
471                         if (need)
472                                 return -AO_FAT_ENOSPC;
473
474                         /* Now go allocate those clusters */
475                         need = new_num - old_num;
476                         loop_cluster {
477                                 if (!ao_fat_entry_read(free)) {
478                                         if (free > highest_allocated)
479                                                 highest_allocated = free;
480                                         if (last_cluster)
481                                                 ao_fat_entry_replace(last_cluster, free);
482                                         else
483                                                 first_cluster = free;
484                                         last_cluster = free;
485                                         need--;
486                                 }
487                                 next_cluster;
488                         }
489                         first_free_cluster = highest_allocated + 1;
490                         if (first_free_cluster >= number_cluster)
491                                 first_free_cluster = 2;
492
493                         /* Mark the new end of the chain */
494                         ao_fat_entry_replace(last_cluster, 0xffff);
495                 }
496         }
497
498         /* Deallocate clusters off the end of the file */
499         if (ao_fat_cluster_valid(clear_cluster))
500                 ao_fat_free_cluster_chain(clear_cluster);
501
502         /* Update the directory entry */
503         dent = ao_fat_root_get(ao_file_dirent.entry);
504         if (!dent)
505                 return -AO_FAT_EIO;
506         put_u32(dent + 0x1c, size);
507         put_u16(dent + 0x1a, first_cluster);
508         ao_fat_root_put(dent, ao_file_dirent.entry, 1);
509         ao_file_dirent.size = size;
510         ao_file_dirent.cluster = first_cluster;
511         return AO_FAT_SUCCESS;
512 }
513
514 /*
515  * ao_fat_root_init
516  *
517  * Initialize a root directory entry
518  */
519 void
520 ao_fat_root_init(uint8_t *dent, char name[11], uint8_t attr)
521 {
522         memset(dent, '\0', 0x20);
523         memmove(dent, name, 11);
524
525         dent[0x0b] = 0x00;
526         dent[0x0c] = 0x00;
527         dent[0x0d] = 0x00;
528
529         /* XXX fix time */
530         put_u16(dent + 0x0e, 0);
531         /* XXX fix date */
532         put_u16(dent + 0x10, 0);
533         /* XXX fix date */
534         put_u16(dent + 0x12, 0);
535
536         /* XXX fix time */
537         put_u16(dent + 0x16, 0);
538         /* XXX fix date */
539         put_u16(dent + 0x18, 0);
540
541         /* cluster number */
542         /* Low cluster bytes */
543         put_u16(dent + 0x1a, 0);
544         /* FAT32 high cluster bytes */
545         put_u16(dent + 0x14, 0);
546
547         /* size */
548         put_u32(dent + 0x1c, 0);
549 }
550
551
552 static void
553 ao_fat_dirent_init(uint8_t *dent, uint16_t entry, struct ao_fat_dirent *dirent)
554 {
555         memcpy(dirent->name, dent + 0x00, 11);
556         dirent->attr = dent[0x0b];
557         dirent->size = get_u32(dent+0x1c);
558         dirent->cluster = get_u16(dent+0x1a);
559         dirent->entry = entry;
560 }
561
562 /*
563  * Public API
564  */
565
566 /*
567  * ao_fat_open
568  *
569  * Open an existing file.
570  */
571 int8_t
572 ao_fat_open(char name[11], uint8_t mode)
573 {
574         uint16_t                entry = 0;
575         struct ao_fat_dirent    dirent;
576
577         if (ao_file_opened)
578                 return -AO_FAT_EMFILE;
579         
580         while (ao_fat_readdir(&entry, &dirent)) {
581                 if (!memcmp(name, dirent.name, 11)) {
582                         if (AO_FAT_IS_DIR(dirent.attr))
583                                 return -AO_FAT_EISDIR;
584                         if (!AO_FAT_IS_FILE(dirent.attr))
585                                 return -AO_FAT_EPERM;
586                         if (mode > AO_FAT_OPEN_READ && (dirent.attr & AO_FAT_FILE_READ_ONLY))
587                                 return -AO_FAT_EACCESS;
588                         ao_file_dirent = dirent;
589                         ao_fat_set_offset(0);
590                         ao_file_opened = 1;
591                         return AO_FAT_SUCCESS;
592                 }
593         }
594         return -AO_FAT_ENOENT;
595 }
596
597 /*
598  * ao_fat_creat
599  *
600  * Open and truncate an existing file or
601  * create a new file
602  */
603 int8_t
604 ao_fat_creat(char name[11])
605 {
606         uint16_t        entry;
607         int8_t          status;
608
609         if (ao_file_opened)
610                 return -AO_FAT_EMFILE;
611
612         status = ao_fat_open(name, AO_FAT_OPEN_WRITE);
613
614         switch (status) {
615         case -AO_FAT_SUCCESS:
616                 status = ao_fat_set_size(0);
617                 break;
618         case -AO_FAT_ENOENT:
619                 for (entry = 0; entry < root_entries; entry++) {
620                         uint8_t *dent = ao_fat_root_get(entry);
621
622                         if (!dent) {
623                                 status = -AO_FAT_EIO;
624                                 ao_fat_root_put(dent, entry, 0);
625                                 break;
626                         }
627                                 
628                         if (dent[0] == AO_FAT_DENT_EMPTY || dent[0] == AO_FAT_DENT_END) {
629                                 ao_fat_root_init(dent, name, AO_FAT_FILE_REGULAR);
630                                 ao_fat_dirent_init(dent, entry,  &ao_file_dirent);
631                                 ao_fat_root_put(dent, entry, 1);
632                                 ao_file_opened = 1;
633                                 ao_fat_set_offset(0);
634                                 status = -AO_FAT_SUCCESS;
635                                 break;
636                         } else {
637                                 ao_fat_root_put(dent, entry, 0);
638                         }
639                 }
640                 if (entry == root_entries)
641                         status = -AO_FAT_ENOSPC;
642         }
643         return status;
644 }
645
646 /*
647  * ao_fat_close
648  *
649  * Close the currently open file
650  */
651 int8_t
652 ao_fat_close(void)
653 {
654         if (!ao_file_opened)
655                 return -AO_FAT_EBADF;
656
657         memset(&ao_file_dirent, '\0', sizeof (struct ao_fat_dirent));
658         ao_file_offset = 0;
659         ao_file_cluster = 0;
660         ao_file_opened = 0;
661         ao_bufio_flush();
662         return AO_FAT_SUCCESS;
663 }
664
665 /*
666  * ao_fat_read
667  *
668  * Read from the file
669  */
670 int
671 ao_fat_read(void *dst, int len)
672 {
673         uint8_t         *dst_b = dst;
674         uint32_t        sector;
675         uint16_t        this_time;
676         uint16_t        offset;
677         uint8_t         *buf;
678         int             ret = 0;
679
680         if (!ao_file_opened)
681                 return -AO_FAT_EBADF;
682
683         if (ao_file_offset + len > ao_file_dirent.size)
684                 len = ao_file_dirent.size - ao_file_offset;
685
686         if (len < 0)
687                 len = 0;
688
689         while (len) {
690                 offset = ao_file_offset & SECTOR_MASK;
691                 if (offset + len < SECTOR_SIZE)
692                         this_time = len;
693                 else
694                         this_time = SECTOR_SIZE - offset;
695
696                 sector = ao_fat_current_sector();
697                 if (sector == 0xffffffff)
698                         break;
699                 buf = ao_fat_sector_get(sector);
700                 if (!buf) {
701                         ret = -AO_FAT_EIO;
702                         break;
703                 }
704                 memcpy(dst_b, buf + offset, this_time);
705                 ao_fat_sector_put(buf, 0);
706
707                 ret += this_time;
708                 len -= this_time;
709                 dst_b += this_time;
710                 ao_fat_set_offset(ao_file_offset + this_time);
711         }
712         return ret;
713 }
714
715 /*
716  * ao_fat_write
717  *
718  * Write to the file, extended as necessary
719  */
720 int
721 ao_fat_write(void *src, int len)
722 {
723         uint8_t         *src_b = src;
724         uint32_t        sector;
725         uint16_t        this_time;
726         uint16_t        offset;
727         uint8_t         *buf;
728         int             ret = 0;
729
730         if (!ao_file_opened)
731                 return -AO_FAT_EBADF;
732
733         if (ao_file_offset + len > ao_file_dirent.size) {
734                 ret = ao_fat_set_size(ao_file_offset + len);
735                 if (ret < 0)
736                         return ret;
737         }
738
739         while (len) {
740                 offset = ao_file_offset & SECTOR_MASK;
741                 if (offset + len < SECTOR_SIZE)
742                         this_time = len;
743                 else
744                         this_time = SECTOR_SIZE - offset;
745
746                 sector = ao_fat_current_sector();
747                 if (sector == 0xffffffff)
748                         break;
749                 buf = ao_fat_sector_get(sector);
750                 if (!buf) {
751                         ret = -AO_FAT_EIO;
752                         break;
753                 }
754                 memcpy(buf + offset, src_b, this_time);
755                 ao_fat_sector_put(buf, 1);
756
757                 ret += this_time;
758                 len -= this_time;
759                 src_b += this_time;
760                 ao_fat_set_offset(ao_file_offset + this_time);
761         }
762         return ret;
763 }
764
765 /*
766  * ao_fat_seek
767  *
768  * Set the position for the next I/O operation
769  * Note that this doesn't actually change the size
770  * of the file if the requested position is beyond
771  * the current file length, that would take a future
772  * write
773  */
774 int32_t
775 ao_fat_seek(int32_t pos, uint8_t whence)
776 {
777         uint32_t        new_offset = ao_file_offset;
778
779         if (!ao_file_opened)
780                 return -AO_FAT_EBADF;
781
782         switch (whence) {
783         case AO_FAT_SEEK_SET:
784                 new_offset = pos;
785                 break;
786         case AO_FAT_SEEK_CUR:
787                 new_offset += pos;
788                 break;
789         case AO_FAT_SEEK_END:
790                 new_offset = ao_file_dirent.size + pos;
791                 break;
792         }
793         ao_fat_set_offset(new_offset);
794         return ao_file_offset;
795 }
796
797 /*
798  * ao_fat_unlink
799  *
800  * Remove a file from the directory, marking
801  * all clusters as free
802  */
803 int8_t
804 ao_fat_unlink(char name[11])
805 {
806         uint16_t                entry = 0;
807         struct ao_fat_dirent    dirent;
808
809         while (ao_fat_readdir(&entry, &dirent)) {
810                 if (memcmp(name, dirent.name, 11) == 0) {
811                         uint8_t *next;
812                         uint8_t *ent;
813                         uint8_t delete;
814
815                         if (AO_FAT_IS_DIR(dirent.attr))
816                                 return -AO_FAT_EISDIR;
817                         if (!AO_FAT_IS_FILE(dirent.attr))
818                                 return -AO_FAT_EPERM;
819
820                         ao_fat_free_cluster_chain(dirent.cluster);
821                         next = ao_fat_root_get(dirent.entry + 1);
822                         if (next && next[0] != AO_FAT_DENT_END)
823                                 delete = AO_FAT_DENT_EMPTY;
824                         else
825                                 delete = AO_FAT_DENT_END;
826                         if (next)
827                                 ao_fat_root_put(next, dirent.entry + 1, 0);
828                         ent = ao_fat_root_get(dirent.entry);
829                         if (ent) {
830                                 memset(ent, '\0', DIRENT_SIZE);
831                                 *ent = delete;
832                                 ao_fat_root_put(ent, dirent.entry, 1);
833                         }
834                         ao_bufio_flush();
835                         return AO_FAT_SUCCESS;
836                 }
837         }
838         return -AO_FAT_ENOENT;
839 }
840
841 int8_t
842 ao_fat_rename(char old[11], char new[11])
843 {
844         return -AO_FAT_EIO;
845 }
846
847 int8_t
848 ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent)
849 {
850         uint8_t *dent;
851
852         if (*entry >= root_entries)
853                 return 0;
854         for (;;) {
855                 dent = ao_fat_root_get(*entry);
856
857                 if (dent[0] == AO_FAT_DENT_END) {
858                         ao_fat_root_put(dent, *entry, 0);
859                         return 0;
860                 }
861                 if (dent[0] != AO_FAT_DENT_EMPTY && (dent[0xb] & 0xf) != 0xf) {
862                         ao_fat_dirent_init(dent, *entry, dirent);
863                         ao_fat_root_put(dent, *entry, 0);
864                         (*entry)++;
865                         return 1;
866                 }
867                 ao_fat_root_put(dent, *entry, 0);
868                 (*entry)++;
869         }
870 }
871
872 static void
873 ao_fat_list(void)
874 {
875         uint16_t                entry = 0;
876         struct ao_fat_dirent    dirent;
877
878         while (ao_fat_readdir(&entry, &dirent)) {
879                 printf ("%-8.8s.%-3.3s %02x %04x %d\n",
880                         dirent.name,
881                         dirent.name + 8,
882                         dirent.attr,
883                         dirent.cluster,
884                         dirent.size);
885         }
886 }
887
888 static void
889 ao_fat_test(void)
890 {
891         ao_fat_setup();
892         ao_fat_list();
893 }
894
895 static const struct ao_cmds ao_fat_cmds[] = {
896         { ao_fat_test,  "F\0Test FAT" },
897         { 0, NULL },
898 };
899
900 void
901 ao_fat_init(void)
902 {
903         ao_bufio_init();
904         ao_cmd_register(&ao_fat_cmds[0]);
905 }