OSDN Git Service

Generic I/O for directories: switch erase_entry().
[android-x86/external-exfat.git] / libexfat / node.c
index 2eada9a..44f4ae3 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-2012  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2016  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"
@@ -28,7 +30,6 @@ struct iterator
 {
        cluster_t cluster;
        off_t offset;
 {
        cluster_t cluster;
        off_t offset;
-       int contiguous;
        char* chunk;
 };
 
        char* chunk;
 };
 
@@ -42,29 +43,47 @@ 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);
        }
 }
 
 /**
        }
 }
 
 /**
+ * This function must be called on rmdir and unlink (after the last
+ * exfat_put_node()) to free clusters.
+ */
+int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node)
+{
+       int rc = 0;
+
+       if (node->references != 0)
+               exfat_bug("unable to cleanup a node with %d references",
+                               node->references);
+
+       if (node->is_unlinked)
+       {
+               /* 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);
+       }
+       return rc;
+}
+
+/**
  * Cluster + offset from the beginning of the directory to absolute offset.
  */
 static off_t co2o(struct exfat* ef, cluster_t cluster, off_t offset)
  * Cluster + offset from the beginning of the directory to absolute offset.
  */
 static off_t co2o(struct exfat* ef, cluster_t cluster, off_t offset)
@@ -75,19 +94,37 @@ static off_t co2o(struct exfat* ef, cluster_t cluster, off_t offset)
 static int opendir(struct exfat* ef, const struct exfat_node* dir,
                struct iterator* it)
 {
 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");
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+       {
+               exfat_get_name(dir, buffer);
+               exfat_bug("'%s' is not a directory", buffer);
+       }
+       if (CLUSTER_INVALID(dir->start_cluster))
+       {
+               exfat_get_name(dir, buffer);
+               exfat_error("'%s' directory starts with invalid cluster %#x", buffer,
+                               dir->start_cluster);
+               return -EIO;
+       }
        it->cluster = dir->start_cluster;
        it->offset = 0;
        it->cluster = dir->start_cluster;
        it->offset = 0;
-       it->contiguous = IS_CONTIGUOUS(*dir);
        it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
        if (it->chunk == NULL)
        {
        it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
        if (it->chunk == NULL)
        {
-               exfat_error("out of memory");
+               exfat_error("failed to allocate memory for directory cluster");
                return -ENOMEM;
        }
                return -ENOMEM;
        }
-       exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
-                       exfat_c2o(ef, it->cluster));
+       if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
+                       exfat_c2o(ef, it->cluster)) < 0)
+       {
+               free(it->chunk);
+               exfat_get_name(dir, buffer);
+               exfat_error("failed to read '%s' directory cluster %#x", buffer,
+                               it->cluster);
+               return -EIO;
+       }
        return 0;
 }
 
        return 0;
 }
 
@@ -95,12 +132,11 @@ static void closedir(struct iterator* it)
 {
        it->cluster = 0;
        it->offset = 0;
 {
        it->cluster = 0;
        it->offset = 0;
-       it->contiguous = 0;
        free(it->chunk);
        it->chunk = NULL;
 }
 
        free(it->chunk);
        it->chunk = NULL;
 }
 
-static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
+static bool fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
                struct iterator* it)
 {
        /* move iterator to the next entry in the directory */
                struct iterator* it)
 {
        /* move iterator to the next entry in the directory */
@@ -111,18 +147,63 @@ static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
                /* reached the end of directory; the caller should check this
                   condition too */
                if (it->offset >= parent->size)
                /* reached the end of directory; the caller should check this
                   condition too */
                if (it->offset >= parent->size)
-                       return 0;
+                       return true;
                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);
                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;
+                       return false;
+               }
+               if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
+                               exfat_c2o(ef, it->cluster)) < 0)
+               {
+                       exfat_error("failed to read the next directory cluster %#x",
+                                       it->cluster);
+                       return false;
                }
                }
-               exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
-                               exfat_c2o(ef, it->cluster));
        }
        }
-       return 0;
+       return true;
+}
+
+static int read_entries(struct exfat* ef, struct exfat_node* dir,
+               struct exfat_entry* entries, int n, off_t offset)
+{
+       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 write_entries(struct exfat* ef, struct exfat_node* dir,
+               const struct exfat_entry* entries, int n, off_t offset)
+{
+       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 +221,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,8 +235,7 @@ 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,
 }
 
 static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
@@ -164,6 +245,100 @@ static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
                        (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
 }
 
                        (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
 }
 
+static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
+               uint16_t reference_checksum, uint64_t valid_size, int 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 (actual_checksum != reference_checksum)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
+                               actual_checksum, reference_checksum);
+               ret = false;
+       }
+
+       /*
+          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 (valid_size > node->size)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has valid size (%"PRIu64") greater than size "
+                               "(%"PRIu64")", buffer, 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 in opendir()
+          to give user a chance to read current 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(node->start_cluster))
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' points to invalid cluster %#x", buffer,
+                               node->start_cluster);
+               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 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;
+       }
+}
+
 /*
  * Reads one entry in directory at position pointed by iterator and fills
  * node structure.
 /*
  * Reads one entry in directory at position pointed by iterator and fills
  * node structure.
@@ -183,7 +358,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
        le16_t* namep = NULL;
        uint16_t reference_checksum = 0;
        uint16_t actual_checksum = 0;
        le16_t* namep = NULL;
        uint16_t reference_checksum = 0;
        uint16_t actual_checksum = 0;
-       uint64_t real_size = 0;
+       uint64_t valid_size = 0;
+       uint64_t upcase_size = 0;
+       le16_t* upcase_comp = NULL;
 
        *node = NULL;
 
 
        *node = NULL;
 
@@ -218,6 +395,12 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                exfat_error("too few continuations (%hhu)", continuations);
                                goto error;
                        }
                                exfat_error("too few continuations (%hhu)", continuations);
                                goto error;
                        }
+                       if (continuations > 1 +
+                                       DIV_ROUND_UP(EXFAT_NAME_MAX, EXFAT_ENAME_MAX))
+                       {
+                               exfat_error("too many continuations (%hhu)", continuations);
+                               goto error;
+                       }
                        reference_checksum = le16_to_cpu(meta1->checksum);
                        actual_checksum = exfat_start_checksum(meta1);
                        *node = allocate_node();
                        reference_checksum = le16_to_cpu(meta1->checksum);
                        actual_checksum = exfat_start_checksum(meta1);
                        *node = allocate_node();
@@ -248,22 +431,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        }
                        init_node_meta2(*node, meta2);
                        actual_checksum = exfat_add_checksum(entry, actual_checksum);
                        }
                        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;
-                       }
+                       valid_size = le64_to_cpu(meta2->valid_size);
                        --continuations;
                        break;
 
                        --continuations;
                        break;
 
@@ -276,41 +444,17 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        file_name = (const struct exfat_entry_name*) entry;
                        actual_checksum = exfat_add_checksum(entry, actual_checksum);
 
                        file_name = (const struct exfat_entry_name*) entry;
                        actual_checksum = exfat_add_checksum(entry, actual_checksum);
 
-                       memcpy(namep, file_name->name, EXFAT_ENAME_MAX * sizeof(le16_t));
+                       memcpy(namep, file_name->name,
+                                       MIN(EXFAT_ENAME_MAX,
+                                               ((*node)->name + EXFAT_NAME_MAX - namep)) *
+                                       sizeof(le16_t));
                        namep += EXFAT_ENAME_MAX;
                        if (--continuations == 0)
                        {
                        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];
-
-                                       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];
-
-                                       exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
-                                       exfat_error("`%s' has invalid checksum (0x%hx != 0x%hx)",
-                                                       buffer, actual_checksum, reference_checksum);
+                               if (!check_node(*node, actual_checksum, reference_checksum,
+                                               valid_size, CLUSTER_SIZE(*ef->sb)))
                                        goto error;
                                        goto error;
-                               }
-                               if (fetch_next_entry(ef, parent, it) != 0)
+                               if (!fetch_next_entry(ef, parent, it))
                                        goto error;
                                return 0; /* entry completed */
                        }
                                        goto error;
                                return 0; /* entry completed */
                        }
@@ -326,26 +470,48 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                                le32_to_cpu(upcase->start_cluster));
                                goto error;
                        }
                                                le32_to_cpu(upcase->start_cluster));
                                goto error;
                        }
-                       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));
+                                               upcase_size);
                                goto error;
                        }
                                goto error;
                        }
-                       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));
+                                               upcase_size);
                                rc = -ENOMEM;
                                goto error;
                        }
                                rc = -ENOMEM;
                                goto error;
                        }
-                       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));
+                               goto error;
+                       }
+
+                       /* 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");
+                               rc = -ENOMEM;
+                               goto error;
+                       }
+                       decompress_upcase(ef->upcase, upcase_comp,
+                                       upcase_size / sizeof(uint16_t));
+                       free(upcase_comp);
                        break;
 
                case EXFAT_ENTRY_BITMAP:
                        break;
 
                case EXFAT_ENTRY_BITMAP:
@@ -359,16 +525,17 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        }
                        ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
                                EXFAT_FIRST_DATA_CLUSTER;
                        }
                        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)
+                       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);
+                                               le64_to_cpu(bitmap->size),
+                                               DIV_ROUND_UP(ef->cmap.size, 8));
                                goto error;
                        }
                        /* FIXME bitmap can be rather big, up to 512 MB */
                        ef->cmap.chunk_size = ef->cmap.size;
                                goto error;
                        }
                        /* 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 "
                        if (ef->cmap.chunk == NULL)
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
@@ -377,8 +544,15 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                goto error;
                        }
 
                                goto error;
                        }
 
-                       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);
+                               goto error;
+                       }
                        break;
 
                case EXFAT_ENTRY_LABEL:
                        break;
 
                case EXFAT_ENTRY_LABEL:
@@ -394,15 +568,25 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        break;
 
                default:
                        break;
 
                default:
-                       if (entry->type & EXFAT_ENTRY_VALID)
+                       if (!(entry->type & EXFAT_ENTRY_VALID))
+                               break; /* deleted entry, ignore it */
+                       if (!(entry->type & EXFAT_ENTRY_OPTIONAL))
+                       {
+                               exfat_error("unknown entry type %#hhx", entry->type);
+                               goto error;
+                       }
+                       /* optional entry, warn and skip */
+                       exfat_warn("unknown entry type %#hhx", entry->type);
+                       if (continuations == 0)
                        {
                        {
-                               exfat_error("unknown entry type 0x%hhx", entry->type);
+                               exfat_error("unexpected continuation");
                                goto error;
                        }
                                goto error;
                        }
+                       --continuations;
                        break;
                }
 
                        break;
                }
 
-               if (fetch_next_entry(ef, parent, it) != 0)
+               if (!fetch_next_entry(ef, parent, it))
                        goto error;
        }
        /* we never reach here */
                        goto error;
        }
        /* we never reach here */
@@ -420,7 +604,7 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
-       if (dir->flags & EXFAT_ATTRIB_CACHED)
+       if (dir->is_cached)
                return 0; /* already cached */
 
        rc = opendir(ef, dir, &it);
                return 0; /* already cached */
 
        rc = opendir(ef, dir, &it);
@@ -453,7 +637,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 +667,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 +676,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,16 +697,24 @@ 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,
+static bool next_entry(struct exfat* ef, const struct exfat_node* parent,
                cluster_t* cluster, off_t* offset)
 {
        *offset += sizeof(struct exfat_entry);
        if (*offset % CLUSTER_SIZE(*ef->sb) == 0)
                cluster_t* cluster, off_t* offset)
 {
        *offset += sizeof(struct exfat_entry);
        if (*offset % CLUSTER_SIZE(*ef->sb) == 0)
-               /* next cluster cannot be invalid */
+       {
                *cluster = exfat_next_cluster(ef, parent, *cluster);
                *cluster = exfat_next_cluster(ef, parent, *cluster);
+               if (CLUSTER_INVALID(*cluster))
+               {
+                       exfat_error("invalid cluster %#x while getting next entry",
+                                       *cluster);
+                       return false;
+               }
+       }
+       return true;
 }
 
 }
 
-void exfat_flush_node(struct exfat* ef, struct exfat_node* node)
+int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
 {
        cluster_t cluster;
        off_t offset;
 {
        cluster_t cluster;
        off_t offset;
@@ -524,64 +722,95 @@ void exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        struct exfat_entry_meta1 meta1;
        struct exfat_entry_meta2 meta2;
 
        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 */
+               return 0; /* do not flush unlinked node */
 
        cluster = node->entry_cluster;
        offset = node->entry_offset;
        meta1_offset = co2o(ef, cluster, offset);
 
        cluster = node->entry_cluster;
        offset = node->entry_offset;
        meta1_offset = co2o(ef, cluster, offset);
-       next_entry(ef, node->parent, &cluster, &offset);
+       if (!next_entry(ef, node->parent, &cluster, &offset))
+               return -EIO;
        meta2_offset = co2o(ef, cluster, offset);
 
        meta2_offset = co2o(ef, cluster, offset);
 
-       exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset);
+       if (exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
+       {
+               exfat_error("failed to read meta1 entry on flush");
+               return -EIO;
+       }
        if (meta1.type != EXFAT_ENTRY_FILE)
                exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
        if (meta1.type != EXFAT_ENTRY_FILE)
                exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
-       meta1.attrib = cpu_to_le16(node->flags);
+       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);
 
        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 (exfat_pread(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
+       {
+               exfat_error("failed to read meta2 entry on flush");
+               return -EIO;
+       }
        if (meta2.type != EXFAT_ENTRY_FILE_INFO)
                exfat_bug("invalid type of meta2: 0x%hhx", meta2.type);
        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.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 */
        meta2.start_cluster = cpu_to_le32(node->start_cluster);
        meta2.flags = EXFAT_FLAG_ALWAYS1;
        /* empty files must not be marked as contiguous */
-       if (node->size != 0 && IS_CONTIGUOUS(*node))
+       if (node->size != 0 && node->is_contiguous)
                meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
        /* name hash remains unchanged, no need to recalculate it */
 
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
 
                meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
        /* 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);
+       if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
+       {
+               exfat_error("failed to write meta1 entry on flush");
+               return -EIO;
+       }
+       if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
+       {
+               exfat_error("failed to write meta2 entry on flush");
+               return -EIO;
+       }
 
 
-       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 +820,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 +855,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,29 +865,44 @@ 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);
@@ -673,7 +913,7 @@ static int grow_directory(struct exfat* ef, struct exfat_node* dir,
 {
        return exfat_truncate(ef, dir,
                        DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
 {
        return exfat_truncate(ef, dir,
                        DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
-                               * CLUSTER_SIZE(*ef->sb));
+                               * CLUSTER_SIZE(*ef->sb), true);
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
@@ -711,7 +951,7 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
                                return rc;
                        }
                }
                                return rc;
                        }
                }
-               if (fetch_next_entry(ef, dir, &it) != 0)
+               if (!fetch_next_entry(ef, dir, &it))
                {
                        closedir(&it);
                        return -EIO;
                {
                        closedir(&it);
                        return -EIO;
@@ -721,60 +961,62 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
        return 0;
 }
 
        return 0;
 }
 
-static int write_entry(struct exfat* ef, struct exfat_node* dir,
+static int commit_entry(struct exfat* ef, struct exfat_node* dir,
                const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
 {
        struct exfat_node* node;
                const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
 {
        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]));
 
 
-       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->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 */
 
 
-       meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
+       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);
+
+       meta1->checksum = exfat_calc_checksum(meta1, meta2, 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);
+       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_cluster = cluster;
+       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;
 }
 
@@ -804,7 +1046,14 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
                exfat_put_node(ef, dir);
                return rc;
        }
                exfat_put_node(ef, dir);
                return rc;
        }
-       rc = write_entry(ef, dir, name, cluster, offset, attrib);
+       rc = commit_entry(ef, dir, name, cluster, 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 +1068,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,7 +1093,7 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        return 0;
 }
 
        return 0;
 }
 
-static void rename_entry(struct exfat* ef, struct exfat_node* dir,
+static int 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)
 {
                struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
                off_t new_offset)
 {
@@ -847,42 +1103,70 @@ static void rename_entry(struct exfat* ef, struct exfat_node* dir,
        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);
        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);
+       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));
+       if (exfat_pread(ef->dev, &meta1, sizeof(meta1),
+                       co2o(ef, old_cluster, old_offset)) < 0)
+       {
+               exfat_error("failed to read meta1 entry on rename");
+               return -EIO;
+       }
+       if (!next_entry(ef, node->parent, &old_cluster, &old_offset))
+               return -EIO;
+       if (exfat_pread(ef->dev, &meta2, sizeof(meta2),
+                       co2o(ef, old_cluster, old_offset)) < 0)
+       {
+               exfat_error("failed to read meta2 entry on rename");
+               return -EIO;
+       }
        meta1.continuations = 1 + name_entries;
        meta1.continuations = 1 + name_entries;
-       meta2.name_hash = exfat_calc_name_hash(ef, name);
+       meta2.name_hash = exfat_calc_name_hash(ef, name, name_length);
        meta2.name_length = name_length;
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
 
        meta2.name_length = name_length;
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
 
-       erase_entry(ef, node);
+       rc = erase_node(ef, node);
+       if (rc != 0)
+               return rc;
 
        node->entry_cluster = new_cluster;
        node->entry_offset = new_offset;
 
        node->entry_cluster = new_cluster;
        node->entry_offset = new_offset;
+       node->continuations = 1 + name_entries;
 
 
-       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));
+       if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1),
+                       co2o(ef, new_cluster, new_offset)) < 0)
+       {
+               exfat_error("failed to write meta1 entry on rename");
+               return -EIO;
+       }
+       if (!next_entry(ef, dir, &new_cluster, &new_offset))
+               return -EIO;
+       if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2),
+                       co2o(ef, new_cluster, new_offset)) < 0)
+       {
+               exfat_error("failed to write meta2 entry on rename");
+               return -EIO;
+       }
 
        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,
                                EXFAT_ENAME_MAX * sizeof(le16_t));
 
        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,
                                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));
+               if (!next_entry(ef, dir, &new_cluster, &new_offset))
+                       return -EIO;
+               if (exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
+                               co2o(ef, new_cluster, new_offset)) < 0)
+               {
+                       exfat_error("failed to write name entry on rename");
+                       return -EIO;
+               }
        }
 
        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)
@@ -905,21 +1189,38 @@ 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;
        }
+
+       /* check that target is not a subdirectory of the source */
+       if (node->attrib & EXFAT_ATTRIB_DIR)
+       {
+               struct exfat_node* p;
+
+               for (p = dir; p; p = p->parent)
+                       if (node == p)
+                       {
+                               if (existing != NULL)
+                                       exfat_put_node(ef, existing);
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return -EINVAL;
+                       }
+       }
+
        if (existing != NULL)
        {
                /* remove target if it's not the same node as source */
                if (existing != node)
                {
        if (existing != NULL)
        {
                /* 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;
                                        rc = exfat_unlink(ef, existing);
                                else
                                        rc = -EISDIR;
@@ -927,6 +1228,16 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                        exfat_put_node(ef, existing);
                        if (rc != 0)
                        {
                        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);
                                return rc;
                                exfat_put_node(ef, dir);
                                exfat_put_node(ef, node);
                                return rc;
@@ -944,29 +1255,29 @@ 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, cluster, offset);
        exfat_put_node(ef, dir);
        exfat_put_node(ef, node);
        exfat_put_node(ef, dir);
        exfat_put_node(ef, node);
-       return 0;
+       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)
@@ -974,36 +1285,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;
-               }
        }
 }
 
        }
 }
 
@@ -1016,11 +1310,11 @@ int exfat_set_label(struct exfat* ef, const char* label)
        struct exfat_entry_label entry;
 
        memset(label_utf16, 0, sizeof(label_utf16));
        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)
                rc = find_slot(ef, ef->root, &cluster, &offset, 1);
        if (rc != 0)
        if (rc == -ENOENT)
                rc = find_slot(ef, ef->root, &cluster, &offset, 1);
        if (rc != 0)
@@ -1032,7 +1326,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;
 }