OSDN Git Service

Address clusters bitmap using size_t-sized blocks instead of bytes. This should be...
[android-x86/external-exfat.git] / libexfat / node.c
index 5b43a3a..e83ec9e 100644 (file)
@@ -2,11 +2,12 @@
        node.c (09.10.09)
        exFAT file system implementation library.
 
-       Copyright (C) 2010-2012  Andrew Nayenko
+       Free exFAT implementation.
+       Copyright (C) 2010-2013  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
-       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,
@@ -14,8 +15,9 @@
        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"
@@ -44,23 +46,21 @@ void exfat_put_node(struct exfat* ef, struct exfat_node* node)
 {
        if (--node->references < 0)
        {
-               char buffer[EXFAT_NAME_MAX + 1];
-               exfat_get_name(node, buffer, EXFAT_NAME_MAX);
+               char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+               exfat_get_name(node, buffer, sizeof(buffer) - 1);
                exfat_bug("reference counter of `%s' is below zero", buffer);
        }
 
        if (node->references == 0)
        {
-               if (node->flags & EXFAT_ATTRIB_DIRTY)
-                       exfat_flush_node(ef, node);
+               exfat_flush_node(ef, node);
                if (node->flags & EXFAT_ATTRIB_UNLINKED)
                {
                        /* free all clusters and node structure itself */
-                       exfat_truncate(ef, node, 0);
+                       exfat_truncate(ef, node, 0, true);
                        free(node);
                }
-               if (ef->cmap.dirty)
-                       exfat_flush_cmap(ef);
+               exfat_flush(ef);
        }
 }
 
@@ -218,6 +218,12 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                exfat_error("too few continuations (%hhu)", continuations);
                                goto error;
                        }
+                       if (continuations > 1 +
+                                       DIV_ROUND_UP(EXFAT_NAME_MAX, EXFAT_ENAME_MAX))
+                       {
+                               exfat_error("too many continuations (%hhu)", continuations);
+                               goto error;
+                       }
                        reference_checksum = le16_to_cpu(meta1->checksum);
                        actual_checksum = exfat_start_checksum(meta1);
                        *node = allocate_node();
@@ -276,7 +282,10 @@ 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);
 
-                       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)
                        {
@@ -293,9 +302,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                */
                                if (real_size != (*node)->size)
                                {
-                                       char buffer[EXFAT_NAME_MAX + 1];
+                                       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
 
-                                       exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
+                                       exfat_get_name(*node, buffer, sizeof(buffer) - 1);
                                        exfat_error("`%s' real size does not equal to size "
                                                        "(%"PRIu64" != %"PRIu64")", buffer,
                                                        real_size, (*node)->size);
@@ -303,9 +312,9 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                }
                                if (actual_checksum != reference_checksum)
                                {
-                                       char buffer[EXFAT_NAME_MAX + 1];
+                                       char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
 
-                                       exfat_get_name(*node, buffer, EXFAT_NAME_MAX);
+                                       exfat_get_name(*node, buffer, sizeof(buffer) - 1);
                                        exfat_error("`%s' has invalid checksum (0x%hx != 0x%hx)",
                                                        buffer, actual_checksum, reference_checksum);
                                        goto error;
@@ -359,16 +368,17 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                        }
                        ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
                                EXFAT_FIRST_DATA_CLUSTER;
-                       if (le64_to_cpu(bitmap->size) < (ef->cmap.size + 7) / 8)
+                       if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8))
                        {
                                exfat_error("invalid clusters bitmap size: %"PRIu64
                                                " (expected at least %u)",
-                                               le64_to_cpu(bitmap->size), (ef->cmap.size + 7) / 8);
+                                               le64_to_cpu(bitmap->size),
+                                               DIV_ROUND_UP(ef->cmap.size, 8));
                                goto error;
                        }
                        /* FIXME bitmap can be rather big, up to 512 MB */
                        ef->cmap.chunk_size = ef->cmap.size;
-                       ef->cmap.chunk = malloc(le64_to_cpu(bitmap->size));
+                       ef->cmap.chunk = malloc(BMAP_SIZE(ef->cmap.chunk_size));
                        if (ef->cmap.chunk == NULL)
                        {
                                exfat_error("failed to allocate clusters bitmap chunk "
@@ -377,7 +387,8 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                goto error;
                        }
 
-                       exfat_pread(ef->dev, ef->cmap.chunk, le64_to_cpu(bitmap->size),
+                       exfat_pread(ef->dev, ef->cmap.chunk,
+                                       BMAP_SIZE(ef->cmap.chunk_size),
                                        exfat_c2o(ef, ef->cmap.start_cluster));
                        break;
 
@@ -389,7 +400,7 @@ static int readdir(struct exfat* ef, const struct exfat_node* parent,
                                goto error;
                        }
                        if (utf16_to_utf8(ef->label, label->name,
-                                               sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
+                                               sizeof(ef->label) - 1, EXFAT_ENAME_MAX) != 0)
                                goto error;
                        break;
 
@@ -457,28 +468,49 @@ int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
        return 0;
 }
 
-static void reset_cache(struct exfat* ef, struct exfat_node* node)
+static void tree_attach(struct exfat_node* dir, struct exfat_node* node)
 {
-       struct exfat_node* child;
-       struct exfat_node* next;
+       node->parent = dir;
+       if (dir->child)
+       {
+               dir->child->prev = node;
+               node->next = dir->child;
+       }
+       dir->child = node;
+}
 
-       for (child = node->child; child; child = next)
+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)
+{
+       while (node->child)
        {
-               reset_cache(ef, child);
-               next = child->next;
-               free(child);
+               struct exfat_node* p = node->child;
+               reset_cache(ef, p);
+               tree_detach(p);
+               free(p);
        }
+       node->flags &= ~EXFAT_ATTRIB_CACHED;
        if (node->references != 0)
        {
-               char buffer[EXFAT_NAME_MAX + 1];
-               exfat_get_name(node, buffer, EXFAT_NAME_MAX);
+               char buffer[UTF8_BYTES(EXFAT_NAME_MAX) + 1];
+               exfat_get_name(node, buffer, sizeof(buffer) - 1);
                exfat_warn("non-zero reference counter (%d) for `%s'",
                                node->references, buffer);
        }
-       while (node->references--)
+       while (node->references)
                exfat_put_node(ef, node);
-       node->child = NULL;
-       node->flags &= ~EXFAT_ATTRIB_CACHED;
 }
 
 void exfat_reset_cache(struct exfat* ef)
@@ -486,7 +518,7 @@ void exfat_reset_cache(struct exfat* ef)
        reset_cache(ef, ef->root);
 }
 
-void next_entry(struct exfat* ef, const struct exfat_node* parent,
+static void next_entry(struct exfat* ef, const struct exfat_node* parent,
                cluster_t* cluster, off_t* offset)
 {
        *offset += sizeof(struct exfat_entry);
@@ -503,6 +535,9 @@ void 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))
+               return; /* no need to flush */
+
        if (ef->ro)
                exfat_bug("unable to flush node to read-only FS");
 
@@ -563,30 +598,6 @@ static void erase_entry(struct exfat* ef, struct exfat_node* 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 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 int shrink_directory(struct exfat* ef, struct exfat_node* dir,
                off_t deleted_offset)
 {
@@ -630,7 +641,7 @@ static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
                new_size = CLUSTER_SIZE(*ef->sb);
        if (new_size == dir->size)
                return 0;
-       rc = exfat_truncate(ef, dir, new_size);
+       rc = exfat_truncate(ef, dir, new_size, true);
        if (rc != 0)
                return rc;
        return 0;
@@ -676,7 +687,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))
-                               * CLUSTER_SIZE(*ef->sb));
+                               * CLUSTER_SIZE(*ef->sb), true);
 }
 
 static int find_slot(struct exfat* ef, struct exfat_node* dir,
@@ -767,7 +778,8 @@ static int write_entry(struct exfat* ef, struct exfat_node* dir,
        {
                struct exfat_entry_name name_entry = {EXFAT_ENTRY_FILE_NAME, 0};
                memcpy(name_entry.name, node->name + i * EXFAT_ENAME_MAX,
-                               EXFAT_ENAME_MAX * sizeof(le16_t));
+                               MIN(EXFAT_ENAME_MAX, EXFAT_NAME_MAX - i * EXFAT_ENAME_MAX) *
+                               sizeof(le16_t));
                next_entry(ef, dir, &cluster, &offset);
                exfat_pwrite(ef->dev, &name_entry, sizeof(name_entry),
                                co2o(ef, cluster, offset));
@@ -829,7 +841,7 @@ int exfat_mkdir(struct exfat* ef, const char* 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);
@@ -908,29 +920,52 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
                exfat_put_node(ef, node);
                return rc;
        }
+
+       /* check that target is not a subdirectory of the source */
+       if (node->flags & EXFAT_ATTRIB_DIR)
+       {
+               struct exfat_node* p;
+
+               for (p = dir; p; p = p->parent)
+                       if (node == p)
+                       {
+                               if (existing != NULL)
+                                       exfat_put_node(ef, existing);
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return -EINVAL;
+                       }
+       }
+
        if (existing != NULL)
        {
-               if (existing->flags & EXFAT_ATTRIB_DIR)
+               /* remove target if it's not the same node as source */
+               if (existing != node)
                {
-                       if (node->flags & EXFAT_ATTRIB_DIR)
-                               rc = exfat_rmdir(ef, existing);
+                       if (existing->flags & EXFAT_ATTRIB_DIR)
+                       {
+                               if (node->flags & EXFAT_ATTRIB_DIR)
+                                       rc = exfat_rmdir(ef, existing);
+                               else
+                                       rc = -ENOTDIR;
+                       }
                        else
-                               rc = -ENOTDIR;
+                       {
+                               if (!(node->flags & EXFAT_ATTRIB_DIR))
+                                       rc = exfat_unlink(ef, existing);
+                               else
+                                       rc = -EISDIR;
+                       }
+                       exfat_put_node(ef, existing);
+                       if (rc != 0)
+                       {
+                               exfat_put_node(ef, dir);
+                               exfat_put_node(ef, node);
+                               return rc;
+                       }
                }
                else
-               {
-                       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,
@@ -1031,5 +1066,6 @@ int exfat_set_label(struct exfat* ef, const char* label)
 
        exfat_pwrite(ef->dev, &entry, sizeof(struct exfat_entry_label),
                        co2o(ef, cluster, offset));
+       strcpy(ef->label, label);
        return 0;
 }