[PATCH v3 3/6] composefs: Add descriptor parsing code

From: Alexander Larsson
Date: Fri Jan 20 2023 - 10:25:16 EST


This adds the code to load and decode the filesystem descriptor file
format.

We open the descriptor at mount time and keep the struct file *
around. Most accesses to it happens via cfs_get_buf() which reads the
descriptor data directly from the page cache. Although in a few cases
(like when we need to directly copy data) we use kernel_read()
instead.

Signed-off-by: Alexander Larsson <alexl@xxxxxxxxxx>
Co-developed-by: Giuseppe Scrivano <gscrivan@xxxxxxxxxx>
Signed-off-by: Giuseppe Scrivano <gscrivan@xxxxxxxxxx>
---
fs/composefs/cfs-internals.h | 55 +++
fs/composefs/cfs-reader.c | 720 +++++++++++++++++++++++++++++++++++
2 files changed, 775 insertions(+)
create mode 100644 fs/composefs/cfs-internals.h
create mode 100644 fs/composefs/cfs-reader.c

diff --git a/fs/composefs/cfs-internals.h b/fs/composefs/cfs-internals.h
new file mode 100644
index 000000000000..3524b977c8a8
--- /dev/null
+++ b/fs/composefs/cfs-internals.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _CFS_INTERNALS_H
+#define _CFS_INTERNALS_H
+
+#include "cfs.h"
+
+#define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */
+
+struct cfs_inode_extra_data {
+ char *path_payload; /* Real pathname for files, target for symlinks */
+
+ u64 xattrs_offset;
+ u32 xattrs_len;
+
+ u64 dirents_offset;
+ u32 dirents_len;
+
+ bool has_digest;
+ u8 digest[SHA256_DIGEST_SIZE]; /* fs-verity digest */
+};
+
+struct cfs_context {
+ struct file *descriptor;
+ u64 vdata_offset;
+ u32 num_inodes;
+
+ u64 descriptor_len;
+};
+
+int cfs_init_ctx(const char *descriptor_path, const u8 *required_digest,
+ struct cfs_context *ctx);
+
+void cfs_ctx_put(struct cfs_context *ctx);
+
+int cfs_init_inode(struct cfs_context *ctx, u32 inode_num, struct inode *inode,
+ struct cfs_inode_extra_data *data);
+
+ssize_t cfs_list_xattrs(struct cfs_context *ctx,
+ struct cfs_inode_extra_data *inode_data, char *names,
+ size_t size);
+int cfs_get_xattr(struct cfs_context *ctx, struct cfs_inode_extra_data *inode_data,
+ const char *name, void *value, size_t size);
+
+typedef bool (*cfs_dir_iter_cb)(void *private, const char *name, int namelen,
+ u64 ino, unsigned int dtype);
+
+int cfs_dir_iterate(struct cfs_context *ctx, u64 index,
+ struct cfs_inode_extra_data *inode_data, loff_t first,
+ cfs_dir_iter_cb cb, void *private);
+
+int cfs_dir_lookup(struct cfs_context *ctx, u64 index,
+ struct cfs_inode_extra_data *inode_data, const char *name,
+ size_t name_len, u64 *index_out);
+
+#endif
diff --git a/fs/composefs/cfs-reader.c b/fs/composefs/cfs-reader.c
new file mode 100644
index 000000000000..6ff7d3e70d39
--- /dev/null
+++ b/fs/composefs/cfs-reader.c
@@ -0,0 +1,720 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * composefs
+ *
+ * Copyright (C) 2021 Giuseppe Scrivano
+ * Copyright (C) 2022 Alexander Larsson
+ *
+ * This file is released under the GPL.
+ */
+
+#include "cfs-internals.h"
+
+#include <linux/file.h>
+#include <linux/fsverity.h>
+#include <linux/pagemap.h>
+#include <linux/vmalloc.h>
+
+/* When mapping buffers via page arrays this is an "arbitrary" limit
+ * to ensure we're not ballooning memory use for the page array and
+ * mapping. On a 4k page, 64bit machine this limit will make the page
+ * array fit in one page, and will allow a mapping of 2MB. When
+ * applied to e.g. dirents this will allow more than 27000 filenames
+ * of length 64, which seems ok. If we need to support more, at that
+ * point we should probably fall back to an approach that maps pages
+ * incrementally.
+ */
+#define CFS_BUF_MAXPAGES 512
+
+#define CFS_BUF_PREALLOC_SIZE 4
+
+/* Check if the element, which is supposed to be offset from section_start
+ * actually fits in the section starting at section_start ending at section_end,
+ * and doesn't wrap.
+ */
+static bool cfs_is_in_section(u64 section_start, u64 section_end,
+ u64 element_offset, u64 element_size)
+{
+ u64 element_end;
+ u64 element_start;
+
+ element_start = section_start + element_offset;
+ if (element_start < section_start || element_start >= section_end)
+ return false;
+
+ element_end = element_start + element_size;
+ if (element_end < element_start || element_end > section_end)
+ return false;
+
+ return true;
+}
+
+struct cfs_buf {
+ struct page **pages;
+ size_t n_pages;
+ void *base;
+
+ /* Used as "pages" above to avoid allocation for small buffers */
+ struct page *prealloc[CFS_BUF_PREALLOC_SIZE];
+};
+
+static void cfs_buf_put(struct cfs_buf *buf)
+{
+ if (buf->pages) {
+ if (buf->n_pages == 1)
+ kunmap_local(buf->base);
+ else
+ vm_unmap_ram(buf->base, buf->n_pages);
+ for (size_t i = 0; i < buf->n_pages; i++)
+ put_page(buf->pages[i]);
+ if (buf->n_pages > CFS_BUF_PREALLOC_SIZE)
+ kfree(buf->pages);
+ buf->pages = NULL;
+ }
+}
+
+/* Map data from anywhere in the descriptor */
+static void *cfs_get_buf(struct cfs_context *ctx, u64 offset, u32 size,
+ struct cfs_buf *buf)
+{
+ struct inode *inode = ctx->descriptor->f_inode;
+ struct address_space *const mapping = inode->i_mapping;
+ size_t n_pages, read_pages;
+ u64 index, last_index;
+ struct page **pages;
+ void *base;
+
+ if (buf->pages)
+ return ERR_PTR(-EINVAL);
+
+ if (!cfs_is_in_section(0, ctx->descriptor_len, offset, size) || size == 0)
+ return ERR_PTR(-EFSCORRUPTED);
+
+ index = offset >> PAGE_SHIFT;
+ last_index = (offset + size - 1) >> PAGE_SHIFT;
+ n_pages = last_index - index + 1;
+
+ if (n_pages > CFS_BUF_MAXPAGES)
+ return ERR_PTR(-ENOMEM);
+
+ if (n_pages > CFS_BUF_PREALLOC_SIZE) {
+ pages = kmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!pages)
+ return ERR_PTR(-ENOMEM);
+ } else {
+ /* Avoid allocation in common (small) cases */
+ pages = buf->prealloc;
+ }
+
+ for (read_pages = 0; read_pages < n_pages; read_pages++) {
+ struct page *page =
+ read_cache_page(mapping, index + read_pages, NULL, NULL);
+ if (IS_ERR(page))
+ goto nomem;
+ pages[read_pages] = page;
+ }
+
+ if (n_pages == 1) {
+ base = kmap_local_page(pages[0]);
+ } else {
+ base = vm_map_ram(pages, n_pages, -1);
+ if (!base)
+ goto nomem;
+ }
+
+ buf->pages = pages;
+ buf->n_pages = n_pages;
+ buf->base = base;
+
+ return base + (offset & (PAGE_SIZE - 1));
+
+nomem:
+ for (size_t i = 0; i < read_pages; i++)
+ put_page(pages[i]);
+ if (n_pages > CFS_BUF_PREALLOC_SIZE)
+ kfree(pages);
+
+ return ERR_PTR(-ENOMEM);
+}
+
+/* Map data from the inode table */
+static void *cfs_get_inode_buf(struct cfs_context *ctx, u64 offset, u32 len,
+ struct cfs_buf *buf)
+{
+ if (!cfs_is_in_section(CFS_INODE_TABLE_OFFSET, ctx->vdata_offset, offset, len))
+ return ERR_PTR(-EINVAL);
+
+ return cfs_get_buf(ctx, CFS_INODE_TABLE_OFFSET + offset, len, buf);
+}
+
+/* Map data from the variable data section */
+static void *cfs_get_vdata_buf(struct cfs_context *ctx, u64 offset, u32 len,
+ struct cfs_buf *buf)
+{
+ if (!cfs_is_in_section(ctx->vdata_offset, ctx->descriptor_len, offset, len))
+ return ERR_PTR(-EINVAL);
+
+ return cfs_get_buf(ctx, ctx->vdata_offset + offset, len, buf);
+}
+
+/* Read data from anywhere in the descriptor */
+static void *cfs_read_data(struct cfs_context *ctx, u64 offset, u32 size, u8 *dest)
+{
+ loff_t pos = offset;
+ size_t copied;
+
+ if (!cfs_is_in_section(0, ctx->descriptor_len, offset, size))
+ return ERR_PTR(-EFSCORRUPTED);
+
+ copied = 0;
+ while (copied < size) {
+ ssize_t bytes;
+
+ bytes = kernel_read(ctx->descriptor, dest + copied,
+ size - copied, &pos);
+ if (bytes < 0)
+ return ERR_PTR(bytes);
+ if (bytes == 0)
+ return ERR_PTR(-EINVAL);
+
+ copied += bytes;
+ }
+
+ if (copied != size)
+ return ERR_PTR(-EFSCORRUPTED);
+ return dest;
+}
+
+/* Read data from the variable data section */
+static void *cfs_read_vdata(struct cfs_context *ctx, u64 offset, u32 len, char *buf)
+{
+ void *res;
+
+ if (!cfs_is_in_section(ctx->vdata_offset, ctx->descriptor_len, offset, len))
+ return ERR_PTR(-EINVAL);
+
+ res = cfs_read_data(ctx, ctx->vdata_offset + offset, len, buf);
+ if (IS_ERR(res))
+ return ERR_CAST(res);
+
+ return buf;
+}
+
+/* Allocate, read and null-terminate paths from the variable data section */
+static char *cfs_read_vdata_path(struct cfs_context *ctx, u64 offset, u32 len)
+{
+ char *path;
+ void *res;
+
+ if (len > PATH_MAX)
+ return ERR_PTR(-EINVAL);
+
+ path = kmalloc(len + 1, GFP_KERNEL);
+ if (!path)
+ return ERR_PTR(-ENOMEM);
+
+ res = cfs_read_vdata(ctx, offset, len, path);
+ if (IS_ERR(res)) {
+ kfree(path);
+ return ERR_CAST(res);
+ }
+
+ /* zero terminate */
+ path[len] = 0;
+
+ return path;
+}
+
+int cfs_init_ctx(const char *descriptor_path, const u8 *required_digest,
+ struct cfs_context *ctx_out)
+{
+ u8 verity_digest[FS_VERITY_MAX_DIGEST_SIZE];
+ struct cfs_superblock superblock_buf;
+ struct cfs_superblock *superblock;
+ enum hash_algo verity_algo;
+ struct cfs_context ctx;
+ struct file *descriptor;
+ u64 num_inodes;
+ loff_t i_size;
+ int res;
+
+ descriptor = filp_open(descriptor_path, O_RDONLY, 0);
+ if (IS_ERR(descriptor))
+ return PTR_ERR(descriptor);
+
+ if (required_digest) {
+ res = fsverity_get_digest(d_inode(descriptor->f_path.dentry),
+ verity_digest, &verity_algo);
+ if (res < 0) {
+ pr_err("ERROR: composefs descriptor has no fs-verity digest\n");
+ goto fail;
+ }
+ if (verity_algo != HASH_ALGO_SHA256 ||
+ memcmp(required_digest, verity_digest, SHA256_DIGEST_SIZE) != 0) {
+ pr_err("ERROR: composefs descriptor has wrong fs-verity digest\n");
+ res = -EINVAL;
+ goto fail;
+ }
+ }
+
+ i_size = i_size_read(file_inode(descriptor));
+ if (i_size <= CFS_DESCRIPTOR_MIN_SIZE) {
+ res = -EINVAL;
+ goto fail;
+ }
+
+ /* Need this temporary ctx for cfs_read_data() */
+ ctx.descriptor = descriptor;
+ ctx.descriptor_len = i_size;
+
+ superblock = cfs_read_data(&ctx, CFS_SUPERBLOCK_OFFSET,
+ sizeof(struct cfs_superblock),
+ (u8 *)&superblock_buf);
+ if (IS_ERR(superblock)) {
+ res = PTR_ERR(superblock);
+ goto fail;
+ }
+
+ ctx.vdata_offset = le64_to_cpu(superblock->vdata_offset);
+
+ /* Some basic validation of the format */
+ if (le32_to_cpu(superblock->version) != CFS_VERSION ||
+ le32_to_cpu(superblock->magic) != CFS_MAGIC ||
+ /* vdata is in file */
+ ctx.vdata_offset > ctx.descriptor_len ||
+ ctx.vdata_offset <= CFS_INODE_TABLE_OFFSET ||
+ /* vdata is aligned */
+ ctx.vdata_offset % 4 != 0) {
+ res = -EFSCORRUPTED;
+ goto fail;
+ }
+
+ num_inodes = (ctx.vdata_offset - CFS_INODE_TABLE_OFFSET) / CFS_INODE_SIZE;
+ if (num_inodes > U32_MAX) {
+ res = -EFSCORRUPTED;
+ goto fail;
+ }
+ ctx.num_inodes = num_inodes;
+
+ *ctx_out = ctx;
+ return 0;
+
+fail:
+ fput(descriptor);
+ return res;
+}
+
+void cfs_ctx_put(struct cfs_context *ctx)
+{
+ if (ctx->descriptor) {
+ fput(ctx->descriptor);
+ ctx->descriptor = NULL;
+ }
+}
+
+static bool cfs_validate_filename(const char *name, size_t name_len)
+{
+ if (name_len == 0)
+ return false;
+
+ if (name_len == 1 && name[0] == '.')
+ return false;
+
+ if (name_len == 2 && name[0] == '.' && name[1] == '.')
+ return false;
+
+ if (memchr(name, '/', name_len))
+ return false;
+
+ return true;
+}
+
+int cfs_init_inode(struct cfs_context *ctx, u32 inode_num, struct inode *inode,
+ struct cfs_inode_extra_data *inode_data)
+{
+ struct cfs_buf vdata_buf = { NULL };
+ struct cfs_inode_data *disk_data;
+ char *path_payload = NULL;
+ void *res;
+ int ret = 0;
+ u64 variable_data_off;
+ u32 variable_data_len;
+ u64 digest_off;
+ u32 digest_len;
+ u32 st_type;
+ u64 size;
+
+ if (inode_num >= ctx->num_inodes)
+ return -EFSCORRUPTED;
+
+ disk_data = cfs_get_inode_buf(ctx, inode_num * CFS_INODE_SIZE,
+ CFS_INODE_SIZE, &vdata_buf);
+ if (IS_ERR(disk_data))
+ return PTR_ERR(disk_data);
+
+ inode->i_ino = inode_num;
+
+ inode->i_mode = le32_to_cpu(disk_data->st_mode);
+ set_nlink(inode, le32_to_cpu(disk_data->st_nlink));
+ inode->i_uid = make_kuid(current_user_ns(), le32_to_cpu(disk_data->st_uid));
+ inode->i_gid = make_kgid(current_user_ns(), le32_to_cpu(disk_data->st_gid));
+ inode->i_rdev = le32_to_cpu(disk_data->st_rdev);
+
+ size = le64_to_cpu(disk_data->st_size);
+ i_size_write(inode, size);
+ inode_set_bytes(inode, size);
+
+ inode->i_mtime.tv_sec = le64_to_cpu(disk_data->st_mtim_sec);
+ inode->i_mtime.tv_nsec = le32_to_cpu(disk_data->st_mtim_nsec);
+ inode->i_ctime.tv_sec = le64_to_cpu(disk_data->st_ctim_sec);
+ inode->i_ctime.tv_nsec = le32_to_cpu(disk_data->st_ctim_nsec);
+ inode->i_atime = inode->i_mtime;
+
+ variable_data_off = le64_to_cpu(disk_data->variable_data.off);
+ variable_data_len = le32_to_cpu(disk_data->variable_data.len);
+
+ st_type = inode->i_mode & S_IFMT;
+ if (st_type == S_IFDIR) {
+ inode_data->dirents_offset = variable_data_off;
+ inode_data->dirents_len = variable_data_len;
+ } else if ((st_type == S_IFLNK || st_type == S_IFREG) &&
+ variable_data_len > 0) {
+ path_payload = cfs_read_vdata_path(ctx, variable_data_off,
+ variable_data_len);
+ if (IS_ERR(path_payload)) {
+ ret = PTR_ERR(path_payload);
+ goto fail;
+ }
+ inode_data->path_payload = path_payload;
+ }
+
+ if (st_type == S_IFLNK) {
+ /* Symbolic link must have a non-empty target */
+ if (!inode_data->path_payload || *inode_data->path_payload == 0) {
+ ret = -EFSCORRUPTED;
+ goto fail;
+ }
+ } else if (st_type == S_IFREG) {
+ /* Regular file must have backing file except empty files */
+ if ((inode_data->path_payload && size == 0) ||
+ (!inode_data->path_payload && size > 0)) {
+ ret = -EFSCORRUPTED;
+ goto fail;
+ }
+ }
+
+ inode_data->xattrs_offset = le64_to_cpu(disk_data->xattrs.off);
+ inode_data->xattrs_len = le32_to_cpu(disk_data->xattrs.len);
+
+ if (inode_data->xattrs_len != 0) {
+ /* Validate xattr size */
+ if (inode_data->xattrs_len < sizeof(struct cfs_xattr_header)) {
+ ret = -EFSCORRUPTED;
+ goto fail;
+ }
+ }
+
+ digest_off = le64_to_cpu(disk_data->digest.off);
+ digest_len = le32_to_cpu(disk_data->digest.len);
+
+ if (digest_len > 0) {
+ if (digest_len != SHA256_DIGEST_SIZE) {
+ ret = -EFSCORRUPTED;
+ goto fail;
+ }
+
+ res = cfs_read_vdata(ctx, digest_off, digest_len, inode_data->digest);
+ if (IS_ERR(res)) {
+ ret = PTR_ERR(res);
+ goto fail;
+ }
+ inode_data->has_digest = true;
+ }
+
+ cfs_buf_put(&vdata_buf);
+ return 0;
+
+fail:
+ cfs_buf_put(&vdata_buf);
+ return ret;
+}
+
+ssize_t cfs_list_xattrs(struct cfs_context *ctx,
+ struct cfs_inode_extra_data *inode_data, char *names,
+ size_t size)
+{
+ const struct cfs_xattr_header *xattrs;
+ struct cfs_buf vdata_buf = { NULL };
+ size_t n_xattrs = 0;
+ u8 *data, *data_end;
+ ssize_t copied = 0;
+
+ if (inode_data->xattrs_len == 0)
+ return 0;
+
+ /* xattrs_len basic size req was verified in cfs_init_inode_data */
+
+ xattrs = cfs_get_vdata_buf(ctx, inode_data->xattrs_offset,
+ inode_data->xattrs_len, &vdata_buf);
+ if (IS_ERR(xattrs))
+ return PTR_ERR(xattrs);
+
+ n_xattrs = le16_to_cpu(xattrs->n_attr);
+ if (n_xattrs == 0 || n_xattrs > CFS_MAX_XATTRS ||
+ inode_data->xattrs_len < cfs_xattr_header_size(n_xattrs)) {
+ copied = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ data = ((u8 *)xattrs) + cfs_xattr_header_size(n_xattrs);
+ data_end = ((u8 *)xattrs) + inode_data->xattrs_len;
+
+ for (size_t i = 0; i < n_xattrs; i++) {
+ const struct cfs_xattr_element *e = &xattrs->attr[i];
+ u16 this_value_len = le16_to_cpu(e->value_length);
+ u16 this_key_len = le16_to_cpu(e->key_length);
+ const char *this_key;
+
+ if (this_key_len > XATTR_NAME_MAX ||
+ /* key and data needs to fit in data */
+ data_end - data < this_key_len + this_value_len) {
+ copied = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ this_key = data;
+ data += this_key_len + this_value_len;
+
+ if (size) {
+ if (size - copied < this_key_len + 1) {
+ copied = -E2BIG;
+ goto exit;
+ }
+
+ memcpy(names + copied, this_key, this_key_len);
+ names[copied + this_key_len] = '\0';
+ }
+
+ copied += this_key_len + 1;
+ }
+
+exit:
+ cfs_buf_put(&vdata_buf);
+
+ return copied;
+}
+
+int cfs_get_xattr(struct cfs_context *ctx, struct cfs_inode_extra_data *inode_data,
+ const char *name, void *value, size_t size)
+{
+ struct cfs_xattr_header *xattrs;
+ struct cfs_buf vdata_buf = { NULL };
+ size_t name_len = strlen(name);
+ size_t n_xattrs = 0;
+ u8 *data, *data_end;
+ int res;
+
+ if (inode_data->xattrs_len == 0)
+ return -ENODATA;
+
+ /* xattrs_len minimal size req was verified in cfs_init_inode_data */
+
+ xattrs = cfs_get_vdata_buf(ctx, inode_data->xattrs_offset,
+ inode_data->xattrs_len, &vdata_buf);
+ if (IS_ERR(xattrs))
+ return PTR_ERR(xattrs);
+
+ n_xattrs = le16_to_cpu(xattrs->n_attr);
+ if (n_xattrs == 0 || n_xattrs > CFS_MAX_XATTRS ||
+ inode_data->xattrs_len < cfs_xattr_header_size(n_xattrs)) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ data = ((u8 *)xattrs) + cfs_xattr_header_size(n_xattrs);
+ data_end = ((u8 *)xattrs) + inode_data->xattrs_len;
+
+ for (size_t i = 0; i < n_xattrs; i++) {
+ const struct cfs_xattr_element *e = &xattrs->attr[i];
+ u16 this_value_len = le16_to_cpu(e->value_length);
+ u16 this_key_len = le16_to_cpu(e->key_length);
+ const char *this_key, *this_value;
+
+ if (this_key_len > XATTR_NAME_MAX ||
+ /* key and data needs to fit in data */
+ data_end - data < this_key_len + this_value_len) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ this_key = data;
+ this_value = data + this_key_len;
+ data += this_key_len + this_value_len;
+
+ if (this_key_len != name_len || memcmp(this_key, name, name_len) != 0)
+ continue;
+
+ if (size > 0) {
+ if (size < this_value_len) {
+ res = -E2BIG;
+ goto exit;
+ }
+ memcpy(value, this_value, this_value_len);
+ }
+
+ res = this_value_len;
+ goto exit;
+ }
+
+ res = -ENODATA;
+
+exit:
+ cfs_buf_put(&vdata_buf);
+ return res;
+}
+
+/* This is essentially strmcp() for non-null-terminated strings */
+static inline int memcmp2(const void *a, const size_t a_size, const void *b,
+ size_t b_size)
+{
+ size_t common_size = min(a_size, b_size);
+ int res;
+
+ res = memcmp(a, b, common_size);
+ if (res != 0 || a_size == b_size)
+ return res;
+
+ return a_size < b_size ? -1 : 1;
+}
+
+int cfs_dir_iterate(struct cfs_context *ctx, u64 index,
+ struct cfs_inode_extra_data *inode_data, loff_t first,
+ cfs_dir_iter_cb cb, void *private)
+{
+ struct cfs_buf vdata_buf = { NULL };
+ const struct cfs_dir_header *dir;
+ u32 n_dirents;
+ char *namedata, *namedata_end;
+ loff_t pos;
+ int res;
+
+ if (inode_data->dirents_len == 0)
+ return 0;
+
+ dir = cfs_get_vdata_buf(ctx, inode_data->dirents_offset,
+ inode_data->dirents_len, &vdata_buf);
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+
+ n_dirents = le32_to_cpu(dir->n_dirents);
+ if (n_dirents == 0 || n_dirents > CFS_MAX_DIRENTS ||
+ inode_data->dirents_len < cfs_dir_header_size(n_dirents)) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ if (first >= n_dirents) {
+ res = 0;
+ goto exit;
+ }
+
+ namedata = ((u8 *)dir) + cfs_dir_header_size(n_dirents);
+ namedata_end = ((u8 *)dir) + inode_data->dirents_len;
+ pos = 0;
+ for (size_t i = 0; i < n_dirents; i++) {
+ const struct cfs_dirent *dirent = &dir->dirents[i];
+ char *dirent_name =
+ (char *)namedata + le32_to_cpu(dirent->name_offset);
+ size_t dirent_name_len = dirent->name_len;
+
+ /* name needs to fit in namedata */
+ if (dirent_name >= namedata_end ||
+ namedata_end - dirent_name < dirent_name_len) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ if (!cfs_validate_filename(dirent_name, dirent_name_len)) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ if (pos++ < first)
+ continue;
+
+ if (!cb(private, dirent_name, dirent_name_len,
+ le32_to_cpu(dirent->inode_num), dirent->d_type)) {
+ break;
+ }
+ }
+
+ res = 0;
+exit:
+ cfs_buf_put(&vdata_buf);
+ return res;
+}
+
+int cfs_dir_lookup(struct cfs_context *ctx, u64 index,
+ struct cfs_inode_extra_data *inode_data, const char *name,
+ size_t name_len, u64 *index_out)
+{
+ struct cfs_buf vdata_buf = { NULL };
+ const struct cfs_dir_header *dir;
+ u32 start_dirent, end_dirent, n_dirents;
+ char *namedata, *namedata_end;
+ int cmp, res;
+
+ if (inode_data->dirents_len == 0)
+ return 0;
+
+ dir = cfs_get_vdata_buf(ctx, inode_data->dirents_offset,
+ inode_data->dirents_len, &vdata_buf);
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+
+ n_dirents = le32_to_cpu(dir->n_dirents);
+ if (n_dirents == 0 || n_dirents > CFS_MAX_DIRENTS ||
+ inode_data->dirents_len < cfs_dir_header_size(n_dirents)) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ namedata = ((u8 *)dir) + cfs_dir_header_size(n_dirents);
+ namedata_end = ((u8 *)dir) + inode_data->dirents_len;
+
+ start_dirent = 0;
+ end_dirent = n_dirents - 1;
+ while (start_dirent <= end_dirent) {
+ int mid_dirent = start_dirent + (end_dirent - start_dirent) / 2;
+ const struct cfs_dirent *dirent = &dir->dirents[mid_dirent];
+ char *dirent_name =
+ (char *)namedata + le32_to_cpu(dirent->name_offset);
+ size_t dirent_name_len = dirent->name_len;
+
+ /* name needs to fit in namedata */
+ if (dirent_name >= namedata_end ||
+ namedata_end - dirent_name < dirent_name_len) {
+ res = -EFSCORRUPTED;
+ goto exit;
+ }
+
+ cmp = memcmp2(name, name_len, dirent_name, dirent_name_len);
+ if (cmp == 0) {
+ *index_out = le32_to_cpu(dirent->inode_num);
+ res = 1;
+ goto exit;
+ }
+
+ if (cmp > 0)
+ start_dirent = mid_dirent + 1;
+ else
+ end_dirent = mid_dirent - 1;
+ }
+
+ /* not found */
+ res = 0;
+
+exit:
+ cfs_buf_put(&vdata_buf);
+ return res;
+}
--
2.39.0