aboutsummaryrefslogtreecommitdiffstats
path: root/libexfat/lookup.c
diff options
context:
space:
mode:
authorrelan <relan@users.noreply.github.com>2009-09-14 18:44:13 +0000
committerrelan <relan@users.noreply.github.com>2015-08-24 08:26:09 +0300
commit542774e7cd420b4acbb5a6ff0fdb73269807da56 (patch)
treeeafe7fd4240ef011be3b30d55fa18e38d479ef7b /libexfat/lookup.c
downloadandroid_external_exfat-542774e7cd420b4acbb5a6ff0fdb73269807da56.tar.gz
android_external_exfat-542774e7cd420b4acbb5a6ff0fdb73269807da56.tar.bz2
android_external_exfat-542774e7cd420b4acbb5a6ff0fdb73269807da56.zip
Initial code drop.
Diffstat (limited to 'libexfat/lookup.c')
-rw-r--r--libexfat/lookup.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/libexfat/lookup.c b/libexfat/lookup.c
new file mode 100644
index 0000000..a125895
--- /dev/null
+++ b/libexfat/lookup.c
@@ -0,0 +1,268 @@
+/*
+ * lookup.c
+ * exFAT file system implementation library.
+ *
+ * Created by Andrew Nayenko on 02.09.09.
+ * This software is distributed under the GNU General Public License
+ * version 3 or any later.
+ */
+
+#include "exfat.h"
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+void exfat_opendir(struct exfat_node* node, struct exfat_iterator* it)
+{
+ if (!(node->flags & EXFAT_ATTRIB_DIR))
+ exfat_bug("`%s' is not a directory", node->name);
+ it->cluster = node->start_cluster;
+ it->offset = 0;
+ it->contiguous = IS_CONTIGUOUS(*node);
+ it->chunk = NULL;
+}
+
+void exfat_closedir(struct exfat_iterator* it)
+{
+ it->cluster = 0;
+ it->offset = 0;
+ it->contiguous = 0;
+ free(it->chunk);
+ it->chunk = NULL;
+}
+
+/*
+ * Reads one entry in directory at position pointed by iterator and fills
+ * node structure.
+ */
+int exfat_readdir(struct exfat* ef, struct exfat_node* node,
+ struct exfat_iterator* it)
+{
+ const struct exfat_entry* entry;
+ const struct exfat_file* file;
+ const struct exfat_file_info* file_info;
+ const struct exfat_file_name* file_name;
+ const struct exfat_upcase* upcase;
+ uint8_t continuations = 0;
+ le16_t* namep = NULL;
+
+ if (it->chunk == NULL)
+ {
+ it->chunk = malloc(CLUSTER_SIZE(*ef->sb));
+ if (it->chunk == NULL)
+ {
+ exfat_error("out of memory");
+ return -ENOMEM;
+ }
+ exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
+ exfat_c2o(ef, it->cluster), ef->fd);
+ }
+
+ 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));
+ /* move iterator to the next entry in the directory */
+ it->offset += sizeof(struct exfat_entry);
+
+ switch (entry->type)
+ {
+ case EXFAT_ENTRY_EOD:
+ if (continuations != 0)
+ {
+ exfat_error("expected %hhu continuations before EOD",
+ continuations);
+ return -EIO;
+ }
+ return -ENOENT; /* that's OK, means end of directory */
+
+ case EXFAT_ENTRY_FILE:
+ if (continuations != 0)
+ {
+ exfat_error("expected %hhu continuations before new entry",
+ continuations);
+ return -EIO;
+ }
+ memset(node, 0, sizeof(struct exfat_node));
+ file = (const struct exfat_file*) entry;
+ node->flags = le16_to_cpu(file->attrib);
+ node->mtime = exfat_exfat2unix(file->mdate, file->mtime);
+ node->atime = exfat_exfat2unix(file->adate, file->atime);
+ namep = node->name;
+ continuations = file->continuations;
+ /* each file entry must have at least 2 continuations:
+ info and name */
+ if (continuations < 2)
+ {
+ exfat_error("too few continuations (%hhu)", continuations);
+ return -EIO;
+ }
+ break;
+
+ case EXFAT_ENTRY_FILE_INFO:
+ if (continuations < 2)
+ {
+ exfat_error("unexpected continuation (%hhu)",
+ continuations);
+ return -EIO;
+ }
+ file_info = (const struct exfat_file_info*) entry;
+ node->size = le64_to_cpu(file_info->size);
+ node->start_cluster = le32_to_cpu(file_info->start_cluster);
+ if (file_info->flag == EXFAT_FLAG_CONTIGUOUS)
+ node->flags |= EXFAT_ATTRIB_CONTIGUOUS;
+ --continuations;
+ break;
+
+ case EXFAT_ENTRY_FILE_NAME:
+ if (continuations == 0)
+ {
+ exfat_error("unexpected continuation");
+ return -EIO;
+ }
+ file_name = (const struct exfat_file_name*) entry;
+ memcpy(namep, file_name->name, EXFAT_ENAME_MAX * sizeof(le16_t));
+ namep += EXFAT_ENAME_MAX;
+ if (--continuations == 0)
+ return 0; /* entry completed */
+ break;
+
+ case EXFAT_ENTRY_UPCASE:
+ if (ef->upcase != NULL)
+ break;
+ upcase = (const struct exfat_upcase*) entry;
+ if (CLUSTER_INVALID(le32_to_cpu(upcase->start_cluster)))
+ {
+ exfat_error("invalid cluster in upcase table");
+ return -EIO;
+ }
+ 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)
+ {
+ exfat_error("bad upcase table size (%"PRIu64" bytes)",
+ le64_to_cpu(upcase->size));
+ return -EIO;
+ }
+ ef->upcase = malloc(le64_to_cpu(upcase->size));
+ ef->upcase_chars = le64_to_cpu(upcase->size) / sizeof(le16_t);
+
+ /* set up a few fields in node just to satisfy exfat_read() */
+ memset(node, 0, sizeof(struct exfat_node));
+ node->start_cluster = le32_to_cpu(upcase->start_cluster);
+ node->flags = EXFAT_ATTRIB_CONTIGUOUS;
+ node->size = le64_to_cpu(upcase->size);
+ if (exfat_read(ef, node, ef->upcase, node->size, 0) != node->size)
+ return -EIO;
+ break;
+ }
+
+ /* fetch the next cluster if needed */
+ if ((it->offset & (CLUSTER_SIZE(*ef->sb) - 1)) == 0)
+ {
+ it->cluster = exfat_next_cluster(ef, it->cluster, it->contiguous);
+ if (CLUSTER_INVALID(it->cluster))
+ {
+ exfat_error("invalid cluster while reading directory");
+ return -EIO;
+ }
+ exfat_read_raw(it->chunk, CLUSTER_SIZE(*ef->sb),
+ exfat_c2o(ef, it->cluster), ef->fd);
+ }
+ }
+ /* we never reach here */
+}
+
+static int compare_char(struct exfat* ef, uint16_t a, uint16_t b)
+{
+ if (a >= ef->upcase_chars || b >= ef->upcase_chars)
+ return a - b;
+ else
+ return le16_to_cpu(ef->upcase[a]) - le16_to_cpu(ef->upcase[b]);
+}
+
+static int compare_name(struct exfat* ef, const le16_t* a, const le16_t* b)
+{
+ while (le16_to_cpu(*a) && le16_to_cpu(*b))
+ {
+ if (compare_char(ef, le16_to_cpu(*a), le16_to_cpu(*b)) != 0)
+ break;
+ a++;
+ b++;
+ }
+ return le16_to_cpu(*a) - le16_to_cpu(*b);
+}
+
+static int lookup_name(struct exfat* ef, struct exfat_node* node,
+ const le16_t* name)
+{
+ struct exfat_iterator it;
+
+ exfat_opendir(node, &it);
+ while (exfat_readdir(ef, node, &it) == 0)
+ {
+ if (compare_name(ef, name, node->name) == 0)
+ {
+ exfat_closedir(&it);
+ return 0;
+ }
+ }
+ exfat_closedir(&it);
+ return -ENOENT;
+}
+
+int exfat_lookup(struct exfat* ef, struct exfat_node* node,
+ const char* path)
+{
+ le16_t buffer[EXFAT_NAME_MAX + 1];
+ int rc;
+ le16_t* p;
+ le16_t* subpath;
+
+ if (strlen(path) > EXFAT_NAME_MAX)
+ {
+ exfat_error("file name `%s' is too long", path);
+ return -ENAMETOOLONG;
+ }
+
+ rc = utf8_to_utf16(buffer, path, EXFAT_NAME_MAX, strlen(path));
+ if (rc != 0)
+ return rc;
+
+ /* start from the root directory */
+ node->flags = EXFAT_ATTRIB_DIR;
+ node->size = 0;
+ node->start_cluster = le32_to_cpu(ef->sb->rootdir_cluster);
+ node->name[0] = cpu_to_le16('\0');
+ /* exFAT does not have time attributes for the root directory */
+ node->mtime = ef->mount_time;
+ node->atime = ef->mount_time;
+
+ for (subpath = p = buffer; p; subpath = p + 1)
+ {
+ while (le16_to_cpu(*subpath) == '/')
+ subpath++; /* skip leading slashes */
+ for (p = subpath; ; p++)
+ {
+ if (le16_to_cpu(*p) == '\0')
+ {
+ p = NULL;
+ break;
+ }
+ if (le16_to_cpu(*p) == '/')
+ {
+ *p = cpu_to_le16('\0');
+ break;
+ }
+ }
+ if (le16_to_cpu(*subpath) == '\0')
+ break; /* skip trailing slashes */
+ if (le16_to_cpu(subpath[0]) == '.' && le16_to_cpu(subpath[1]) == '\0')
+ continue; /* skip "." component */
+ if (lookup_name(ef, node, subpath) != 0)
+ return -ENOENT;
+ }
+ return 0;
+}