OSDN Git Service

Rename node field flags to attrib.
[android-x86/external-exfat.git] / libexfat / node.c
index 4020874..d64ae29 100644 (file)
@@ -3,7 +3,7 @@
        exFAT file system implementation library.
 
        Free exFAT implementation.
-       Copyright (C) 2010-2013  Andrew Nayenko
+       Copyright (C) 2010-2016  Andrew Nayenko
 
        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
@@ -30,7 +30,6 @@ struct iterator
 {
        cluster_t cluster;
        off_t offset;
-       int contiguous;
        char* chunk;
 };
 
@@ -44,20 +43,20 @@ struct exfat_node* exfat_get_node(struct exfat_node* node)
 
 void exfat_put_node(struct exfat* ef, struct exfat_node* node)
 {
-       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
 
        --node->references;
        if (node->references < 0)
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
-               exfat_bug("reference counter of `%s' is below zero", buffer);
+               exfat_get_name(node, buffer);
+               exfat_bug("reference counter of '%s' is below zero", buffer);
        }
        else if (node->references == 0 && node != ef->root)
        {
-               if (node->flags & EXFAT_ATTRIB_DIRTY)
+               if (node->is_dirty)
                {
-                       exfat_get_name(node, buffer, sizeof(buffer) - 1);
-                       exfat_warn("dirty node `%s' with zero references", buffer);
+                       exfat_get_name(node, buffer);
+                       exfat_warn("dirty node '%s' with zero references", buffer);
                }
        }
 }
@@ -74,7 +73,7 @@ int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node)
                exfat_bug("unable to cleanup a node with %d references",
                                node->references);
 
-       if (node->flags & EXFAT_ATTRIB_UNLINKED)
+       if (node->is_unlinked)
        {
                /* free all clusters and node structure itself */
                rc = exfat_truncate(ef, node, 0, true);
@@ -95,21 +94,35 @@ static off_t co2o(struct exfat* ef, cluster_t cluster, off_t offset)
 static int opendir(struct exfat* ef, const struct exfat_node* dir,
                struct iterator* it)
 {
-       if (!(dir->flags & EXFAT_ATTRIB_DIR))
-               exfat_bug("not a directory");
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+       {
+               exfat_get_name(dir, buffer);
+               exfat_bug("'%s' is not a directory", buffer);
+       }
+       if (CLUSTER_INVALID(dir->start_cluster))
+       {
+               exfat_get_name(dir, buffer);
+               exfat_error("'%s' directory starts with invalid cluster %#x", buffer,
+                               dir->start_cluster);
+               return -EIO;
+       }
        it->cluster = dir->start_cluster;
        it->offset = 0;
-       it->contiguous = IS_CONTIGUOUS(*dir);
        it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
        if (it->chunk == NULL)
        {
-               exfat_error("out of memory");
+               exfat_error("failed to allocate memory for directory cluster");
                return -ENOMEM;
        }
        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);
+               free(it->chunk);
+               exfat_get_name(dir, buffer);
+               exfat_error("failed to read '%s' directory cluster %#x", buffer,
+                               it->cluster);
                return -EIO;
        }
        return 0;
@@ -119,12 +132,11 @@ static void closedir(struct iterator* it)
 {
        it->cluster = 0;
        it->offset = 0;
-       it->contiguous = 0;
        free(it->chunk);
        it->chunk = NULL;
 }
 
-static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
+static bool fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
                struct iterator* it)
 {
        /* move iterator to the next entry in the directory */
@@ -135,23 +147,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)
-                       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);
-                       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 1;
+                       return false;
                }
        }
-       return 0;
+       return true;
 }
 
 static struct exfat_node* allocate_node(void)
@@ -169,7 +181,7 @@ static struct exfat_node* allocate_node(void)
 static void init_node_meta1(struct exfat_node* node,
                const struct exfat_entry_meta1* meta1)
 {
-       node->flags = le16_to_cpu(meta1->attrib);
+       node->attrib = le16_to_cpu(meta1->attrib);
        node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime,
                        meta1->mtime_cs);
        /* there is no centiseconds field for atime */
@@ -182,8 +194,7 @@ static void init_node_meta2(struct exfat_node* node,
        node->size = le64_to_cpu(meta2->size);
        node->start_cluster = le32_to_cpu(meta2->start_cluster);
        node->fptr_cluster = node->start_cluster;
-       if (meta2->flags & EXFAT_FLAG_CONTIGUOUS)
-               node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+       node->is_contiguous = ((meta2->flags & EXFAT_FLAG_CONTIGUOUS) != 0);
 }
 
 static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
@@ -194,9 +205,10 @@ static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
 }
 
 static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
-               uint16_t reference_checksum, uint64_t real_size)
+               uint16_t reference_checksum, uint64_t valid_size, int cluster_size)
 {
-       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+       bool ret = true;
 
        /*
           Validate checksum first. If it's invalid all other fields probably
@@ -204,25 +216,86 @@ static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
        */
        if (actual_checksum != reference_checksum)
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
-               exfat_error("`%s' has invalid checksum (%#hx != %#hx)", buffer,
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
                                actual_checksum, reference_checksum);
-               return false;
+               ret = false;
        }
 
        /*
-          It's unclear what is real_size field needed for. It usually equals to
-          the size but may contain any value less than size (including 0).
+          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 (real_size > node->size)
+       if (valid_size > node->size)
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
-               exfat_error("`%s' has real size (%"PRIu64") greater than size "
-                               "(%"PRIu64")", buffer, real_size, node->size);
-               return false;
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has valid size (%"PRIu64") greater than size "
+                               "(%"PRIu64")", buffer, valid_size, node->size);
+               ret = false;
        }
 
-       return true;
+       /*
+          Empty file must have zero start cluster. Non-empty file must start
+          with a valid cluster. Directories cannot be empty (i.e. must always
+          have a valid start cluster), but we will check this later in opendir()
+          to give user a chance to read current directory.
+       */
+       if (node->size == 0 && node->start_cluster != EXFAT_CLUSTER_FREE)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but start cluster is %#x", buffer,
+                               node->start_cluster);
+               ret = false;
+       }
+       if (node->size > 0 && CLUSTER_INVALID(node->start_cluster))
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' points to invalid cluster %#x", buffer,
+                               node->start_cluster);
+               ret = false;
+       }
+
+       /* Empty file or directory must be marked as non-contiguous. */
+       if (node->size == 0 && node->is_contiguous)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but marked as contiguous (%#hx)", buffer,
+                               node->attrib);
+               ret = false;
+       }
+
+       /* Directory size must be aligned on at cluster boundary. */
+       if ((node->attrib & EXFAT_ATTRIB_DIR) && node->size % cluster_size != 0)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' directory size %"PRIu64" is not divisible by %d", buffer,
+                               node->size, cluster_size);
+               ret = false;
+       }
+
+       return ret;
+}
+
+static void decompress_upcase(uint16_t* output, const le16_t* source,
+               size_t size)
+{
+       size_t si;
+       size_t oi;
+
+       for (oi = 0; oi < EXFAT_UPCASE_CHARS; oi++)
+               output[oi] = oi;
+
+       for (si = 0, oi = 0; si < size && oi < EXFAT_UPCASE_CHARS; si++)
+       {
+               uint16_t ch = le16_to_cpu(source[si]);
+
+               if (ch == 0xffff && si + 1 < size)      /* indicates a run */
+                       oi += le16_to_cpu(source[++si]);
+               else
+                       output[oi++] = ch;
+       }
 }
 
 /*
@@ -244,7 +317,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
        le16_t* namep = NULL;
        uint16_t reference_checksum = 0;
        uint16_t actual_checksum = 0;
-       uint64_t real_size = 0;
+       uint64_t valid_size = 0;
+       uint64_t upcase_size = 0;
+       le16_t* upcase_comp = NULL;
 
        *node = NULL;
 
@@ -315,22 +390,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);
-                       real_size = le64_to_cpu(meta2->real_size);
-                       /* empty files must be marked as non-contiguous */
-                       if ((*node)->size == 0 && (meta2->flags & EXFAT_FLAG_CONTIGUOUS))
-                       {
-                               exfat_error("empty file marked as contiguous (0x%hhx)",
-                                               meta2->flags);
-                               goto error;
-                       }
-                       /* directories must be aligned on at cluster boundary */
-                       if (((*node)->flags & EXFAT_ATTRIB_DIR) &&
-                               (*node)->size % CLUSTER_SIZE(*ef->sb) != 0)
-                       {
-                               exfat_error("directory has invalid size %"PRIu64" bytes",
-                                               (*node)->size);
-                               goto error;
-                       }
+                       valid_size = le64_to_cpu(meta2->valid_size);
                        --continuations;
                        break;
 
@@ -351,9 +411,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (--continuations == 0)
                        {
                                if (!check_node(*node, actual_checksum, reference_checksum,
-                                               real_size))
+                                               valid_size, CLUSTER_SIZE(*ef->sb)))
                                        goto error;
-                               if (fetch_next_entry(ef, parent, it) != 0)
+                               if (!fetch_next_entry(ef, parent, it))
                                        goto error;
                                return 0; /* entry completed */
                        }
@@ -369,33 +429,48 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                                le32_to_cpu(upcase->start_cluster));
                                goto error;
                        }
-                       if (le64_to_cpu(upcase->size) == 0 ||
-                               le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
-                               le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0)
+                       upcase_size = le64_to_cpu(upcase->size);
+                       if (upcase_size == 0 ||
+                               upcase_size > EXFAT_UPCASE_CHARS * sizeof(uint16_t) ||
+                               upcase_size % sizeof(uint16_t) != 0)
                        {
                                exfat_error("bad upcase table size (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
+                                               upcase_size);
                                goto error;
                        }
-                       ef->upcase = malloc(le64_to_cpu(upcase->size));
-                       if (ef->upcase == NULL)
+                       upcase_comp = malloc(upcase_size);
+                       if (upcase_comp == NULL)
                        {
                                exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
+                                               upcase_size);
                                rc = -ENOMEM;
                                goto error;
                        }
-                       ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
 
-                       if (exfat_pread(ef->dev, ef->upcase, le64_to_cpu(upcase->size),
+                       /* read compressed upcase table */
+                       if (exfat_pread(ef->dev, upcase_comp, upcase_size,
                                        exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0)
                        {
+                               free(upcase_comp);
                                exfat_error("failed to read upper case table "
                                                "(%"PRIu64" bytes starting at cluster %#x)",
-                                               le64_to_cpu(upcase->size),
+                                               upcase_size,
                                                le32_to_cpu(upcase->start_cluster));
                                goto error;
                        }
+
+                       /* decompress upcase table */
+                       ef->upcase = calloc(EXFAT_UPCASE_CHARS, sizeof(uint16_t));
+                       if (ef->upcase == NULL)
+                       {
+                               free(upcase_comp);
+                               exfat_error("failed to allocate decompressed upcase table");
+                               rc = -ENOMEM;
+                               goto error;
+                       }
+                       decompress_upcase(ef->upcase, upcase_comp,
+                                       upcase_size / sizeof(uint16_t));
+                       free(upcase_comp);
                        break;
 
                case EXFAT_ENTRY_BITMAP:
@@ -447,20 +522,30 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                goto error;
                        }
                        if (utf16_to_utf8(ef->label, label->name,
-                                               sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0)
+                                               sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
                                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 %#hhx", entry->type);
+                               goto error;
+                       }
+                       /* optional entry, warn and skip */
+                       exfat_warn("unknown entry type %#hhx", entry->type);
+                       if (continuations == 0)
                        {
-                               exfat_error("unknown entry type 0x%hhx", entry->type);
+                               exfat_error("unexpected continuation");
                                goto error;
                        }
+                       --continuations;
                        break;
                }
 
-               if (fetch_next_entry(ef, parent, it) != 0)
+               if (!fetch_next_entry(ef, parent, it))
                        goto error;
        }
        /* we never reach here */
@@ -478,7 +563,7 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
-       if (dir->flags & EXFAT_ATTRIB_CACHED)
+       if (dir->is_cached)
                return 0; /* already cached */
 
        rc = opendir(ef, dir, &it);
@@ -511,7 +596,7 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
                return rc;
        }
 
-       dir->flags |= EXFAT_ATTRIB_CACHED;
+       dir->is_cached = true;
        return 0;
 }
 
@@ -541,7 +626,7 @@ static void tree_detach(struct exfat_node* node)
 
 static void reset_cache(struct exfat* ef, struct exfat_node* node)
 {
-       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
 
        while (node->child)
        {
@@ -550,17 +635,17 @@ static void reset_cache(struct exfat* ef, struct exfat_node* node)
                tree_detach(p);
                free(p);
        }
-       node->flags &= ~EXFAT_ATTRIB_CACHED;
+       node->is_cached = false;
        if (node->references != 0)
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
-               exfat_warn("non-zero reference counter (%d) for `%s'",
+               exfat_get_name(node, buffer);
+               exfat_warn("non-zero reference counter (%d) for '%s'",
                                node->references, buffer);
        }
-       if (node != ef->root && (node->flags & EXFAT_ATTRIB_DIRTY))
+       if (node != ef->root && node->is_dirty)
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
-               exfat_bug("node `%s' is dirty", buffer);
+               exfat_get_name(node, buffer);
+               exfat_bug("node '%s' is dirty", buffer);
        }
        while (node->references)
                exfat_put_node(ef, node);
@@ -596,7 +681,7 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        struct exfat_entry_meta1 meta1;
        struct exfat_entry_meta2 meta2;
 
-       if (!(node->flags & EXFAT_ATTRIB_DIRTY))
+       if (!node->is_dirty)
                return 0; /* no need to flush */
 
        if (ef->ro)
@@ -619,7 +704,7 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        }
        if (meta1.type != EXFAT_ENTRY_FILE)
                exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
-       meta1.attrib = cpu_to_le16(node->flags);
+       meta1.attrib = cpu_to_le16(node->attrib);
        exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime, &meta1.mtime_cs);
        exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime, NULL);
 
@@ -630,11 +715,11 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        }
        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 */
-       if (node->size != 0 && IS_CONTIGUOUS(*node))
+       if (node->size != 0 && node->is_contiguous)
                meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
        /* name hash remains unchanged, no need to recalculate it */
 
@@ -651,8 +736,8 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
                return -EIO;
        }
 
-       node->flags &= ~EXFAT_ATTRIB_DIRTY;
-       return 0;
+       node->is_dirty = false;
+       return exfat_flush(ef);
 }
 
 static bool erase_entry(struct exfat* ef, struct exfat_node* node)
@@ -701,9 +786,9 @@ static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
        uint64_t entries = 0;
        uint64_t new_size;
 
-       if (!(dir->flags & EXFAT_ATTRIB_DIR))
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
                exfat_bug("attempted to shrink a file");
-       if (!(dir->flags & EXFAT_ATTRIB_CACHED))
+       if (!dir->is_cached)
                exfat_bug("attempted to shrink uncached directory");
 
        for (last_node = node = dir->child; node; node = node->next)
@@ -753,7 +838,7 @@ static int delete(struct exfat* ef, struct exfat_node* node)
        exfat_update_mtime(parent);
        tree_detach(node);
        rc = shrink_directory(ef, parent, deleted_offset);
-       node->flags |= EXFAT_ATTRIB_UNLINKED;
+       node->is_unlinked = true;
        if (rc != 0)
        {
                exfat_flush_node(ef, parent);
@@ -767,7 +852,7 @@ static int delete(struct exfat* ef, struct exfat_node* node)
 
 int exfat_unlink(struct exfat* ef, struct exfat_node* node)
 {
-       if (node->flags & EXFAT_ATTRIB_DIR)
+       if (node->attrib & EXFAT_ATTRIB_DIR)
                return -EISDIR;
        return delete(ef, node);
 }
@@ -776,7 +861,7 @@ int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
        int rc;
 
-       if (!(node->flags & EXFAT_ATTRIB_DIR))
+       if (!(node->attrib & EXFAT_ATTRIB_DIR))
                return -ENOTDIR;
        /* check that directory is empty */
        rc = exfat_cache_directory(ef, node);
@@ -830,7 +915,7 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
                                return rc;
                        }
                }
-               if (fetch_next_entry(ef, dir, &it) != 0)
+               if (!fetch_next_entry(ef, dir, &it))
                {
                        closedir(&it);
                        return -EIO;
@@ -840,7 +925,7 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
        return 0;
 }
 
-static int write_entry(struct exfat* ef, struct exfat_node* dir,
+static int commit_entry(struct exfat* ef, struct exfat_node* dir,
                const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
 {
        struct exfat_node* node;
@@ -871,7 +956,7 @@ static int write_entry(struct exfat* ef, struct exfat_node* dir,
        meta2.type = EXFAT_ENTRY_FILE_INFO;
        meta2.flags = EXFAT_FLAG_ALWAYS1;
        meta2.name_length = name_length;
-       meta2.name_hash = exfat_calc_name_hash(ef, node->name);
+       meta2.name_hash = exfat_calc_name_hash(ef, node->name, name_length);
        meta2.start_cluster = cpu_to_le32(EXFAT_CLUSTER_FREE);
 
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
@@ -940,7 +1025,7 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
                exfat_put_node(ef, dir);
                return rc;
        }
-       rc = write_entry(ef, dir, name, cluster, offset, attrib);
+       rc = commit_entry(ef, dir, name, cluster, offset, attrib);
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
@@ -961,7 +1046,7 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        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);
@@ -1013,7 +1098,7 @@ static int rename_entry(struct exfat* ef, struct exfat_node* dir,
                return -EIO;
        }
        meta1.continuations = 1 + name_entries;
-       meta2.name_hash = exfat_calc_name_hash(ef, name);
+       meta2.name_hash = exfat_calc_name_hash(ef, name, name_length);
        meta2.name_length = name_length;
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
 
@@ -1081,7 +1166,7 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
        }
 
        /* check that target is not a subdirectory of the source */
-       if (node->flags & EXFAT_ATTRIB_DIR)
+       if (node->attrib & EXFAT_ATTRIB_DIR)
        {
                struct exfat_node* p;
 
@@ -1101,16 +1186,16 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                /* remove target if it's not the same node as source */
                if (existing != node)
                {
-                       if (existing->flags & EXFAT_ATTRIB_DIR)
+                       if (existing->attrib & EXFAT_ATTRIB_DIR)
                        {
-                               if (node->flags & EXFAT_ATTRIB_DIR)
+                               if (node->attrib & EXFAT_ATTRIB_DIR)
                                        rc = exfat_rmdir(ef, existing);
                                else
                                        rc = -ENOTDIR;
                        }
                        else
                        {
-                               if (!(node->flags & EXFAT_ATTRIB_DIR))
+                               if (!(node->attrib & EXFAT_ATTRIB_DIR))
                                        rc = exfat_unlink(ef, existing);
                                else
                                        rc = -EISDIR;
@@ -1118,6 +1203,16 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                        exfat_put_node(ef, existing);
                        if (rc != 0)
                        {
+                               /* free clusters even if something went wrong; overwise they
+                                  will be just lost */
+                               exfat_cleanup_node(ef, existing);
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return rc;
+                       }
+                       rc = exfat_cleanup_node(ef, existing);
+                       if (rc != 0)
+                       {
                                exfat_put_node(ef, dir);
                                exfat_put_node(ef, node);
                                return rc;
@@ -1145,19 +1240,19 @@ void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
 {
        node->atime = tv[0].tv_sec;
        node->mtime = tv[1].tv_sec;
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 void exfat_update_atime(struct exfat_node* node)
 {
        node->atime = time(NULL);
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 void exfat_update_mtime(struct exfat_node* node)
 {
        node->mtime = time(NULL);
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 const char* exfat_get_label(struct exfat* ef)
@@ -1190,7 +1285,7 @@ static int find_label(struct exfat* ef, cluster_t* cluster, off_t* offset)
                        return 0;
                }
 
-               if (fetch_next_entry(ef, ef->root, &it) != 0)
+               if (!fetch_next_entry(ef, ef->root, &it))
                {
                        closedir(&it);
                        return -EIO;
@@ -1207,7 +1302,7 @@ int exfat_set_label(struct exfat* ef, const char* label)
        struct exfat_entry_label entry;
 
        memset(label_utf16, 0, sizeof(label_utf16));
-       rc = utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX, strlen(label));
+       rc = utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX + 1, strlen(label));
        if (rc != 0)
                return rc;