OSDN Git Service

Generic I/O for directories: switch erase_entry().
[android-x86/external-exfat.git] / libexfat / node.c
index ddbb637..44f4ae3 100644 (file)
@@ -2,11 +2,12 @@
        node.c (09.10.09)
        exFAT file system implementation library.
 
        node.c (09.10.09)
        exFAT file system implementation library.
 
-       Copyright (C) 2009, 2010  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2016  Andrew Nayenko
 
 
-       This program is free software: you can redistribute it and/or modify
+       This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        it under the terms of the GNU General Public License as published by
-       the Free Software Foundation, either version 3 of the License, or
+       the Free Software Foundation, either version 2 of the License, or
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
        (at your option) any later version.
 
        This program is distributed in the hope that it will be useful,
@@ -14,8 +15,9 @@
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
 
-       You should have received a copy of the GNU General Public License
-       along with this program.  If not, see <http://www.gnu.org/licenses/>.
+       You should have received a copy of the GNU General Public License along
+       with this program; if not, write to the Free Software Foundation, Inc.,
+       51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
 #include "exfat.h"
 */
 
 #include "exfat.h"
@@ -28,7 +30,6 @@ struct iterator
 {
        cluster_t cluster;
        off_t offset;
 {
        cluster_t cluster;
        off_t offset;
-       int contiguous;
        char* chunk;
 };
 
        char* chunk;
 };
 
@@ -42,44 +43,88 @@ struct exfat_node* exfat_get_node(struct exfat_node* node)
 
 void exfat_put_node(struct exfat* ef, struct exfat_node* node)
 {
 
 void exfat_put_node(struct exfat* ef, struct exfat_node* node)
 {
-       if (--node->references < 0)
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
+       --node->references;
+       if (node->references < 0)
        {
        {
-               char buffer[EXFAT_NAME_MAX + 1];
-               exfat_get_name(node, buffer, EXFAT_NAME_MAX);
-               exfat_bug("reference counter of `%s' is below zero", buffer);
+               exfat_get_name(node, buffer);
+               exfat_bug("reference counter of '%s' is below zero", buffer);
        }
        }
-
-       if (node->references == 0)
+       else if (node->references == 0 && node != ef->root)
        {
        {
-               if (node->flags & EXFAT_ATTRIB_DIRTY)
-                       exfat_flush_node(ef, node);
-               if (node->flags & EXFAT_ATTRIB_UNLINKED)
+               if (node->is_dirty)
                {
                {
-                       /* free all clusters and node structure itself */
-                       exfat_truncate(ef, node, 0);
-                       free(node);
+                       exfat_get_name(node, buffer);
+                       exfat_warn("dirty node '%s' with zero references", buffer);
                }
                }
-               if (ef->cmap.dirty)
-                       exfat_flush_cmap(ef);
        }
 }
 
        }
 }
 
+/**
+ * This function must be called on rmdir and unlink (after the last
+ * exfat_put_node()) to free clusters.
+ */
+int exfat_cleanup_node(struct exfat* ef, struct exfat_node* node)
+{
+       int rc = 0;
+
+       if (node->references != 0)
+               exfat_bug("unable to cleanup a node with %d references",
+                               node->references);
+
+       if (node->is_unlinked)
+       {
+               /* free all clusters and node structure itself */
+               rc = exfat_truncate(ef, node, 0, true);
+               /* free the node even in case of error or its memory will be lost */
+               free(node);
+       }
+       return rc;
+}
+
+/**
+ * Cluster + offset from the beginning of the directory to absolute offset.
+ */
+static off_t co2o(struct exfat* ef, cluster_t cluster, off_t offset)
+{
+       return exfat_c2o(ef, cluster) + offset % CLUSTER_SIZE(*ef->sb);
+}
+
 static int opendir(struct exfat* ef, const struct exfat_node* dir,
                struct iterator* it)
 {
 static int opendir(struct exfat* ef, const struct exfat_node* dir,
                struct iterator* it)
 {
-       if (!(dir->flags & EXFAT_ATTRIB_DIR))
-               exfat_bug("not a directory");
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+       {
+               exfat_get_name(dir, buffer);
+               exfat_bug("'%s' is not a directory", buffer);
+       }
+       if (CLUSTER_INVALID(dir->start_cluster))
+       {
+               exfat_get_name(dir, buffer);
+               exfat_error("'%s' directory starts with invalid cluster %#x", buffer,
+                               dir->start_cluster);
+               return -EIO;
+       }
        it->cluster = dir->start_cluster;
        it->offset = 0;
        it->cluster = dir->start_cluster;
        it->offset = 0;
-       it->contiguous = IS_CONTIGUOUS(*dir);
        it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
        if (it->chunk == NULL)
        {
        it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
        if (it->chunk == NULL)
        {
-               exfat_error("out of memory");
+               exfat_error("failed to allocate memory for directory cluster");
                return -ENOMEM;
        }
                return -ENOMEM;
        }
-       exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
-                       exfat_c2o(ef, it->cluster), ef->fd);
+       if (exfat_pread(ef->dev, it->chunk, CLUSTER_SIZE(*ef->sb),
+                       exfat_c2o(ef, it->cluster)) < 0)
+       {
+               free(it->chunk);
+               exfat_get_name(dir, buffer);
+               exfat_error("failed to read '%s' directory cluster %#x", buffer,
+                               it->cluster);
+               return -EIO;
+       }
        return 0;
 }
 
        return 0;
 }
 
@@ -87,12 +132,11 @@ static void closedir(struct iterator* it)
 {
        it->cluster = 0;
        it->offset = 0;
 {
        it->cluster = 0;
        it->offset = 0;
-       it->contiguous = 0;
        free(it->chunk);
        it->chunk = NULL;
 }
 
        free(it->chunk);
        it->chunk = NULL;
 }
 
-static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
+static bool fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
                struct iterator* it)
 {
        /* move iterator to the next entry in the directory */
                struct iterator* it)
 {
        /* move iterator to the next entry in the directory */
@@ -100,16 +144,66 @@ static int fetch_next_entry(struct exfat* ef, const struct exfat_node* parent,
        /* fetch the next cluster if needed */
        if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
        {
        /* fetch the next cluster if needed */
        if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
        {
+               /* reached the end of directory; the caller should check this
+                  condition too */
+               if (it->offset >= parent->size)
+                       return 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_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
-                               exfat_c2o(ef, it->cluster), ef->fd);
        }
        }
-       return 0;
+       return true;
+}
+
+static int read_entries(struct exfat* ef, struct exfat_node* dir,
+               struct exfat_entry* entries, int n, off_t offset)
+{
+       ssize_t size;
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+               exfat_bug("attempted to read entries from a file");
+
+       size = exfat_generic_pread(ef, dir, entries,
+                       sizeof(struct exfat_entry[n]), offset);
+       if (size == sizeof(struct exfat_entry[n]))
+               return 0; /* success */
+       if (size == 0)
+               return -ENOENT;
+       if (size < 0)
+               return -EIO;
+       exfat_error("read %zd bytes instead of %zu bytes", size,
+                       sizeof(struct exfat_entry[n]));
+       return -EIO;
+}
+
+static int write_entries(struct exfat* ef, struct exfat_node* dir,
+               const struct exfat_entry* entries, int n, off_t offset)
+{
+       ssize_t size;
+
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+               exfat_bug("attempted to write entries into a file");
+
+       size = exfat_generic_pwrite(ef, dir, entries,
+                       sizeof(struct exfat_entry[n]), offset);
+       if (size == sizeof(struct exfat_entry[n]))
+               return 0; /* success */
+       if (size < 0)
+               return -EIO;
+       exfat_error("wrote %zd bytes instead of %zu bytes", size,
+                       sizeof(struct exfat_entry[n]));
+       return -EIO;
 }
 
 static struct exfat_node* allocate_node(void)
 }
 
 static struct exfat_node* allocate_node(void)
@@ -127,9 +221,12 @@ static struct exfat_node* allocate_node(void)
 static void init_node_meta1(struct exfat_node* node,
                const struct exfat_entry_meta1* meta1)
 {
 static void init_node_meta1(struct exfat_node* node,
                const struct exfat_entry_meta1* meta1)
 {
-       node->flags = le16_to_cpu(meta1->attrib);
-       node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime);
-       node->atime = exfat_exfat2unix(meta1->adate, meta1->atime);
+       node->attrib = le16_to_cpu(meta1->attrib);
+       node->continuations = meta1->continuations;
+       node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime,
+                       meta1->mtime_cs);
+       /* there is no centiseconds field for atime */
+       node->atime = exfat_exfat2unix(meta1->adate, meta1->atime, 0);
 }
 
 static void init_node_meta2(struct exfat_node* node,
 }
 
 static void init_node_meta2(struct exfat_node* node,
@@ -138,8 +235,108 @@ static void init_node_meta2(struct exfat_node* node,
        node->size = le64_to_cpu(meta2->size);
        node->start_cluster = le32_to_cpu(meta2->start_cluster);
        node->fptr_cluster = node->start_cluster;
        node->size = le64_to_cpu(meta2->size);
        node->start_cluster = le32_to_cpu(meta2->start_cluster);
        node->fptr_cluster = node->start_cluster;
-       if (meta2->flag == EXFAT_FLAG_CONTIGUOUS)
-               node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+       node->is_contiguous = ((meta2->flags & EXFAT_FLAG_CONTIGUOUS) != 0);
+}
+
+static const struct exfat_entry* get_entry_ptr(const struct exfat* ef,
+               const struct iterator* it)
+{
+       return (const struct exfat_entry*)
+                       (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
+}
+
+static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
+               uint16_t reference_checksum, uint64_t valid_size, int cluster_size)
+{
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+       bool ret = true;
+
+       /*
+          Validate checksum first. If it's invalid all other fields probably
+          contain just garbage.
+       */
+       if (actual_checksum != reference_checksum)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
+                               actual_checksum, reference_checksum);
+               ret = false;
+       }
+
+       /*
+          exFAT does not support sparse files but allows files with uninitialized
+          clusters. For such files valid_size means initialized data size and
+          cannot be greater than file size. See SetFileValidData() function
+          description in MSDN.
+       */
+       if (valid_size > node->size)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' has valid size (%"PRIu64") greater than size "
+                               "(%"PRIu64")", buffer, valid_size, node->size);
+               ret = false;
+       }
+
+       /*
+          Empty file must have zero start cluster. Non-empty file must start
+          with a valid cluster. Directories cannot be empty (i.e. must always
+          have a valid start cluster), but we will check this later in opendir()
+          to give user a chance to read current directory.
+       */
+       if (node->size == 0 && node->start_cluster != EXFAT_CLUSTER_FREE)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but start cluster is %#x", buffer,
+                               node->start_cluster);
+               ret = false;
+       }
+       if (node->size > 0 && CLUSTER_INVALID(node->start_cluster))
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' points to invalid cluster %#x", buffer,
+                               node->start_cluster);
+               ret = false;
+       }
+
+       /* Empty file or directory must be marked as non-contiguous. */
+       if (node->size == 0 && node->is_contiguous)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but marked as contiguous (%#hx)", buffer,
+                               node->attrib);
+               ret = false;
+       }
+
+       /* Directory size must be aligned on at cluster boundary. */
+       if ((node->attrib & EXFAT_ATTRIB_DIR) && node->size % cluster_size != 0)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' directory size %"PRIu64" is not divisible by %d", buffer,
+                               node->size, cluster_size);
+               ret = false;
+       }
+
+       return ret;
+}
+
+static void decompress_upcase(uint16_t* output, const le16_t* source,
+               size_t size)
+{
+       size_t si;
+       size_t oi;
+
+       for (oi = 0; oi < EXFAT_UPCASE_CHARS; oi++)
+               output[oi] = oi;
+
+       for (si = 0, oi = 0; si < size && oi < EXFAT_UPCASE_CHARS; si++)
+       {
+               uint16_t ch = le16_to_cpu(source[si]);
+
+               if (ch == 0xffff && si + 1 < size)      /* indicates a run */
+                       oi += le16_to_cpu(source[++si]);
+               else
+                       output[oi++] = ch;
+       }
 }
 
 /*
 }
 
 /*
@@ -149,6 +346,7 @@ static void init_node_meta2(struct exfat_node* node,
 static int readdir(struct exfat* ef, const struct exfat_node* parent,
                struct exfat_node** node, struct iterator* it)
 {
 static int readdir(struct exfat* ef, const struct exfat_node* parent,
                struct exfat_node** node, struct iterator* it)
 {
+       int rc = -EIO;
        const struct exfat_entry* entry;
        const struct exfat_entry_meta1* meta1;
        const struct exfat_entry_meta2* meta2;
        const struct exfat_entry* entry;
        const struct exfat_entry_meta1* meta1;
        const struct exfat_entry_meta2* meta2;
@@ -160,27 +358,27 @@ 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;
+       uint64_t upcase_size = 0;
+       le16_t* upcase_comp = NULL;
 
        *node = NULL;
 
        for (;;)
        {
 
        *node = NULL;
 
        for (;;)
        {
-               /* every directory (even empty one) occupies at least one cluster and
-                  must contain EOD entry */
-               entry = (const struct exfat_entry*)
-                               (it->chunk + it->offset % CLUSTER_SIZE(*ef->sb));
-
-               switch (entry->type)
+               if (it->offset >= parent->size)
                {
                {
-               case EXFAT_ENTRY_EOD:
                        if (continuations != 0)
                        {
                        if (continuations != 0)
                        {
-                               exfat_error("expected %hhu continuations before EOD",
-                                               continuations);
+                               exfat_error("expected %hhu continuations", continuations);
                                goto error;
                        }
                        return -ENOENT; /* that's OK, means end of directory */
                                goto error;
                        }
                        return -ENOENT; /* that's OK, means end of directory */
+               }
 
 
+               entry = get_entry_ptr(ef, it);
+               switch (entry->type)
+               {
                case EXFAT_ENTRY_FILE:
                        if (continuations != 0)
                        {
                case EXFAT_ENTRY_FILE:
                        if (continuations != 0)
                        {
@@ -195,16 +393,25 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (continuations < 2)
                        {
                                exfat_error("too few continuations (%hhu)", continuations);
                        if (continuations < 2)
                        {
                                exfat_error("too few continuations (%hhu)", continuations);
-                               return -EIO;
+                               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();
                        if (*node == NULL)
                        }
                        reference_checksum = le16_to_cpu(meta1->checksum);
                        actual_checksum = exfat_start_checksum(meta1);
                        *node = allocate_node();
                        if (*node == NULL)
-                               return -ENOMEM;
+                       {
+                               rc = -ENOMEM;
+                               goto error;
+                       }
                        /* new node has zero reference counter */
                        (*node)->entry_cluster = it->cluster;
                        /* new node has zero reference counter */
                        (*node)->entry_cluster = it->cluster;
-                       (*node)->entry_offset = it->offset % CLUSTER_SIZE(*ef->sb);
+                       (*node)->entry_offset = it->offset;
                        init_node_meta1(*node, meta1);
                        namep = (*node)->name;
                        break;
                        init_node_meta1(*node, meta1);
                        namep = (*node)->name;
                        break;
@@ -217,31 +424,14 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                goto error;
                        }
                        meta2 = (const struct exfat_entry_meta2*) entry;
                                goto error;
                        }
                        meta2 = (const struct exfat_entry_meta2*) entry;
-                       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)
+                       if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
                        {
                        {
-                               exfat_error("real size does not equal to size "
-                                               "(%"PRIu64" != %"PRIu64")",
-                                               le64_to_cpu(meta2->real_size), (*node)->size);
-                               goto error;
-                       }
-                       /* directories must be aligned on at cluster boundary */
-                       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("unknown flags in meta2 (0x%hhx)", meta2->flags);
                                goto error;
                        }
                                goto error;
                        }
+                       init_node_meta2(*node, meta2);
+                       actual_checksum = exfat_add_checksum(entry, actual_checksum);
+                       valid_size = le64_to_cpu(meta2->valid_size);
                        --continuations;
                        break;
 
                        --continuations;
                        break;
 
@@ -254,17 +444,17 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        file_name = (const struct exfat_entry_name*) entry;
                        actual_checksum = exfat_add_checksum(entry, actual_checksum);
 
                        file_name = (const struct exfat_entry_name*) entry;
                        actual_checksum = exfat_add_checksum(entry, actual_checksum);
 
-                       memcpy(namep, file_name->name, EXFAT_ENAME_MAX * sizeof(le16_t));
+                       memcpy(namep, file_name->name,
+                                       MIN(EXFAT_ENAME_MAX,
+                                               ((*node)->name + EXFAT_NAME_MAX - namep)) *
+                                       sizeof(le16_t));
                        namep += EXFAT_ENAME_MAX;
                        if (--continuations == 0)
                        {
                        namep += EXFAT_ENAME_MAX;
                        if (--continuations == 0)
                        {
-                               if (actual_checksum != reference_checksum)
-                               {
-                                       exfat_error("invalid checksum (0x%hx != 0x%hx)",
-                                                       actual_checksum, reference_checksum);
-                                       return -EIO;
-                               }
-                               if (fetch_next_entry(ef, parent, it) != 0)
+                               if (!check_node(*node, actual_checksum, reference_checksum,
+                                               valid_size, CLUSTER_SIZE(*ef->sb)))
+                                       goto error;
+                               if (!fetch_next_entry(ef, parent, it))
                                        goto error;
                                return 0; /* entry completed */
                        }
                                        goto error;
                                return 0; /* entry completed */
                        }
@@ -276,59 +466,93 @@ 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");
-                               return -EIO;
+                               exfat_error("invalid cluster 0x%x in upcase table",
+                                               le32_to_cpu(upcase->start_cluster));
+                               goto error;
                        }
                        }
-                       if (le64_to_cpu(upcase->size) == 0 ||
-                               le64_to_cpu(upcase->size) > 0xffff * sizeof(uint16_t) ||
-                               le64_to_cpu(upcase->size) % sizeof(uint16_t) != 0)
+                       upcase_size = le64_to_cpu(upcase->size);
+                       if (upcase_size == 0 ||
+                               upcase_size > EXFAT_UPCASE_CHARS * sizeof(uint16_t) ||
+                               upcase_size % sizeof(uint16_t) != 0)
                        {
                                exfat_error("bad upcase table size (%"PRIu64" bytes)",
                        {
                                exfat_error("bad upcase table size (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
-                               return -EIO;
+                                               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)",
                        {
                                exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
-                                               le64_to_cpu(upcase->size));
-                               return -ENOMEM;
+                                               upcase_size);
+                               rc = -ENOMEM;
+                               goto error;
                        }
                        }
-                       ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
 
 
-                       exfat_read_raw(ef->upcase, le64_to_cpu(upcase->size),
-                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster)), ef->fd);
+                       /* read compressed upcase table */
+                       if (exfat_pread(ef->dev, upcase_comp, upcase_size,
+                                       exfat_c2o(ef, le32_to_cpu(upcase->start_cluster))) < 0)
+                       {
+                               free(upcase_comp);
+                               exfat_error("failed to read upper case table "
+                                               "(%"PRIu64" bytes starting at cluster %#x)",
+                                               upcase_size,
+                                               le32_to_cpu(upcase->start_cluster));
+                               goto error;
+                       }
+
+                       /* decompress upcase table */
+                       ef->upcase = calloc(EXFAT_UPCASE_CHARS, sizeof(uint16_t));
+                       if (ef->upcase == NULL)
+                       {
+                               free(upcase_comp);
+                               exfat_error("failed to allocate decompressed upcase table");
+                               rc = -ENOMEM;
+                               goto error;
+                       }
+                       decompress_upcase(ef->upcase, upcase_comp,
+                                       upcase_size / sizeof(uint16_t));
+                       free(upcase_comp);
                        break;
 
                case EXFAT_ENTRY_BITMAP:
                        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");
-                               return -EIO;
+                               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;
                        }
                        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 bitmap size: %"PRIu64
+                               exfat_error("invalid clusters bitmap size: %"PRIu64
                                                " (expected at least %u)",
                                                " (expected at least %u)",
-                                               le64_to_cpu(bitmap->size), (ef->cmap.size + 7) / 8);
-                               return -EIO;
+                                               le64_to_cpu(bitmap->size),
+                                               DIV_ROUND_UP(ef->cmap.size, 8));
+                               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)
                        {
                        if (ef->cmap.chunk == NULL)
                        {
-                               exfat_error("failed to allocate clusters map chunk "
+                               exfat_error("failed to allocate clusters bitmap chunk "
                                                "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
                                                "(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
-                               return -ENOMEM;
+                               rc = -ENOMEM;
+                               goto error;
                        }
 
                        }
 
-                       exfat_read_raw(ef->cmap.chunk, le64_to_cpu(bitmap->size),
-                                       exfat_c2o(ef, ef->cmap.start_cluster), ef->fd);
+                       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:
@@ -336,20 +560,33 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        if (label->length > EXFAT_ENAME_MAX)
                        {
                                exfat_error("too long label (%hhu chars)", label->length);
                        if (label->length > EXFAT_ENAME_MAX)
                        {
                                exfat_error("too long label (%hhu chars)", label->length);
-                               return -EIO;
+                               goto error;
                        }
                        }
+                       if (utf16_to_utf8(ef->label, label->name,
+                                               sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
+                               goto error;
                        break;
 
                default:
                        break;
 
                default:
-                       if (entry->type & EXFAT_ENTRY_VALID)
+                       if (!(entry->type & EXFAT_ENTRY_VALID))
+                               break; /* deleted entry, ignore it */
+                       if (!(entry->type & EXFAT_ENTRY_OPTIONAL))
                        {
                        {
-                               exfat_error("unknown entry type 0x%hhu", 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 */
@@ -357,7 +594,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
 error:
        free(*node);
        *node = NULL;
 error:
        free(*node);
        *node = NULL;
-       return -EIO;
+       return rc;
 }
 
 int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
 }
 
 int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
@@ -367,7 +604,7 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
        struct exfat_node* node;
        struct exfat_node* current = NULL;
 
-       if (dir->flags & EXFAT_ATTRIB_CACHED)
+       if (dir->is_cached)
                return 0; /* already cached */
 
        rc = opendir(ef, dir, &it);
                return 0; /* already cached */
 
        rc = opendir(ef, dir, &it);
@@ -400,32 +637,59 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
                return rc;
        }
 
                return rc;
        }
 
-       dir->flags |= EXFAT_ATTRIB_CACHED;
+       dir->is_cached = true;
        return 0;
 }
 
        return 0;
 }
 
+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[EXFAT_UTF8_NAME_BUFFER_MAX];
 
 
-       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->is_cached = false;
        if (node->references != 0)
        {
        if (node->references != 0)
        {
-               char buffer[EXFAT_NAME_MAX + 1];
-               exfat_get_name(node, buffer, EXFAT_NAME_MAX);
-               exfat_warn("non-zero reference counter (%d) for `%s'",
+               exfat_get_name(node, buffer);
+               exfat_warn("non-zero reference counter (%d) for '%s'",
                                node->references, buffer);
        }
                                node->references, buffer);
        }
-       while (node->references--)
+       if (node != ef->root && node->is_dirty)
+       {
+               exfat_get_name(node, buffer);
+               exfat_bug("node '%s' is dirty", buffer);
+       }
+       while (node->references)
                exfat_put_node(ef, node);
                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)
@@ -433,21 +697,24 @@ void exfat_reset_cache(struct exfat* ef)
        reset_cache(ef, ef->root);
 }
 
        reset_cache(ef, ef->root);
 }
 
-void next_entry(struct exfat* ef, const struct exfat_node* parent,
+static bool next_entry(struct exfat* ef, const struct exfat_node* parent,
                cluster_t* cluster, off_t* offset)
 {
                cluster_t* cluster, off_t* offset)
 {
-       if (*offset + sizeof(struct exfat_entry) == CLUSTER_SIZE(*ef->sb))
+       *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);
-               *offset = 0;
+               if (CLUSTER_INVALID(*cluster))
+               {
+                       exfat_error("invalid cluster %#x while getting next entry",
+                                       *cluster);
+                       return false;
+               }
        }
        }
-       else
-               *offset += sizeof(struct exfat_entry);
-
+       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;
@@ -455,119 +722,190 @@ void exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        struct exfat_entry_meta1 meta1;
        struct exfat_entry_meta2 meta2;
 
        struct exfat_entry_meta1 meta1;
        struct exfat_entry_meta2 meta2;
 
+       if (!node->is_dirty)
+               return 0; /* no need to flush */
+
        if (ef->ro)
                exfat_bug("unable to flush node to read-only FS");
 
        if (node->parent == NULL)
        if (ef->ro)
                exfat_bug("unable to flush node to read-only FS");
 
        if (node->parent == NULL)
-               return; /* do not flush unlinked node */
+               return 0; /* do not flush unlinked node */
 
        cluster = node->entry_cluster;
        offset = node->entry_offset;
 
        cluster = node->entry_cluster;
        offset = node->entry_offset;
-       meta1_offset = exfat_c2o(ef, cluster) + offset;
-       next_entry(ef, node->parent, &cluster, &offset);
-       meta2_offset = exfat_c2o(ef, cluster) + offset;
+       meta1_offset = co2o(ef, cluster, offset);
+       if (!next_entry(ef, node->parent, &cluster, &offset))
+               return -EIO;
+       meta2_offset = co2o(ef, cluster, offset);
 
 
-       exfat_read_raw(&meta1, sizeof(meta1), meta1_offset, ef->fd);
+       if (exfat_pread(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
+       {
+               exfat_error("failed to read meta1 entry on flush");
+               return -EIO;
+       }
        if (meta1.type != EXFAT_ENTRY_FILE)
                exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
        if (meta1.type != EXFAT_ENTRY_FILE)
                exfat_bug("invalid type of meta1: 0x%hhx", meta1.type);
-       meta1.attrib = cpu_to_le16(node->flags);
-       exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime);
-       exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime);
+       meta1.attrib = cpu_to_le16(node->attrib);
+       exfat_unix2exfat(node->mtime, &meta1.mdate, &meta1.mtime, &meta1.mtime_cs);
+       exfat_unix2exfat(node->atime, &meta1.adate, &meta1.atime, NULL);
 
 
-       exfat_read_raw(&meta2, sizeof(meta2), meta2_offset, ef->fd);
+       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.start_cluster = cpu_to_le32(node->start_cluster);
-       /* empty files must be marked as fragmented */
-       if (node->size != 0 && IS_CONTIGUOUS(*node))
-               meta2.flag = EXFAT_FLAG_CONTIGUOUS;
-       else
-               meta2.flag = EXFAT_FLAG_FRAGMENTED;
+       meta2.flags = EXFAT_FLAG_ALWAYS1;
+       /* empty files must not be marked as contiguous */
+       if (node->size != 0 && node->is_contiguous)
+               meta2.flags |= EXFAT_FLAG_CONTIGUOUS;
        /* name hash remains unchanged, no need to recalculate it */
 
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
 
        /* name hash remains unchanged, no need to recalculate it */
 
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
 
-       exfat_write_raw(&meta1, sizeof(meta1), meta1_offset, ef->fd);
-       exfat_write_raw(&meta2, sizeof(meta2), meta2_offset, ef->fd);
+       if (exfat_pwrite(ef->dev, &meta1, sizeof(meta1), meta1_offset) < 0)
+       {
+               exfat_error("failed to write meta1 entry on flush");
+               return -EIO;
+       }
+       if (exfat_pwrite(ef->dev, &meta2, sizeof(meta2), meta2_offset) < 0)
+       {
+               exfat_error("failed to write meta2 entry on flush");
+               return -EIO;
+       }
 
 
-       node->flags &= ~EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = false;
+       return exfat_flush(ef);
 }
 
 }
 
-static void erase_entry(struct exfat* ef, struct exfat_node* node)
+static int erase_entries(struct exfat* ef, struct exfat_node* dir, int n,
+               off_t offset)
 {
 {
-       cluster_t cluster = node->entry_cluster;
-       off_t offset = node->entry_offset;
-       int name_entries = DIV_ROUND_UP(utf16_length(node->name), EXFAT_ENAME_MAX);
-       uint8_t entry_type;
+       struct exfat_entry entries[n];
+       int rc;
+       int i;
 
 
-       entry_type = EXFAT_ENTRY_FILE & ~EXFAT_ENTRY_VALID;
-       exfat_write_raw(&entry_type, 1, exfat_c2o(ef, cluster) + offset, ef->fd);
+       rc = read_entries(ef, dir, entries, n, offset);
+       if (rc != 0)
+               return rc;
+       for (i = 0; i < n; i++)
+               entries[i].type &= ~EXFAT_ENTRY_VALID;
+       return write_entries(ef, dir, entries, n, offset);
+}
 
 
-       next_entry(ef, node->parent, &cluster, &offset);
-       entry_type = EXFAT_ENTRY_FILE_INFO & ~EXFAT_ENTRY_VALID;
-       exfat_write_raw(&entry_type, 1, exfat_c2o(ef, cluster) + offset, ef->fd);
+static int erase_node(struct exfat* ef, struct exfat_node* node)
+{
+       int rc;
 
 
-       while (name_entries--)
+       exfat_get_node(node->parent);
+       rc = erase_entries(ef, node->parent, 1 + node->continuations,
+                       node->entry_offset);
+       if (rc != 0)
        {
        {
-               next_entry(ef, node->parent, &cluster, &offset);
-               entry_type = EXFAT_ENTRY_FILE_NAME & ~EXFAT_ENTRY_VALID;
-               exfat_write_raw(&entry_type, 1, exfat_c2o(ef, cluster) + offset,
-                               ef->fd);
+               exfat_put_node(ef, node->parent);
+               return rc;
        }
        }
+       rc = exfat_flush_node(ef, node->parent);
+       exfat_put_node(ef, node->parent);
+       return rc;
 }
 
 }
 
-static void tree_detach(struct exfat_node* node)
+static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
+               off_t deleted_offset)
 {
 {
-       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;
-}
+       const struct exfat_node* node;
+       const struct exfat_node* last_node;
+       uint64_t entries = 0;
+       uint64_t new_size;
 
 
-static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
-{
-       node->parent = dir;
-       if (dir->child)
+       if (!(dir->attrib & EXFAT_ATTRIB_DIR))
+               exfat_bug("attempted to shrink a file");
+       if (!dir->is_cached)
+               exfat_bug("attempted to shrink uncached directory");
+
+       for (last_node = node = dir->child; node; node = node->next)
        {
        {
-               dir->child->prev = node;
-               node->next = dir->child;
+               if (deleted_offset < node->entry_offset)
+               {
+                       /* there are other entries after the removed one, no way to shrink
+                          this directory */
+                       return 0;
+               }
+               if (last_node->entry_offset < node->entry_offset)
+                       last_node = node;
        }
        }
-       dir->child = node;
+
+       if (last_node)
+       {
+               /* offset of the last entry */
+               entries += last_node->entry_offset / sizeof(struct exfat_entry);
+               /* two subentries with meta info */
+               entries += 2;
+               /* subentries with file name */
+               entries += DIV_ROUND_UP(utf16_length(last_node->name),
+                               EXFAT_ENAME_MAX);
+       }
+
+       new_size = DIV_ROUND_UP(entries * sizeof(struct exfat_entry),
+                                CLUSTER_SIZE(*ef->sb)) * CLUSTER_SIZE(*ef->sb);
+       if (new_size == 0) /* directory always has at least 1 cluster */
+               new_size = CLUSTER_SIZE(*ef->sb);
+       if (new_size == dir->size)
+               return 0;
+       return exfat_truncate(ef, dir, new_size, true);
 }
 
 }
 
-static void delete(struct exfat* ef, struct exfat_node* node)
+static int delete(struct exfat* ef, struct exfat_node* node)
 {
 {
-       erase_entry(ef, node);
-       exfat_update_mtime(node->parent);
+       struct exfat_node* parent = node->parent;
+       off_t deleted_offset = node->entry_offset;
+       int rc;
+
+       exfat_get_node(parent);
+       rc = erase_node(ef, node);
+       if (rc != 0)
+       {
+               exfat_put_node(ef, parent);
+               return rc;
+       }
        tree_detach(node);
        tree_detach(node);
-       /* file clusters will be freed when node reference counter becomes 0 */
-       node->flags |= EXFAT_ATTRIB_UNLINKED;
+       rc = shrink_directory(ef, parent, deleted_offset);
+       node->is_unlinked = true;
+       if (rc != 0)
+       {
+               exfat_flush_node(ef, parent);
+               exfat_put_node(ef, parent);
+               return rc;
+       }
+       exfat_update_mtime(parent);
+       rc = exfat_flush_node(ef, parent);
+       exfat_put_node(ef, parent);
+       return rc;
 }
 
 int exfat_unlink(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 -EISDIR;
-       delete(ef, node);
-       return 0;
+       return delete(ef, node);
 }
 
 int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
 }
 
 int exfat_rmdir(struct exfat* ef, struct exfat_node* node)
 {
-       if (!(node->flags & EXFAT_ATTRIB_DIR))
+       int rc;
+
+       if (!(node->attrib & EXFAT_ATTRIB_DIR))
                return -ENOTDIR;
        /* check that directory is empty */
                return -ENOTDIR;
        /* check that directory is empty */
-       exfat_cache_directory(ef, node);
+       rc = exfat_cache_directory(ef, node);
+       if (rc != 0)
+               return rc;
        if (node->child)
                return -ENOTEMPTY;
        if (node->child)
                return -ENOTEMPTY;
-       delete(ef, node);
-       return 0;
+       return delete(ef, node);
 }
 
 static int grow_directory(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int grow_directory(struct exfat* ef, struct exfat_node* dir,
@@ -575,7 +913,7 @@ static int grow_directory(struct exfat* ef, struct exfat_node* dir,
 {
        return exfat_truncate(ef, dir,
                        DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
 {
        return exfat_truncate(ef, dir,
                        DIV_ROUND_UP(asize + difference, CLUSTER_SIZE(*ef->sb))
-                               * CLUSTER_SIZE(*ef->sb));
+                               * CLUSTER_SIZE(*ef->sb), true);
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
@@ -594,29 +932,26 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
                if (contiguous == 0)
                {
                        *cluster = it.cluster;
                if (contiguous == 0)
                {
                        *cluster = it.cluster;
-                       *offset = it.offset % CLUSTER_SIZE(*ef->sb);
+                       *offset = it.offset;
                }
                }
-               entry = (const struct exfat_entry*)
-                               (it.chunk + it.offset % CLUSTER_SIZE(*ef->sb));
-               if (entry->type == EXFAT_ENTRY_EOD)
+               entry = get_entry_ptr(ef, &it);
+               if (entry->type & EXFAT_ENTRY_VALID)
+                       contiguous = 0;
+               else
+                       contiguous++;
+               if (contiguous == subentries)
+                       break;  /* suitable slot is found */
+               if (it.offset + sizeof(struct exfat_entry) >= dir->size)
                {
                {
-                       rc = grow_directory(ef, dir,
-                                       it.offset + sizeof(struct exfat_entry), /* actual size */
+                       rc = grow_directory(ef, dir, dir->size,
                                        (subentries - contiguous) * sizeof(struct exfat_entry));
                        if (rc != 0)
                        {
                                closedir(&it);
                                return rc;
                        }
                                        (subentries - contiguous) * sizeof(struct exfat_entry));
                        if (rc != 0)
                        {
                                closedir(&it);
                                return rc;
                        }
-                       break;
                }
                }
-               if (entry->type & EXFAT_ENTRY_VALID)
-                       contiguous = 0;
-               else
-                       contiguous++;
-               if (contiguous == subentries)
-                       break;  /* suitable slot it found */
-               if (fetch_next_entry(ef, dir, &it) != 0)
+               if (!fetch_next_entry(ef, dir, &it))
                {
                        closedir(&it);
                        return -EIO;
                {
                        closedir(&it);
                        return -EIO;
@@ -626,62 +961,62 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
        return 0;
 }
 
        return 0;
 }
 
-static int write_entry(struct exfat* ef, struct exfat_node* dir,
+static int commit_entry(struct exfat* ef, struct exfat_node* dir,
                const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
 {
        struct exfat_node* node;
                const le16_t* name, cluster_t cluster, off_t offset, uint16_t attrib)
 {
        struct exfat_node* node;
-       struct exfat_entry_meta1 meta1;
-       struct exfat_entry_meta2 meta2;
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+       struct exfat_entry entries[2 + name_entries];
+       struct exfat_entry_meta1* meta1 = (struct exfat_entry_meta1*) &entries[0];
+       struct exfat_entry_meta2* meta2 = (struct exfat_entry_meta2*) &entries[1];
        int i;
        int i;
+       int rc;
 
 
-       node = allocate_node();
-       if (node == NULL)
-               return -ENOMEM;
-       node->entry_cluster = cluster;
-       node->entry_offset = offset;
-       memcpy(node->name, name, name_length * sizeof(le16_t));
+       memset(entries, 0, sizeof(struct exfat_entry[2]));
 
 
-       memset(&meta1, 0, sizeof(meta1));
-       meta1.type = EXFAT_ENTRY_FILE;
-       meta1.continuations = 1 + name_entries;
-       meta1.attrib = cpu_to_le16(attrib);
-       exfat_unix2exfat(time(NULL), &meta1.crdate, &meta1.crtime);
-       meta1.adate = meta1.mdate = meta1.crdate;
-       meta1.atime = meta1.mtime = meta1.crtime;
-       /* crtime_cs and mtime_cs contain addition to the time in centiseconds;
-          just ignore those fields because we operate with 2 sec resolution */
-
-       memset(&meta2, 0, sizeof(meta2));
-       meta2.type = EXFAT_ENTRY_FILE_INFO;
-       meta2.flag = EXFAT_FLAG_FRAGMENTED;
-       meta2.name_length = name_length;
-       meta2.name_hash = exfat_calc_name_hash(ef, node->name);
-       meta2.start_cluster = cpu_to_le32(EXFAT_CLUSTER_FREE);
+       meta1->type = EXFAT_ENTRY_FILE;
+       meta1->continuations = 1 + name_entries;
+       meta1->attrib = cpu_to_le16(attrib);
+       exfat_unix2exfat(time(NULL), &meta1->crdate, &meta1->crtime,
+                       &meta1->crtime_cs);
+       meta1->adate = meta1->mdate = meta1->crdate;
+       meta1->atime = meta1->mtime = meta1->crtime;
+       meta1->mtime_cs = meta1->crtime_cs; /* there is no atime_cs */
 
 
-       meta1.checksum = exfat_calc_checksum(&meta1, &meta2, node->name);
+       meta2->type = EXFAT_ENTRY_FILE_INFO;
+       meta2->flags = EXFAT_FLAG_ALWAYS1;
+       meta2->name_length = name_length;
+       meta2->name_hash = exfat_calc_name_hash(ef, name, name_length);
+       meta2->start_cluster = cpu_to_le32(EXFAT_CLUSTER_FREE);
+
+       meta1->checksum = exfat_calc_checksum(meta1, meta2, name);
 
 
-       exfat_write_raw(&meta1, sizeof(meta1), exfat_c2o(ef, cluster) + offset,
-                       ef->fd);
-       next_entry(ef, dir, &cluster, &offset);
-       exfat_write_raw(&meta2, sizeof(meta2), exfat_c2o(ef, cluster) + offset,
-                       ef->fd);
        for (i = 0; i < name_entries; i++)
        {
        for (i = 0; i < name_entries; i++)
        {
-               struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
-               memcpy(name_entry.name, node->name + i * EXFAT_ENAME_MAX,
+               struct exfat_entry_name* name_entry;
+
+               name_entry = (struct exfat_entry_name*) &entries[2 + i];
+               name_entry->type = EXFAT_ENTRY_FILE_NAME;
+               name_entry->__unknown = 0;
+               memcpy(name_entry->name, name + i * EXFAT_ENAME_MAX,
                                EXFAT_ENAME_MAX * sizeof(le16_t));
                                EXFAT_ENAME_MAX * sizeof(le16_t));
-               next_entry(ef, dir, &cluster, &offset);
-               exfat_write_raw(&name_entry, sizeof(name_entry),
-                               exfat_c2o(ef, cluster) + offset, ef->fd);
        }
 
        }
 
-       init_node_meta1(node, &meta1);
-       init_node_meta2(node, &meta2);
+       rc = write_entries(ef, dir, entries, 2 + name_entries, offset);
+       if (rc != 0)
+               return rc;
+
+       node = allocate_node();
+       if (node == NULL)
+               return -ENOMEM;
+       node->entry_cluster = cluster;
+       node->entry_offset = offset;
+       memcpy(node->name, name, name_length * sizeof(le16_t));
+       init_node_meta1(node, meta1);
+       init_node_meta2(node, meta2);
 
        tree_attach(dir, node);
 
        tree_attach(dir, node);
-       exfat_update_mtime(dir);
        return 0;
 }
 
        return 0;
 }
 
@@ -711,7 +1046,14 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
                exfat_put_node(ef, dir);
                return rc;
        }
                exfat_put_node(ef, dir);
                return rc;
        }
-       rc = write_entry(ef, dir, name, cluster, offset, attrib);
+       rc = commit_entry(ef, dir, name, cluster, offset, attrib);
+       if (rc != 0)
+       {
+               exfat_put_node(ef, dir);
+               return rc;
+       }
+       exfat_update_mtime(dir);
+       rc = exfat_flush_node(ef, dir);
        exfat_put_node(ef, dir);
        return rc;
 }
        exfat_put_node(ef, dir);
        return rc;
 }
@@ -726,14 +1068,21 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        int rc;
        struct exfat_node* node;
 
        int rc;
        struct exfat_node* node;
 
-       rc = create(ef, path, EXFAT_ATTRIB_ARCH | EXFAT_ATTRIB_DIR);
+       rc = create(ef, path, EXFAT_ATTRIB_DIR);
        if (rc != 0)
                return rc;
        rc = exfat_lookup(ef, &node, path);
        if (rc != 0)
                return 0;
        /* directories always have at least one cluster */
        if (rc != 0)
                return rc;
        rc = exfat_lookup(ef, &node, path);
        if (rc != 0)
                return 0;
        /* directories always have at least one cluster */
-       rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb));
+       rc = exfat_truncate(ef, node, CLUSTER_SIZE(*ef->sb), true);
+       if (rc != 0)
+       {
+               delete(ef, node);
+               exfat_put_node(ef, node);
+               return rc;
+       }
+       rc = exfat_flush_node(ef, node);
        if (rc != 0)
        {
                delete(ef, node);
        if (rc != 0)
        {
                delete(ef, node);
@@ -744,7 +1093,7 @@ int exfat_mkdir(struct exfat* ef, const char* path)
        return 0;
 }
 
        return 0;
 }
 
-static void rename_entry(struct exfat* ef, struct exfat_node* dir,
+static int rename_entry(struct exfat* ef, struct exfat_node* dir,
                struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
                off_t new_offset)
 {
                struct exfat_node* node, const le16_t* name, cluster_t new_cluster,
                off_t new_offset)
 {
@@ -754,42 +1103,70 @@ static void rename_entry(struct exfat* ef, struct exfat_node* dir,
        off_t old_offset = node->entry_offset;
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
        off_t old_offset = node->entry_offset;
        const size_t name_length = utf16_length(name);
        const int name_entries = DIV_ROUND_UP(name_length, EXFAT_ENAME_MAX);
+       int rc;
        int i;
 
        int i;
 
-       exfat_read_raw(&meta1, sizeof(meta1),
-                       exfat_c2o(ef, old_cluster) + old_offset, ef->fd);
-       next_entry(ef, node->parent, &old_cluster, &old_offset);
-       exfat_read_raw(&meta2, sizeof(meta2),
-                       exfat_c2o(ef, old_cluster) + old_offset, ef->fd);
+       if (exfat_pread(ef->dev, &meta1, sizeof(meta1),
+                       co2o(ef, old_cluster, old_offset)) < 0)
+       {
+               exfat_error("failed to read meta1 entry on rename");
+               return -EIO;
+       }
+       if (!next_entry(ef, node->parent, &old_cluster, &old_offset))
+               return -EIO;
+       if (exfat_pread(ef->dev, &meta2, sizeof(meta2),
+                       co2o(ef, old_cluster, old_offset)) < 0)
+       {
+               exfat_error("failed to read meta2 entry on rename");
+               return -EIO;
+       }
        meta1.continuations = 1 + name_entries;
        meta1.continuations = 1 + name_entries;
-       meta2.name_hash = exfat_calc_name_hash(ef, name);
+       meta2.name_hash = exfat_calc_name_hash(ef, name, name_length);
        meta2.name_length = name_length;
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
 
        meta2.name_length = name_length;
        meta1.checksum = exfat_calc_checksum(&meta1, &meta2, name);
 
-       erase_entry(ef, node);
+       rc = erase_node(ef, node);
+       if (rc != 0)
+               return rc;
 
        node->entry_cluster = new_cluster;
        node->entry_offset = new_offset;
 
        node->entry_cluster = new_cluster;
        node->entry_offset = new_offset;
+       node->continuations = 1 + name_entries;
 
 
-       exfat_write_raw(&meta1, sizeof(meta1),
-            exfat_c2o(ef, new_cluster) + new_offset, ef->fd);
-       next_entry(ef, dir, &new_cluster, &new_offset);
-       exfat_write_raw(&meta2, sizeof(meta2),
-            exfat_c2o(ef, new_cluster) + new_offset, ef->fd);
+       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_write_raw(&name_entry, sizeof(name_entry),
-                               exfat_c2o(ef, new_cluster) + new_offset, ef->fd);
+               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)
@@ -812,29 +1189,62 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                exfat_put_node(ef, node);
                return rc;
        }
                exfat_put_node(ef, node);
                return rc;
        }
+
+       /* check that target is not a subdirectory of the source */
+       if (node->attrib & EXFAT_ATTRIB_DIR)
+       {
+               struct exfat_node* p;
+
+               for (p = dir; p; p = p->parent)
+                       if (node == p)
+                       {
+                               if (existing != NULL)
+                                       exfat_put_node(ef, existing);
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return -EINVAL;
+                       }
+       }
+
        if (existing != NULL)
        {
        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->attrib & EXFAT_ATTRIB_DIR)
+                       {
+                               if (node->attrib & EXFAT_ATTRIB_DIR)
+                                       rc = exfat_rmdir(ef, existing);
+                               else
+                                       rc = -ENOTDIR;
+                       }
                        else
                        else
-                               rc = -ENOTDIR;
+                       {
+                               if (!(node->attrib & EXFAT_ATTRIB_DIR))
+                                       rc = exfat_unlink(ef, existing);
+                               else
+                                       rc = -EISDIR;
+                       }
+                       exfat_put_node(ef, existing);
+                       if (rc != 0)
+                       {
+                               /* free clusters even if something went wrong; overwise they
+                                  will be just lost */
+                               exfat_cleanup_node(ef, existing);
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return rc;
+                       }
+                       rc = exfat_cleanup_node(ef, existing);
+                       if (rc != 0)
+                       {
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               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,
@@ -845,27 +1255,81 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                exfat_put_node(ef, node);
                return rc;
        }
                exfat_put_node(ef, node);
                return rc;
        }
-       rename_entry(ef, dir, node, name, cluster, offset);
+       rc = rename_entry(ef, dir, node, name, cluster, offset);
        exfat_put_node(ef, dir);
        exfat_put_node(ef, node);
        exfat_put_node(ef, dir);
        exfat_put_node(ef, node);
-       return 0;
+       return rc;
 }
 
 void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
 {
        node->atime = tv[0].tv_sec;
        node->mtime = tv[1].tv_sec;
 }
 
 void exfat_utimes(struct exfat_node* node, const struct timespec tv[2])
 {
        node->atime = tv[0].tv_sec;
        node->mtime = tv[1].tv_sec;
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 void exfat_update_atime(struct exfat_node* node)
 {
        node->atime = time(NULL);
 }
 
 void exfat_update_atime(struct exfat_node* node)
 {
        node->atime = time(NULL);
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
 }
 
 void exfat_update_mtime(struct exfat_node* node)
 {
        node->mtime = time(NULL);
 }
 
 void exfat_update_mtime(struct exfat_node* node)
 {
        node->mtime = time(NULL);
-       node->flags |= EXFAT_ATTRIB_DIRTY;
+       node->is_dirty = true;
+}
+
+const char* exfat_get_label(struct exfat* ef)
+{
+       return ef->label;
+}
+
+static int find_label(struct exfat* ef, off_t* offset)
+{
+       struct exfat_entry entry;
+       int rc;
+
+       for (*offset = 0; ; *offset += sizeof(entry))
+       {
+               rc = read_entries(ef, ef->root, &entry, 1, *offset);
+               if (rc != 0)
+                       return rc;
+
+               if (entry.type == EXFAT_ENTRY_LABEL)
+                       return 0;
+       }
+}
+
+int exfat_set_label(struct exfat* ef, const char* label)
+{
+       le16_t label_utf16[EXFAT_ENAME_MAX + 1];
+       int rc;
+       cluster_t cluster;
+       off_t offset;
+       struct exfat_entry_label entry;
+
+       memset(label_utf16, 0, sizeof(label_utf16));
+       rc = utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX + 1, strlen(label));
+       if (rc != 0)
+               return rc;
+
+       rc = find_label(ef, &offset);
+       if (rc == -ENOENT)
+               rc = find_slot(ef, ef->root, &cluster, &offset, 1);
+       if (rc != 0)
+               return rc;
+
+       entry.type = EXFAT_ENTRY_LABEL;
+       entry.length = utf16_length(label_utf16);
+       memcpy(entry.name, label_utf16, sizeof(entry.name));
+       if (entry.length == 0)
+               entry.type ^= EXFAT_ENTRY_VALID;
+
+       rc = write_entries(ef, ef->root, (struct exfat_entry*) &entry, 1, offset);
+       if (rc != 0)
+               return rc;
+
+       strcpy(ef->label, label);
+       return 0;
 }
 }