]> git.sven.stormbind.net Git - sven/exfat-utils.git/blobdiff - libexfat/node.c
Update the Homepage field in d/control.
[sven/exfat-utils.git] / libexfat / node.c
index 2ae724d78228876ccb976bd8e821e52f4bdc5e90..f3d10e0ef8d05cc1a6be8c162f919b73e1071011 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) 2009, 2010  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2015  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,17 +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))
                {
                it->cluster = exfat_next_cluster(ef, parent, it->cluster);
                if (CLUSTER_INVALID(it->cluster))
                {
-                       exfat_error("invalid cluster while reading directory");
-                       return 1;
+                       exfat_error("invalid cluster 0x%x while reading directory",
+                                       it->cluster);
+                       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)
@@ -163,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.
@@ -182,6 +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 valid_size = 0;
 
        *node = NULL;
 
 
        *node = NULL;
 
@@ -216,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();
@@ -246,18 +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);
-                       /* 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. */
-                       if (le64_to_cpu(meta2->real_size) != (*node)->size)
-                       {
-                               exfat_error("real size does not equal to size "
-                                               "(%"PRIu64" != %"PRIu64")",
-                                               le64_to_cpu(meta2->real_size), (*node)->size);
-                               goto error;
-                       }
+                       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))
                        {
@@ -269,11 +329,8 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
                                (*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
                        {
                        if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
                                (*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
                        {
-                               char buffer[EXFAT_NAME_MAX + 1];
-
-                               exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
-                               exfat_error("directory `%s' has invalid size %"PRIu64" bytes",
-                                               buffer, (*node)->size);
+                               exfat_error("directory has invalid size %"PRIu64" bytes",
+                                               (*node)->size);
                                goto error;
                        }
                        --continuations;
                                goto error;
                        }
                        --continuations;
@@ -288,17 +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)
                        {
-                               if (actual_checksum != reference_checksum)
-                               {
-                                       exfat_error("invalid checksum (0x%hx != 0x%hx)",
-                                                       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 */
                        }
@@ -310,7 +367,8 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        upcase = (const struct exfat_entry_upcase*) entry;
                        if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
                        {
                        upcase = (const struct exfat_entry_upcase*) entry;
                        if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
                        {
-                               exfat_error("invalid cluster in upcase table");
+                               exfat_error("invalid cluster 0x%x in upcase table",
+                                               le32_to_cpu(upcase->start_cluster));
                                goto error;
                        }
                        if (le64_to_cpu(upcase->size) == 0 ||
                                goto error;
                        }
                        if (le64_to_cpu(upcase->size) == 0 ||
@@ -331,30 +389,39 @@ 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:
                        bitmap = (const struct exfat_entry_bitmap*) entry;
                        break;
 
                case EXFAT_ENTRY_BITMAP:
                        bitmap = (const struct exfat_entry_bitmap*) entry;
-                       if (CLUSTER_INVALID(le32_to_cpu(bitmap->start_cluster)))
+                       ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
+                       if (CLUSTER_INVALID(ef->cmap.start_cluster))
                        {
                        {
-                               exfat_error("invalid cluster in clusters bitmap");
+                               exfat_error("invalid cluster 0x%x in clusters bitmap",
+                                               ef->cmap.start_cluster);
                                goto error;
                        }
                        ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
                                EXFAT_FIRST_DATA_CLUSTER;
                                goto error;
                        }
                        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;
                        }
                                goto error;
                        }
-                       ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
                        /* FIXME bitmap can be rather big, up to 512 MB */
                        ef->cmap.chunk_size = ef->cmap.size;
                        /* FIXME bitmap can be rather big, up to 512 MB */
                        ef->cmap.chunk_size = ef->cmap.size;
-                       ef->cmap.chunk = malloc(le64_to_cpu(bitmap->size));
+                       ef->cmap.chunk = malloc(BMAP_SIZE(ef->cmap.chunk_size));
                        if (ef->cmap.chunk == NULL)
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
                        if (ef->cmap.chunk == NULL)
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
@@ -363,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:
@@ -375,20 +449,30 @@ 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;
 
                default:
                                goto error;
                        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 0x%hhx", entry->type);
+                               exfat_error("unknown entry type %#hhx", entry->type);
                                goto error;
                        }
                                goto error;
                        }
+                       /* optional entry, warn and skip */
+                       exfat_warn("unknown entry type %#hhx", entry->type);
+                       if (continuations == 0)
+                       {
+                               exfat_error("unexpected continuation");
+                               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 */
@@ -443,28 +527,55 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
        return 0;
 }
 
        return 0;
 }
 
+static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
+{
+       node->parent = dir;
+       if (dir->child)
+       {
+               dir->child->prev = node;
+               node->next = dir->child;
+       }
+       dir->child = node;
+}
+
+static void tree_detach(struct exfat_node* node)
+{
+       if (node->prev)
+               node->prev->next = node->next;
+       else /* this is the first node in the list */
+               node->parent->child = node->next;
+       if (node->next)
+               node->next->prev = node->prev;
+       node->parent = NULL;
+       node->prev = NULL;
+       node->next = NULL;
+}
+
 static void reset_cache(struct exfat* ef, struct exfat_node* node)
 {
 static void reset_cache(struct exfat* ef, struct exfat_node* node)
 {
-       struct exfat_node* child;
-       struct exfat_node* next;
+       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
 
 
-       for (child = node->child; child; child = next)
+       while (node->child)
        {
        {
-               reset_cache(ef, child);
-               next = child->next;
-               free(child);
+               struct exfat_node* p = node->child;
+               reset_cache(ef, p);
+               tree_detach(p);
+               free(p);
        }
        }
+       node->flags &= ~EXFAT_ATTRIB_CACHED;
        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, sizeof(buffer) - 1);
+               exfat_warn("non-zero reference counter (%d) for '%s'",
                                node->references, buffer);
        }
                                node->references, buffer);
        }
-       while (node->references--)
+       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);
                exfat_put_node(ef, node);
-       node->child = NULL;
-       node->flags &= ~EXFAT_ATTRIB_CACHED;
 }
 
 void exfat_reset_cache(struct exfat* ef)
 }
 
 void exfat_reset_cache(struct exfat* ef)
@@ -472,16 +583,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;
@@ -489,29 +608,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 */
@@ -521,13 +652,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;
@@ -535,42 +675,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));
-
-       while (name_entries--)
+       if (exfat_pwrite(ef->dev, &entry_type, 1, co2o(ef, cluster, offset)) < 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_error("failed to erase meta2 entry");
+               return false;
        }
        }
-}
 
 
-static void tree_detach(struct exfat_node* node)
-{
-       if (node->prev)
-               node->prev->next = node->next;
-       else /* this is the first node in the list */
-               node->parent->child = node->next;
-       if (node->next)
-               node->next->prev = node->prev;
-       node->parent = NULL;
-       node->prev = NULL;
-       node->next = NULL;
-}
-
-static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
-{
-       node->parent = dir;
-       if (dir->child)
+       while (name_entries--)
        {
        {
-               dir->child->prev = node;
-               node->next = dir->child;
+               if (!next_entry(ef, node->parent, &cluster, &offset))
+                       return false;
+               entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
+               if (exfat_pwrite(ef->dev, &entry_type, 1,
+                               co2o(ef, cluster, offset)) < 0)
+               {
+                       exfat_error("failed to erase name entry");
+                       return false;
+               }
        }
        }
-       dir->child = node;
+       return true;
 }
 
 static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
@@ -580,7 +712,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");
@@ -616,10 +747,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)
@@ -629,13 +757,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;
 }
 
@@ -648,10 +786,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);
@@ -662,7 +804,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,
@@ -700,7 +842,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;
@@ -746,17 +888,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);
@@ -794,6 +953,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;
 }
@@ -808,14 +973,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);
@@ -826,7 +998,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)
 {
@@ -838,40 +1010,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)
@@ -894,29 +1091,52 @@ 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->flags & 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)
        {
        if (existing != NULL)
        {
-               if (existing->flags & EXFAT_ATTRIB_DIR)
+               /* remove target if it's not the same node as source */
+               if (existing != node)
                {
                {
-                       if (node->flags & EXFAT_ATTRIB_DIR)
-                               rc = exfat_rmdir(ef, existing);
+                       if (existing->flags & EXFAT_ATTRIB_DIR)
+                       {
+                               if (node->flags & EXFAT_ATTRIB_DIR)
+                                       rc = exfat_rmdir(ef, existing);
+                               else
+                                       rc = -ENOTDIR;
+                       }
                        else
                        else
-                               rc = -ENOTDIR;
+                       {
+                               if (!(node->flags & EXFAT_ATTRIB_DIR))
+                                       rc = exfat_unlink(ef, existing);
+                               else
+                                       rc = -EISDIR;
+                       }
+                       exfat_put_node(ef, existing);
+                       if (rc != 0)
+                       {
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return rc;
+                       }
                }
                else
                }
                else
-               {
-                       if (!(node->flags & EXFAT_ATTRIB_DIR))
-                               rc = exfat_unlink(ef, existing);
-                       else
-                               rc = -EISDIR;
-               }
-               exfat_put_node(ef, existing);
-               if (rc != 0)
-               {
-                       exfat_put_node(ef, dir);
-                       exfat_put_node(ef, node);
-                       return rc;
-               }
+                       exfat_put_node(ef, existing);
        }
 
        rc = find_slot(ef, dir, &cluster, &offset,
        }
 
        rc = find_slot(ef, dir, &cluster, &offset,
@@ -927,10 +1147,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])
@@ -982,7 +1202,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;
@@ -1015,7 +1235,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;
 }