OSDN Git Service

Implement existing files writing in libexfat.
authorrelan <relan@users.noreply.github.com>
Sat, 31 Oct 2009 18:20:49 +0000 (18:20 +0000)
committerrelan <relan@users.noreply.github.com>
Mon, 24 Aug 2015 05:26:10 +0000 (08:26 +0300)
libexfat/cluster.c
libexfat/exfat.h
libexfat/exfatfs.h
libexfat/io.c
libexfat/mount.c
libexfat/node.c

index fa499a2..463b0e6 100644 (file)
@@ -8,6 +8,8 @@
  */
 
 #include "exfat.h"
+#include <errno.h>
+#include <string.h>
 
 /*
  * Cluster to block.
@@ -36,6 +38,15 @@ static off_t b2o(const struct exfat* ef, uint32_t block)
        return (off_t) block << ef->sb->block_bits;
 }
 
+/*
+ * Size in bytes to size in clusters (rounded upwards).
+ */
+static uint32_t bytes2clusters(const struct exfat* ef, uint64_t bytes)
+{
+       uint64_t cluster_size = CLUSTER_SIZE(*ef->sb);
+       return (bytes + cluster_size - 1) / cluster_size;
+}
+
 cluster_t exfat_next_cluster(const struct exfat* ef,
                const struct exfat_node* node, cluster_t cluster)
 {
@@ -63,3 +74,218 @@ cluster_t exfat_advance_cluster(const struct exfat* ef,
        }
        return cluster;
 }
+
+static cluster_t find_bit_and_set(uint8_t* bitmap, uint64_t size_in_bits)
+{
+       uint32_t byte;
+
+       /* FIXME this is too straightforward algorithm that often produces
+          fragmentation */
+       for (byte = 0; byte < size_in_bits / 8; byte++)
+               if (bitmap[byte] != 0xff)
+               {
+                       const uint32_t last_bit = MIN(8, size_in_bits - byte * 8);
+                       uint32_t bit;
+
+                       for (bit = 0; bit < last_bit; bit++)
+                               if (!(bitmap[byte] & (1u << bit)))
+                               {
+                                       bitmap[byte] |= (1u << bit);
+                                       return byte * 8 + bit;
+                               }
+               }
+
+       return EXFAT_CLUSTER_END;
+}
+
+static void flush_cmap(struct exfat* ef)
+{
+       exfat_write_raw(ef->cmap.chunk, (ef->cmap.chunk_size + 7) / 8,
+                       exfat_c2o(ef, ef->cmap.start_cluster), ef->fd);
+}
+
+static void set_next_cluster(const struct exfat* ef, int contiguous,
+               cluster_t current, cluster_t next)
+{
+       off_t fat_offset;
+
+       if (contiguous)
+               return;
+       fat_offset = b2o(ef, le32_to_cpu(ef->sb->fat_block_start))
+               + current * sizeof(cluster_t);
+       exfat_write_raw(&next, sizeof(next), fat_offset, ef->fd);
+}
+
+static void erase_cluster(struct exfat* ef, cluster_t cluster)
+{
+       const int block_size = BLOCK_SIZE(*ef->sb);
+       const int blocks_in_cluster = CLUSTER_SIZE(*ef->sb) / block_size;
+       int i;
+
+       for (i = 0; i < blocks_in_cluster; i++)
+               exfat_write_raw(ef->zero_block, block_size,
+                               exfat_c2o(ef, cluster) + i * block_size, ef->fd);
+}
+
+static cluster_t allocate_cluster(struct exfat* ef)
+{
+       cluster_t cluster = find_bit_and_set(ef->cmap.chunk, ef->cmap.chunk_size);
+
+       if (cluster == EXFAT_CLUSTER_END)
+       {
+               exfat_error("no free space left");
+               return EXFAT_CLUSTER_END;
+       }
+
+       erase_cluster(ef, cluster);
+       /* FIXME no need to flush immediately */
+       flush_cmap(ef);
+       /* FIXME update percentage of used space */
+       return cluster;
+}
+
+static void free_cluster(struct exfat* ef, cluster_t cluster)
+{
+       if (CLUSTER_INVALID(cluster))
+               exfat_bug("attempting to free invalid cluster");
+
+       ef->cmap.chunk[cluster / 8] &= ~(1u << cluster % 8);
+       /* FIXME no need to flush immediately */
+       flush_cmap(ef);
+}
+
+static void make_noncontiguous(const struct exfat* ef, cluster_t first,
+               cluster_t last)
+{
+       cluster_t c;
+
+       for (c = first; c < last; c++)
+               set_next_cluster(ef, 0, c, c + 1);
+}
+
+static int grow_file(struct exfat* ef, struct exfat_node* node,
+               uint32_t difference)
+{
+       cluster_t previous;
+       cluster_t next;
+
+
+       if (difference == 0)
+               exfat_bug("zero clusters count passed");
+
+       if (node->start_cluster != EXFAT_CLUSTER_FREE)
+       {
+               /* get the last cluster of the file */
+               previous = exfat_advance_cluster(ef, node, node->start_cluster,
+                               bytes2clusters(ef, node->size) - 1);
+               if (CLUSTER_INVALID(previous))
+               {
+                       exfat_error("invalid cluster in file");
+                       return -EIO;
+               }
+       }
+       else
+       {
+               /* file does not have clusters (i.e. is empty), allocate
+                  the first one for it */
+               previous = allocate_cluster(ef);
+               if (CLUSTER_INVALID(previous))
+                       return -ENOSPC;
+               node->start_cluster = previous;
+               difference--;
+               /* file consists of only one cluster, so it's contiguous */
+               node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+       }
+
+       while (difference--)
+       {
+               next = allocate_cluster(ef);
+               if (CLUSTER_INVALID(next))
+                       return -ENOSPC;
+               if (next != previous - 1 && IS_CONTIGUOUS(*node))
+               {
+                       /* it's a pity, but we are not able to keep the file contiguous
+                          anymore */
+                       make_noncontiguous(ef, node->start_cluster, previous);
+                       node->flags &= ~EXFAT_ATTRIB_CONTIGUOUS;
+               }
+               set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, next);
+               previous = next;
+       }
+
+       set_next_cluster(ef, IS_CONTIGUOUS(*node), previous, EXFAT_CLUSTER_END);
+       return 0;
+}
+
+static int shrink_file(struct exfat* ef, struct exfat_node* node,
+               uint32_t difference)
+{
+       uint32_t current = bytes2clusters(ef, node->size);
+       cluster_t previous;
+       cluster_t next;
+
+       if (difference == 0)
+               exfat_bug("zero difference passed");
+       if (node->start_cluster == EXFAT_CLUSTER_FREE)
+               exfat_bug("unable to shrink empty file (%u clusters)", current);
+       if (current < difference)
+               exfat_bug("file underflow (%u < %u)", current, difference);
+
+       /* crop the file */
+       if (current > difference)
+       {
+               cluster_t last = exfat_advance_cluster(ef, node, node->start_cluster,
+                               current - difference - 1);
+               if (CLUSTER_INVALID(last))
+               {
+                       exfat_error("invalid cluster in file");
+                       return -EIO;
+               }
+               previous = exfat_next_cluster(ef, node, last);
+               set_next_cluster(ef, IS_CONTIGUOUS(*node), last, EXFAT_CLUSTER_END);
+       }
+       else
+       {
+               previous = node->start_cluster;
+               node->start_cluster = EXFAT_CLUSTER_FREE;
+       }
+
+       /* free remaining clusters */
+       while (difference--)
+       {
+               if (CLUSTER_INVALID(previous))
+               {
+                       exfat_error("invalid cluster in file");
+                       return -EIO;
+               }
+               next = exfat_next_cluster(ef, node, previous);
+               set_next_cluster(ef, IS_CONTIGUOUS(*node), previous,
+                               EXFAT_CLUSTER_FREE);
+               free_cluster(ef, previous);
+               previous = next;
+       }
+       return 0;
+}
+
+int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size)
+{
+       uint32_t c1 = bytes2clusters(ef, node->size);
+       uint32_t c2 = bytes2clusters(ef, size);
+       int rc = 0;
+
+       if (c1 < c2)
+               rc = grow_file(ef, node, c2 - c1);
+       else if (c1 > c2)
+               rc = shrink_file(ef, node, c1 - c2);
+
+       if (rc != 0)
+               return rc;
+
+       if (node->size != size)
+       {
+               node->size = size;
+               /* FIXME no need to flush immediately */
+               exfat_flush_node(ef, node);
+       }
+       return 0;
+}
index 74d8c5d..af92c98 100644 (file)
@@ -38,6 +38,7 @@ struct exfat_node
        struct exfat_node* prev;
 
        int references;
+       off_t meta1_offset, meta2_offset;
        cluster_t start_cluster;
        int flags;
        uint64_t size;
@@ -52,6 +53,15 @@ struct exfat
        le16_t* upcase;
        size_t upcase_chars;
        struct exfat_node* root;
+       struct
+       {
+               cluster_t start_cluster;
+               uint32_t size;                          /* in bits */
+               uint8_t* chunk;
+               uint32_t chunk_size;            /* in bits */
+       }
+       cmap;
+       void* zero_block;
 };
 
 /* in-core nodes iterator */
@@ -69,8 +79,11 @@ void exfat_warn(const char* format, ...);
 void exfat_debug(const char* format, ...);
 
 void exfat_read_raw(void* buffer, size_t size, off_t offset, int fd);
+void exfat_write_raw(const void* buffer, size_t size, off_t offset, int fd);
 ssize_t exfat_read(const struct exfat* ef, const struct exfat_node* node,
                void* buffer, size_t size, off_t offset);
+ssize_t exfat_write(struct exfat* ef, struct exfat_node* node,
+               const void* buffer, size_t size, off_t offset);
 
 int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
                struct exfat_iterator* it);
@@ -84,6 +97,10 @@ cluster_t exfat_next_cluster(const struct exfat* ef,
                const struct exfat_node* node, cluster_t cluster);
 cluster_t exfat_advance_cluster(const struct exfat* ef,
                const struct exfat_node* node, cluster_t cluster, uint32_t count);
+cluster_t exfat_allocate_cluster(struct exfat* ef, cluster_t previous);
+void exfat_free_cluster(struct exfat* ef, cluster_t cluster,
+               cluster_t previous);
+int exfat_truncate(struct exfat* ef, struct exfat_node* node, uint64_t size);
 
 void exfat_stat(const struct exfat_node* node, struct stat *stbuf);
 time_t exfat_exfat2unix(le16_t date, le16_t time);
@@ -101,6 +118,7 @@ struct exfat_node* exfat_get_node(struct exfat_node* node);
 void exfat_put_node(struct exfat_node* node);
 int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir);
 void exfat_reset_cache(struct exfat* ef);
+void exfat_flush_node(struct exfat* ef, const struct exfat_node* node);
 
 int exfat_mount(struct exfat* ef, const char* spec);
 void exfat_unmount(struct exfat* ef);
index 79a7a61..d9d933a 100644 (file)
@@ -28,6 +28,7 @@ static inline le64_t cpu_to_le64(uint64_t v) { le64_t t = {v}; return t; }
 
 #define EXFAT_FIRST_DATA_CLUSTER 2
 
+#define EXFAT_CLUSTER_FREE         0 /* free cluster */
 #define EXFAT_CLUSTER_BAD 0xfffffff7 /* cluster contains bad block */
 #define EXFAT_CLUSTER_END 0xffffffff /* final cluster of file or directory */
 
@@ -130,7 +131,7 @@ struct exfat_file_info                              /* file or directory info */
        uint8_t flag;                                   /* fragmented or contiguous */
        uint8_t __unknown1;
        uint8_t name_length;
-       le16_t hash;
+       le16_t name_hash;
        uint8_t __unknown[14];
        le32_t start_cluster;
        le64_t size;                                    /* in bytes */
index ddff88c..d826079 100644 (file)
@@ -23,6 +23,12 @@ void exfat_read_raw(void* buffer, size_t size, off_t offset, int fd)
                exfat_bug("failed to read %zu bytes from file at %llu", size, offset);
 }
 
+void exfat_write_raw(const void* buffer, size_t size, off_t offset, int fd)
+{
+       if (pwrite(fd, buffer, size, offset) != size)
+               exfat_bug("failed to write %zu bytes to file at %llu", size, offset);
+}
+
 ssize_t exfat_read(const struct exfat* ef, const struct exfat_node* node,
                void* buffer, size_t size, off_t offset)
 {
@@ -47,17 +53,59 @@ ssize_t exfat_read(const struct exfat* ef, const struct exfat_node* node,
        remainder = MIN(size, node->size - offset);
        while (remainder > 0)
        {
+               if (CLUSTER_INVALID(cluster))
+               {
+                       exfat_error("got invalid cluster");
+                       return -1;
+               }
                lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
                exfat_read_raw(bufp, lsize, exfat_c2o(ef, cluster) + loffset, ef->fd);
                bufp += lsize;
                loffset = 0;
                remainder -= lsize;
                cluster = exfat_next_cluster(ef, node, cluster);
+       }
+       return size - remainder;
+}
+
+ssize_t exfat_write(struct exfat* ef, struct exfat_node* node,
+               const void* buffer, size_t size, off_t offset)
+{
+       cluster_t cluster;
+       const char* bufp = buffer;
+       off_t lsize, loffset, remainder;
+
+       if (offset + size > node->size)
+               exfat_truncate(ef, node, offset + size);
+       if (size == 0)
+               return 0;
+
+       cluster = exfat_advance_cluster(ef, node, node->start_cluster,
+                       offset / CLUSTER_SIZE(*ef->sb));
+       if (CLUSTER_INVALID(cluster))
+       {
+               exfat_error("got invalid cluster");
+               return -1;
+       }
+
+       loffset = offset % CLUSTER_SIZE(*ef->sb);
+       remainder = size;
+       while (remainder > 0)
+       {
                if (CLUSTER_INVALID(cluster))
                {
                        exfat_error("got invalid cluster");
                        return -1;
                }
+               lsize = MIN(CLUSTER_SIZE(*ef->sb) - loffset, remainder);
+               exfat_write_raw(bufp, lsize, exfat_c2o(ef, cluster) + loffset, ef->fd);
+               bufp += lsize;
+               loffset = 0;
+               remainder -= lsize;
+               cluster = exfat_next_cluster(ef, node, cluster);
        }
+       /* FIXME update modification time */
+       /* FIXME no need to flush immediately */
+       exfat_flush_node(ef, node);
        return size - remainder;
 }
index 2efc416..6c8a512 100644 (file)
@@ -40,6 +40,7 @@ int exfat_mount(struct exfat* ef, const char* spec)
                exfat_error("memory allocation failed");
                return -ENOMEM;
        }
+       memset(ef->sb, 0, sizeof(struct exfat_super_block));
 
        ef->fd = open(spec, O_RDWR);
        if (ef->fd < 0)
@@ -58,12 +59,20 @@ int exfat_mount(struct exfat* ef, const char* spec)
                return -EIO;
        }
 
-       ef->upcase = NULL;
-       ef->upcase_chars = 0;
+       ef->zero_block = malloc(BLOCK_SIZE(*ef->sb));
+       if (ef->zero_block == NULL)
+       {
+               close(ef->fd);
+               free(ef->sb);
+               exfat_error("failed to allocate zero block");
+               return -ENOMEM;
+       }
+       memset(ef->zero_block, 0, BLOCK_SIZE(*ef->sb));
 
        ef->root = malloc(sizeof(struct exfat_node));
        if (ef->root == NULL)
        {
+               free(ef->zero_block);
                close(ef->fd);
                free(ef->sb);
                exfat_error("failed to allocate root node");
@@ -88,6 +97,10 @@ void exfat_unmount(struct exfat* ef)
        exfat_put_node(ef->root);
        exfat_reset_cache(ef);
        ef->root = NULL;
+       free(ef->zero_block);
+       ef->zero_block = NULL;
+       free(ef->cmap.chunk);
+       ef->cmap.chunk = NULL;
        close(ef->fd);
        ef->fd = 0;
        free(ef->sb);
index 67bd617..4208170 100644 (file)
@@ -137,6 +137,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        }
                        memset(*node, 0, sizeof(struct exfat_node));
                        /* new node has zero reference counter */
+                       (*node)->meta1_offset = exfat_c2o(ef, it->cluster) +
+                                       (it->offset - sizeof(struct exfat_entry)) %
+                                       CLUSTER_SIZE(*ef->sb);
                        (*node)->flags = le16_to_cpu(file->attrib);
                        (*node)->mtime = exfat_exfat2unix(file->mdate, file->mtime);
                        (*node)->atime = exfat_exfat2unix(file->adate, file->atime);
@@ -152,6 +155,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        }
                        file_info = (const struct exfat_file_info*) entry;
                        actual_checksum = exfat_add_checksum(entry, actual_checksum);
+                       (*node)->meta2_offset = exfat_c2o(ef, it->cluster) +
+                                       (it->offset - sizeof(struct exfat_entry)) %
+                                       CLUSTER_SIZE(*ef->sb);
                        (*node)->size = le64_to_cpu(file_info->size);
                        (*node)->start_cluster = le32_to_cpu(file_info->start_cluster);
                        if (file_info->flag == EXFAT_FLAG_CONTIGUOUS)
@@ -227,6 +233,20 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                                (le32_to_cpu(ef->sb->cluster_count) + 7) / 8);
                                return -EIO;
                        }
+                       ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
+                       ef->cmap.size = le32_to_cpu(ef->sb->cluster_count);
+                       /* FIXME bitmap can be rather big, up to 512 MB */
+                       ef->cmap.chunk_size = ef->cmap.size;
+                       ef->cmap.chunk = malloc(le64_to_cpu(bitmap->size));
+                       if (ef->cmap.chunk == NULL)
+                       {
+                               exfat_error("failed to allocate clusters map chunk "
+                                               "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
+                               return -ENOMEM;
+                       }
+
+                       exfat_read_raw(ef->cmap.chunk, le64_to_cpu(bitmap->size),
+                                       exfat_c2o(ef, ef->cmap.start_cluster), ef->fd);
                        break;
 
                case EXFAT_ENTRY_LABEL:
@@ -335,3 +355,41 @@ void exfat_reset_cache(struct exfat* ef)
 {
        reset_cache(ef->root);
 }
+
+void exfat_flush_node(struct exfat* ef, const struct exfat_node* node)
+{
+       struct exfat_file meta1;
+       struct exfat_file_info meta2;
+       uint16_t checksum;
+       uint8_t i;
+
+       exfat_read_raw(&meta1, sizeof(meta1), node->meta1_offset, ef->fd);
+       if (meta1.type != EXFAT_ENTRY_FILE)
+               exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
+       meta1.attrib = cpu_to_le16(node->flags);
+       exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime);
+       exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime);
+
+       exfat_read_raw(&meta2, sizeof(meta2), node->meta2_offset, ef->fd);
+       if (meta2.type != EXFAT_ENTRY_FILE_INFO)
+               exfat_bug("invalid type of meta2: 0x%hhx", meta2.type);
+       meta2.size = cpu_to_le64(node->size);
+       meta2.start_cluster = cpu_to_le32(node->start_cluster);
+       meta2.flag = (IS_CONTIGUOUS(*node) ?
+                       EXFAT_FLAG_CONTIGUOUS : EXFAT_FLAG_FRAGMENTED);
+       /* FIXME name hash */
+
+       checksum = exfat_start_checksum(&meta1);
+       checksum = exfat_add_checksum(&meta2, checksum);
+       for (i = 1; i < meta1.continuations; i++)
+       {
+               struct exfat_file_name name = {EXFAT_ENTRY_FILE_NAME, 0};
+               memcpy(name.name, node->name + (i - 1) * EXFAT_ENAME_MAX,
+                               EXFAT_ENAME_MAX * sizeof(le16_t));
+               checksum = exfat_add_checksum(&name, checksum);
+       }
+       meta1.checksum = cpu_to_le16(checksum);
+
+       exfat_write_raw(&meta1, sizeof(meta1), node->meta1_offset, ef->fd);
+       exfat_write_raw(&meta2, sizeof(meta2), node->meta2_offset, ef->fd);
+}