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