summaryrefslogtreecommitdiffstats
path: root/lib/exfat_fs.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/exfat_fs.c')
-rw-r--r--lib/exfat_fs.c304
1 files changed, 304 insertions, 0 deletions
diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c
new file mode 100644
index 0000000..d563c61
--- /dev/null
+++ b/lib/exfat_fs.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 LG Electronics.
+ *
+ * Author(s): Hyunchul Lee <hyc.lee@gmail.com>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+
+struct exfat_inode *exfat_alloc_inode(__u16 attr)
+{
+ struct exfat_inode *node;
+ int size;
+
+ size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE;
+ node = (struct exfat_inode *)calloc(1, size);
+ if (!node) {
+ exfat_err("failed to allocate exfat_node\n");
+ return NULL;
+ }
+
+ node->parent = NULL;
+ INIT_LIST_HEAD(&node->children);
+ INIT_LIST_HEAD(&node->sibling);
+ INIT_LIST_HEAD(&node->list);
+
+ node->attr = attr;
+ return node;
+}
+
+void exfat_free_inode(struct exfat_inode *node)
+{
+ if (node) {
+ if (node->dentry_set)
+ free(node->dentry_set);
+ free(node);
+ }
+}
+
+void exfat_free_children(struct exfat_inode *dir, bool file_only)
+{
+ struct exfat_inode *node, *i;
+
+ list_for_each_entry_safe(node, i, &dir->children, sibling) {
+ if (file_only) {
+ if (!(node->attr & ATTR_SUBDIR)) {
+ list_del(&node->sibling);
+ exfat_free_inode(node);
+ }
+ } else {
+ list_del(&node->sibling);
+ list_del(&node->list);
+ exfat_free_inode(node);
+ }
+ }
+}
+
+void exfat_free_file_children(struct exfat_inode *dir)
+{
+ exfat_free_children(dir, true);
+}
+
+/* delete @child and all ancestors that does not have
+ * children
+ */
+void exfat_free_ancestors(struct exfat_inode *child)
+{
+ struct exfat_inode *parent;
+
+ while (child && list_empty(&child->children)) {
+ if (!child->parent || !(child->attr & ATTR_SUBDIR))
+ return;
+
+ parent = child->parent;
+ list_del(&child->sibling);
+ exfat_free_inode(child);
+
+ child = parent;
+ }
+ return;
+}
+
+void exfat_free_dir_list(struct exfat *exfat)
+{
+ struct exfat_inode *dir, *i;
+
+ list_for_each_entry_safe(dir, i, &exfat->dir_list, list) {
+ if (!dir->parent)
+ continue;
+ exfat_free_file_children(dir);
+ list_del(&dir->list);
+ exfat_free_inode(dir);
+ }
+}
+
+void exfat_free_exfat(struct exfat *exfat)
+{
+ if (exfat) {
+ if (exfat->bs)
+ free(exfat->bs);
+ if (exfat->alloc_bitmap)
+ free(exfat->alloc_bitmap);
+ if (exfat->disk_bitmap)
+ free(exfat->disk_bitmap);
+ if (exfat->ohead_bitmap)
+ free(exfat->ohead_bitmap);
+ if (exfat->upcase_table)
+ free(exfat->upcase_table);
+ if (exfat->root)
+ exfat_free_inode(exfat->root);
+ if (exfat->zero_cluster)
+ free(exfat->zero_cluster);
+ free(exfat);
+ }
+}
+
+struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs)
+{
+ struct exfat *exfat;
+
+ exfat = (struct exfat *)calloc(1, sizeof(*exfat));
+ if (!exfat)
+ return NULL;
+
+ INIT_LIST_HEAD(&exfat->dir_list);
+ exfat->blk_dev = blk_dev;
+ exfat->bs = bs;
+ exfat->clus_count = le32_to_cpu(bs->bsx.clu_count);
+ exfat->clus_size = EXFAT_CLUSTER_SIZE(bs);
+ exfat->sect_size = EXFAT_SECTOR_SIZE(bs);
+
+ /* TODO: bitmap could be very large. */
+ exfat->alloc_bitmap = (char *)calloc(1,
+ EXFAT_BITMAP_SIZE(exfat->clus_count));
+ if (!exfat->alloc_bitmap) {
+ exfat_err("failed to allocate bitmap\n");
+ goto err;
+ }
+
+ exfat->ohead_bitmap =
+ calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count));
+ if (!exfat->ohead_bitmap) {
+ exfat_err("failed to allocate bitmap\n");
+ goto err;
+ }
+
+ exfat->disk_bitmap =
+ calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count));
+ if (!exfat->disk_bitmap) {
+ exfat_err("failed to allocate bitmap\n");
+ goto err;
+ }
+
+ exfat->zero_cluster = calloc(1, exfat->clus_size);
+ if (!exfat->zero_cluster) {
+ exfat_err("failed to allocate a zero-filled cluster buffer\n");
+ goto err;
+ }
+
+ exfat->start_clu = EXFAT_FIRST_CLUSTER;
+ return exfat;
+err:
+ exfat_free_exfat(exfat);
+ return NULL;
+}
+
+struct buffer_desc *exfat_alloc_buffer(int count,
+ unsigned int clu_size, unsigned int sect_size)
+{
+ struct buffer_desc *bd;
+ int i;
+
+ bd = (struct buffer_desc *)calloc(sizeof(*bd), count);
+ if (!bd)
+ return NULL;
+
+ for (i = 0; i < count; i++) {
+ bd[i].buffer = (char *)malloc(clu_size);
+ if (!bd[i].buffer)
+ goto err;
+ bd[i].dirty = (char *)calloc(clu_size / sect_size, 1);
+ if (!bd[i].dirty)
+ goto err;
+ }
+ return bd;
+err:
+ exfat_free_buffer(bd, count);
+ return NULL;
+}
+
+void exfat_free_buffer(struct buffer_desc *bd, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++) {
+ if (bd[i].buffer)
+ free(bd[i].buffer);
+ if (bd[i].dirty)
+ free(bd[i].dirty);
+ }
+ free(bd);
+}
+
+/*
+ * get references of ancestors that include @child until the count of
+ * ancesters is not larger than @count and the count of characters of
+ * their names is not larger than @max_char_len.
+ * return true if root is reached.
+ */
+static bool get_ancestors(struct exfat_inode *child,
+ struct exfat_inode **ancestors, int count,
+ int max_char_len,
+ int *ancestor_count)
+{
+ struct exfat_inode *dir;
+ int name_len, char_len;
+ int root_depth, depth, i;
+
+ root_depth = 0;
+ char_len = 0;
+ max_char_len += 1;
+
+ dir = child;
+ while (dir) {
+ name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE);
+ if (char_len + name_len > max_char_len)
+ break;
+
+ /* include '/' */
+ char_len += name_len + 1;
+ root_depth++;
+
+ dir = dir->parent;
+ }
+
+ depth = MIN(root_depth, count);
+
+ for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--)
+ ancestors[i] = dir;
+
+ *ancestor_count = depth;
+ return !dir;
+}
+
+int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
+{
+ int depth, i;
+ int name_len;
+ __le16 *utf16_path;
+ static const __le16 utf16_slash = cpu_to_le16(0x002F);
+ static const __le16 utf16_null = cpu_to_le16(0x0000);
+ size_t in_size;
+
+ ctx->local_path[0] = '\0';
+
+ get_ancestors(child,
+ ctx->ancestors,
+ sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]),
+ PATH_MAX,
+ &depth);
+
+ utf16_path = ctx->utf16_path;
+ for (i = 0; i < depth; i++) {
+ name_len = exfat_utf16_len(ctx->ancestors[i]->name,
+ NAME_BUFFER_SIZE);
+ memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name,
+ name_len * 2);
+ utf16_path += name_len;
+ memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash));
+ utf16_path++;
+ }
+
+ if (depth > 1)
+ utf16_path--;
+ memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null));
+ utf16_path++;
+
+ in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16);
+ return exfat_utf16_dec(ctx->utf16_path, in_size,
+ ctx->local_path, sizeof(ctx->local_path));
+}
+
+int exfat_resolve_path_parent(struct path_resolve_ctx *ctx,
+ struct exfat_inode *parent, struct exfat_inode *child)
+{
+ int ret;
+ struct exfat_inode *old;
+
+ old = child->parent;
+ child->parent = parent;
+
+ ret = exfat_resolve_path(ctx, child);
+ child->parent = old;
+ return ret;
+}