OSDN Git Service

Implemented nodes cache (in-core directories structure representation). This is a...
authorresver <resver@60bc1c72-a15a-11de-b98f-4500b42dc123>
Thu, 22 Oct 2009 18:38:01 +0000 (18:38 +0000)
committerresver <resver@60bc1c72-a15a-11de-b98f-4500b42dc123>
Thu, 22 Oct 2009 18:38:01 +0000 (18:38 +0000)
git-svn-id: http://exfat.googlecode.com/svn/trunk@30 60bc1c72-a15a-11de-b98f-4500b42dc123

fsck/main.c
fuse/main.c
libexfat/exfat.h
libexfat/lookup.c
libexfat/node.c [new file with mode: 0644]
libexfat/utils.c

index e516650..9d21049 100644 (file)
@@ -63,6 +63,7 @@ static void dirck(struct exfat* ef, const char* path)
        struct exfat_node* parent;
        struct exfat_node* node;
        struct exfat_iterator it;
+       int rc;
        char subpath[EXFAT_NAME_MAX + 1];
 
        if (exfat_lookup(ef, &parent, path) != 0)
@@ -70,8 +71,14 @@ static void dirck(struct exfat* ef, const char* path)
        if (!(parent->flags & EXFAT_ATTRIB_DIR))
                exfat_bug("`%s' is not a directory (0x%x)", path, parent->flags);
 
-       exfat_opendir(parent, &it);
-       while (exfat_readdir(ef, parent, &node, &it) == 0)
+       rc = exfat_opendir(ef, parent, &it);
+       if (rc != 0)
+       {
+               exfat_put_node(parent);
+               exfat_error("failed to open directory `%s'", path);
+               return;
+       }
+       while ((node = exfat_readdir(ef, &it)))
        {
                strcpy(subpath, path);
                strcat(subpath, "/");
index ecc4698..698541d 100644 (file)
@@ -66,18 +66,25 @@ static int fuse_exfat_readdir(const char *path, void *buffer,
        if (!(parent->flags & EXFAT_ATTRIB_DIR))
        {
                exfat_put_node(parent);
+               exfat_error("`%s' is not a directory (0x%x)", path, parent->flags);
                return -ENOTDIR;
        }
 
        filler(buffer, ".", NULL, 0);
        filler(buffer, "..", NULL, 0);
 
-       exfat_opendir(parent, &it);
-       while (exfat_readdir(&ef, parent, &node, &it) == 0)
+       rc = exfat_opendir(&ef, parent, &it);
+       if (rc != 0)
+       {
+               exfat_put_node(parent);
+               exfat_error("failed to open directory `%s'", path);
+               return rc;
+       }
+       while ((node = exfat_readdir(&ef, &it)))
        {
                exfat_get_name(node, name, EXFAT_NAME_MAX);
                exfat_debug("[fuse_exfat_readdir] %s: %s, %llu bytes, cluster %u",
-                               name, IS_CONTIGUOUS(node) ? "contiguous" : "fragmented",
+                               name, IS_CONTIGUOUS(*node) ? "contiguous" : "fragmented",
                                node->size, node->start_cluster);
                filler(buffer, name, NULL, 0);
                exfat_put_node(node);
index e1591dd..4199191 100644 (file)
 
 #define EXFAT_NAME_MAX 256
 #define EXFAT_ATTRIB_CONTIGUOUS 0x10000
+#define EXFAT_ATTRIB_CACHED     0x20000
 #define IS_CONTIGUOUS(node) (((node).flags & EXFAT_ATTRIB_CONTIGUOUS) != 0)
 #define BLOCK_SIZE(sb) (1 << (sb).block_bits)
 #define CLUSTER_SIZE(sb) (BLOCK_SIZE(sb) << (sb).bpc_bits)
 #define CLUSTER_INVALID(c) ((c) == EXFAT_CLUSTER_BAD || (c) == EXFAT_CLUSTER_END)
 
-struct exfat
-{
-       struct exfat_super_block* sb;
-       int fd;
-       uint64_t rootdir_size;
-       le16_t* upcase;
-       size_t upcase_chars;
-};
-
 struct exfat_node
 {
+       struct exfat_node* child;
+       struct exfat_node* next;
+       struct exfat_node* prev;
+
+       int references;
        cluster_t start_cluster;
        int flags;
        uint64_t size;
@@ -46,12 +43,21 @@ struct exfat_node
        le16_t name[EXFAT_NAME_MAX + 1];
 };
 
+struct exfat
+{
+       struct exfat_super_block* sb;
+       int fd;
+       uint64_t rootdir_size;
+       le16_t* upcase;
+       size_t upcase_chars;
+       struct exfat_node* root;
+};
+
+/* in-core nodes iterator */
 struct exfat_iterator
 {
-       cluster_t cluster;
-       off_t offset;
-       int contiguous;
-       char* chunk;
+       struct exfat_node* parent;
+       struct exfat_node* current;
 };
 
 extern int exfat_errors;
@@ -65,11 +71,10 @@ void exfat_read_raw(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);
 
-void exfat_put_node(struct exfat_node* node);
-void exfat_opendir(const struct exfat_node* node, struct exfat_iterator* it);
+int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
+               struct exfat_iterator* it);
 void exfat_closedir(struct exfat_iterator* it);
-int exfat_readdir(struct exfat* ef, const struct exfat_node* parent,
-               struct exfat_node** node, struct exfat_iterator* it);
+struct exfat_node* exfat_readdir(struct exfat* ef, struct exfat_iterator* it);
 int exfat_lookup(struct exfat* ef, struct exfat_node** node,
                const char* path);
 
@@ -91,4 +96,9 @@ int utf16_to_utf8(char* output, const le16_t* input, size_t outsize,
 int utf8_to_utf16(le16_t* output, const char* input, size_t outsize,
                size_t insize);
 
+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);
+
 #endif /* ifndef EXFAT_H_INCLUDED */
index 66e8d1b..5f810d9 100644 (file)
 #include <errno.h>
 #include <inttypes.h>
 
-void exfat_put_node(struct exfat_node* node)
+int exfat_opendir(struct exfat* ef, struct exfat_node* dir,
+               struct exfat_iterator* it)
 {
-       free(node);
-}
+       int rc;
 
-void exfat_opendir(const struct exfat_node* node, struct exfat_iterator* it)
-{
-       if (!(node->flags & EXFAT_ATTRIB_DIR))
-               exfat_bug("`%s' is not a directory", node->name);
-       it->cluster = node->start_cluster;
-       it->offset = 0;
-       it->contiguous = IS_CONTIGUOUS(*node);
-       it->chunk = NULL;
+       exfat_get_node(dir);
+       it->parent = dir;
+       it->current = NULL;
+       rc = exfat_cache_directory(ef, dir);
+       if (rc != 0)
+               exfat_put_node(dir);
+       return rc;
 }
 
 void exfat_closedir(struct exfat_iterator* it)
 {
-       it->cluster = 0;
-       it->offset = 0;
-       it->contiguous = 0;
-       free(it->chunk);
-       it->chunk = NULL;
+       exfat_put_node(it->parent);
+       it->parent = NULL;
+       it->current = NULL;
 }
 
-/*
- * Reads one entry in directory at position pointed by iterator and fills
- * node structure.
- */
-int exfat_readdir(struct exfat* ef, const struct exfat_node* parent,
-               struct exfat_node** node, struct exfat_iterator* it)
+struct exfat_node* exfat_readdir(struct exfat* ef, struct exfat_iterator* it)
 {
-       const struct exfat_entry* entry;
-       const struct exfat_file* file;
-       const struct exfat_file_info* file_info;
-       const struct exfat_file_name* file_name;
-       const struct exfat_upcase* upcase;
-       const struct exfat_bitmap* bitmap;
-       const struct exfat_label* label;
-       uint8_t continuations = 0;
-       le16_t* namep = NULL;
-
-       *node = NULL;
-
-       if (it->chunk == NULL)
-       {
-               it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
-               if (it->chunk == NULL)
-               {
-                       exfat_error("out of memory");
-                       return -ENOMEM;
-               }
-               exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
-                               exfat_c2o(ef, it->cluster), ef->fd);
-       }
-
-       for (;;)
-       {
-               /* every directory (even empty one) occupies at least one cluster and
-                  must contain EOD entry */
-               entry = (const struct exfat_entry*)
-                               (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
-               /* move iterator to the next entry in the directory */
-               it->offset += sizeof(struct exfat_entry);
-
-               switch (entry->type)
-               {
-               case EXFAT_ENTRY_EOD:
-                       if (continuations != 0)
-                       {
-                               exfat_error("expected %hhu continuations before EOD",
-                                               continuations);
-                               goto error;
-                       }
-                       return -ENOENT; /* that's OK, means end of directory */
-
-               case EXFAT_ENTRY_FILE:
-                       if (continuations != 0)
-                       {
-                               exfat_error("expected %hhu continuations before new entry",
-                                               continuations);
-                               goto error;
-                       }
-                       file = (const struct exfat_file*) entry;
-                       continuations = file->continuations;
-                       /* each file entry must have at least 2 continuations:
-                          info and name */
-                       if (continuations < 2)
-                       {
-                               exfat_error("too few continuations (%hhu)", continuations);
-                               return -EIO;
-                       }
-                       *node = malloc(sizeof(struct exfat_node));
-                       if (*node == NULL)
-                       {
-                               exfat_error("failed to allocate node");
-                               return -ENOMEM;
-                       }
-                       memset(*node, 0, sizeof(struct exfat_node));
-                       (*node)->flags = le16_to_cpu(file->attrib);
-                       (*node)->mtime = exfat_exfat2unix(file->mdate, file->mtime);
-                       (*node)->atime = exfat_exfat2unix(file->adate, file->atime);
-                       namep = (*node)->name;
-                       break;
-
-               case EXFAT_ENTRY_FILE_INFO:
-                       if (continuations < 2)
-                       {
-                               exfat_error("unexpected continuation (%hhu)",
-                                               continuations);
-                               goto error;
-                       }
-                       file_info = (const struct exfat_file_info*) entry;
-                       (*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)
-                               (*node)->flags |= EXFAT_ATTRIB_CONTIGUOUS;
-                       --continuations;
-                       break;
-
-               case EXFAT_ENTRY_FILE_NAME:
-                       if (continuations == 0)
-                       {
-                               exfat_error("unexpected continuation");
-                               goto error;
-                       }
-                       file_name = (const struct exfat_file_name*) entry;
-                       memcpy(namep, file_name->name, EXFAT_ENAME_MAX * sizeof(le16_t));
-                       namep += EXFAT_ENAME_MAX;
-                       if (--continuations == 0)
-                               return 0; /* entry completed */
-                       break;
-
-               case EXFAT_ENTRY_UPCASE:
-                       if (ef->upcase != NULL)
-                               break;
-                       upcase = (const struct exfat_upcase*) entry;
-                       if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
-                       {
-                               exfat_error("invalid cluster in upcase table");
-                               return -EIO;
-                       }
-                       if (le64_to_cpu(upcase->size) == 0 ||
-                               le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
-                               le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0)
-                       {
-                               exfat_error("bad upcase table size (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
-                               return -EIO;
-                       }
-                       ef->upcase = malloc(le64_to_cpu(upcase->size));
-                       if (ef->upcase == NULL)
-                       {
-                               exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
-                               return -ENOMEM;
-                       }
-                       ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
-
-                       exfat_read_raw(ef->upcase, le64_to_cpu(upcase->size),
-                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster)), ef->fd);
-                       break;
-
-               case EXFAT_ENTRY_BITMAP:
-                       bitmap = (const struct exfat_bitmap*) entry;
-                       if (CLUSTER_INVALID(le32_to_cpu(bitmap->start_cluster)))
-                       {
-                               exfat_error("invalid cluster in clusters bitmap");
-                               return -EIO;
-                       }
-                       if (le64_to_cpu(bitmap->size) !=
-                                       ((le32_to_cpu(ef->sb->cluster_count) + 7) / 8))
-                       {
-                               exfat_error("invalid bitmap size: %"PRIu64" (expected %u)",
-                                               le64_to_cpu(bitmap->size),
-                                               (le32_to_cpu(ef->sb->cluster_count) + 7) / 8);
-                               return -EIO;
-                       }
-                       break;
-
-               case EXFAT_ENTRY_LABEL:
-                       label = (const struct exfat_label*) entry;
-                       if (label->length > EXFAT_ENAME_MAX)
-                       {
-                               exfat_error("too long label (%hhu chars)", label->length);
-                               return -EIO;
-                       }
-                       break;
-
-               default:
-                       if (entry->type & EXFAT_ENTRY_VALID)
-                       {
-                               exfat_error("unknown entry type 0x%hhu", entry->type);
-                               goto error;
-                       }
-                       break;
-               }
-
-               /* fetch the next cluster if needed */
-               if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
-               {
-                       it->cluster = exfat_next_cluster(ef, it->cluster, it->contiguous);
-                       if (CLUSTER_INVALID(it->cluster))
-                       {
-                               exfat_error("invalid cluster while reading directory");
-                               goto error;
-                       }
-                       exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
-                                       exfat_c2o(ef, it->cluster), ef->fd);
-               }
-       }
-       /* we never reach here */
+       if (it->current == NULL)
+               it->current = it->parent->child;
+       else
+               it->current = it->current->next;
 
-error:
-       if (*node != NULL)
-       {
-               free(*node);
-               *node = NULL;
-       }
-       return -EIO;
+       if (it->current != NULL)
+               return exfat_get_node(it->current);
+       else
+               return NULL;
 }
 
 static int compare_char(struct exfat* ef, uint16_t a, uint16_t b)
@@ -254,7 +67,7 @@ static int compare_name(struct exfat* ef, const le16_t* a, const le16_t* b)
        return compare_char(ef, le16_to_cpu(*a), le16_to_cpu(*b));
 }
 
-static int lookup_name(struct exfat* ef, const struct exfat_node* parent,
+static int lookup_name(struct exfat* ef, struct exfat_node* parent,
                struct exfat_node** node, const char* name, size_t n)
 {
        struct exfat_iterator it;
@@ -265,8 +78,10 @@ static int lookup_name(struct exfat* ef, const struct exfat_node* parent,
        if (rc != 0)
                return rc;
 
-       exfat_opendir(parent, &it);
-       while (exfat_readdir(ef, parent, node, &it) == 0)
+       rc = exfat_opendir(ef, parent, &it);
+       if (rc != 0)
+               return rc;
+       while ((*node = exfat_readdir(ef, &it)))
        {
                if (compare_name(ef, buffer, (*node)->name) == 0)
                {
@@ -298,28 +113,17 @@ int exfat_lookup(struct exfat* ef, struct exfat_node** node,
        const char* p;
        size_t n;
 
-       parent = *node = malloc(sizeof(struct exfat_node));
-       if (parent == NULL)
-       {
-               exfat_error("failed to allocate root node");
-               return -ENOMEM;
-       }
-
        /* start from the root directory */
-       parent->flags = EXFAT_ATTRIB_DIR;
-       parent->size = ef->rootdir_size;
-       parent->start_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
-       parent->name[0] = cpu_to_le16('\0');
-       /* exFAT does not have time attributes for the root directory */
-       parent->mtime = 0;
-       parent->atime = 0;
-
+       parent = *node = exfat_get_node(ef->root);
        for (p = path; (n = get_comp(p, &p)); p += n)
        {
                if (n == 1 && *p == '.')                                /* skip "." component */
                        continue;
                if (lookup_name(ef, parent, node, p, n) != 0)
+               {
+                       exfat_put_node(parent);
                        return -ENOENT;
+               }
                exfat_put_node(parent);
                parent = *node;
        }
diff --git a/libexfat/node.c b/libexfat/node.c
new file mode 100644 (file)
index 0000000..5a05255
--- /dev/null
@@ -0,0 +1,322 @@
+/*
+ *  node.c
+ *  exFAT file system implementation library.
+ *
+ *  Created by Andrew Nayenko on 09.10.09.
+ *  This software is distributed under the GNU General Public License 
+ *  version 3 or any later.
+ */
+
+#include "exfat.h"
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+
+/* on-disk nodes iterator */
+struct iterator
+{
+       cluster_t cluster;
+       off_t offset;
+       int contiguous;
+       char* chunk;
+};
+
+struct exfat_node* exfat_get_node(struct exfat_node* node)
+{
+       /* if we switch to multi-threaded mode we will need atomic
+          increment here and atomic decrement in exfat_put_node() */
+       node->references++;
+       return node;
+}
+
+void exfat_put_node(struct exfat_node* node)
+{
+       if (--node->references < 0)
+       {
+               char buffer[EXFAT_NAME_MAX + 1];
+               exfat_get_name(node, buffer, EXFAT_NAME_MAX);
+               exfat_bug("reference counter of `%s' is below zero", buffer);
+       }
+}
+
+static void opendir(const struct exfat_node* dir, struct iterator* it)
+{
+       if (!(dir->flags & EXFAT_ATTRIB_DIR))
+               exfat_bug("`%s' is not a directory", dir->name);
+       it->cluster = dir->start_cluster;
+       it->offset = 0;
+       it->contiguous = IS_CONTIGUOUS(*dir);
+       it->chunk = NULL;
+}
+
+static void closedir(struct iterator* it)
+{
+       it->cluster = 0;
+       it->offset = 0;
+       it->contiguous = 0;
+       free(it->chunk);
+       it->chunk = NULL;
+}
+
+/*
+ * Reads one entry in directory at position pointed by iterator and fills
+ * node structure.
+ */
+static int readdir(struct exfat* ef, const struct exfat_node* parent,
+               struct exfat_node** node, struct iterator* it)
+{
+       const struct exfat_entry* entry;
+       const struct exfat_file* file;
+       const struct exfat_file_info* file_info;
+       const struct exfat_file_name* file_name;
+       const struct exfat_upcase* upcase;
+       const struct exfat_bitmap* bitmap;
+       const struct exfat_label* label;
+       uint8_t continuations = 0;
+       le16_t* namep = NULL;
+
+       *node = NULL;
+
+       if (it->chunk == NULL)
+       {
+               it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
+               if (it->chunk == NULL)
+               {
+                       exfat_error("out of memory");
+                       return -ENOMEM;
+               }
+               exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
+                               exfat_c2o(ef, it->cluster), ef->fd);
+       }
+
+       for (;;)
+       {
+               /* every directory (even empty one) occupies at least one cluster and
+                  must contain EOD entry */
+               entry = (const struct exfat_entry*)
+                               (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
+               /* move iterator to the next entry in the directory */
+               it->offset += sizeof(struct exfat_entry);
+
+               switch (entry->type)
+               {
+               case EXFAT_ENTRY_EOD:
+                       if (continuations != 0)
+                       {
+                               exfat_error("expected %hhu continuations before EOD",
+                                               continuations);
+                               goto error;
+                       }
+                       return -ENOENT; /* that's OK, means end of directory */
+
+               case EXFAT_ENTRY_FILE:
+                       if (continuations != 0)
+                       {
+                               exfat_error("expected %hhu continuations before new entry",
+                                               continuations);
+                               goto error;
+                       }
+                       file = (const struct exfat_file*) entry;
+                       continuations = file->continuations;
+                       /* each file entry must have at least 2 continuations:
+                          info and name */
+                       if (continuations < 2)
+                       {
+                               exfat_error("too few continuations (%hhu)", continuations);
+                               return -EIO;
+                       }
+                       *node = malloc(sizeof(struct exfat_node));
+                       if (*node == NULL)
+                       {
+                               exfat_error("failed to allocate node");
+                               return -ENOMEM;
+                       }
+                       memset(*node, 0, sizeof(struct exfat_node));
+                       /* new node has zero reference counter */
+                       (*node)->flags = le16_to_cpu(file->attrib);
+                       (*node)->mtime = exfat_exfat2unix(file->mdate, file->mtime);
+                       (*node)->atime = exfat_exfat2unix(file->adate, file->atime);
+                       namep = (*node)->name;
+                       break;
+
+               case EXFAT_ENTRY_FILE_INFO:
+                       if (continuations < 2)
+                       {
+                               exfat_error("unexpected continuation (%hhu)",
+                                               continuations);
+                               goto error;
+                       }
+                       file_info = (const struct exfat_file_info*) entry;
+                       (*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)
+                               (*node)->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+                       --continuations;
+                       break;
+
+               case EXFAT_ENTRY_FILE_NAME:
+                       if (continuations == 0)
+                       {
+                               exfat_error("unexpected continuation");
+                               goto error;
+                       }
+                       file_name = (const struct exfat_file_name*) entry;
+                       memcpy(namep, file_name->name, EXFAT_ENAME_MAX * sizeof(le16_t));
+                       namep += EXFAT_ENAME_MAX;
+                       if (--continuations == 0)
+                               return 0; /* entry completed */
+                       break;
+
+               case EXFAT_ENTRY_UPCASE:
+                       if (ef->upcase != NULL)
+                               break;
+                       upcase = (const struct exfat_upcase*) entry;
+                       if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
+                       {
+                               exfat_error("invalid cluster in upcase table");
+                               return -EIO;
+                       }
+                       if (le64_to_cpu(upcase->size) == 0 ||
+                               le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
+                               le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0)
+                       {
+                               exfat_error("bad upcase table size (%"PRIu64" bytes)",
+                                               le64_to_cpu(upcase->size));
+                               return -EIO;
+                       }
+                       ef->upcase = malloc(le64_to_cpu(upcase->size));
+                       if (ef->upcase == NULL)
+                       {
+                               exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
+                                               le64_to_cpu(upcase->size));
+                               return -ENOMEM;
+                       }
+                       ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
+
+                       exfat_read_raw(ef->upcase, le64_to_cpu(upcase->size),
+                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster)), ef->fd);
+                       break;
+
+               case EXFAT_ENTRY_BITMAP:
+                       bitmap = (const struct exfat_bitmap*) entry;
+                       if (CLUSTER_INVALID(le32_to_cpu(bitmap->start_cluster)))
+                       {
+                               exfat_error("invalid cluster in clusters bitmap");
+                               return -EIO;
+                       }
+                       if (le64_to_cpu(bitmap->size) !=
+                                       ((le32_to_cpu(ef->sb->cluster_count) + 7) / 8))
+                       {
+                               exfat_error("invalid bitmap size: %"PRIu64" (expected %u)",
+                                               le64_to_cpu(bitmap->size),
+                                               (le32_to_cpu(ef->sb->cluster_count) + 7) / 8);
+                               return -EIO;
+                       }
+                       break;
+
+               case EXFAT_ENTRY_LABEL:
+                       label = (const struct exfat_label*) entry;
+                       if (label->length > EXFAT_ENAME_MAX)
+                       {
+                               exfat_error("too long label (%hhu chars)", label->length);
+                               return -EIO;
+                       }
+                       break;
+
+               default:
+                       if (entry->type & EXFAT_ENTRY_VALID)
+                       {
+                               exfat_error("unknown entry type 0x%hhu", entry->type);
+                               goto error;
+                       }
+                       break;
+               }
+
+               /* fetch the next cluster if needed */
+               if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
+               {
+                       it->cluster = exfat_next_cluster(ef, it->cluster, it->contiguous);
+                       if (CLUSTER_INVALID(it->cluster))
+                       {
+                               exfat_error("invalid cluster while reading directory");
+                               goto error;
+                       }
+                       exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
+                                       exfat_c2o(ef, it->cluster), ef->fd);
+               }
+       }
+       /* we never reach here */
+
+error:
+       free(*node);
+       *node = NULL;
+       return -EIO;
+}
+
+int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
+{
+       struct iterator it;
+       int rc;
+       struct exfat_node* node;
+       struct exfat_node* current = NULL;
+
+       if (dir->flags & EXFAT_ATTRIB_CACHED)
+               return 0; /* already cached */
+
+       opendir(dir, &it);
+       while ((rc = readdir(ef, dir, &node, &it)) == 0)
+       {
+               if (current != NULL)
+               {
+                       current->next = node;
+                       node->prev = current;
+               }
+               else
+                       dir->child = node;
+
+               current = node;
+       }
+       closedir(&it);
+
+       if (rc != -ENOENT)
+       {
+               /* rollback */
+               for (current = dir->child; current; current = node)
+               {
+                       node = current->next;
+                       free(current);
+               }
+               dir->child = NULL;
+               return rc;
+       }
+
+       dir->flags |= EXFAT_ATTRIB_CACHED;
+       return 0;
+}
+
+static void reset_cache(struct exfat_node* node)
+{
+       struct exfat_node* child;
+       struct exfat_node* next;
+
+       for (child = node->child; child; child = next)
+       {
+               reset_cache(child);
+               next = child->next;
+               free(child);
+       }
+       if (node->references != 0)
+       {
+               char buffer[EXFAT_NAME_MAX + 1];
+               exfat_get_name(node, buffer, EXFAT_NAME_MAX);
+               exfat_warn("non-zero reference counter (%d) for `%s'",
+                               node->references, buffer);
+       }
+       node->child = NULL;
+       node->flags &= ~EXFAT_ATTRIB_CACHED;
+}
+
+void exfat_reset_cache(struct exfat* ef)
+{
+       reset_cache(ef->root);
+}
index d526f4f..1d6ebd1 100644 (file)
@@ -64,11 +64,33 @@ int exfat_mount(struct exfat* ef, const char* spec)
        ef->upcase_chars = 0;
        ef->rootdir_size = rootdir_size(ef);
 
+       ef->root = malloc(sizeof(struct exfat_node));
+       if (ef->root == NULL)
+       {
+               close(ef->fd);
+               free(ef->sb);
+               exfat_error("failed to allocate root node");
+               return -ENOMEM;
+       }
+       memset(ef->root, 0, sizeof(struct exfat_node));
+       ef->root->flags = EXFAT_ATTRIB_DIR;
+       ef->root->size = ef->rootdir_size;
+       ef->root->start_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
+       ef->root->name[0] = cpu_to_le16('\0');
+       /* exFAT does not have time attributes for the root directory */
+       ef->root->mtime = 0;
+       ef->root->atime = 0;
+       /* always keep at least 1 reference to the root node */
+       exfat_get_node(ef->root);
+
        return 0;
 }
 
 void exfat_unmount(struct exfat* ef)
 {
+       exfat_put_node(ef->root);
+       exfat_reset_cache(ef);
+       ef->root = NULL;
        close(ef->fd);
        ef->fd = 0;
        free(ef->sb);