OSDN Git Service

Rename write_entry() to commit_entry().
[android-x86/external-exfat.git] / libexfat / node.c
index f3207fb..5b623be 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,19 +43,19 @@ 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_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)
                {
-                       exfat_get_name(node, buffer, sizeof(buffer) - 1);
+                       exfat_get_name(node, buffer);
                        exfat_warn("dirty node '%s' with zero references", buffer);
                }
        }
@@ -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)
 {
+       char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
+
        if (!(dir->flags & EXFAT_ATTRIB_DIR))
-               exfat_bug("not a directory");
+       {
+               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)
@@ -194,9 +206,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 +217,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_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->flags & EXFAT_ATTRIB_CONTIGUOUS))
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is empty but marked as contiguous (%#x)", buffer,
+                               node->flags);
+               ret = false;
+       }
+
+       /* Directory size must be aligned on at cluster boundary. */
+       if ((node->flags & 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 +318,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 +391,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 +412,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 +430,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 +523,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 */
@@ -541,7 +627,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)
        {
@@ -553,13 +639,13 @@ static void reset_cache(struct exfat* ef, struct exfat_node* node)
        node->flags &= ~EXFAT_ATTRIB_CACHED;
        if (node->references != 0)
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
+               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))
        {
-               exfat_get_name(node, buffer, sizeof(buffer) - 1);
+               exfat_get_name(node, buffer);
                exfat_bug("node '%s' is dirty", buffer);
        }
        while (node->references)
@@ -630,7 +716,7 @@ 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 */
@@ -652,7 +738,7 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        }
 
        node->flags &= ~EXFAT_ATTRIB_DIRTY;
-       return 0;
+       return exfat_flush(ef);
 }
 
 static bool erase_entry(struct exfat* ef, struct exfat_node* node)
@@ -830,7 +916,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 +926,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 +957,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 +1026,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 +1047,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 +1099,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);
 
@@ -1118,6 +1204,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;
@@ -1190,7 +1286,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 +1303,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;