aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrelan <relan@users.noreply.github.com>2017-01-10 10:02:14 +0300
committerrelan <relan@users.noreply.github.com>2017-01-16 09:36:23 +0300
commitfe052b1c4cbc693036e498f92cbb4a80ff18e983 (patch)
tree7757b08588f4b3919cb6183361d145fdc9bbc96f
parent09ac9d9cee3db7183de003cacee19af5458af7d9 (diff)
downloadandroid_external_exfat-fe052b1c4cbc693036e498f92cbb4a80ff18e983.tar.gz
android_external_exfat-fe052b1c4cbc693036e498f92cbb4a80ff18e983.tar.bz2
android_external_exfat-fe052b1c4cbc693036e498f92cbb4a80ff18e983.zip
Add tail entries support.
Tail entries are optional continuations that can appear after the name entries. Their type is in 0xe0-0xff range. Sony uses them to save some metadata along with video clips files. Those entries can be safely ignored but must be included into checksum calculations.
-rw-r--r--libexfat/exfat.h3
-rw-r--r--libexfat/exfatfs.h3
-rw-r--r--libexfat/node.c335
-rw-r--r--libexfat/utils.c16
4 files changed, 168 insertions, 189 deletions
diff --git a/libexfat/exfat.h b/libexfat/exfat.h
index 9b7a7ec..9ae5e98 100644
--- a/libexfat/exfat.h
+++ b/libexfat/exfat.h
@@ -185,8 +185,7 @@ void exfat_get_name(const struct exfat_node* node,
char buffer[EXFAT_UTF8_NAME_BUFFER_MAX]);
uint16_t exfat_start_checksum(const struct exfat_entry_meta1* entry);
uint16_t exfat_add_checksum(const void* entry, uint16_t sum);
-le16_t exfat_calc_checksum(const struct exfat_entry_meta1* meta1,
- const struct exfat_entry_meta2* meta2, const le16_t* name);
+le16_t exfat_calc_checksum(const struct exfat_entry* entries, int n);
uint32_t exfat_vbr_start_checksum(const void* sector, size_t size);
uint32_t exfat_vbr_add_checksum(const void* sector, size_t size, uint32_t sum);
le16_t exfat_calc_name_hash(const struct exfat* ef, const le16_t* name,
diff --git a/libexfat/exfatfs.h b/libexfat/exfatfs.h
index c155312..a4739d1 100644
--- a/libexfat/exfatfs.h
+++ b/libexfat/exfatfs.h
@@ -78,6 +78,9 @@ STATIC_ASSERT(sizeof(struct exfat_super_block) == 512);
#define EXFAT_ENTRY_FILE (0x05 | EXFAT_ENTRY_VALID)
#define EXFAT_ENTRY_FILE_INFO (0x00 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED)
#define EXFAT_ENTRY_FILE_NAME (0x01 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED)
+#define EXFAT_ENTRY_FILE_TAIL (0x00 | EXFAT_ENTRY_VALID \
+ | EXFAT_ENTRY_CONTINUED \
+ | EXFAT_ENTRY_OPTIONAL)
struct exfat_entry /* common container for all entries */
{
diff --git a/libexfat/node.c b/libexfat/node.c
index 0ee3ca8..8e42c02 100644
--- a/libexfat/node.c
+++ b/libexfat/node.c
@@ -25,6 +25,8 @@
#include <string.h>
#include <inttypes.h>
+#define EXFAT_ENTRY_NONE (-1)
+
struct exfat_node* exfat_get_node(struct exfat_node* node)
{
/* if we switch to multi-threaded mode we will need atomic
@@ -147,8 +149,64 @@ static void init_node_meta2(struct exfat_node* node,
node->is_contiguous = ((meta2->flags & EXFAT_FLAG_CONTIGUOUS) != 0);
}
-static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
- uint16_t reference_checksum, uint64_t valid_size, int cluster_size)
+static void init_node_name(struct exfat_node* node,
+ const struct exfat_entry* entries, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++)
+ memcpy(node->name + i * EXFAT_ENAME_MAX,
+ ((const struct exfat_entry_name*) &entries[i])->name,
+ EXFAT_ENAME_MAX * sizeof(le16_t));
+}
+
+static bool check_entries(const struct exfat_entry* entry, int n)
+{
+ int previous = EXFAT_ENTRY_NONE;
+ int current;
+ int i;
+
+ /* check transitions between entries types */
+ for (i = 0; i < n + 1; previous = current, i++)
+ {
+ bool valid = false;
+
+ current = (i < n) ? entry[i].type : EXFAT_ENTRY_NONE;
+ switch (previous)
+ {
+ case EXFAT_ENTRY_NONE:
+ valid = (current == EXFAT_ENTRY_FILE);
+ break;
+ case EXFAT_ENTRY_FILE:
+ valid = (current == EXFAT_ENTRY_FILE_INFO);
+ break;
+ case EXFAT_ENTRY_FILE_INFO:
+ valid = (current == EXFAT_ENTRY_FILE_NAME);
+ break;
+ case EXFAT_ENTRY_FILE_NAME:
+ valid = (current == EXFAT_ENTRY_FILE_NAME ||
+ current == EXFAT_ENTRY_NONE ||
+ current >= EXFAT_ENTRY_FILE_TAIL);
+ break;
+ case EXFAT_ENTRY_FILE_TAIL ... 0xff:
+ valid = (current >= EXFAT_ENTRY_FILE_TAIL ||
+ current == EXFAT_ENTRY_NONE);
+ break;
+ }
+
+ if (!valid)
+ {
+ exfat_error("unexpected entry type %#x after %#x at %d/%d",
+ current, previous, i, n);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool check_node(const struct exfat_node* node, le16_t actual_checksum,
+ int cluster_size, const struct exfat_entry_meta1* meta1,
+ const struct exfat_entry_meta2* meta2)
{
char buffer[EXFAT_UTF8_NAME_BUFFER_MAX];
bool ret = true;
@@ -157,11 +215,11 @@ static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
Validate checksum first. If it's invalid all other fields probably
contain just garbage.
*/
- if (actual_checksum != reference_checksum)
+ if (le16_to_cpu(actual_checksum) != le16_to_cpu(meta1->checksum))
{
exfat_get_name(node, buffer);
exfat_error("'%s' has invalid checksum (%#hx != %#hx)", buffer,
- actual_checksum, reference_checksum);
+ le16_to_cpu(actual_checksum), le16_to_cpu(meta1->checksum));
ret = false;
}
@@ -171,11 +229,12 @@ static bool check_node(const struct exfat_node* node, uint16_t actual_checksum,
cannot be greater than file size. See SetFileValidData() function
description in MSDN.
*/
- if (valid_size > node->size)
+ if (le64_to_cpu(meta2->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);
+ "(%"PRIu64")", buffer, le64_to_cpu(meta2->valid_size),
+ node->size);
ret = false;
}
@@ -221,6 +280,74 @@ static bool check_node(const struct exfat_node* node, uint16_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)
+{
+ const struct exfat_entry_meta1* meta1;
+ const struct exfat_entry_meta2* meta2;
+ int mandatory_entries;
+
+ if (!check_entries(entries, n))
+ return -EIO;
+
+ meta1 = (const struct exfat_entry_meta1*) &entries[0];
+ if (meta1->continuations < 2)
+ {
+ exfat_error("too few continuations (%hhu)", meta1->continuations);
+ return -EIO;
+ }
+ meta2 = (const struct exfat_entry_meta2*) &entries[1];
+ if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
+ {
+ exfat_error("unknown flags in meta2 (%#hhx)", meta2->flags);
+ return -EIO;
+ }
+ mandatory_entries = 2 + DIV_ROUND_UP(meta2->name_length, EXFAT_ENAME_MAX);
+ if (meta1->continuations < mandatory_entries - 1)
+ {
+ exfat_error("too few continuations (%hhu < %d)",
+ meta1->continuations, mandatory_entries - 1);
+ return -EIO;
+ }
+
+ init_node_meta1(node, meta1);
+ 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))
+ return -EIO;
+
+ return 0;
+}
+
+static int parse_file_entry(struct exfat* ef, struct exfat_node* parent,
+ struct exfat_node** node, off_t* offset, int n)
+{
+ struct exfat_entry entries[n];
+ int rc;
+
+ rc = read_entries(ef, parent, entries, n, *offset);
+ if (rc != 0)
+ return rc;
+
+ /* a new node has zero references */
+ *node = allocate_node();
+ if (*node == NULL)
+ return -ENOMEM;
+ (*node)->entry_offset = *offset;
+
+ rc = parse_file_entries(ef, parent, *node, entries, n);
+ if (rc != 0)
+ {
+ free(*node);
+ return rc;
+ }
+
+ *offset += sizeof(struct exfat_entry[n]);
+ return 0;
+}
+
static void decompress_upcase(uint16_t* output, const le16_t* source,
size_t size)
{
@@ -251,126 +378,24 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
int rc;
struct exfat_entry entry;
const struct exfat_entry_meta1* meta1;
- const struct exfat_entry_meta2* meta2;
- const struct exfat_entry_name* file_name;
const struct exfat_entry_upcase* upcase;
const struct exfat_entry_bitmap* bitmap;
const struct exfat_entry_label* label;
- uint8_t continuations = 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 (;;)
{
rc = read_entries(ef, parent, &entry, 1, *offset);
- if (rc == -ENOENT)
- {
- if (continuations != 0)
- {
- exfat_error("expected %hhu continuations", continuations);
- rc = -EIO;
- goto error;
- }
- return -ENOENT; /* that's OK, means end of directory */
- }
if (rc != 0)
- goto error;
+ return rc;
switch (entry.type)
{
case EXFAT_ENTRY_FILE:
- if (continuations != 0)
- {
- exfat_error("expected %hhu continuations before new entry",
- continuations);
- rc = -EIO;
- goto error;
- }
meta1 = (const struct exfat_entry_meta1*) &entry;
- continuations = meta1->continuations;
- /* each file entry must have at least 2 continuations:
- info and name */
- if (continuations < 2)
- {
- exfat_error("too few continuations (%hhu)", continuations);
- rc = -EIO;
- goto error;
- }
- if (continuations > 1 +
- DIV_ROUND_UP(EXFAT_NAME_MAX, EXFAT_ENAME_MAX))
- {
- exfat_error("too many continuations (%hhu)", continuations);
- rc = -EIO;
- goto error;
- }
- reference_checksum = le16_to_cpu(meta1->checksum);
- actual_checksum = exfat_start_checksum(meta1);
- *node = allocate_node();
- if (*node == NULL)
- {
- rc = -ENOMEM;
- goto error;
- }
- /* new node has zero reference counter */
- (*node)->entry_offset = *offset;
- init_node_meta1(*node, meta1);
- namep = (*node)->name;
- break;
-
- case EXFAT_ENTRY_FILE_INFO:
- if (continuations < 2)
- {
- exfat_error("unexpected continuation (%hhu)",
- continuations);
- rc = -EIO;
- goto error;
- }
- meta2 = (const struct exfat_entry_meta2*) &entry;
- if (meta2->flags & ~(EXFAT_FLAG_ALWAYS1 | EXFAT_FLAG_CONTIGUOUS))
- {
- exfat_error("unknown flags in meta2 (0x%hhx)", meta2->flags);
- rc = -EIO;
- 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;
-
- case EXFAT_ENTRY_FILE_NAME:
- if (continuations == 0)
- {
- exfat_error("unexpected continuation");
- rc = -EIO;
- goto error;
- }
- file_name = (const struct exfat_entry_name*) &entry;
- actual_checksum = exfat_add_checksum(&entry, actual_checksum);
-
- 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)
- {
- if (!check_node(*node, actual_checksum, reference_checksum,
- valid_size, CLUSTER_SIZE(*ef->sb)))
- {
- rc = -EIO;
- goto error;
- }
- *offset += sizeof(entry);
- return 0; /* entry completed */
- }
- break;
+ return parse_file_entry(ef, parent, node, offset,
+ 1 + meta1->continuations);
case EXFAT_ENTRY_UPCASE:
if (ef->upcase != NULL)
@@ -380,8 +405,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
{
exfat_error("invalid cluster 0x%x in upcase table",
le32_to_cpu(upcase->start_cluster));
- rc = -EIO;
- goto error;
+ return -EIO;
}
upcase_size = le64_to_cpu(upcase->size);
if (upcase_size == 0 ||
@@ -390,16 +414,14 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
{
exfat_error("bad upcase table size (%"PRIu64" bytes)",
upcase_size);
- rc = -EIO;
- goto error;
+ return -EIO;
}
upcase_comp = malloc(upcase_size);
if (upcase_comp == NULL)
{
exfat_error("failed to allocate upcase table (%"PRIu64" bytes)",
upcase_size);
- rc = -ENOMEM;
- goto error;
+ return -ENOMEM;
}
/* read compressed upcase table */
@@ -411,8 +433,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
"(%"PRIu64" bytes starting at cluster %#x)",
upcase_size,
le32_to_cpu(upcase->start_cluster));
- rc = -EIO;
- goto error;
+ return -EIO;
}
/* decompress upcase table */
@@ -421,8 +442,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
{
free(upcase_comp);
exfat_error("failed to allocate decompressed upcase table");
- rc = -ENOMEM;
- goto error;
+ return -ENOMEM;
}
decompress_upcase(ef->upcase, upcase_comp,
upcase_size / sizeof(uint16_t));
@@ -436,8 +456,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
{
exfat_error("invalid cluster 0x%x in clusters bitmap",
ef->cmap.start_cluster);
- rc = -EIO;
- goto error;
+ return -EIO;
}
ef->cmap.size = le32_to_cpu(ef->sb->cluster_count) -
EXFAT_FIRST_DATA_CLUSTER;
@@ -447,8 +466,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
" (expected at least %u)",
le64_to_cpu(bitmap->size),
DIV_ROUND_UP(ef->cmap.size, 8));
- rc = -EIO;
- goto error;
+ return -EIO;
}
/* FIXME bitmap can be rather big, up to 512 MB */
ef->cmap.chunk_size = ef->cmap.size;
@@ -457,8 +475,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
{
exfat_error("failed to allocate clusters bitmap chunk "
"(%"PRIu64" bytes)", le64_to_cpu(bitmap->size));
- rc = -ENOMEM;
- goto error;
+ return -ENOMEM;
}
if (exfat_pread(ef->dev, ef->cmap.chunk,
@@ -468,8 +485,7 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
exfat_error("failed to read clusters bitmap "
"(%"PRIu64" bytes starting at cluster %#x)",
le64_to_cpu(bitmap->size), ef->cmap.start_cluster);
- rc = -EIO;
- goto error;
+ return -EIO;
}
break;
@@ -478,45 +494,23 @@ static int readdir(struct exfat* ef, struct exfat_node* parent,
if (label->length > EXFAT_ENAME_MAX)
{
exfat_error("too long label (%hhu chars)", label->length);
- rc = -EIO;
- goto error;
+ return -EIO;
}
if (utf16_to_utf8(ef->label, label->name,
sizeof(ef->label), EXFAT_ENAME_MAX) != 0)
- {
- rc = -EIO;
- goto error;
- }
+ return -EIO;
break;
default:
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);
- rc = -EIO;
- goto error;
- }
- /* optional entry, warn and skip */
- exfat_warn("unknown entry type %#hhx", entry.type);
- if (continuations == 0)
- {
- exfat_error("unexpected continuation");
- rc = -EIO;
- goto error;
- }
- --continuations;
- break;
+
+ exfat_error("unknown entry type %#hhx", entry.type);
+ return -EIO;
}
*offset += sizeof(entry);
}
/* we never reach here */
-
-error:
- free(*node);
- *node = NULL;
- return rc;
}
int exfat_cache_directory(struct exfat* ef, struct exfat_node* dir)
@@ -617,7 +611,7 @@ void exfat_reset_cache(struct exfat* ef)
int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
{
- struct exfat_entry entries[2];
+ struct exfat_entry entries[1 + node->continuations];
struct exfat_entry_meta1* meta1 = (struct exfat_entry_meta1*) &entries[0];
struct exfat_entry_meta2* meta2 = (struct exfat_entry_meta2*) &entries[1];
int rc;
@@ -631,25 +625,17 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
if (node->parent == NULL)
return 0; /* do not flush unlinked node */
- rc = read_entries(ef, node->parent, entries, 2, node->entry_offset);
+ rc = read_entries(ef, node->parent, entries, 1 + node->continuations,
+ node->entry_offset);
if (rc != 0)
return rc;
-
- if (meta1->type != EXFAT_ENTRY_FILE)
- {
- exfat_error("invalid type of meta1: %#hhx", meta1->type);
+ if (!check_entries(entries, 1 + node->continuations))
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);
-
- if (meta2->type != EXFAT_ENTRY_FILE_INFO)
- {
- exfat_error("invalid type of meta2: %#hhx", meta2->type);
- return -EIO;
- }
meta2->size = meta2->valid_size = cpu_to_le64(node->size);
meta2->start_cluster = cpu_to_le32(node->start_cluster);
meta2->flags = EXFAT_FLAG_ALWAYS1;
@@ -658,9 +644,9 @@ int exfat_flush_node(struct exfat* ef, struct exfat_node* node)
meta2->flags |= EXFAT_FLAG_CONTIGUOUS;
/* name hash remains unchanged, no need to recalculate it */
- meta1->checksum = exfat_calc_checksum(meta1, meta2, node->name);
-
- rc = write_entries(ef, node->parent, entries, 2, node->entry_offset);
+ meta1->checksum = exfat_calc_checksum(entries, 1 + node->continuations);
+ rc = write_entries(ef, node->parent, entries, 1 + node->continuations,
+ node->entry_offset);
if (rc != 0)
return rc;
@@ -907,8 +893,6 @@ static int commit_entry(struct exfat* ef, struct exfat_node* dir,
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);
-
for (i = 0; i < name_entries; i++)
{
struct exfat_entry_name* name_entry;
@@ -920,6 +904,7 @@ static int commit_entry(struct exfat* ef, struct exfat_node* dir,
EXFAT_ENAME_MAX * sizeof(le16_t));
}
+ meta1->checksum = exfat_calc_checksum(entries, 2 + name_entries);
rc = write_entries(ef, dir, entries, 2 + name_entries, offset);
if (rc != 0)
return rc;
@@ -1028,7 +1013,6 @@ static int rename_entry(struct exfat* ef, struct exfat_node* dir,
meta1->continuations = 1 + name_entries;
meta2->name_length = name_length;
meta2->name_hash = exfat_calc_name_hash(ef, name, name_length);
- meta1->checksum = exfat_calc_checksum(meta1, meta2, name);
rc = erase_node(ef, node);
if (rc != 0)
@@ -1048,6 +1032,7 @@ static int rename_entry(struct exfat* ef, struct exfat_node* dir,
EXFAT_ENAME_MAX * sizeof(le16_t));
}
+ meta1->checksum = exfat_calc_checksum(entries, 2 + name_entries);
rc = write_entries(ef, dir, entries, 2 + name_entries, new_offset);
if (rc != 0)
return rc;
diff --git a/libexfat/utils.c b/libexfat/utils.c
index c633a29..9e099a8 100644
--- a/libexfat/utils.c
+++ b/libexfat/utils.c
@@ -83,22 +83,14 @@ uint16_t exfat_add_checksum(const void* entry, uint16_t sum)
return add_checksum_bytes(sum, entry, sizeof(struct exfat_entry));
}
-le16_t exfat_calc_checksum(const struct exfat_entry_meta1* meta1,
- const struct exfat_entry_meta2* meta2, const le16_t* name)
+le16_t exfat_calc_checksum(const struct exfat_entry* entries, int n)
{
uint16_t checksum;
- const int name_entries = DIV_ROUND_UP(meta2->name_length, EXFAT_ENAME_MAX);
int i;
- checksum = exfat_start_checksum(meta1);
- checksum = exfat_add_checksum(meta2, checksum);
- for (i = 0; i < name_entries; i++)
- {
- checksum = add_checksum_byte(checksum, EXFAT_ENTRY_FILE_NAME);
- checksum = add_checksum_byte(checksum, 0);
- checksum = add_checksum_bytes(checksum, name + i * EXFAT_ENAME_MAX,
- EXFAT_ENAME_MAX * sizeof(le16_t));
- }
+ checksum = exfat_start_checksum((const struct exfat_entry_meta1*) entries);
+ for (i = 1; i < n; i++)
+ checksum = exfat_add_checksum(entries + i, checksum);
return cpu_to_le16(checksum);
}