]> git.sven.stormbind.net Git - sven/exfat-utils.git/blobdiff - libexfat/node.c
New upstream version 1.3.0
[sven/exfat-utils.git] / libexfat / node.c
index bc342b780d59292b905ba43a755350bbaf45d82d..ab1d7d6d04c7d36831c34c20dda7b0c5a4fade6e 100644 (file)
@@ -2,11 +2,12 @@
        node.c (09.10.09)
        exFAT file system implementation library.
 
        node.c (09.10.09)
        exFAT file system implementation library.
 
-       Copyright (C) 2010-2013  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2018  Andrew Nayenko
 
 
-       This program is free software: you can redistribute it and/or modify
+       This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        it under the terms of the GNU General Public License as published by
-       the Free Software Foundation, either version 3 of the License, or
+       the Free Software Foundation, either version 2 of the License, or
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
@@ -14,8 +15,9 @@
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
-       You should have received a copy of the GNU General Public License
-       along with this program.  If not, see <http://www.gnu.org/licenses/>.
+       You should have received a copy of the GNU General Public License along
+       with this program; if not, write to the Free Software Foundation, Inc.,
+       51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
 #include "exfat.h"
 */
 
 #include "exfat.h"
 #include <string.h>
 #include <inttypes.h>
 
 #include <string.h>
 #include <inttypes.h>
 
-/* on-disk nodes iterator */
-struct iterator
-{
-       cluster_t cluster;
-       off_t offset;
-       int contiguous;
-       char* chunk;
-};
+#define EXFAT_ENTRY_NONE (-1)
 
 struct exfat_node* exfat_get_node(struct exfat_node* node)
 {
 
 struct exfat_node* exfat_get_node(struct exfat_node* node)
 {
@@ -42,87 +37,84 @@ struct exfat_node* exfat_get_node(struct exfat_node* node)
 
 void exfat_put_node(struct exfat* ef, struct exfat_node* node)
 {
 
 void exfat_put_node(struct exfat* ef, struct exfat_node* node)
 {
-       if (--node->references < 0)
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
+       --node->references;
+       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);
+               exfat_get_name(node, buffer);
+               exfat_bug("reference counter of '%s' is below zero", buffer);
        }
        }
-
-       if (node->references == 0)
+       else if (node->references == 0 && node != ef->root)
        {
        {
-               if (node->flags & EXFAT_ATTRIB_DIRTY)
-                       exfat_flush_node(ef, node);
-               if (node->flags & EXFAT_ATTRIB_UNLINKED)
+               if (node->is_dirty)
                {
                {
-                       /* free all clusters and node structure itself */
-                       exfat_truncate(ef, node, 0);
-                       free(node);
+                       exfat_get_name(node, buffer);
+                       exfat_warn("dirty node '%s' with zero references", buffer);
                }
                }
-               if (ef->cmap.dirty)
-                       exfat_flush_cmap(ef);
        }
 }
 
 /**
        }
 }
 
 /**
- * Cluster + offset from the beginning of the directory to absolute offset.
+ * This function must be called on rmdir and unlink (after the last
+ * exfat_put_node()) to free clusters.
  */
  */
-static off_t co2o(struct exfat* ef, cluster_t cluster, off_t offset)
+int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node)
 {
 {
-       return exfat_c2o(ef, cluster) + offset % CLUSTER_SIZE(*ef->sb);
-}
+       int rc = 0;
 
 
-static int opendir(struct exfat* ef, const struct exfat_node* dir,
-               struct iterator* it)
-{
-       if (!(dir->flags & EXFAT_ATTRIB_DIR))
-               exfat_bug("not a directory");
-       it->cluster = dir->start_cluster;
-       it->offset = 0;
-       it->contiguous = IS_CONTIGUOUS(*dir);
-       it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
-       if (it->chunk == NULL)
+       if (node->references != 0)
+               exfat_bug("unable to cleanup a node with %d references",
+                               node->references);
+
+       if (node->is_unlinked)
        {
        {
-               exfat_error("out of memory");
-               return -ENOMEM;
+               /* free all clusters and node structure itself */
+               rc = exfat_truncate(ef, node, 0, true);
+               /* free the node even in case of error or its memory will be lost */
+               free(node);
        }
        }
-       exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
-                       exfat_c2o(ef, it->cluster));
-       return 0;
+       return rc;
 }
 
 }
 
-static void closedir(struct iterator* it)
+static int read_entries(struct exfat* ef, struct exfat_node* dir,
+               struct exfat_entry* entries, int n, off_t offset)
 {
 {
-       it->cluster = 0;
-       it->offset = 0;
-       it->contiguous = 0;
-       free(it->chunk);
-       it->chunk = NULL;
+       ssize_t size;
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+               exfat_bug("attempted to read entries from a file");
+
+       size = exfat_generic_pread(ef, dir, entries,
+                       sizeof(struct exfat_entry[n]), offset);
+       if (size == sizeof(struct exfat_entry[n]))
+               return 0; /* success */
+       if (size == 0)
+               return -ENOENT;
+       if (size < 0)
+               return -EIO;
+       exfat_error("read %zd bytes instead of %zu bytes", size,
+                       sizeof(struct exfat_entry[n]));
+       return -EIO;
 }
 
 }
 
-static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
-               struct iterator* it)
+static int write_entries(struct exfat* ef, struct exfat_node* dir,
+               const struct exfat_entry* entries, int n, off_t offset)
 {
 {
-       /* move iterator to the next entry in the directory */
-       it->offset += sizeof(struct exfat_entry);
-       /* fetch the next cluster if needed */
-       if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
-       {
-               /* reached the end of directory; the caller should check this
-                  condition too */
-               if (it->offset >= parent->size)
-                       return 0;
-               it->cluster = exfat_next_cluster(ef, parent, it->cluster);
-               if (CLUSTER_INVALID(it->cluster))
-               {
-                       exfat_error("invalid cluster 0x%x while reading directory",
-                                       it->cluster);
-                       return 1;
-               }
-               exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
-                               exfat_c2o(ef, it->cluster));
-       }
-       return 0;
+       ssize_t size;
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+               exfat_bug("attempted to write entries into a file");
+
+       size = exfat_generic_pwrite(ef, dir, entries,
+                       sizeof(struct exfat_entry[n]), offset);
+       if (size == sizeof(struct exfat_entry[n]))
+               return 0; /* success */
+       if (size < 0)
+               return -EIO;
+       exfat_error("wrote %zd bytes instead of %zu bytes", size,
+                       sizeof(struct exfat_entry[n]));
+       return -EIO;
 }
 
 static struct exfat_node* allocate_node(void)
 }
 
 static struct exfat_node* allocate_node(void)
@@ -140,7 +132,8 @@ static struct exfat_node* allocate_node(void)
 static void init_node_meta1(struct exfat_node* node,
                const struct exfat_entry_meta1* meta1)
 {
 static void init_node_meta1(struct exfat_node* node,
                const struct exfat_entry_meta1* meta1)
 {
-       node->flags = le16_to_cpu(meta1->attrib);
+       node->attrib = le16_to_cpu(meta1->attrib);
+       node->continuations = meta1->continuations;
        node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime,
                        meta1->mtime_cs);
        /* there is no centiseconds field for atime */
        node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime,
                        meta1->mtime_cs);
        /* there is no centiseconds field for atime */
@@ -153,280 +146,396 @@ static void init_node_meta2(struct exfat_node* node,
        node->size = le64_to_cpu(meta2->size);
        node->start_cluster = le32_to_cpu(meta2->start_cluster);
        node->fptr_cluster = node->start_cluster;
        node->size = le64_to_cpu(meta2->size);
        node->start_cluster = le32_to_cpu(meta2->start_cluster);
        node->fptr_cluster = node->start_cluster;
-       if (meta2->flags & EXFAT_FLAG_CONTIGUOUS)
-               node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+       node->is_contiguous = ((meta2->flags & EXFAT_FLAG_CONTIGUOUS) != 0);
 }
 
 }
 
-static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
-               const struct iterator* it)
+static void init_node_name(struct exfat_node* node,
+               const struct exfat_entry* entries, int n)
 {
 {
-       return (const struct exfat_entry*)
-                       (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
+       int i;
+
+       for (i = 0; i < n; i++)
+               memcpy(node->name + i * EXFAT_ENAME_MAX,
+                               ((const struct exfat_entry_name*) &entries[i])->name,
+                               EXFAT_ENAME_MAX * sizeof(le16_t));
 }
 
 }
 
-/*
- * 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)
+static bool check_entries(const struct exfat_entry* entry, int n)
 {
 {
-       int rc = -EIO;
-       const struct exfat_entry* entry;
-       const struct exfat_entry_meta1* meta1;
-       const struct exfat_entry_meta2* meta2;
-       const struct exfat_entry_name* file_name;
-       const struct exfat_entry_upcase* upcase;
-       const struct exfat_entry_bitmap* bitmap;
-       const struct exfat_entry_label* label;
-       uint8_t continuations = 0;
-       le16_t* namep = NULL;
-       uint16_t reference_checksum = 0;
-       uint16_t actual_checksum = 0;
-       uint64_t real_size = 0;
-
-       *node = NULL;
+       int previous = EXFAT_ENTRY_NONE;
+       int current;
+       int i;
 
 
-       for (;;)
+       /* check transitions between entries types */
+       for (i = 0; i < n + 1; previous = current, i++)
        {
        {
-               if (it->offset >= parent->size)
-               {
-                       if (continuations != 0)
-                       {
-                               exfat_error("expected %hhu continuations", continuations);
-                               goto error;
-                       }
-                       return -ENOENT; /* that's OK, means end of directory */
-               }
+               bool valid = false;
 
 
-               entry = get_entry_ptr(ef, it);
-               switch (entry->type)
+               current = (i < n) ? entry[i].type : EXFAT_ENTRY_NONE;
+               switch (previous)
                {
                {
+               case EXFAT_ENTRY_NONE:
+                       valid = (current == EXFAT_ENTRY_FILE);
+                       break;
                case EXFAT_ENTRY_FILE:
                case EXFAT_ENTRY_FILE:
-                       if (continuations != 0)
-                       {
-                               exfat_error("expected %hhu continuations before new entry",
-                                               continuations);
-                               goto error;
-                       }
-                       meta1 = (const struct exfat_entry_meta1*) entry;
-                       continuations = meta1->continuations;
-                       /* each file entry must have at least 2 continuations:
-                          info and name */
-                       if (continuations < 2)
-                       {
-                               exfat_error("too few continuations (%hhu)", continuations);
-                               goto error;
-                       }
-                       reference_checksum = le16_to_cpu(meta1->checksum);
-                       actual_checksum = exfat_start_checksum(meta1);
-                       *node = allocate_node();
-                       if (*node == NULL)
-                       {
-                               rc = -ENOMEM;
-                               goto error;
-                       }
-                       /* new node has zero reference counter */
-                       (*node)->entry_cluster = it->cluster;
-                       (*node)->entry_offset = it->offset;
-                       init_node_meta1(*node, meta1);
-                       namep = (*node)->name;
+                       valid = (current == EXFAT_ENTRY_FILE_INFO);
                        break;
                        break;
-
                case EXFAT_ENTRY_FILE_INFO:
                case EXFAT_ENTRY_FILE_INFO:
-                       if (continuations < 2)
-                       {
-                               exfat_error("unexpected continuation (%hhu)",
-                                               continuations);
-                               goto error;
-                       }
-                       meta2 = (const struct exfat_entry_meta2*) entry;
-                       if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
-                       {
-                               exfat_error("unknown flags in meta2 (0x%hhx)", meta2->flags);
-                               goto error;
-                       }
-                       init_node_meta2(*node, meta2);
-                       actual_checksum = exfat_add_checksum(entry, actual_checksum);
-                       real_size = le64_to_cpu(meta2->real_size);
-                       /* empty files must be marked as non-contiguous */
-                       if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
-                       {
-                               exfat_error("empty file marked as contiguous (0x%hhx)",
-                                               meta2->flags);
-                               goto error;
-                       }
-                       /* directories must be aligned on at cluster boundary */
-                       if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
-                               (*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
-                       {
-                               exfat_error("directory has invalid size %"PRIu64" bytes",
-                                               (*node)->size);
-                               goto error;
-                       }
-                       --continuations;
+                       valid = (current == EXFAT_ENTRY_FILE_NAME);
                        break;
                        break;
-
                case EXFAT_ENTRY_FILE_NAME:
                case EXFAT_ENTRY_FILE_NAME:
-                       if (continuations == 0)
-                       {
-                               exfat_error("unexpected continuation");
-                               goto error;
-                       }
-                       file_name = (const struct exfat_entry_name*) entry;
-                       actual_checksum = exfat_add_checksum(entry, actual_checksum);
+                       valid = (current == EXFAT_ENTRY_FILE_NAME ||
+                                current == EXFAT_ENTRY_NONE ||
+                                current >= EXFAT_ENTRY_FILE_TAIL);
+                       break;
+               case EXFAT_ENTRY_FILE_TAIL ... 0xff:
+                       valid = (current >= EXFAT_ENTRY_FILE_TAIL ||
+                                current == EXFAT_ENTRY_NONE);
+                       break;
+               }
 
 
-                       memcpy(namep, file_name->name, EXFAT_ENAME_MAX * sizeof(le16_t));
-                       namep += EXFAT_ENAME_MAX;
-                       if (--continuations == 0)
-                       {
-                               /*
-                                  There are two fields that contain file size. Maybe they
-                                  plan to add compression support in the future and one of
-                                  those fields is visible (uncompressed) size and the other
-                                  is real (compressed) size. Anyway, currently it looks like
-                                  exFAT does not support compression and both fields must be
-                                  equal.
-
-                                  There is an exception though: pagefile.sys (its real_size
-                                  is always 0).
-                               */
-                               if (real_size != (*node)->size)
-                               {
-                                       char buffer[EXFAT_NAME_MAX + 1];
+               if (!valid)
+               {
+                       exfat_error("unexpected entry type %#x after %#x at %d/%d",
+                                       current, previous, i, n);
+                       return false;
+               }
+       }
+       return true;
+}
 
 
-                                       exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
-                                       exfat_error("`%s' real size does not equal to size "
-                                                       "(%"PRIu64" != %"PRIu64")", buffer,
-                                                       real_size, (*node)->size);
-                                       goto error;
-                               }
-                               if (actual_checksum != reference_checksum)
-                               {
-                                       char buffer[EXFAT_NAME_MAX + 1];
+static bool check_node(const struct exfat* ef, struct exfat_node* node,
+               le16_t actual_checksum, const struct exfat_entry_meta1* meta1,
+               const struct exfat_entry_meta2* meta2)
+{
+       int cluster_size = CLUSTER_SIZE(*ef->sb);
+       uint64_t clusters_heap_size =
+                       (uint64_t) le32_to_cpu(ef->sb->cluster_count) * cluster_size;
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+       bool ret = true;
+
+       /*
+          Validate checksum first. If it's invalid all other fields probably
+          contain just garbage.
+       */
+       if (le16_to_cpu(actual_checksum) != le16_to_cpu(meta1->checksum))
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
+                               le16_to_cpu(actual_checksum), le16_to_cpu(meta1->checksum));
+               if (!EXFAT_REPAIR(invalid_node_checksum, ef, node))
+                       ret = false;
+       }
 
 
-                                       exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
-                                       exfat_error("`%s' has invalid checksum (0x%hx != 0x%hx)",
-                                                       buffer, actual_checksum, reference_checksum);
-                                       goto error;
-                               }
-                               if (fetch_next_entry(ef, parent, it) != 0)
-                                       goto error;
-                               return 0; /* entry completed */
-                       }
-                       break;
+       /*
+          exFAT does not support sparse files but allows files with uninitialized
+          clusters. For such files valid_size means initialized data size and
+          cannot be greater than file size. See SetFileValidData() function
+          description in MSDN.
+       */
+       if (le64_to_cpu(meta2->valid_size) > node->size)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has valid size (%"PRIu64") greater than size "
+                               "(%"PRIu64")", buffer, le64_to_cpu(meta2->valid_size),
+                               node->size);
+               ret = false;
+       }
+
+       /*
+          Empty file must have zero start cluster. Non-empty file must start
+          with a valid cluster. Directories cannot be empty (i.e. must always
+          have a valid start cluster), but we will check this later while
+          reading that directory to give user a chance to read this directory.
+       */
+       if (node->size == 0 && node->start_cluster != EXFAT_CLUSTER_FREE)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but start cluster is %#x", buffer,
+                               node->start_cluster);
+               ret = false;
+       }
+       if (node->size > 0 && CLUSTER_INVALID(*ef->sb, node->start_cluster))
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' points to invalid cluster %#x", buffer,
+                               node->start_cluster);
+               ret = false;
+       }
+
+       /* File or directory cannot be larger than clusters heap. */
+       if (node->size > clusters_heap_size)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is larger than clusters heap: %"PRIu64" > %"PRIu64,
+                               buffer, node->size, clusters_heap_size);
+               ret = false;
+       }
+
+       /* Empty file or directory must be marked as non-contiguous. */
+       if (node->size == 0 && node->is_contiguous)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but marked as contiguous (%#hx)", buffer,
+                               node->attrib);
+               ret = false;
+       }
+
+       /* Directory size must be aligned on at cluster boundary. */
+       if ((node->attrib & EXFAT_ATTRIB_DIR) && node->size % cluster_size != 0)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' directory size %"PRIu64" is not divisible by %d", buffer,
+                               node->size, cluster_size);
+               ret = false;
+       }
+
+       return ret;
+}
+
+static int parse_file_entries(struct exfat* ef, struct exfat_node* node,
+               const struct exfat_entry* entries, int n)
+{
+       const struct exfat_entry_meta1* meta1;
+       const struct exfat_entry_meta2* meta2;
+       int mandatory_entries;
+
+       if (!check_entries(entries, n))
+               return -EIO;
+
+       meta1 = (const struct exfat_entry_meta1*) &entries[0];
+       if (meta1->continuations < 2)
+       {
+               exfat_error("too few continuations (%hhu)", meta1->continuations);
+               return -EIO;
+       }
+       meta2 = (const struct exfat_entry_meta2*) &entries[1];
+       if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
+       {
+               exfat_error("unknown flags in meta2 (%#hhx)", meta2->flags);
+               return -EIO;
+       }
+       mandatory_entries = 2 + DIV_ROUND_UP(meta2->name_length, EXFAT_ENAME_MAX);
+       if (meta1->continuations < mandatory_entries - 1)
+       {
+               exfat_error("too few continuations (%hhu < %d)",
+                               meta1->continuations, mandatory_entries - 1);
+               return -EIO;
+       }
+
+       init_node_meta1(node, meta1);
+       init_node_meta2(node, meta2);
+       init_node_name(node, entries + 2, mandatory_entries - 2);
+
+       if (!check_node(ef, node, exfat_calc_checksum(entries, n), meta1, meta2))
+               return -EIO;
+
+       return 0;
+}
+
+static int parse_file_entry(struct exfat* ef, struct exfat_node* parent,
+               struct exfat_node** node, off_t* offset, int n)
+{
+       struct exfat_entry entries[n];
+       int rc;
+
+       rc = read_entries(ef, parent, entries, n, *offset);
+       if (rc != 0)
+               return rc;
+
+       /* a new node has zero references */
+       *node = allocate_node();
+       if (*node == NULL)
+               return -ENOMEM;
+       (*node)->entry_offset = *offset;
+
+       rc = parse_file_entries(ef, *node, entries, n);
+       if (rc != 0)
+       {
+               free(*node);
+               return rc;
+       }
+
+       *offset += sizeof(struct exfat_entry[n]);
+       return 0;
+}
+
+static void decompress_upcase(uint16_t* output, const le16_t* source,
+               size_t size)
+{
+       size_t si;
+       size_t oi;
+
+       for (oi = 0; oi < EXFAT_UPCASE_CHARS; oi++)
+               output[oi] = oi;
+
+       for (si = 0, oi = 0; si < size && oi < EXFAT_UPCASE_CHARS; si++)
+       {
+               uint16_t ch = le16_to_cpu(source[si]);
+
+               if (ch == 0xffff && si + 1 < size)      /* indicates a run */
+                       oi += le16_to_cpu(source[++si]);
+               else
+                       output[oi++] = ch;
+       }
+}
+
+/*
+ * Read one entry in a directory at offset position and build a new node
+ * structure.
+ */
+static int readdir(struct exfat* ef, struct exfat_node* parent,
+               struct exfat_node** node, off_t* offset)
+{
+       int rc;
+       struct exfat_entry entry;
+       const struct exfat_entry_meta1* meta1;
+       const struct exfat_entry_upcase* upcase;
+       const struct exfat_entry_bitmap* bitmap;
+       const struct exfat_entry_label* label;
+       uint64_t upcase_size = 0;
+       le16_t* upcase_comp = NULL;
+
+       for (;;)
+       {
+               rc = read_entries(ef, parent, &entry, 1, *offset);
+               if (rc != 0)
+                       return rc;
+
+               switch (entry.type)
+               {
+               case EXFAT_ENTRY_FILE:
+                       meta1 = (const struct exfat_entry_meta1*) &entry;
+                       return parse_file_entry(ef, parent, node, offset,
+                                       1 + meta1->continuations);
 
                case EXFAT_ENTRY_UPCASE:
                        if (ef->upcase != NULL)
                                break;
 
                case EXFAT_ENTRY_UPCASE:
                        if (ef->upcase != NULL)
                                break;
-                       upcase = (const struct exfat_entry_upcase*) entry;
-                       if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
+                       upcase = (const struct exfat_entry_upcase*) &entry;
+                       if (CLUSTER_INVALID(*ef->sb, le32_to_cpu(upcase->start_cluster)))
                        {
                                exfat_error("invalid cluster 0x%x in upcase table",
                                                le32_to_cpu(upcase->start_cluster));
                        {
                                exfat_error("invalid cluster 0x%x in upcase table",
                                                le32_to_cpu(upcase->start_cluster));
-                               goto error;
+                               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)
+                       upcase_size = le64_to_cpu(upcase->size);
+                       if (upcase_size == 0 ||
+                               upcase_size > EXFAT_UPCASE_CHARS * sizeof(uint16_t) ||
+                               upcase_size % sizeof(uint16_t) != 0)
                        {
                                exfat_error("bad upcase table size (%"PRIu64" bytes)",
                        {
                                exfat_error("bad upcase table size (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
-                               goto error;
+                                               upcase_size);
+                               return -EIO;
                        }
                        }
-                       ef->upcase = malloc(le64_to_cpu(upcase->size));
-                       if (ef->upcase == NULL)
+                       upcase_comp = malloc(upcase_size);
+                       if (upcase_comp == NULL)
                        {
                                exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
                        {
                                exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
-                               rc = -ENOMEM;
-                               goto error;
+                                               upcase_size);
+                               return -ENOMEM;
                        }
                        }
-                       ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
 
 
-                       exfat_pread(ef->dev, ef->upcase, le64_to_cpu(upcase->size),
-                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster)));
+                       /* read compressed upcase table */
+                       if (exfat_pread(ef->dev, upcase_comp, upcase_size,
+                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0)
+                       {
+                               free(upcase_comp);
+                               exfat_error("failed to read upper case table "
+                                               "(%"PRIu64" bytes starting at cluster %#x)",
+                                               upcase_size,
+                                               le32_to_cpu(upcase->start_cluster));
+                               return -EIO;
+                       }
+
+                       /* decompress upcase table */
+                       ef->upcase = calloc(EXFAT_UPCASE_CHARS, sizeof(uint16_t));
+                       if (ef->upcase == NULL)
+                       {
+                               free(upcase_comp);
+                               exfat_error("failed to allocate decompressed upcase table");
+                               return -ENOMEM;
+                       }
+                       decompress_upcase(ef->upcase, upcase_comp,
+                                       upcase_size / sizeof(uint16_t));
+                       free(upcase_comp);
                        break;
 
                case EXFAT_ENTRY_BITMAP:
                        break;
 
                case EXFAT_ENTRY_BITMAP:
-                       bitmap = (const struct exfat_entry_bitmap*) entry;
+                       bitmap = (const struct exfat_entry_bitmap*) &entry;
                        ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
                        ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
-                       if (CLUSTER_INVALID(ef->cmap.start_cluster))
+                       if (CLUSTER_INVALID(*ef->sb, ef->cmap.start_cluster))
                        {
                                exfat_error("invalid cluster 0x%x in clusters bitmap",
                                                ef->cmap.start_cluster);
                        {
                                exfat_error("invalid cluster 0x%x in clusters bitmap",
                                                ef->cmap.start_cluster);
-                               goto error;
+                               return -EIO;
                        }
                        }
-                       ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
-                               EXFAT_FIRST_DATA_CLUSTER;
-                       if (le64_to_cpu(bitmap->size) < (ef->cmap.size + 7) / 8)
+                       ef->cmap.size = le32_to_cpu(ef->sb->cluster_count);
+                       if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8))
                        {
                                exfat_error("invalid clusters bitmap size: %"PRIu64
                                                " (expected at least %u)",
                        {
                                exfat_error("invalid clusters bitmap size: %"PRIu64
                                                " (expected at least %u)",
-                                               le64_to_cpu(bitmap->size), (ef->cmap.size + 7) / 8);
-                               goto error;
+                                               le64_to_cpu(bitmap->size),
+                                               DIV_ROUND_UP(ef->cmap.size, 8));
+                               return -EIO;
                        }
                        /* FIXME bitmap can be rather big, up to 512 MB */
                        ef->cmap.chunk_size = ef->cmap.size;
                        }
                        /* 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));
+                       ef->cmap.chunk = malloc(BMAP_SIZE(ef->cmap.chunk_size));
                        if (ef->cmap.chunk == NULL)
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
                                                "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
                        if (ef->cmap.chunk == NULL)
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
                                                "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
-                               rc = -ENOMEM;
-                               goto error;
+                               return -ENOMEM;
                        }
 
                        }
 
-                       exfat_pread(ef->dev, ef->cmap.chunk, le64_to_cpu(bitmap->size),
-                                       exfat_c2o(ef, ef->cmap.start_cluster));
+                       if (exfat_pread(ef->dev, ef->cmap.chunk,
+                                       BMAP_SIZE(ef->cmap.chunk_size),
+                                       exfat_c2o(ef, ef->cmap.start_cluster)) < 0)
+                       {
+                               exfat_error("failed to read clusters bitmap "
+                                               "(%"PRIu64" bytes starting at cluster %#x)",
+                                               le64_to_cpu(bitmap->size), ef->cmap.start_cluster);
+                               return -EIO;
+                       }
                        break;
 
                case EXFAT_ENTRY_LABEL:
                        break;
 
                case EXFAT_ENTRY_LABEL:
-                       label = (const struct exfat_entry_label*) entry;
+                       label = (const struct exfat_entry_label*) &entry;
                        if (label->length > EXFAT_ENAME_MAX)
                        {
                                exfat_error("too long label (%hhu chars)", label->length);
                        if (label->length > EXFAT_ENAME_MAX)
                        {
                                exfat_error("too long label (%hhu chars)", label->length);
-                               goto error;
+                               return -EIO;
                        }
                        if (utf16_to_utf8(ef->label, label->name,
                                                sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
                        }
                        if (utf16_to_utf8(ef->label, label->name,
                                                sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
-                               goto error;
+                               return -EIO;
                        break;
 
                default:
                        break;
 
                default:
-                       if (entry->type & EXFAT_ENTRY_VALID)
-                       {
-                               exfat_error("unknown entry type 0x%hhx", entry->type);
-                               goto error;
-                       }
-                       break;
-               }
+                       if (!(entry.type & EXFAT_ENTRY_VALID))
+                               break; /* deleted entry, ignore it */
 
 
-               if (fetch_next_entry(ef, parent, it) != 0)
-                       goto error;
+                       exfat_error("unknown entry type %#hhx", entry.type);
+                       if (!EXFAT_REPAIR(unknown_entry, ef, parent, &entry, *offset))
+                               return -EIO;
+               }
+               *offset += sizeof(entry);
        }
        /* we never reach here */
        }
        /* we never reach here */
-
-error:
-       free(*node);
-       *node = NULL;
-       return rc;
 }
 
 int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
 {
 }
 
 int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
 {
-       struct iterator it;
+       off_t offset = 0;
        int rc;
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
        int rc;
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
-       if (dir->flags & EXFAT_ATTRIB_CACHED)
+       if (dir->is_cached)
                return 0; /* already cached */
 
                return 0; /* already cached */
 
-       rc = opendir(ef, dir, &it);
-       if (rc != 0)
-               return rc;
-       while ((rc = readdir(ef, dir, &node, &it)) == 0)
+       while ((rc = readdir(ef, dir, &node, &offset)) == 0)
        {
                node->parent = dir;
                if (current != NULL)
        {
                node->parent = dir;
                if (current != NULL)
@@ -439,7 +548,6 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
 
                current = node;
        }
 
                current = node;
        }
-       closedir(&it);
 
        if (rc != -ENOENT)
        {
 
        if (rc != -ENOENT)
        {
@@ -453,7 +561,7 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
                return rc;
        }
 
                return rc;
        }
 
-       dir->flags |= EXFAT_ATTRIB_CACHED;
+       dir->is_cached = true;
        return 0;
 }
 
        return 0;
 }
 
@@ -483,6 +591,8 @@ static void tree_detach(struct exfat_node* node)
 
 static void reset_cache(struct exfat* ef, struct exfat_node* node)
 {
 
 static void reset_cache(struct exfat* ef, struct exfat_node* node)
 {
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
        while (node->child)
        {
                struct exfat_node* p = node->child;
        while (node->child)
        {
                struct exfat_node* p = node->child;
@@ -490,14 +600,18 @@ static void reset_cache(struct exfat* ef, struct exfat_node* node)
                tree_detach(p);
                free(p);
        }
                tree_detach(p);
                free(p);
        }
-       node->flags &= ~EXFAT_ATTRIB_CACHED;
+       node->is_cached = false;
        if (node->references != 0)
        {
        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'",
+               exfat_get_name(node, buffer);
+               exfat_warn("non-zero reference counter (%d) for '%s'",
                                node->references, buffer);
        }
                                node->references, buffer);
        }
+       if (node != ef->root && node->is_dirty)
+       {
+               exfat_get_name(node, buffer);
+               exfat_bug("node '%s' is dirty", buffer);
+       }
        while (node->references)
                exfat_put_node(ef, node);
 }
        while (node->references)
                exfat_put_node(ef, node);
 }
@@ -507,81 +621,81 @@ void exfat_reset_cache(struct exfat* ef)
        reset_cache(ef, ef->root);
 }
 
        reset_cache(ef, ef->root);
 }
 
-void next_entry(struct exfat* ef, const struct exfat_node* parent,
-               cluster_t* cluster, off_t* offset)
+int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
 {
 {
-       *offset += sizeof(struct exfat_entry);
-       if (*offset % CLUSTER_SIZE(*ef->sb) == 0)
-               /* next cluster cannot be invalid */
-               *cluster = exfat_next_cluster(ef, parent, *cluster);
-}
+       struct exfat_entry entries[1 + node->continuations];
+       struct exfat_entry_meta1* meta1 = (struct exfat_entry_meta1*) &entries[0];
+       struct exfat_entry_meta2* meta2 = (struct exfat_entry_meta2*) &entries[1];
+       int rc;
 
 
-void exfat_flush_node(struct exfat* ef, struct exfat_node* node)
-{
-       cluster_t cluster;
-       off_t offset;
-       off_t meta1_offset, meta2_offset;
-       struct exfat_entry_meta1 meta1;
-       struct exfat_entry_meta2 meta2;
+       if (!node->is_dirty)
+               return 0; /* no need to flush */
 
        if (ef->ro)
                exfat_bug("unable to flush node to read-only FS");
 
        if (node->parent == NULL)
 
        if (ef->ro)
                exfat_bug("unable to flush node to read-only FS");
 
        if (node->parent == NULL)
-               return; /* do not flush unlinked node */
-
-       cluster = node->entry_cluster;
-       offset = node->entry_offset;
-       meta1_offset = co2o(ef, cluster, offset);
-       next_entry(ef, node->parent, &cluster, &offset);
-       meta2_offset = co2o(ef, cluster, offset);
-
-       exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset);
-       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, &meta1.mtime_cs);
-       exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime, NULL);
-
-       exfat_pread(ef->dev, &meta2, sizeof(meta2), meta2_offset);
-       if (meta2.type != EXFAT_ENTRY_FILE_INFO)
-               exfat_bug("invalid type of meta2: 0x%hhx", meta2.type);
-       meta2.size = meta2.real_size = cpu_to_le64(node->size);
-       meta2.start_cluster = cpu_to_le32(node->start_cluster);
-       meta2.flags = EXFAT_FLAG_ALWAYS1;
+               return 0; /* do not flush unlinked node */
+
+       rc = read_entries(ef, node->parent, entries, 1 + node->continuations,
+                       node->entry_offset);
+       if (rc != 0)
+               return rc;
+       if (!check_entries(entries, 1 + node->continuations))
+               return -EIO;
+
+       meta1->attrib = cpu_to_le16(node->attrib);
+       exfat_unix2exfat(node->mtime, &meta1->mdate, &meta1->mtime,
+                       &meta1->mtime_cs);
+       exfat_unix2exfat(node->atime, &meta1->adate, &meta1->atime, NULL);
+       meta2->size = meta2->valid_size = cpu_to_le64(node->size);
+       meta2->start_cluster = cpu_to_le32(node->start_cluster);
+       meta2->flags = EXFAT_FLAG_ALWAYS1;
        /* empty files must not be marked as contiguous */
        /* empty files must not be marked as contiguous */
-       if (node->size != 0 && IS_CONTIGUOUS(*node))
-               meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
+       if (node->size != 0 && node->is_contiguous)
+               meta2->flags |= EXFAT_FLAG_CONTIGUOUS;
        /* name hash remains unchanged, no need to recalculate it */
 
        /* name hash remains unchanged, no need to recalculate it */
 
-       meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
-
-       exfat_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset);
-       exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset);
+       meta1->checksum = exfat_calc_checksum(entries, 1 + node->continuations);
+       rc = write_entries(ef, node->parent, entries, 1 + node->continuations,
+                       node->entry_offset);
+       if (rc != 0)
+               return rc;
 
 
-       node->flags &= ~EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = false;
+       return exfat_flush(ef);
 }
 
 }
 
-static void erase_entry(struct exfat* ef, struct exfat_node* node)
+static int erase_entries(struct exfat* ef, struct exfat_node* dir, int n,
+               off_t offset)
 {
 {
-       cluster_t cluster = node->entry_cluster;
-       off_t offset = node->entry_offset;
-       int name_entries = DIV_ROUND_UP(utf16_length(node->name), EXFAT_ENAME_MAX);
-       uint8_t entry_type;
+       struct exfat_entry entries[n];
+       int rc;
+       int i;
 
 
-       entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID;
-       exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset));
+       rc = read_entries(ef, dir, entries, n, offset);
+       if (rc != 0)
+               return rc;
+       for (i = 0; i < n; i++)
+               entries[i].type &= ~EXFAT_ENTRY_VALID;
+       return write_entries(ef, dir, entries, n, offset);
+}
 
 
-       next_entry(ef, node->parent, &cluster, &offset);
-       entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID;
-       exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset));
+static int erase_node(struct exfat* ef, struct exfat_node* node)
+{
+       int rc;
 
 
-       while (name_entries--)
+       exfat_get_node(node->parent);
+       rc = erase_entries(ef, node->parent, 1 + node->continuations,
+                       node->entry_offset);
+       if (rc != 0)
        {
        {
-               next_entry(ef, node->parent, &cluster, &offset);
-               entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
-               exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset));
+               exfat_put_node(ef, node->parent);
+               return rc;
        }
        }
+       rc = exfat_flush_node(ef, node->parent);
+       exfat_put_node(ef, node->parent);
+       return rc;
 }
 
 static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
@@ -591,11 +705,10 @@ static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
        const struct exfat_node* last_node;
        uint64_t entries = 0;
        uint64_t new_size;
        const struct exfat_node* last_node;
        uint64_t entries = 0;
        uint64_t new_size;
-       int rc;
 
 
-       if (!(dir->flags & EXFAT_ATTRIB_DIR))
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
                exfat_bug("attempted to shrink a file");
                exfat_bug("attempted to shrink a file");
-       if (!(dir->flags & EXFAT_ATTRIB_CACHED))
+       if (!dir->is_cached)
                exfat_bug("attempted to shrink uncached directory");
 
        for (last_node = node = dir->child; node; node = node->next)
                exfat_bug("attempted to shrink uncached directory");
 
        for (last_node = node = dir->child; node; node = node->next)
@@ -627,10 +740,7 @@ static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
                new_size = CLUSTER_SIZE(*ef->sb);
        if (new_size == dir->size)
                return 0;
                new_size = CLUSTER_SIZE(*ef->sb);
        if (new_size == dir->size)
                return 0;
-       rc = exfat_truncate(ef, dir, new_size);
-       if (rc != 0)
-               return rc;
-       return 0;
+       return exfat_truncate(ef, dir, new_size, true);
 }
 
 static int delete(struct exfat* ef, struct exfat_node* node)
 }
 
 static int delete(struct exfat* ef, struct exfat_node* node)
@@ -640,141 +750,187 @@ static int delete(struct exfat* ef, struct exfat_node* node)
        int rc;
 
        exfat_get_node(parent);
        int rc;
 
        exfat_get_node(parent);
-       erase_entry(ef, node);
-       exfat_update_mtime(parent);
+       rc = erase_node(ef, node);
+       if (rc != 0)
+       {
+               exfat_put_node(ef, parent);
+               return rc;
+       }
        tree_detach(node);
        rc = shrink_directory(ef, parent, deleted_offset);
        tree_detach(node);
        rc = shrink_directory(ef, parent, deleted_offset);
+       node->is_unlinked = true;
+       if (rc != 0)
+       {
+               exfat_flush_node(ef, parent);
+               exfat_put_node(ef, parent);
+               return rc;
+       }
+       exfat_update_mtime(parent);
+       rc = exfat_flush_node(ef, parent);
        exfat_put_node(ef, parent);
        exfat_put_node(ef, parent);
-       /* file clusters will be freed when node reference counter becomes 0 */
-       node->flags |= EXFAT_ATTRIB_UNLINKED;
        return rc;
 }
 
 int exfat_unlink(struct exfat* ef, struct exfat_node* node)
 {
        return rc;
 }
 
 int exfat_unlink(struct exfat* ef, struct exfat_node* node)
 {
-       if (node->flags & EXFAT_ATTRIB_DIR)
+       if (node->attrib & EXFAT_ATTRIB_DIR)
                return -EISDIR;
        return delete(ef, node);
 }
 
 int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
                return -EISDIR;
        return delete(ef, node);
 }
 
 int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
-       if (!(node->flags & EXFAT_ATTRIB_DIR))
+       int rc;
+
+       if (!(node->attrib & EXFAT_ATTRIB_DIR))
                return -ENOTDIR;
        /* check that directory is empty */
                return -ENOTDIR;
        /* check that directory is empty */
-       exfat_cache_directory(ef, node);
+       rc = exfat_cache_directory(ef, node);
+       if (rc != 0)
+               return rc;
        if (node->child)
                return -ENOTEMPTY;
        return delete(ef, node);
 }
 
        if (node->child)
                return -ENOTEMPTY;
        return delete(ef, node);
 }
 
-static int grow_directory(struct exfat* ef, struct exfat_node* dir,
-               uint64_t asize, uint32_t difference)
+static int check_slot(struct exfat* ef, struct exfat_node* dir, off_t offset,
+               int n)
 {
 {
-       return exfat_truncate(ef, dir,
-                       DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
-                               * CLUSTER_SIZE(*ef->sb));
+       struct exfat_entry entries[n];
+       int rc;
+       size_t i;
+
+       /* Root directory contains entries, that don't have any nodes associated
+          with them (clusters bitmap, upper case table, label). We need to be
+          careful not to overwrite them. */
+       if (dir != ef->root)
+               return 0;
+
+       rc = read_entries(ef, dir, entries, n, offset);
+       if (rc != 0)
+               return rc;
+       for (i = 0; i < n; i++)
+               if (entries[i].type & EXFAT_ENTRY_VALID)
+                       return -EINVAL;
+       return 0;
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
-               cluster_t* cluster, off_t* offset, int subentries)
+               off_t* offset, int n)
 {
 {
-       struct iterator it;
-       int rc;
-       const struct exfat_entry* entry;
+       bitmap_t* dmap;
+       struct exfat_node* p;
+       size_t i;
        int contiguous = 0;
 
        int contiguous = 0;
 
-       rc = opendir(ef, dir, &it);
-       if (rc != 0)
-               return rc;
-       for (;;)
+       if (!dir->is_cached)
+               exfat_bug("directory is not cached");
+
+       /* build a bitmap of valid entries in the directory */
+       dmap = calloc(BMAP_SIZE(dir->size / sizeof(struct exfat_entry)),
+                       sizeof(bitmap_t));
+       if (dmap == NULL)
        {
        {
-               if (contiguous == 0)
+               exfat_error("failed to allocate directory bitmap (%"PRIu64")",
+                               dir->size / sizeof(struct exfat_entry));
+               return -ENOMEM;
+       }
+       for (p = dir->child; p != NULL; p = p->next)
+               for (i = 0; i < 1 + p->continuations; i++)
+                       BMAP_SET(dmap, p->entry_offset / sizeof(struct exfat_entry) + i);
+
+       /* find a slot in the directory entries bitmap */
+       for (i = 0; i < dir->size / sizeof(struct exfat_entry); i++)
+       {
+               if (BMAP_GET(dmap, i) == 0)
                {
                {
-                       *cluster = it.cluster;
-                       *offset = it.offset;
+                       if (contiguous++ == 0)
+                               *offset = (off_t) i * sizeof(struct exfat_entry);
+                       if (contiguous == n)
+                               /* suitable slot is found, check that it's not occupied */
+                               switch (check_slot(ef, dir, *offset, n))
+                               {
+                               case 0:
+                                       free(dmap);
+                                       return 0;
+                               case -EIO:
+                                       free(dmap);
+                                       return -EIO;
+                               case -EINVAL:
+                                       /* slot at (i-n) is occupied, go back and check (i-n+1) */
+                                       i -= contiguous - 1;
+                                       contiguous = 0;
+                                       break;
+                               }
                }
                }
-               entry = get_entry_ptr(ef, &it);
-               if (entry->type & EXFAT_ENTRY_VALID)
-                       contiguous = 0;
                else
                else
-                       contiguous++;
-               if (contiguous == subentries)
-                       break;  /* suitable slot is found */
-               if (it.offset + sizeof(struct exfat_entry) >= dir->size)
-               {
-                       rc = grow_directory(ef, dir, dir->size,
-                                       (subentries - contiguous) * sizeof(struct exfat_entry));
-                       if (rc != 0)
-                       {
-                               closedir(&it);
-                               return rc;
-                       }
-               }
-               if (fetch_next_entry(ef, dir, &it) != 0)
-               {
-                       closedir(&it);
-                       return -EIO;
-               }
+                       contiguous = 0;
        }
        }
-       closedir(&it);
-       return 0;
+       free(dmap);
+
+       /* no suitable slots found, extend the directory */
+       if (contiguous == 0)
+               *offset = dir->size;
+       return exfat_truncate(ef, dir,
+                       ROUND_UP(dir->size + sizeof(struct exfat_entry[n - contiguous]),
+                                       CLUSTER_SIZE(*ef->sb)),
+                       true);
 }
 
 }
 
-static int write_entry(struct exfat* ef, struct exfat_node* dir,
-               const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
+static int commit_entry(struct exfat* ef, struct exfat_node* dir,
+               const le16_t* name, off_t offset, uint16_t attrib)
 {
        struct exfat_node* node;
 {
        struct exfat_node* node;
-       struct exfat_entry_meta1 meta1;
-       struct exfat_entry_meta2 meta2;
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+       struct exfat_entry entries[2 + name_entries];
+       struct exfat_entry_meta1* meta1 = (struct exfat_entry_meta1*) &entries[0];
+       struct exfat_entry_meta2* meta2 = (struct exfat_entry_meta2*) &entries[1];
        int i;
        int i;
+       int rc;
 
 
-       node = allocate_node();
-       if (node == NULL)
-               return -ENOMEM;
-       node->entry_cluster = cluster;
-       node->entry_offset = offset;
-       memcpy(node->name, name, name_length * sizeof(le16_t));
+       memset(entries, 0, sizeof(struct exfat_entry[2]));
+
+       meta1->type = EXFAT_ENTRY_FILE;
+       meta1->continuations = 1 + name_entries;
+       meta1->attrib = cpu_to_le16(attrib);
+       exfat_unix2exfat(time(NULL), &meta1->crdate, &meta1->crtime,
+                       &meta1->crtime_cs);
+       meta1->adate = meta1->mdate = meta1->crdate;
+       meta1->atime = meta1->mtime = meta1->crtime;
+       meta1->mtime_cs = meta1->crtime_cs; /* there is no atime_cs */
+
+       meta2->type = EXFAT_ENTRY_FILE_INFO;
+       meta2->flags = EXFAT_FLAG_ALWAYS1;
+       meta2->name_length = name_length;
+       meta2->name_hash = exfat_calc_name_hash(ef, name, name_length);
+       meta2->start_cluster = cpu_to_le32(EXFAT_CLUSTER_FREE);
 
 
-       memset(&meta1, 0, sizeof(meta1));
-       meta1.type = EXFAT_ENTRY_FILE;
-       meta1.continuations = 1 + name_entries;
-       meta1.attrib = cpu_to_le16(attrib);
-       exfat_unix2exfat(time(NULL), &meta1.crdate, &meta1.crtime,
-                       &meta1.crtime_cs);
-       meta1.adate = meta1.mdate = meta1.crdate;
-       meta1.atime = meta1.mtime = meta1.crtime;
-       meta1.mtime_cs = meta1.crtime_cs; /* there is no atime_cs */
-
-       memset(&meta2, 0, sizeof(meta2));
-       meta2.type = EXFAT_ENTRY_FILE_INFO;
-       meta2.flags = EXFAT_FLAG_ALWAYS1;
-       meta2.name_length = name_length;
-       meta2.name_hash = exfat_calc_name_hash(ef, node->name);
-       meta2.start_cluster = cpu_to_le32(EXFAT_CLUSTER_FREE);
-
-       meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
-
-       exfat_pwrite(ef->dev, &meta1, sizeof(meta1), co2o(ef, cluster, offset));
-       next_entry(ef, dir, &cluster, &offset);
-       exfat_pwrite(ef->dev, &meta2, sizeof(meta2), co2o(ef, cluster, offset));
        for (i = 0; i < name_entries; i++)
        {
        for (i = 0; i < name_entries; i++)
        {
-               struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
-               memcpy(name_entry.name, node->name + i * EXFAT_ENAME_MAX,
+               struct exfat_entry_name* name_entry;
+
+               name_entry = (struct exfat_entry_name*) &entries[2 + i];
+               name_entry->type = EXFAT_ENTRY_FILE_NAME;
+               name_entry->__unknown = 0;
+               memcpy(name_entry->name, name + i * EXFAT_ENAME_MAX,
                                EXFAT_ENAME_MAX * sizeof(le16_t));
                                EXFAT_ENAME_MAX * sizeof(le16_t));
-               next_entry(ef, dir, &cluster, &offset);
-               exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
-                               co2o(ef, cluster, offset));
        }
 
        }
 
-       init_node_meta1(node, &meta1);
-       init_node_meta2(node, &meta2);
+       meta1->checksum = exfat_calc_checksum(entries, 2 + name_entries);
+       rc = write_entries(ef, dir, entries, 2 + name_entries, offset);
+       if (rc != 0)
+               return rc;
+
+       node = allocate_node();
+       if (node == NULL)
+               return -ENOMEM;
+       node->entry_offset = offset;
+       memcpy(node->name, name, name_length * sizeof(le16_t));
+       init_node_meta1(node, meta1);
+       init_node_meta2(node, meta2);
 
        tree_attach(dir, node);
 
        tree_attach(dir, node);
-       exfat_update_mtime(dir);
        return 0;
 }
 
        return 0;
 }
 
@@ -782,7 +938,6 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
 {
        struct exfat_node* dir;
        struct exfat_node* existing;
 {
        struct exfat_node* dir;
        struct exfat_node* existing;
-       cluster_t cluster = EXFAT_CLUSTER_BAD;
        off_t offset = -1;
        le16_t name[EXFAT_NAME_MAX + 1];
        int rc;
        off_t offset = -1;
        le16_t name[EXFAT_NAME_MAX + 1];
        int rc;
@@ -797,14 +952,21 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
                return -EEXIST;
        }
 
                return -EEXIST;
        }
 
-       rc = find_slot(ef, dir, &cluster, &offset,
+       rc = find_slot(ef, dir, &offset,
                        2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
                return rc;
        }
                        2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
                return rc;
        }
-       rc = write_entry(ef, dir, name, cluster, offset, attrib);
+       rc = commit_entry(ef, dir, name, offset, attrib);
+       if (rc != 0)
+       {
+               exfat_put_node(ef, dir);
+               return rc;
+       }
+       exfat_update_mtime(dir);
+       rc = exfat_flush_node(ef, dir);
        exfat_put_node(ef, dir);
        return rc;
 }
        exfat_put_node(ef, dir);
        return rc;
 }
@@ -819,14 +981,21 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        int rc;
        struct exfat_node* node;
 
        int rc;
        struct exfat_node* node;
 
-       rc = create(ef, path, EXFAT_ATTRIB_ARCH | EXFAT_ATTRIB_DIR);
+       rc = create(ef, path, EXFAT_ATTRIB_DIR);
        if (rc != 0)
                return rc;
        rc = exfat_lookup(ef, &node, path);
        if (rc != 0)
                return 0;
        /* directories always have at least one cluster */
        if (rc != 0)
                return rc;
        rc = exfat_lookup(ef, &node, path);
        if (rc != 0)
                return 0;
        /* directories always have at least one cluster */
-       rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb));
+       rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb), true);
+       if (rc != 0)
+       {
+               delete(ef, node);
+               exfat_put_node(ef, node);
+               return rc;
+       }
+       rc = exfat_flush_node(ef, node);
        if (rc != 0)
        {
                delete(ef, node);
        if (rc != 0)
        {
                delete(ef, node);
@@ -837,52 +1006,52 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        return 0;
 }
 
        return 0;
 }
 
-static void rename_entry(struct exfat* ef, struct exfat_node* dir,
-               struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
-               off_t new_offset)
+static int rename_entry(struct exfat* ef, struct exfat_node* dir,
+               struct exfat_node* node, const le16_t* name, off_t new_offset)
 {
 {
-       struct exfat_entry_meta1 meta1;
-       struct exfat_entry_meta2 meta2;
-       cluster_t old_cluster = node->entry_cluster;
-       off_t old_offset = node->entry_offset;
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+       struct exfat_entry entries[2 + name_entries];
+       struct exfat_entry_meta1* meta1 = (struct exfat_entry_meta1*) &entries[0];
+       struct exfat_entry_meta2* meta2 = (struct exfat_entry_meta2*) &entries[1];
+       int rc;
        int i;
 
        int i;
 
-       exfat_pread(ef->dev, &meta1, sizeof(meta1),
-                       co2o(ef, old_cluster, old_offset));
-       next_entry(ef, node->parent, &old_cluster, &old_offset);
-       exfat_pread(ef->dev, &meta2, sizeof(meta2),
-                       co2o(ef, old_cluster, old_offset));
-       meta1.continuations = 1 + name_entries;
-       meta2.name_hash = exfat_calc_name_hash(ef, name);
-       meta2.name_length = name_length;
-       meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
+       rc = read_entries(ef, node->parent, entries, 2, node->entry_offset);
+       if (rc != 0)
+               return rc;
 
 
-       erase_entry(ef, node);
+       meta1->continuations = 1 + name_entries;
+       meta2->name_length = name_length;
+       meta2->name_hash = exfat_calc_name_hash(ef, name, name_length);
 
 
-       node->entry_cluster = new_cluster;
-       node->entry_offset = new_offset;
+       rc = erase_node(ef, node);
+       if (rc != 0)
+               return rc;
 
 
-       exfat_pwrite(ef->dev, &meta1, sizeof(meta1),
-                       co2o(ef, new_cluster, new_offset));
-       next_entry(ef, dir, &new_cluster, &new_offset);
-       exfat_pwrite(ef->dev, &meta2, sizeof(meta2),
-                       co2o(ef, new_cluster, new_offset));
+       node->entry_offset = new_offset;
+       node->continuations = 1 + name_entries;
 
        for (i = 0; i < name_entries; i++)
        {
 
        for (i = 0; i < name_entries; i++)
        {
-               struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
-               memcpy(name_entry.name, name + i * EXFAT_ENAME_MAX,
+               struct exfat_entry_name* name_entry;
+
+               name_entry = (struct exfat_entry_name*) &entries[2 + i];
+               name_entry->type = EXFAT_ENTRY_FILE_NAME;
+               name_entry->__unknown = 0;
+               memcpy(name_entry->name, name + i * EXFAT_ENAME_MAX,
                                EXFAT_ENAME_MAX * sizeof(le16_t));
                                EXFAT_ENAME_MAX * sizeof(le16_t));
-               next_entry(ef, dir, &new_cluster, &new_offset);
-               exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
-                               co2o(ef, new_cluster, new_offset));
        }
 
        }
 
+       meta1->checksum = exfat_calc_checksum(entries, 2 + name_entries);
+       rc = write_entries(ef, dir, entries, 2 + name_entries, new_offset);
+       if (rc != 0)
+               return rc;
+
        memcpy(node->name, name, (EXFAT_NAME_MAX + 1) * sizeof(le16_t));
        tree_detach(node);
        tree_attach(dir, node);
        memcpy(node->name, name, (EXFAT_NAME_MAX + 1) * sizeof(le16_t));
        tree_detach(node);
        tree_attach(dir, node);
+       return 0;
 }
 
 int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
 }
 
 int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
@@ -890,7 +1059,6 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
        struct exfat_node* node;
        struct exfat_node* existing;
        struct exfat_node* dir;
        struct exfat_node* node;
        struct exfat_node* existing;
        struct exfat_node* dir;
-       cluster_t cluster = EXFAT_CLUSTER_BAD;
        off_t offset = -1;
        le16_t name[EXFAT_NAME_MAX + 1];
        int rc;
        off_t offset = -1;
        le16_t name[EXFAT_NAME_MAX + 1];
        int rc;
@@ -907,7 +1075,7 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
        }
 
        /* check that target is not a subdirectory of the source */
        }
 
        /* check that target is not a subdirectory of the source */
-       if (node->flags & EXFAT_ATTRIB_DIR)
+       if (node->attrib & EXFAT_ATTRIB_DIR)
        {
                struct exfat_node* p;
 
        {
                struct exfat_node* p;
 
@@ -927,22 +1095,32 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                /* remove target if it's not the same node as source */
                if (existing != node)
                {
                /* remove target if it's not the same node as source */
                if (existing != node)
                {
-                       if (existing->flags & EXFAT_ATTRIB_DIR)
+                       if (existing->attrib & EXFAT_ATTRIB_DIR)
                        {
                        {
-                               if (node->flags & EXFAT_ATTRIB_DIR)
+                               if (node->attrib & EXFAT_ATTRIB_DIR)
                                        rc = exfat_rmdir(ef, existing);
                                else
                                        rc = -ENOTDIR;
                        }
                        else
                        {
                                        rc = exfat_rmdir(ef, existing);
                                else
                                        rc = -ENOTDIR;
                        }
                        else
                        {
-                               if (!(node->flags & EXFAT_ATTRIB_DIR))
+                               if (!(node->attrib & EXFAT_ATTRIB_DIR))
                                        rc = exfat_unlink(ef, existing);
                                else
                                        rc = -EISDIR;
                        }
                        exfat_put_node(ef, existing);
                        if (rc != 0)
                                        rc = exfat_unlink(ef, existing);
                                else
                                        rc = -EISDIR;
                        }
                        exfat_put_node(ef, existing);
                        if (rc != 0)
+                       {
+                               /* free clusters even if something went wrong; overwise they
+                                  will be just lost */
+                               exfat_cleanup_node(ef, existing);
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return rc;
+                       }
+                       rc = exfat_cleanup_node(ef, existing);
+                       if (rc != 0)
                        {
                                exfat_put_node(ef, dir);
                                exfat_put_node(ef, node);
                        {
                                exfat_put_node(ef, dir);
                                exfat_put_node(ef, node);
@@ -953,7 +1131,7 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                        exfat_put_node(ef, existing);
        }
 
                        exfat_put_node(ef, existing);
        }
 
-       rc = find_slot(ef, dir, &cluster, &offset,
+       rc = find_slot(ef, dir, &offset,
                        2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
        if (rc != 0)
        {
                        2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
        if (rc != 0)
        {
@@ -961,29 +1139,37 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                exfat_put_node(ef, node);
                return rc;
        }
                exfat_put_node(ef, node);
                return rc;
        }
-       rename_entry(ef, dir, node, name, cluster, offset);
+       rc = rename_entry(ef, dir, node, name, offset);
+       if (rc != 0)
+       {
+               exfat_put_node(ef, dir);
+               exfat_put_node(ef, node);
+               return rc;
+       }
+       rc = exfat_flush_node(ef, dir);
        exfat_put_node(ef, dir);
        exfat_put_node(ef, node);
        exfat_put_node(ef, dir);
        exfat_put_node(ef, node);
-       return 0;
+       /* node itself is not marked as dirty, no need to flush it */
+       return rc;
 }
 
 void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
 {
        node->atime = tv[0].tv_sec;
        node->mtime = tv[1].tv_sec;
 }
 
 void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
 {
        node->atime = tv[0].tv_sec;
        node->mtime = tv[1].tv_sec;
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 void exfat_update_atime(struct exfat_node* node)
 {
        node->atime = time(NULL);
 }
 
 void exfat_update_atime(struct exfat_node* node)
 {
        node->atime = time(NULL);
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 void exfat_update_mtime(struct exfat_node* node)
 {
        node->mtime = time(NULL);
 }
 
 void exfat_update_mtime(struct exfat_node* node)
 {
        node->mtime = time(NULL);
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 const char* exfat_get_label(struct exfat* ef)
 }
 
 const char* exfat_get_label(struct exfat* ef)
@@ -991,36 +1177,19 @@ const char* exfat_get_label(struct exfat* ef)
        return ef->label;
 }
 
        return ef->label;
 }
 
-static int find_label(struct exfat* ef, cluster_t* cluster, off_t* offset)
+static int find_label(struct exfat* ef, off_t* offset)
 {
 {
-       struct iterator it;
+       struct exfat_entry entry;
        int rc;
 
        int rc;
 
-       rc = opendir(ef, ef->root, &it);
-       if (rc != 0)
-               return rc;
-
-       for (;;)
+       for (*offset = 0; ; *offset += sizeof(entry))
        {
        {
-               if (it.offset >= ef->root->size)
-               {
-                       closedir(&it);
-                       return -ENOENT;
-               }
+               rc = read_entries(ef, ef->root, &entry, 1, *offset);
+               if (rc != 0)
+                       return rc;
 
 
-               if (get_entry_ptr(ef, &it)->type == EXFAT_ENTRY_LABEL)
-               {
-                       *cluster = it.cluster;
-                       *offset = it.offset;
-                       closedir(&it);
+               if (entry.type == EXFAT_ENTRY_LABEL)
                        return 0;
                        return 0;
-               }
-
-               if (fetch_next_entry(ef, ef->root, &it) != 0)
-               {
-                       closedir(&it);
-                       return -EIO;
-               }
        }
 }
 
        }
 }
 
@@ -1028,18 +1197,17 @@ int exfat_set_label(struct exfat* ef, const char* label)
 {
        le16_t label_utf16[EXFAT_ENAME_MAX + 1];
        int rc;
 {
        le16_t label_utf16[EXFAT_ENAME_MAX + 1];
        int rc;
-       cluster_t cluster;
        off_t offset;
        struct exfat_entry_label entry;
 
        memset(label_utf16, 0, sizeof(label_utf16));
        off_t offset;
        struct exfat_entry_label entry;
 
        memset(label_utf16, 0, sizeof(label_utf16));
-       rc = utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX, strlen(label));
+       rc = utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX + 1, strlen(label));
        if (rc != 0)
                return rc;
 
        if (rc != 0)
                return rc;
 
-       rc = find_label(ef, &cluster, &offset);
+       rc = find_label(ef, &offset);
        if (rc == -ENOENT)
        if (rc == -ENOENT)
-               rc = find_slot(ef, ef->root, &cluster, &offset, 1);
+               rc = find_slot(ef, ef->root, &offset, 1);
        if (rc != 0)
                return rc;
 
        if (rc != 0)
                return rc;
 
@@ -1049,7 +1217,10 @@ int exfat_set_label(struct exfat* ef, const char* label)
        if (entry.length == 0)
                entry.type ^= EXFAT_ENTRY_VALID;
 
        if (entry.length == 0)
                entry.type ^= EXFAT_ENTRY_VALID;
 
-       exfat_pwrite(ef->dev, &entry, sizeof(struct exfat_entry_label),
-                       co2o(ef, cluster, offset));
+       rc = write_entries(ef, ef->root, (struct exfat_entry*) &entry, 1, offset);
+       if (rc != 0)
+               return rc;
+
+       strcpy(ef->label, label);
        return 0;
 }
        return 0;
 }