altos: Clean up fat driver API. Improve fat test
authorKeith Packard <keithp@keithp.com>
Thu, 28 Mar 2013 23:05:24 +0000 (16:05 -0700)
committerKeith Packard <keithp@keithp.com>
Thu, 28 Mar 2013 23:05:24 +0000 (16:05 -0700)
Make FAT api provide reasonable error return values, change the tests
to write and then read a pile of files, checking that the contents are
correct (using md5sum).

Signed-off-by: Keith Packard <keithp@keithp.com>
src/drivers/ao_fat.c
src/drivers/ao_fat.h
src/test/Makefile
src/test/ao_fat_test.c

index a147616853f28f3ed1a3e6a625ed9290ff5135ac..c0412380d839fd8da1bcf0262f107a656f570a9e 100644 (file)
 #include "ao_fat.h"
 #include "ao_bufio.h"
 
-/* Partition information, block numbers */
+/* Partition information, sector numbers */
 
 static uint8_t partition_type;
 static uint32_t        partition_start, partition_end;
 
+#define SECTOR_SIZE    512
+#define SECTOR_MASK    (SECTOR_SIZE - 1)
+#define SECTOR_SHIFT   9
+
+#define DIRENT_SIZE    32
+
 /* File system parameters */
 static uint8_t sectors_per_cluster;
 static uint32_t        bytes_per_cluster;
@@ -34,10 +40,15 @@ static uint16_t     reserved_sector_count;
 static uint8_t number_fat;
 static uint16_t        root_entries;
 static uint16_t sectors_per_fat;
+static uint16_t        number_cluster;
 static uint32_t        fat_start;
 static uint32_t root_start;
 static uint32_t data_start;
+static uint16_t        first_free_cluster;
 
+/*
+ * Deal with LSB FAT data structures
+ */
 static uint32_t
 get_u32(uint8_t *base)
 {
@@ -72,32 +83,32 @@ put_u16(uint8_t *base, uint16_t value)
 static uint8_t
 ao_fat_cluster_valid(uint16_t cluster)
 {
-       return (2 <= cluster && cluster < 0xfff0);
+       return (2 <= cluster && cluster < number_cluster);
 }
 
-/* Start using a block */
+/* Start using a sector */
 static uint8_t *
-ao_fat_block_get(uint32_t block)
+ao_fat_sector_get(uint32_t sector)
 {
-       block += partition_start;
-       if (block >= partition_end)
+       sector += partition_start;
+       if (sector >= partition_end)
                return NULL;
-       return ao_bufio_get(block);
+       return ao_bufio_get(sector);
 }
 
-/* Finish using a block, 'w' is 1 if modified */
-#define ao_fat_block_put(b,w) ao_bufio_put(b,w)
+/* Finish using a sector, 'w' is 1 if modified */
+#define ao_fat_sector_put(b,w) ao_bufio_put(b,w)
 
 /* Start using a root directory entry */
 static uint8_t *
 ao_fat_root_get(uint16_t e)
 {
-       uint32_t        byte = e * 0x20;
-       uint32_t        sector = byte >> 9;
-       uint16_t        offset = byte & 0x1ff;
+       uint32_t        byte = e * DIRENT_SIZE;
+       uint32_t        sector = byte >> SECTOR_SHIFT;
+       uint16_t        offset = byte & SECTOR_MASK;
        uint8_t         *buf;
 
-       buf = ao_fat_block_get(root_start + sector);
+       buf = ao_fat_sector_get(root_start + sector);
        if (!buf)
                return NULL;
        return buf + offset;
@@ -107,10 +118,10 @@ ao_fat_root_get(uint16_t e)
 static void
 ao_fat_root_put(uint8_t *root, uint16_t e, uint8_t write)
 {
-       uint16_t        offset = ((e * 0x20) & 0x1ff);
+       uint16_t        offset = ((e * DIRENT_SIZE) & SECTOR_MASK);
        uint8_t         *buf = root - offset;
 
-       ao_fat_block_put(buf, write);
+       ao_fat_sector_put(buf, write);
 }
 
 /* Get the next cluster entry in the chain */
@@ -125,17 +136,20 @@ ao_fat_entry_read(uint16_t cluster)
        if (!ao_fat_cluster_valid(cluster))
                return 0xfff7;
 
-       cluster -= 2;
-       sector = cluster >> 8;
-       offset = (cluster << 1) & 0x1ff;
-       buf = ao_fat_block_get(fat_start + sector);
+//     cluster -= 2;
+       sector = cluster >> (SECTOR_SHIFT - 1);
+       offset = (cluster << 1) & SECTOR_MASK;
+       buf = ao_fat_sector_get(fat_start + sector);
        if (!buf)
                return 0;
-       ret = buf[offset] | (buf[offset+1] << 8);
-       ao_fat_block_put(buf, 0);
+       ret = get_u16(buf + offset);
+       ao_fat_sector_put(buf, 0);
        return ret;
 }
 
+/* Replace the referenced cluster entry in the chain with
+ * 'new_value'. Return the previous value.
+ */
 static uint16_t
 ao_fat_entry_replace(uint16_t  cluster, uint16_t new_value)
 {
@@ -148,33 +162,50 @@ ao_fat_entry_replace(uint16_t  cluster, uint16_t new_value)
        if (!ao_fat_cluster_valid(cluster))
                return 0;
 
-       cluster -= 2;
-       sector = cluster >> 8;
-       offset = (cluster << 1) & 0x1ff;
-       buf = ao_fat_block_get(fat_start + sector);
+//     cluster -= 2;
+       sector = cluster >> (SECTOR_SHIFT - 1);
+       offset = (cluster << 1) & SECTOR_MASK;
+       buf = ao_fat_sector_get(fat_start + sector);
        if (!buf)
                return 0;
        ret = get_u16(buf + offset);
        put_u16(buf + offset, new_value);
-       ao_fat_block_put(buf, 1);
+       ao_fat_sector_put(buf, 1);
+
+       /*
+        * Keep the other FATs in sync
+        */
        for (other_fats = 1; other_fats < number_fat; other_fats++) {
-               buf = ao_fat_block_get(fat_start + other_fats * sectors_per_fat);
+               buf = ao_fat_sector_get(fat_start + other_fats * sectors_per_fat + sector);
                if (buf) {
                        put_u16(buf + offset, new_value);
-                       ao_fat_block_put(buf, 1);
+                       ao_fat_sector_put(buf, 1);
                }
        }
        return ret;
        
 }
 
+/*
+ * Walk a cluster chain and mark
+ * all of them as free
+ */
 static void
-ao_fat_clear_cluster_chain(uint16_t cluster)
+ao_fat_free_cluster_chain(uint16_t cluster)
 {
-       while (ao_fat_cluster_valid(cluster))
+       while (ao_fat_cluster_valid(cluster)) {
+               if (cluster < first_free_cluster)
+                       first_free_cluster = cluster;
                cluster = ao_fat_entry_replace(cluster, 0x0000);
+       }
 }
 
+/*
+ * ao_fat_cluster_seek
+ * 
+ * Walk a cluster chain the specified distance and return what we find
+ * there. If distance is zero, return the provided cluster.
+ */
 static uint16_t
 ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
 {
@@ -187,16 +218,39 @@ ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
        return cluster;
 }
 
+/*
+ * ao_fat_sector_seek
+ *
+ * The basic file system operation -- map a file sector number to a
+ * partition sector number. Done by computing the cluster number and
+ * then walking that many clusters from the first cluster, then
+ * adding the sector offset from the start of the cluster. Returns
+ * 0xffffffff if we walk off the end of the file or the cluster chain
+ * is damaged somehow
+ */
 static uint32_t
 ao_fat_sector_seek(uint16_t cluster, uint32_t sector)
 {
-       cluster = ao_fat_cluster_seek(cluster, sector / sectors_per_cluster);
+       uint16_t        distance;
+       uint16_t        offset;
+
+       distance = sector / sectors_per_cluster;
+       offset = sector % sectors_per_cluster;
+
+       cluster = ao_fat_cluster_seek(cluster, distance);
+
        if (!ao_fat_cluster_valid(cluster))
                return 0xffffffff;
-       return data_start + (cluster-2) * sectors_per_cluster + sector % sectors_per_cluster;
+
+       /* Compute the sector within the partition and return it */
+       return data_start + (uint32_t) (cluster-2) * sectors_per_cluster + offset;
 }
 
-/* Load the boot block and find the first partition */
+/*
+ * ao_fat_setup_partition
+ * 
+ * Load the boot block and find the first partition
+ */
 static uint8_t
 ao_fat_setup_partition(void)
 {
@@ -216,29 +270,40 @@ ao_fat_setup_partition(void)
                return 0;
        }
 
-       /* Just use the first partition */
-       partition = &mbr[0x1be];
+       /* Check to see if it's actually a boot block, in which
+        * case it's presumably not a paritioned device
+        */
+
+       if (mbr[0] == 0xeb) {
+               partition_start = 0;
+               partition_size = get_u16(mbr + 0x13);
+               if (partition_size == 0)
+                       partition_size = get_u32(mbr + 0x20);
+       } else {
+               /* Just use the first partition */
+               partition = &mbr[0x1be];
        
-       partition_type = partition[4];
-       switch (partition_type) {
-       case 4:         /* FAT16 up to 32M */
-       case 6:         /* FAT16 over 32M */
-               break;
-       case 0x0b:      /* FAT32 up to 2047GB */
-       case 0x0c:      /* FAT32 LBA */
-               break;
-       default:
-               printf ("Invalid partition type %02x\n", partition_type);
-               ao_bufio_put(mbr, 0);
-               return 0;
-       }
+               partition_type = partition[4];
+               switch (partition_type) {
+               case 4:         /* FAT16 up to 32M */
+               case 6:         /* FAT16 over 32M */
+                       break;
+               case 0x0b:      /* FAT32 up to 2047GB */
+               case 0x0c:      /* FAT32 LBA */
+                       break;
+               default:
+                       printf ("Invalid partition type %02x\n", partition_type);
+                       ao_bufio_put(mbr, 0);
+                       return 0;
+               }
 
-       partition_start = get_u32(partition+8);
-       partition_size = get_u32(partition+12);
-       if (partition_size == 0) {
-               printf ("Zero-sized partition\n");
-               ao_bufio_put(mbr, 0);
-               return 0;
+               partition_start = get_u32(partition+8);
+               partition_size = get_u32(partition+12);
+               if (partition_size == 0) {
+                       printf ("Zero-sized partition\n");
+                       ao_bufio_put(mbr, 0);
+                       return 0;
+               }
        }
        partition_end = partition_start + partition_size;
        printf ("Partition type %02x start %08x end %08x\n",
@@ -250,7 +315,8 @@ ao_fat_setup_partition(void)
 static uint8_t
 ao_fat_setup_fs(void)
 {
-       uint8_t *boot = ao_fat_block_get(0);
+       uint8_t         *boot = ao_fat_sector_get(0);
+       uint32_t        data_sectors;
 
        if (!boot)
                return 0;
@@ -259,39 +325,45 @@ ao_fat_setup_fs(void)
        if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) {
                printf ("Invalid BOOT signature %02x %02x\n",
                        boot[0x1fe], boot[0x1ff]);
-               ao_bufio_put(boot, 0);
+               ao_fat_sector_put(boot, 0);
                return 0;
        }
 
        /* Check the sector size */
-       if (get_u16(boot + 0xb) != 0x200) {
+       if (get_u16(boot + 0xb) != SECTOR_SIZE) {
                printf ("Invalid sector size %d\n",
                        get_u16(boot + 0xb));
-               ao_bufio_put(boot, 0);
+               ao_fat_sector_put(boot, 0);
                return 0;
        }
 
        sectors_per_cluster = boot[0xd];
-       bytes_per_cluster = sectors_per_cluster << 9;
+       bytes_per_cluster = sectors_per_cluster << SECTOR_SHIFT;
        reserved_sector_count = get_u16(boot+0xe);
        number_fat = boot[0x10];
        root_entries = get_u16(boot + 0x11);
        sectors_per_fat = get_u16(boot+0x16);
 
+       fat_start = reserved_sector_count;;
+       root_start = fat_start + number_fat * sectors_per_fat;
+       data_start = root_start + ((root_entries * DIRENT_SIZE + SECTOR_MASK) >> SECTOR_SHIFT);
+
+       data_sectors = (partition_end - partition_start) - data_start;
+
+       number_cluster = data_sectors / sectors_per_cluster;
+
        printf ("sectors per cluster %d\n", sectors_per_cluster);
        printf ("reserved sectors %d\n", reserved_sector_count);
        printf ("number of FATs %d\n", number_fat);
        printf ("root entries %d\n", root_entries);
        printf ("sectors per fat %d\n", sectors_per_fat);
 
-       fat_start = reserved_sector_count;;
-       root_start = fat_start + number_fat * sectors_per_fat;
-       data_start = root_start + ((root_entries * 0x20 + 0x1ff) >> 9);
-
        printf ("fat  start %d\n", fat_start);
        printf ("root start %d\n", root_start);
        printf ("data start %d\n", data_start);
 
+       ao_fat_sector_put(boot, 0);
+
        return 1;
 }
 
@@ -300,49 +372,36 @@ ao_fat_setup(void)
 {
        if (!ao_fat_setup_partition())
                return 0;
+       check_bufio("partition setup");
        if (!ao_fat_setup_fs())
                return 0;
+       check_bufio("fs setup");
        return 1;
 }
 
-/*
- * Low-level directory operations
- */
-
 /*
  * Basic file operations
  */
 
 static struct ao_fat_dirent    ao_file_dirent;
 static uint32_t                ao_file_offset;
+static uint8_t                 ao_file_opened;
 
 static uint32_t
-ao_file_offset_to_sector(uint32_t offset)
+ao_fat_offset_to_sector(uint32_t offset)
 {
        if (offset > ao_file_dirent.size)
                return 0xffffffff;
-       return ao_fat_sector_seek(ao_file_dirent.cluster, offset >> 9);
-}
-
-uint8_t
-ao_fat_open(char name[11])
-{
-       uint16_t                entry = 0;
-       struct ao_fat_dirent    dirent;
-
-       while (ao_fat_readdir(&entry, &dirent)) {
-               if (!memcmp(name, dirent.name, 11)) {
-                       ao_file_dirent = dirent;
-                       ao_file_offset = 0;
-                       return 1;
-               }
-       }
-       return 0;
+       return ao_fat_sector_seek(ao_file_dirent.cluster, offset >> SECTOR_SHIFT);
 }
 
-
-
-static uint8_t
+/*
+ * ao_fat_set_size
+ *
+ * Set the size of the current file, truncating or extending
+ * the cluster chain as needed
+ */
+static int8_t
 ao_fat_set_size(uint32_t size)
 {
        uint16_t        clear_cluster = 0;
@@ -350,11 +409,9 @@ ao_fat_set_size(uint32_t size)
        uint16_t        first_cluster;
 
        first_cluster = ao_file_dirent.cluster;
-       printf ("set size to %d\n", size);
        if (size == ao_file_dirent.size)
-               return 1;
+               return AO_FAT_SUCCESS;
        if (size == 0) {
-               printf ("erase file\n");
                clear_cluster = ao_file_dirent.cluster;
                first_cluster = 0;
        } else {
@@ -366,43 +423,50 @@ ao_fat_set_size(uint32_t size)
                if (new_num < old_num) {
                        uint16_t last_cluster;
 
-                       printf("Remove %d clusters\n", old_num - new_num);
                        /* Go find the last cluster we want to preserve in the file */
                        last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, new_num - 1);
 
-                       printf ("Last cluster is now %04x\n", last_cluster);
                        /* Rewrite that cluster entry with 0xffff to mark the end of the chain */
                        clear_cluster = ao_fat_entry_replace(last_cluster, 0xffff);
                } else if (new_num > old_num) {
                        uint16_t        need;
                        uint16_t        free;
                        uint16_t        last_cluster;
+                       uint16_t        highest_allocated = 0;
 
                        if (old_num)
                                last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, old_num - 1);
                        else
                                last_cluster = 0;
 
-                       need = new_num - old_num;
-                       printf ("Need %d clusters\n", need);
+                       if (first_free_cluster < 2 || number_cluster <= first_free_cluster)
+                               first_free_cluster = 2;
+
                        /* See if there are enough free clusters in the file system */
-                       for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) {
-                               if (!ao_fat_entry_read(free)) {
-                                       printf ("\tCluster %04x available\n", free);
+                       need = new_num - old_num;
+
+#define loop_cluster   for (free = first_free_cluster; need > 0;)
+#define next_cluster                                   \
+                       if (++free == number_cluster)   \
+                               free = 2;               \
+                       if (free == first_free_cluster) \
+                               break;                  \
+
+                       loop_cluster {
+                               if (!ao_fat_entry_read(free))
                                        need--;
-                               }
+                               next_cluster;
                        }
                        /* Still need some, tell the user that we've failed */
-                       if (need) {
-                               printf ("File system full\n");
-                               return 0;
-                       }
+                       if (need)
+                               return -AO_FAT_ENOSPC;
 
-                       need = new_num - old_num;
                        /* Now go allocate those clusters */
-                       for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) {
+                       need = new_num - old_num;
+                       loop_cluster {
                                if (!ao_fat_entry_read(free)) {
-                                       printf ("\tAllocate %04x\n", free);
+                                       if (free > highest_allocated)
+                                               highest_allocated = free;
                                        if (last_cluster)
                                                ao_fat_entry_replace(last_cluster, free);
                                        else
@@ -410,153 +474,297 @@ ao_fat_set_size(uint32_t size)
                                        last_cluster = free;
                                        need--;
                                }
+                               next_cluster;
                        }
+                       first_free_cluster = highest_allocated + 1;
+                       if (first_free_cluster >= number_cluster)
+                               first_free_cluster = 2;
+
                        /* Mark the new end of the chain */
                        ao_fat_entry_replace(last_cluster, 0xffff);
                }
        }
 
        /* Deallocate clusters off the end of the file */
-       if (ao_fat_cluster_valid(clear_cluster)) {
-               printf ("Clear clusters starting with %04x\n", clear_cluster);
-               ao_fat_clear_cluster_chain(clear_cluster);
-       }
+       if (ao_fat_cluster_valid(clear_cluster))
+               ao_fat_free_cluster_chain(clear_cluster);
 
+       /* Update the directory entry */
        dent = ao_fat_root_get(ao_file_dirent.entry);
        if (!dent)
-               return 0;
+               return -AO_FAT_EIO;
        put_u32(dent + 0x1c, size);
        put_u16(dent + 0x1a, first_cluster);
        ao_fat_root_put(dent, ao_file_dirent.entry, 1);
        ao_file_dirent.size = size;
        ao_file_dirent.cluster = first_cluster;
-       return 1;
+       return AO_FAT_SUCCESS;
+}
+
+/*
+ * ao_fat_root_init
+ *
+ * Initialize a root directory entry
+ */
+void
+ao_fat_root_init(uint8_t *dent, char name[11], uint8_t attr)
+{
+       memset(dent, '\0', 0x20);
+       memmove(dent, name, 11);
+
+       dent[0x0b] = 0x00;
+       dent[0x0c] = 0x00;
+       dent[0x0d] = 0x00;
+
+       /* XXX fix time */
+       put_u16(dent + 0x0e, 0);
+       /* XXX fix date */
+       put_u16(dent + 0x10, 0);
+       /* XXX fix date */
+       put_u16(dent + 0x12, 0);
+
+       /* XXX fix time */
+       put_u16(dent + 0x16, 0);
+       /* XXX fix date */
+       put_u16(dent + 0x18, 0);
+
+       /* cluster number */
+       /* Low cluster bytes */
+       put_u16(dent + 0x1a, 0);
+       /* FAT32 high cluster bytes */
+       put_u16(dent + 0x14, 0);
+
+       /* size */
+       put_u32(dent + 0x1c, 0);
+}
+
+
+static void
+ao_fat_dirent_init(uint8_t *dent, uint16_t entry, struct ao_fat_dirent *dirent)
+{
+       memcpy(dirent->name, dent + 0x00, 11);
+       dirent->attr = dent[0x0b];
+       dirent->size = get_u32(dent+0x1c);
+       dirent->cluster = get_u16(dent+0x1a);
+       dirent->entry = entry;
+}
+
+/*
+ * Public API
+ */
+
+/*
+ * ao_fat_open
+ *
+ * Open an existing file.
+ */
+int8_t
+ao_fat_open(char name[11], uint8_t mode)
+{
+       uint16_t                entry = 0;
+       struct ao_fat_dirent    dirent;
+
+       if (ao_file_opened)
+               return -AO_FAT_EMFILE;
+       
+       while (ao_fat_readdir(&entry, &dirent)) {
+               if (!memcmp(name, dirent.name, 11)) {
+                       if (AO_FAT_IS_DIR(dirent.attr))
+                               return -AO_FAT_EISDIR;
+                       if (!AO_FAT_IS_FILE(dirent.attr))
+                               return -AO_FAT_EPERM;
+                       if (mode > AO_FAT_OPEN_READ && (dirent.attr & AO_FAT_FILE_READ_ONLY))
+                               return -AO_FAT_EACCESS;
+                       ao_file_dirent = dirent;
+                       ao_file_offset = 0;
+                       ao_file_opened = 1;
+                       return AO_FAT_SUCCESS;
+               }
+       }
+       return -AO_FAT_ENOENT;
 }
 
-uint8_t
+/*
+ * ao_fat_creat
+ *
+ * Open and truncate an existing file or
+ * create a new file
+ */
+int8_t
 ao_fat_creat(char name[11])
 {
        uint16_t        entry;
+       int8_t          status;
+
+       if (ao_file_opened)
+               return -AO_FAT_EMFILE;
+
+       status = ao_fat_open(name, AO_FAT_OPEN_WRITE);
 
-       if (ao_fat_open(name))
-               return ao_fat_set_size(0);
-
-       for (entry = 0; entry < root_entries; entry++) {
-               uint8_t *dent = ao_fat_root_get(entry);
-
-               if (dent[0] == AO_FAT_DENT_EMPTY ||
-                   dent[0] == AO_FAT_DENT_END) {
-                       memmove(dent, name, 11);
-                       dent[0x0b] = 0x00;
-                       dent[0x0c] = 0x00;
-                       dent[0x0d] = 0x00;
-                       /* XXX fix time */
-                       put_u16(dent + 0x0e, 0);
-                       /* XXX fix date */
-                       put_u16(dent + 0x10, 0);
-                       /* XXX fix date */
-                       put_u16(dent + 0x12, 0);
-                       /* XXX FAT32 high cluster bytes */
-                       put_u16(dent + 0x14, 0);
-                       /* XXX fix time */
-                       put_u16(dent + 0x16, 0);
-                       /* XXX fix date */
-                       put_u16(dent + 0x18, 0);
-                       /* cluster number */
-                       put_u16(dent + 0x1a, 0);
-                       /* size */
-                       put_u32(dent + 0x1c, 0);
-                       ao_fat_root_put(dent, entry, 1);
-                       return ao_fat_open(name);
+       switch (status) {
+       case -AO_FAT_SUCCESS:
+               status = ao_fat_set_size(0);
+               break;
+       case -AO_FAT_ENOENT:
+               for (entry = 0; entry < root_entries; entry++) {
+                       uint8_t *dent = ao_fat_root_get(entry);
+
+                       if (!dent) {
+                               status = -AO_FAT_EIO;
+                               ao_fat_root_put(dent, entry, 0);
+                               break;
+                       }
+                               
+                       if (dent[0] == AO_FAT_DENT_EMPTY || dent[0] == AO_FAT_DENT_END) {
+                               ao_fat_root_init(dent, name, AO_FAT_FILE_REGULAR);
+                               ao_fat_dirent_init(dent, entry,  &ao_file_dirent);
+                               ao_fat_root_put(dent, entry, 1);
+                               ao_file_opened = 1;
+                               status = -AO_FAT_SUCCESS;
+                               break;
+                       } else {
+                               ao_fat_root_put(dent, entry, 0);
+                       }
                }
+               if (entry == root_entries)
+                       status = -AO_FAT_ENOSPC;
        }
-       return 0;
+       return status;
 }
 
-void
+/*
+ * ao_fat_close
+ *
+ * Close the currently open file
+ */
+int8_t
 ao_fat_close(void)
 {
+       if (!ao_file_opened)
+               return -AO_FAT_EBADF;
+
        memset(&ao_file_dirent, '\0', sizeof (struct ao_fat_dirent));
+       ao_file_offset = 0;
+       ao_file_opened = 0;
        ao_bufio_flush();
+       return AO_FAT_SUCCESS;
 }
 
+/*
+ * ao_fat_read
+ *
+ * Read from the file
+ */
 int
-ao_fat_read(uint8_t *dest, int len)
+ao_fat_read(void *dst, int len)
 {
+       uint8_t         *dst_b = dst;
        uint32_t        sector;
        uint16_t        this_time;
        uint16_t        offset;
        uint8_t         *buf;
        int             ret = 0;
 
+       if (!ao_file_opened)
+               return -AO_FAT_EBADF;
+
        if (ao_file_offset + len > ao_file_dirent.size)
                len = ao_file_dirent.size - ao_file_offset;
 
+       if (len < 0)
+               len = 0;
+
        while (len) {
-               offset = ao_file_offset & 0x1ff;
-               if (offset + len < 512)
+               offset = ao_file_offset & SECTOR_MASK;
+               if (offset + len < SECTOR_SIZE)
                        this_time = len;
                else
-                       this_time = 512 - offset;
+                       this_time = SECTOR_SIZE - offset;
 
-               sector = ao_file_offset_to_sector(ao_file_offset);
+               sector = ao_fat_offset_to_sector(ao_file_offset);
                if (sector == 0xffffffff)
                        break;
-               buf = ao_fat_block_get(sector);
-               if (!buf)
+               buf = ao_fat_sector_get(sector);
+               if (!buf) {
+                       ret = -AO_FAT_EIO;
                        break;
-               memcpy(dest, buf + offset, this_time);
-               ao_fat_block_put(buf, 0);
+               }
+               memcpy(dst_b, buf + offset, this_time);
+               ao_fat_sector_put(buf, 0);
 
                ret += this_time;
                len -= this_time;
-               dest += this_time;
+               dst_b += this_time;
                ao_file_offset += this_time;
        }
        return ret;
 }
 
+/*
+ * ao_fat_write
+ *
+ * Write to the file, extended as necessary
+ */
 int
-ao_fat_write(uint8_t *src, int len)
+ao_fat_write(void *src, int len)
 {
+       uint8_t         *src_b = src;
        uint32_t        sector;
        uint16_t        this_time;
        uint16_t        offset;
        uint8_t         *buf;
        int             ret = 0;
 
+       if (!ao_file_opened)
+               return -AO_FAT_EBADF;
+
        if (ao_file_offset + len > ao_file_dirent.size) {
-               if (!ao_fat_set_size(ao_file_offset + len))
-                       return 0;
+               ret = ao_fat_set_size(ao_file_offset + len);
+               if (ret < 0)
+                       return ret;
        }
 
        while (len) {
-               offset = ao_file_offset & 0x1ff;
-               if (offset + len < 512)
+               offset = ao_file_offset & SECTOR_MASK;
+               if (offset + len < SECTOR_SIZE)
                        this_time = len;
                else
-                       this_time = 512 - offset;
+                       this_time = SECTOR_SIZE - offset;
 
-               sector = ao_file_offset_to_sector(ao_file_offset);
+               sector = ao_fat_offset_to_sector(ao_file_offset);
                if (sector == 0xffffffff)
                        break;
-               buf = ao_fat_block_get(sector);
-               if (!buf)
+               buf = ao_fat_sector_get(sector);
+               if (!buf) {
+                       ret = -AO_FAT_EIO;
                        break;
-               memcpy(buf + offset, src, this_time);
-               ao_fat_block_put(buf, 1);
+               }
+               memcpy(buf + offset, src_b, this_time);
+               ao_fat_sector_put(buf, 1);
 
                ret += this_time;
                len -= this_time;
-               src += this_time;
+               src_b += this_time;
                ao_file_offset += this_time;
        }
-       return 0;
+       return ret;
 }
 
-uint32_t
+/*
+ * ao_fat_seek
+ *
+ * Set the position for the next I/O operation
+ * Note that this doesn't actually change the size
+ * of the file if the requested position is beyond
+ * the current file length, that would take a future
+ * write
+ */
+int32_t
 ao_fat_seek(int32_t pos, uint8_t whence)
 {
+       if (!ao_file_opened)
+               return -AO_FAT_EBADF;
+
        switch (whence) {
        case AO_FAT_SEEK_SET:
                ao_file_offset = pos;
@@ -568,12 +776,16 @@ ao_fat_seek(int32_t pos, uint8_t whence)
                ao_file_offset = ao_file_dirent.size + pos;
                break;
        }
-       if (ao_file_offset > ao_file_dirent.size)
-               ao_fat_set_size(ao_file_offset);
        return ao_file_offset;
 }
 
-uint8_t
+/*
+ * ao_fat_unlink
+ *
+ * Remove a file from the directory, marking
+ * all clusters as free
+ */
+int8_t
 ao_fat_unlink(char name[11])
 {
        uint16_t                entry = 0;
@@ -584,7 +796,13 @@ ao_fat_unlink(char name[11])
                        uint8_t *next;
                        uint8_t *ent;
                        uint8_t delete;
-                       ao_fat_clear_cluster_chain(dirent.cluster);
+
+                       if (AO_FAT_IS_DIR(dirent.attr))
+                               return -AO_FAT_EISDIR;
+                       if (!AO_FAT_IS_FILE(dirent.attr))
+                               return -AO_FAT_EPERM;
+
+                       ao_fat_free_cluster_chain(dirent.cluster);
                        next = ao_fat_root_get(dirent.entry + 1);
                        if (next && next[0] != AO_FAT_DENT_END)
                                delete = AO_FAT_DENT_EMPTY;
@@ -594,24 +812,24 @@ ao_fat_unlink(char name[11])
                                ao_fat_root_put(next, dirent.entry + 1, 0);
                        ent = ao_fat_root_get(dirent.entry);
                        if (ent) {
-                               memset(ent, '\0', 0x20);
+                               memset(ent, '\0', DIRENT_SIZE);
                                *ent = delete;
                                ao_fat_root_put(ent, dirent.entry, 1);
                        }
                        ao_bufio_flush();
-                       return 1;
+                       return AO_FAT_SUCCESS;
                }
        }
-       return 0;
+       return -AO_FAT_ENOENT;
 }
 
-uint8_t
+int8_t
 ao_fat_rename(char old[11], char new[11])
 {
-       return 0;
+       return -AO_FAT_EIO;
 }
 
-uint8_t
+int8_t
 ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent)
 {
        uint8_t *dent;
@@ -625,20 +843,15 @@ ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent)
                        ao_fat_root_put(dent, *entry, 0);
                        return 0;
                }
-               if (dent[0] != AO_FAT_DENT_EMPTY &&
-                   (dent[0x0b] & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == 0)
-                       break;
+               if (dent[0] != AO_FAT_DENT_EMPTY && (dent[0xb] & 0xf) != 0xf) {
+                       ao_fat_dirent_init(dent, *entry, dirent);
+                       ao_fat_root_put(dent, *entry, 0);
+                       (*entry)++;
+                       return 1;
+               }
                ao_fat_root_put(dent, *entry, 0);
                (*entry)++;
        }
-       memcpy(dirent->name, dent, 11);
-       dirent->attr = dent[0xb];
-       dirent->size = get_u32(dent+0x1c);
-       dirent->cluster = get_u16(dent+0x1a);
-       dirent->entry = *entry;
-       ao_fat_root_put(dent, *entry, 0);
-       (*entry)++;
-       return 1;
 }
 
 static void
@@ -648,8 +861,12 @@ ao_fat_list(void)
        struct ao_fat_dirent    dirent;
 
        while (ao_fat_readdir(&entry, &dirent)) {
-               printf ("%-8.8s.%-3.3s %02x %d\n",
-                       dirent.name, dirent.name + 8, dirent.attr, dirent.size);
+               printf ("%-8.8s.%-3.3s %02x %04x %d\n",
+                       dirent.name,
+                       dirent.name + 8,
+                       dirent.attr,
+                       dirent.cluster,
+                       dirent.size);
        }
 }
 
@@ -671,4 +888,3 @@ ao_fat_init(void)
        ao_bufio_init();
        ao_cmd_register(&ao_fat_cmds[0]);
 }
-
index 2bf6222e69701bf455e65792444b580bbe28ebbb..5b9b300f51d53ebf402658f1e2ac4bb685e0ac35 100644 (file)
@@ -21,6 +21,7 @@
 void
 ao_fat_init(void);
 
+#define AO_FAT_FILE_REGULAR            0x00
 #define AO_FAT_FILE_READ_ONLY          0x01
 #define AO_FAT_FILE_HIDDEN             0x02
 #define AO_FAT_FILE_SYSTEM             0x04
@@ -31,32 +32,52 @@ ao_fat_init(void);
 #define AO_FAT_DENT_EMPTY              0xe5
 #define AO_FAT_DENT_END                        0x00
 
-uint8_t
-ao_fat_open(char name[11]);
-
-uint8_t
+#define AO_FAT_IS_FILE(attr)   (((attr) & (AO_FAT_FILE_VOLUME_LABEL|AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_ARCHIVE)) == 0)
+#define AO_FAT_IS_DIR(attr)    (((attr) & (AO_FAT_FILE_DIRECTORY)) == AO_FAT_FILE_DIRECTORY)
+
+#define AO_FAT_SUCCESS                 0
+#define AO_FAT_EPERM                   1
+#define AO_FAT_ENOENT                  2
+#define AO_FAT_EIO                     4
+#define AO_FAT_EBADF                   9
+#define AO_FAT_EACCESS                 13
+#define AO_FAT_EEXIST                  17
+#define AO_FAT_ENOTDIR                 20
+#define AO_FAT_EISDIR                  21
+#define AO_FAT_EMFILE                  24
+#define AO_FAT_EFBIG                   27
+#define AO_FAT_ENOSPC                  28
+
+int8_t
+ao_fat_open(char name[11], uint8_t mode);
+
+#define AO_FAT_OPEN_READ               0
+#define AO_FAT_OPEN_WRITE              1
+#define AO_FAT_OPEN_RW                 2
+
+int8_t
 ao_fat_creat(char name[11]);
 
-void
+int8_t
 ao_fat_close(void);
 
 int
-ao_fat_read(uint8_t *dest, int len);
+ao_fat_read(void *dest, int len);
 
 int
-ao_fat_write(uint8_t *buf, int len);
+ao_fat_write(void *src, int len);
 
 #define AO_FAT_SEEK_SET        0
 #define AO_FAT_SEEK_CUR        1
 #define AO_FAT_SEEK_END        2
 
-uint32_t
-bao_fat_seek(int32_t pos, uint8_t whence);
+int32_t
+ao_fat_seek(int32_t pos, uint8_t whence);
 
-uint8_t
+int8_t
 ao_fat_unlink(char name[11]);
 
-uint8_t
+int8_t
 ao_fat_rename(char old[11], char new[11]);
 
 struct ao_fat_dirent {
@@ -67,7 +88,7 @@ struct ao_fat_dirent {
        uint16_t        entry;
 };
 
-uint8_t
+int8_t
 ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent);
 
 #endif /* _AO_FAT_H_ */
index 96170cc196dadbb00d0e3b3849e231e9da3c681a..991bdbfcd6d9e14bd31663b9fa431847624c7933 100644 (file)
@@ -66,4 +66,4 @@ ao_micropeak_test: ao_micropeak_test.c ao_microflight.c ao_kalman.h
        cc $(CFLAGS) -o $@ ao_micropeak_test.c -lm
 
 ao_fat_test: ao_fat_test.c ao_fat.c ao_bufio.c
-       cc $(CFLAGS) -o $@ ao_fat_test.c
+       cc $(CFLAGS) -o $@ ao_fat_test.c -lssl
index 22ac03ad2dd059fe617c9adc827c080eb255bc93..3f94703443a98af2bcc576e6fb136436ff76e998 100644 (file)
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdarg.h>
 #include <string.h>
 #include <getopt.h>
 #include <math.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <openssl/md5.h>
 
 #define AO_FAT_TEST
 
@@ -40,7 +42,7 @@ void
 ao_panic(uint8_t panic)
 {
        printf ("panic %d\n", panic);
-       exit(1);
+       abort();
 }
 
 #define AO_PANIC_BUFIO 15
@@ -64,9 +66,12 @@ struct ao_cmds {
 
 int fs_fd;
 
+uint64_t       total_reads, total_writes;
+
 uint8_t
 ao_sdcard_read_block(uint32_t block, uint8_t *data)
 {
+       ++total_reads;
        lseek(fs_fd, block * 512, 0);
        return read(fs_fd, data, 512) == 512;
 }
@@ -74,45 +79,308 @@ ao_sdcard_read_block(uint32_t block, uint8_t *data)
 uint8_t
 ao_sdcard_write_block(uint32_t block, uint8_t *data)
 {
+       ++total_writes;
        lseek(fs_fd, block * 512, 0);
        return write(fs_fd, data, 512) == 512;
 }
 
+char   *fs = "fs.fat";
+
 void
 ao_sdcard_init(void)
 {
-       fs_fd = open("fat.fs", 2);
+       char    cmd[1024];
+
+       snprintf(cmd, sizeof(cmd), "rm -f %s && mkfs.vfat -C %s 16384", fs, fs);
+       if (system (cmd) != 0) {
+               fprintf(stderr, "'%s' failed\n", cmd);
+               exit(1);
+       }
+       fs_fd = open(fs, 2);
+       if (fs_fd < 0) {
+               perror (fs);
+               exit(1);
+       }
 }
 
 #include "ao_bufio.c"
+void
+check_bufio(char *where)
+{
+       int     b;
+
+       for (b = 0; b < AO_NUM_BUF; b++) {
+               if (ao_bufio[b].busy) {
+                       printf ("%s: buffer %d busy. block %d seqno %u\n",
+                               where, b, ao_bufio[b].block, ao_bufio[b].seqno & 0xffff);
+                       abort();
+               }
+       }
+}
+
+
+void
+check_fat(void);
+
 #include "ao_fat.c"
 
+/* Get the next cluster entry in the chain */
+static uint16_t
+ao_fat_entry_raw_read(uint16_t cluster, uint8_t fat)
+{
+       uint32_t        sector;
+       uint16_t        offset;
+       uint8_t         *buf;
+       uint16_t        ret;
+
+//     cluster -= 2;
+       sector = cluster >> (SECTOR_SHIFT - 1);
+       offset = (cluster << 1) & SECTOR_MASK;
+       buf = ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector);
+       if (!buf)
+               return 0;
+       ret = get_u16(buf + offset);
+       ao_fat_sector_put(buf, 0);
+       return ret;
+}
+
+void
+dump_fat(void)
+{
+       int     e;
+
+       printf ("\n **** FAT ****\n\n");
+       for (e = 0; e < number_cluster; e++) {
+               if ((e & 0xf) == 0x0)
+                       printf ("%04x: ", e);
+               printf (" %04x", ao_fat_entry_raw_read(e, 0));
+               if ((e & 0xf) == 0xf)
+                       putchar ('\n');
+       }
+}
+
+void
+fat_list(void)
+{
+       uint16_t                entry = 0;
+       struct ao_fat_dirent    dirent;
+
+       printf ("  **** Root directory ****\n");
+       while (ao_fat_readdir(&entry, &dirent)) {
+               printf ("%04x: %-8.8s.%-3.3s %02x %04x %d\n",
+                       entry,
+                       dirent.name,
+                       dirent.name + 8,
+                       dirent.attr,
+                       dirent.cluster,
+                       dirent.size);
+       }
+
+       printf ("  **** End of root directory ****\n");
+}
+
+void
+fatal(char *msg, ...)
+{
+       dump_fat();
+       fat_list();
+
+       va_list l;
+       va_start(l, msg);
+       vfprintf(stderr, msg, l);
+       va_end(l);
+
+       abort();
+}
+
+void
+check_fat(void)
+{
+       int     e;
+       int     f;
+
+       for (e = 0; e < number_cluster; e++) {
+               uint16_t        v = ao_fat_entry_raw_read(e, 0);
+               for (f = 1; f < number_fat; f++) {
+                       if (ao_fat_entry_raw_read(e, f) != v)
+                               fatal ("fats differ at %d\n", e);
+               }
+       }
+}
+
+uint16_t
+check_file(uint16_t dent, uint16_t first_cluster, uint8_t *used)
+{
+       uint16_t        clusters = 0;
+       uint16_t        cluster;
+
+       if (!first_cluster)
+               return 0;
+       
+       for (cluster = first_cluster;
+            (cluster & 0xfff8) != 0xfff8;
+            cluster = ao_fat_entry_raw_read(cluster, 0))
+       {
+               if (!ao_fat_cluster_valid(cluster))
+                       fatal("file %d: invalid cluster %04x\n", dent, cluster);
+               if (used[cluster])
+                       fatal("file %d: duplicate cluster %04x\n", dent, cluster);
+               used[cluster] = 1;
+               clusters++;
+       }
+       return clusters;
+}
+
+void
+check_fs(void)
+{
+       uint16_t        r;
+       uint16_t        cluster, chain;
+       uint8_t         *used;
+
+       check_fat();
+
+       used = calloc(1, number_cluster);
+
+       for (r = 0; r < root_entries; r++) {
+               uint8_t         *dent = ao_fat_root_get(r);
+               uint16_t        clusters;
+               uint32_t        size;
+               uint16_t        first_cluster;
+               uint8_t         name[11];
+
+               if (!dent)
+                       fatal("cannot map dent %d\n", r);
+               memcpy(name, dent+0, 11);
+               first_cluster = get_u16(dent + 0x1a);
+               size = get_u32(dent + 0x1c);
+               ao_fat_root_put(dent, r, 0);
+
+               if (name[0] == AO_FAT_DENT_END) {
+                       break;
+               }
+
+               clusters = check_file(r, first_cluster, used);
+               if (size > clusters * bytes_per_cluster)
+                       fatal("file %d: size %u beyond clusters %d (%u)\n",
+                             r, size, clusters, clusters * bytes_per_cluster);
+               if (size <= (clusters - 1) * bytes_per_cluster)
+                       fatal("file %d: size %u too small clusters %d (%u)\n",
+                             r, size, clusters, clusters * bytes_per_cluster);
+       }
+       for (; r < root_entries; r++) {
+               uint8_t *dent = ao_fat_root_get(r);
+               if (!dent)
+                       fatal("cannot map dent %d\n", r);
+               if (dent[0] != AO_FAT_DENT_END)
+                       fatal("found non-zero dent past end %d\n", r);
+               ao_fat_root_put(dent, r, 0);
+       }
+
+       for (cluster = 0; cluster < 2; cluster++) {
+               chain = ao_fat_entry_raw_read(cluster, 0);
+
+               if ((chain & 0xfff8) != 0xfff8)
+                       fatal("cluster %d: not marked busy\n", cluster);
+       }
+       for (; cluster < number_cluster; cluster++) {
+               chain = ao_fat_entry_raw_read(cluster, 0);
+
+               if (chain != 0) {
+                       if (used[cluster] == 0)
+                               fatal("cluster %d: marked busy, but not in any file\n", cluster);
+               } else {
+                       if (used[cluster] != 0)
+                               fatal("cluster %d: marked free, but foudn in file\n", cluster);
+               }
+       }
+}
+
+#define NUM_FILES      512
+#define LINES_FILE     1000
+
+uint32_t               sizes[NUM_FILES];
+
+unsigned char          md5[NUM_FILES][MD5_DIGEST_LENGTH];
+
 int
 main(int argc, char **argv)
 {
-       uint8_t data[15];
-       int     len;
+       char    name[12];
+       int     id;
+       MD5_CTX ctx;
+       unsigned char   md5_check[MD5_DIGEST_LENGTH];
+
+       if (argv[1])
+               fs = argv[1];
+
        ao_fat_init();
-       ao_fat_test();
-       if (ao_fat_open("DATALOG TXT")) {
-               printf ("DATALOG.TXT\n");
-               while ((len = ao_fat_read(data, sizeof (data))) > 0) {
-                       write(1, data, len);
+
+       check_bufio("top");
+       ao_fat_setup();
+
+       check_fs();
+       check_bufio("after setup");
+       printf ("   **** Creating %d files\n", NUM_FILES);
+
+       for (id = 0; id < NUM_FILES; id++) {
+               sprintf(name, "D%07dTXT", id);
+               if (ao_fat_creat(name) == AO_FAT_SUCCESS) {
+                       int j;
+                       char    line[64];
+                       check_bufio("file created");
+                       MD5_Init(&ctx);
+                       for (j = 0; j < 1000; j++) {
+                               int len;
+                               sprintf (line, "Hello, world %d %d\r\n", id, j);
+                               len = strlen(line);
+                               ao_fat_write((uint8_t *) line, len);
+                               MD5_Update(&ctx, line, len);
+                               sizes[id] += len;
+                       }
+                       ao_fat_close();
+                       MD5_Final(&md5[id][0], &ctx);
+                       if (id == 0) {
+                               printf ("MD5 write %d:", id);
+                               for (j = 0; j < MD5_DIGEST_LENGTH; j++)
+                                       printf(" %02x", md5[id][j]);
+                               printf ("\n");
+                       }
+                       check_bufio("file written");
                }
-               ao_fat_close();
-//             ao_fat_unlink("DATALOG TXT");
        }
-       if (ao_fat_open("NEWFILE TXT")) {
-               printf ("NEWFILE.TXT\n");
-               while ((len = ao_fat_read(data, sizeof (data))) > 0) {
-                       write(1, data, len);
+
+       check_bufio("all files created");
+       printf ("   **** All done creating files\n");
+       check_fs();
+
+       printf ("   **** Comparing %d files\n", NUM_FILES);
+
+       for (id = 0; id < NUM_FILES; id++) {
+               char    buf[337];
+               sprintf(name, "D%07dTXT", id);
+               if (ao_fat_open(name, AO_FAT_OPEN_READ) == AO_FAT_SUCCESS) {
+                       int     len;
+
+                       MD5_Init(&ctx);
+                       while ((len = ao_fat_read((uint8_t *) buf, sizeof(buf))) > 0) {
+                               MD5_Update(&ctx, buf, len);
+                       }
+                       ao_fat_close();
+                       MD5_Final(md5_check, &ctx);
+                       if (id == 0) {
+                               int j;
+                               printf ("MD5 read %d:", id);
+                               for (j = 0; j < MD5_DIGEST_LENGTH; j++)
+                                       printf(" %02x", md5_check[j]);
+                               printf ("\n");
+                       }
+                       if (memcmp(md5_check, &md5[id][0], sizeof (md5_check)) != 0)
+                               fatal ("checksum failed file %d\n", id);
+                       check_bufio("file shown");
                }
-               ao_fat_close();
-       }
-       if (ao_fat_creat ("NEWFILE TXT")) {
-               for (len = 0; len < 4095; len++)
-                       ao_fat_write((uint8_t *) "hello, world!\n", 14);
-               ao_fat_close();
        }
+
+       printf ("\n    **** Total IO: read %llu write %llu\n", total_reads, total_writes);
        return 0;
 }