]> git.sven.stormbind.net Git - sven/fuse-exfat.git/blobdiff - libexfat/node.c
New upstream version 1.3.0+git20220115
[sven/fuse-exfat.git] / libexfat / node.c
index a3f00af1ba0d700a43d18397731fbd754eb13640..9cffbcb8a68cfc36da6965560578ddb0cc1d6c4c 100644 (file)
@@ -3,7 +3,7 @@
        exFAT file system implementation library.
 
        Free exFAT implementation.
-       Copyright (C) 2010-2017  Andrew Nayenko
+       Copyright (C) 2010-2018  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
@@ -87,7 +87,7 @@ static int read_entries(struct exfat* ef, struct exfat_node* dir,
 
        size = exfat_generic_pread(ef, dir, entries,
                        sizeof(struct exfat_entry[n]), offset);
-       if (size == sizeof(struct exfat_entry[n]))
+       if (size == (ssize_t) sizeof(struct exfat_entry) * n)
                return 0; /* success */
        if (size == 0)
                return -ENOENT;
@@ -108,7 +108,7 @@ static int write_entries(struct exfat* ef, struct exfat_node* dir,
 
        size = exfat_generic_pwrite(ef, dir, entries,
                        sizeof(struct exfat_entry[n]), offset);
-       if (size == sizeof(struct exfat_entry[n]))
+       if (size == (ssize_t) sizeof(struct exfat_entry) * n)
                return 0; /* success */
        if (size < 0)
                return -EIO;
@@ -135,9 +135,10 @@ static void init_node_meta1(struct exfat_node* node,
        node->attrib = le16_to_cpu(meta1->attrib);
        node->continuations = meta1->continuations;
        node->mtime = exfat_exfat2unix(meta1->mdate, meta1->mtime,
-                       meta1->mtime_cs);
+                       meta1->mtime_cs, meta1->mtime_tzo);
        /* there is no centiseconds field for atime */
-       node->atime = exfat_exfat2unix(meta1->adate, meta1->atime, 0);
+       node->atime = exfat_exfat2unix(meta1->adate, meta1->atime,
+                       0, meta1->atime_tzo);
 }
 
 static void init_node_meta2(struct exfat_node* node,
@@ -204,10 +205,13 @@ static bool check_entries(const struct exfat_entry* entry, int n)
        return true;
 }
 
-static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
-               int cluster_size, const struct exfat_entry_meta1* meta1,
+static bool check_node(const struct exfat* ef, struct exfat_node* node,
+               le16_t actual_checksum, const struct exfat_entry_meta1* meta1,
                const struct exfat_entry_meta2* meta2)
 {
+       int cluster_size = CLUSTER_SIZE(*ef->sb);
+       uint64_t clusters_heap_size =
+                       (uint64_t) le32_to_cpu(ef->sb->cluster_count) * cluster_size;
        char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
        bool ret = true;
 
@@ -220,7 +224,8 @@ static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
                exfat_get_name(node, buffer);
                exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
                                le16_to_cpu(actual_checksum), le16_to_cpu(meta1->checksum));
-               ret = false;
+               if (!EXFAT_REPAIR(invalid_node_checksum, ef, node))
+                       ret = false;
        }
 
        /*
@@ -251,7 +256,7 @@ static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
                                node->start_cluster);
                ret = false;
        }
-       if (node->size > 0 && CLUSTER_INVALID(node->start_cluster))
+       if (node->size > 0 && CLUSTER_INVALID(*ef->sb, node->start_cluster))
        {
                exfat_get_name(node, buffer);
                exfat_error("'%s' points to invalid cluster %#x", buffer,
@@ -259,6 +264,15 @@ static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
                ret = false;
        }
 
+       /* File or directory cannot be larger than clusters heap. */
+       if (node->size > clusters_heap_size)
+       {
+               exfat_get_name(node, buffer);
+               exfat_error("'%s' is larger than clusters heap: %"PRIu64" > %"PRIu64,
+                               buffer, node->size, clusters_heap_size);
+               ret = false;
+       }
+
        /* Empty file or directory must be marked as non-contiguous. */
        if (node->size == 0 && node->is_contiguous)
        {
@@ -280,8 +294,8 @@ static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
        return ret;
 }
 
-static int parse_file_entries(struct exfat* ef, struct exfat_node* parent,
-               struct exfat_node* node, const struct exfat_entry* entries, int n)
+static int parse_file_entries(struct exfat* ef, struct exfat_node* node,
+               const struct exfat_entry* entries, int n)
 {
        const struct exfat_entry_meta1* meta1;
        const struct exfat_entry_meta2* meta2;
@@ -314,8 +328,7 @@ static int parse_file_entries(struct exfat* ef, struct exfat_node* parent,
        init_node_meta2(node, meta2);
        init_node_name(node, entries + 2, mandatory_entries - 2);
 
-       if (!check_node(node, exfat_calc_checksum(entries, n),
-                       CLUSTER_SIZE(*ef->sb), meta1, meta2))
+       if (!check_node(ef, node, exfat_calc_checksum(entries, n), meta1, meta2))
                return -EIO;
 
        return 0;
@@ -337,7 +350,7 @@ static int parse_file_entry(struct exfat* ef, struct exfat_node* parent,
                return -ENOMEM;
        (*node)->entry_offset = *offset;
 
-       rc = parse_file_entries(ef, parent, *node, entries, n);
+       rc = parse_file_entries(ef, *node, entries, n);
        if (rc != 0)
        {
                free(*node);
@@ -383,6 +396,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
        const struct exfat_entry_label* label;
        uint64_t upcase_size = 0;
        le16_t* upcase_comp = NULL;
+       le16_t label_name[EXFAT_ENAME_MAX];
 
        for (;;)
        {
@@ -401,7 +415,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
                        if (ef->upcase != NULL)
                                break;
                        upcase = (const struct exfat_entry_upcase*) &entry;
-                       if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
+                       if (CLUSTER_INVALID(*ef->sb, le32_to_cpu(upcase->start_cluster)))
                        {
                                exfat_error("invalid cluster 0x%x in upcase table",
                                                le32_to_cpu(upcase->start_cluster));
@@ -452,14 +466,13 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
                case EXFAT_ENTRY_BITMAP:
                        bitmap = (const struct exfat_entry_bitmap*) &entry;
                        ef->cmap.start_cluster = le32_to_cpu(bitmap->start_cluster);
-                       if (CLUSTER_INVALID(ef->cmap.start_cluster))
+                       if (CLUSTER_INVALID(*ef->sb, ef->cmap.start_cluster))
                        {
                                exfat_error("invalid cluster 0x%x in clusters bitmap",
                                                ef->cmap.start_cluster);
                                return -EIO;
                        }
-                       ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
-                               EXFAT_FIRST_DATA_CLUSTER;
+                       ef->cmap.size = le32_to_cpu(ef->sb->cluster_count);
                        if (le64_to_cpu(bitmap->size) < DIV_ROUND_UP(ef->cmap.size, 8))
                        {
                                exfat_error("invalid clusters bitmap size: %"PRIu64
@@ -496,7 +509,10 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
                                exfat_error("too long label (%hhu chars)", label->length);
                                return -EIO;
                        }
-                       if (utf16_to_utf8(ef->label, label->name,
+                       /* copy to a temporary buffer to avoid unaligned access to a
+                          packed member */
+                       memcpy(label_name, label->name, sizeof(label_name));
+                       if (exfat_utf16_to_utf8(ef->label, label_name,
                                                sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
                                return -EIO;
                        break;
@@ -506,7 +522,8 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
                                break; /* deleted entry, ignore it */
 
                        exfat_error("unknown entry type %#hhx", entry.type);
-                       return -EIO;
+                       if (!EXFAT_REPAIR(unknown_entry, ef, parent, &entry, *offset))
+                               return -EIO;
                }
                *offset += sizeof(entry);
        }
@@ -615,6 +632,7 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
        struct exfat_entry_meta1* meta1 = (struct exfat_entry_meta1*) &entries[0];
        struct exfat_entry_meta2* meta2 = (struct exfat_entry_meta2*) &entries[1];
        int rc;
+       le16_t edate, etime;
 
        if (!node->is_dirty)
                return 0; /* no need to flush */
@@ -633,9 +651,14 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
                return -EIO;
 
        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_unix2exfat(node->mtime, &edate, &etime,
+                       &meta1->mtime_cs, &meta1->mtime_tzo);
+       meta1->mdate = edate;
+       meta1->mtime = etime;
+       exfat_unix2exfat(node->atime, &edate, &etime,
+                       NULL, &meta1->atime_tzo);
+       meta1->adate = edate;
+       meta1->atime = etime;
        meta2->size = meta2->valid_size = cpu_to_le64(node->size);
        meta2->start_cluster = cpu_to_le32(node->start_cluster);
        meta2->flags = EXFAT_FLAG_ALWAYS1;
@@ -718,7 +741,7 @@ static int shrink_directory(struct exfat* ef, struct exfat_node* dir,
                /* two subentries with meta info */
                entries += 2;
                /* subentries with file name */
-               entries += DIV_ROUND_UP(utf16_length(last_node->name),
+               entries += DIV_ROUND_UP(exfat_utf16_length(last_node->name),
                                EXFAT_ENAME_MAX);
        }
 
@@ -786,7 +809,7 @@ static int check_slot(struct exfat* ef, struct exfat_node* dir, off_t offset,
 {
        struct exfat_entry entries[n];
        int rc;
-       size_t i;
+       int i;
 
        /* Root directory contains entries, that don't have any nodes associated
           with them (clusters bitmap, upper case table, label). We need to be
@@ -824,7 +847,7 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
                return -ENOMEM;
        }
        for (p = dir->child; p != NULL; p = p->next)
-               for (i = 0; i < 1 + p->continuations; i++)
+               for (i = 0; i < 1u + p->continuations; i++)
                        BMAP_SET(dmap, p->entry_offset / sizeof(struct exfat_entry) + i);
 
        /* find a slot in the directory entries bitmap */
@@ -835,20 +858,24 @@ static int find_slot(struct exfat* ef, struct exfat_node* dir,
                        if (contiguous++ == 0)
                                *offset = (off_t) i * sizeof(struct exfat_entry);
                        if (contiguous == n)
+                       {
+                               int rc;
+
                                /* suitable slot is found, check that it's not occupied */
-                               switch (check_slot(ef, dir, *offset, n))
+                               rc = check_slot(ef, dir, *offset, n);
+                               if (rc == -EINVAL)
                                {
-                               case 0:
-                                       free(dmap);
-                                       return 0;
-                               case -EIO:
-                                       free(dmap);
-                                       return -EIO;
-                               case -EINVAL:
-                                       /* slot is occupied, continue searching */
+                                       /* slot at (i-n) is occupied, go back and check (i-n+1) */
+                                       i -= contiguous - 1;
                                        contiguous = 0;
-                                       break;
                                }
+                               else
+                               {
+                                       /* slot is free or an error occurred */
+                                       free(dmap);
+                                       return rc;
+                               }
+                       }
                }
                else
                        contiguous = 0;
@@ -865,27 +892,29 @@ static int find_slot(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)
+               const le16_t* name, off_t offset, uint16_t attrib)
 {
        struct exfat_node* node;
-       const size_t name_length = utf16_length(name);
+       const size_t name_length = exfat_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 rc;
+       le16_t edate, etime;
 
        memset(entries, 0, sizeof(struct exfat_entry[2]));
 
        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;
+       exfat_unix2exfat(time(NULL), &edate, &etime,
+                       &meta1->crtime_cs, &meta1->crtime_tzo);
+       meta1->adate = meta1->mdate = meta1->crdate = edate;
+       meta1->atime = meta1->mtime = meta1->crtime = etime;
        meta1->mtime_cs = meta1->crtime_cs; /* there is no atime_cs */
+       meta1->atime_tzo = meta1->mtime_tzo = meta1->crtime_tzo;
 
        meta2->type = EXFAT_ENTRY_FILE_INFO;
        meta2->flags = EXFAT_FLAG_ALWAYS1;
@@ -925,7 +954,6 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
 {
        struct exfat_node* dir;
        struct exfat_node* existing;
-       cluster_t cluster = EXFAT_CLUSTER_BAD;
        off_t offset = -1;
        le16_t name[EXFAT_NAME_MAX + 1];
        int rc;
@@ -941,13 +969,13 @@ static int create(struct exfat* ef, const char* path, uint16_t attrib)
        }
 
        rc = find_slot(ef, dir, &offset,
-                       2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
+                       2 + DIV_ROUND_UP(exfat_utf16_length(name), EXFAT_ENAME_MAX));
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
                return rc;
        }
-       rc = commit_entry(ef, dir, name, cluster, offset, attrib);
+       rc = commit_entry(ef, dir, name, offset, attrib);
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
@@ -995,10 +1023,9 @@ int exfat_mkdir(struct exfat* ef, const char* path)
 }
 
 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, off_t new_offset)
 {
-       const size_t name_length = utf16_length(name);
+       const size_t name_length = exfat_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];
@@ -1048,7 +1075,6 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
        struct exfat_node* node;
        struct exfat_node* existing;
        struct exfat_node* dir;
-       cluster_t cluster = EXFAT_CLUSTER_BAD;
        off_t offset = -1;
        le16_t name[EXFAT_NAME_MAX + 1];
        int rc;
@@ -1122,14 +1148,14 @@ int exfat_rename(struct exfat* ef, const char* old_path, const char* new_path)
        }
 
        rc = find_slot(ef, dir, &offset,
-                       2 + DIV_ROUND_UP(utf16_length(name), EXFAT_ENAME_MAX));
+                       2 + DIV_ROUND_UP(exfat_utf16_length(name), EXFAT_ENAME_MAX));
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
                exfat_put_node(ef, node);
                return rc;
        }
-       rc = rename_entry(ef, dir, node, name, cluster, offset);
+       rc = rename_entry(ef, dir, node, name, offset);
        if (rc != 0)
        {
                exfat_put_node(ef, dir);
@@ -1191,7 +1217,8 @@ 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 + 1, strlen(label));
+       rc = exfat_utf8_to_utf16(label_utf16, label, EXFAT_ENAME_MAX + 1,
+                       strlen(label));
        if (rc != 0)
                return rc;
 
@@ -1202,7 +1229,7 @@ int exfat_set_label(struct exfat* ef, const char* label)
                return rc;
 
        entry.type = EXFAT_ENTRY_LABEL;
-       entry.length = utf16_length(label_utf16);
+       entry.length = exfat_utf16_length(label_utf16);
        memcpy(entry.name, label_utf16, sizeof(entry.name));
        if (entry.length == 0)
                entry.type ^= EXFAT_ENTRY_VALID;