]> git.sven.stormbind.net Git - sven/exfat-utils.git/blobdiff - libexfat/node.c
Imported Upstream version 1.1.0
[sven/exfat-utils.git] / libexfat / node.c
index bc342b780d59292b905ba43a755350bbaf45d82d..ba5146a0b4667ed38fe473166a8efd857ae2fad8 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-2014  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"
@@ -42,28 +44,46 @@ 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[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+       --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, sizeof(buffer) - 1);
+               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)
        {
                if (node->flags & EXFAT_ATTRIB_DIRTY)
-                       exfat_flush_node(ef, node);
-               if (node->flags & EXFAT_ATTRIB_UNLINKED)
                {
                {
-                       /* free all clusters and node structure itself */
-                       exfat_truncate(ef, node, 0);
-                       free(node);
+                       exfat_get_name(node, buffer, sizeof(buffer) - 1);
+                       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->flags & EXFAT_ATTRIB_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.
  */
 /**
  * Cluster + offset from the beginning of the directory to absolute offset.
  */
@@ -86,8 +106,12 @@ static int opendir(struct exfat* ef, const struct exfat_node* dir,
                exfat_error("out of memory");
                return -ENOMEM;
        }
                exfat_error("out of memory");
                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)
+       {
+               exfat_error("failed to read directory cluster %#x", it->cluster);
+               return -EIO;
+       }
        return 0;
 }
 
        return 0;
 }
 
@@ -100,7 +124,7 @@ static void closedir(struct iterator* it)
        it->chunk = NULL;
 }
 
        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 +135,23 @@ 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 struct exfat_node* allocate_node(void)
 }
 
 static struct exfat_node* allocate_node(void)
@@ -164,6 +193,40 @@ 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)
+{
+       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
+       /*
+          Validate checksum first. If it's invalid all other fields probably
+          contain just garbage.
+       */
+       if (actual_checksum != reference_checksum)
+       {
+               exfat_get_name(node, buffer, sizeof(buffer) - 1);
+               exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
+                               actual_checksum, reference_checksum);
+               return 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, sizeof(buffer) - 1);
+               exfat_error("'%s' has valid size (%"PRIu64") greater than size "
+                               "(%"PRIu64")", buffer, valid_size, node->size);
+               return false;
+       }
+
+       return true;
+}
+
 /*
  * 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 +246,7 @@ 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;
 
        *node = NULL;
 
 
        *node = NULL;
 
@@ -218,6 +281,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,7 +317,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);
+                       valid_size = le64_to_cpu(meta2->valid_size);
                        /* empty files must be marked as non-contiguous */
                        if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
                        {
                        /* empty files must be marked as non-contiguous */
                        if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
                        {
@@ -276,41 +345,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))
                                        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 */
                        }
@@ -344,8 +389,15 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        }
                        ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
 
                        }
                        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)));
+                       if (exfat_pread(ef->dev, ef->upcase, le64_to_cpu(upcase->size),
+                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0)
+                       {
+                               exfat_error("failed to read upper case table "
+                                               "(%"PRIu64" bytes starting at cluster %#x)",
+                                               le64_to_cpu(upcase->size),
+                                               le32_to_cpu(upcase->start_cluster));
+                               goto error;
+                       }
                        break;
 
                case EXFAT_ENTRY_BITMAP:
                        break;
 
                case EXFAT_ENTRY_BITMAP:
@@ -359,16 +411,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 +430,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:
@@ -389,7 +449,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                goto error;
                        }
                        if (utf16_to_utf8(ef->label, label->name,
                                goto error;
                        }
                        if (utf16_to_utf8(ef->label, label->name,
-                                               sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
+                                               sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0)
                                goto error;
                        break;
 
                                goto error;
                        break;
 
@@ -402,7 +462,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        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 */
@@ -483,6 +543,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[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+
        while (node->child)
        {
                struct exfat_node* p = node->child;
        while (node->child)
        {
                struct exfat_node* p = node->child;
@@ -493,11 +555,15 @@ static void reset_cache(struct exfat* ef, struct exfat_node* node)
        node->flags &= ~EXFAT_ATTRIB_CACHED;
        if (node->references != 0)
        {
        node->flags &= ~EXFAT_ATTRIB_CACHED;
        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, sizeof(buffer) - 1);
+               exfat_warn("non-zero reference counter (%d) for '%s'",
                                node->references, buffer);
        }
                                node->references, buffer);
        }
+       if (node != ef->root && (node->flags & EXFAT_ATTRIB_DIRTY))
+       {
+               exfat_get_name(node, buffer, sizeof(buffer) - 1);
+               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 +573,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,29 +598,41 @@ 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->flags & EXFAT_ATTRIB_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);
        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);
 
        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 (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 */
@@ -556,13 +642,22 @@ void exfat_flush_node(struct exfat* ef, struct exfat_node* node)
 
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
 
 
        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->flags &= ~EXFAT_ATTRIB_DIRTY;
+       return 0;
 }
 
 }
 
-static void erase_entry(struct exfat* ef, struct exfat_node* node)
+static bool erase_entry(struct exfat* ef, struct exfat_node* node)
 {
        cluster_t cluster = node->entry_cluster;
        off_t offset = node->entry_offset;
 {
        cluster_t cluster = node->entry_cluster;
        off_t offset = node->entry_offset;
@@ -570,18 +665,34 @@ static void erase_entry(struct exfat* ef, struct exfat_node* node)
        uint8_t entry_type;
 
        entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID;
        uint8_t entry_type;
 
        entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID;
-       exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset));
+       if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0)
+       {
+               exfat_error("failed to erase meta1 entry");
+               return false;
+       }
 
 
-       next_entry(ef, node->parent, &cluster, &offset);
+       if (!next_entry(ef, node->parent, &cluster, &offset))
+               return false;
        entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID;
        entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID;
-       exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset));
+       if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 0)
+       {
+               exfat_error("failed to erase meta2 entry");
+               return false;
+       }
 
        while (name_entries--)
        {
 
        while (name_entries--)
        {
-               next_entry(ef, node->parent, &cluster, &offset);
+               if (!next_entry(ef, node->parent, &cluster, &offset))
+                       return false;
                entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
                entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
-               exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset));
+               if (exfat_pwrite(ef->dev, &entry_type, 1,
+                               co2o(ef, cluster, offset)) < 0)
+               {
+                       exfat_error("failed to erase name entry");
+                       return false;
+               }
        }
        }
+       return true;
 }
 
 static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
@@ -591,7 +702,6 @@ 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))
                exfat_bug("attempted to shrink a file");
 
        if (!(dir->flags & EXFAT_ATTRIB_DIR))
                exfat_bug("attempted to shrink a file");
@@ -627,10 +737,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,13 +747,23 @@ 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);
+       if (!erase_entry(ef, node))
+       {
+               exfat_put_node(ef, parent);
+               return -EIO;
+       }
        exfat_update_mtime(parent);
        tree_detach(node);
        rc = shrink_directory(ef, parent, deleted_offset);
        exfat_update_mtime(parent);
        tree_detach(node);
        rc = shrink_directory(ef, parent, deleted_offset);
-       exfat_put_node(ef, parent);
-       /* file clusters will be freed when node reference counter becomes 0 */
        node->flags |= EXFAT_ATTRIB_UNLINKED;
        node->flags |= EXFAT_ATTRIB_UNLINKED;
+       if (rc != 0)
+       {
+               exfat_flush_node(ef, parent);
+               exfat_put_node(ef, parent);
+               return rc;
+       }
+       rc = exfat_flush_node(ef, parent);
+       exfat_put_node(ef, parent);
        return rc;
 }
 
        return rc;
 }
 
@@ -659,10 +776,14 @@ int exfat_unlink(struct exfat* ef, struct exfat_node* node)
 
 int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
 
 int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
+       int rc;
+
        if (!(node->flags & EXFAT_ATTRIB_DIR))
                return -ENOTDIR;
        /* check that directory is empty */
        if (!(node->flags & EXFAT_ATTRIB_DIR))
                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 +794,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 +832,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;
@@ -757,17 +878,34 @@ static int write_entry(struct exfat* ef, struct exfat_node* dir,
 
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
 
 
        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));
+       if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1),
+                       co2o(ef, cluster, offset)) < 0)
+       {
+               exfat_error("failed to write meta1 entry");
+               return -EIO;
+       }
+       if (!next_entry(ef, dir, &cluster, &offset))
+               return -EIO;
+       if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2),
+                       co2o(ef, cluster, offset)) < 0)
+       {
+               exfat_error("failed to write meta2 entry");
+               return -EIO;
+       }
        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,
        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,
-                               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));
+                               MIN(EXFAT_ENAME_MAX, EXFAT_NAME_MAX - i * EXFAT_ENAME_MAX) *
+                               sizeof(le16_t));
+               if (!next_entry(ef, dir, &cluster, &offset))
+                       return -EIO;
+               if (exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
+                               co2o(ef, cluster, offset)) < 0)
+               {
+                       exfat_error("failed to write name entry");
+                       return -EIO;
+               }
        }
 
        init_node_meta1(node, &meta1);
        }
 
        init_node_meta1(node, &meta1);
@@ -805,6 +943,12 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
                return rc;
        }
        rc = write_entry(ef, dir, name, cluster, offset, attrib);
                return rc;
        }
        rc = write_entry(ef, dir, name, cluster, offset, attrib);
+       if (rc != 0)
+       {
+               exfat_put_node(ef, dir);
+               return rc;
+       }
+       rc = exfat_flush_node(ef, dir);
        exfat_put_node(ef, dir);
        return rc;
 }
        exfat_put_node(ef, dir);
        return rc;
 }
@@ -826,7 +970,14 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        if (rc != 0)
                return 0;
        /* directories always have at least one cluster */
        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 +988,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)
 {
@@ -849,40 +1000,65 @@ static void rename_entry(struct exfat* ef, struct exfat_node* dir,
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
        int i;
 
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
        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;
        meta2.name_hash = exfat_calc_name_hash(ef, name);
        meta2.name_length = name_length;
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
 
        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);
 
-       erase_entry(ef, node);
+       if (!erase_entry(ef, node))
+               return -EIO;
 
        node->entry_cluster = new_cluster;
        node->entry_offset = new_offset;
 
 
        node->entry_cluster = new_cluster;
        node->entry_offset = new_offset;
 
-       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)
@@ -961,10 +1137,10 @@ 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])
 }
 
 void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
@@ -1016,7 +1192,7 @@ static int find_label(struct exfat* ef, cluster_t* cluster, off_t* offset)
                        return 0;
                }
 
                        return 0;
                }
 
-               if (fetch_next_entry(ef, ef->root, &it) != 0)
+               if (!fetch_next_entry(ef, ef->root, &it))
                {
                        closedir(&it);
                        return -EIO;
                {
                        closedir(&it);
                        return -EIO;
@@ -1049,7 +1225,12 @@ 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));
+       if (exfat_pwrite(ef->dev, &entry, sizeof(struct exfat_entry_label),
+                       co2o(ef, cluster, offset)) < 0)
+       {
+               exfat_error("failed to write label entry");
+               return -EIO;
+       }
+       strcpy(ef->label, label);
        return 0;
 }
        return 0;
 }