aboutsummaryrefslogtreecommitdiff
path: root/lib/dir.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dir.c')
-rw-r--r--lib/dir.c169
1 files changed, 169 insertions, 0 deletions
diff --git a/lib/dir.c b/lib/dir.c
new file mode 100644
index 0000000..46805b4
--- /dev/null
+++ b/lib/dir.c
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
+#include "erofs/print.h"
+#include "erofs/dir.h"
+#include <stdlib.h>
+#include <sys/stat.h>
+
+static int traverse_dirents(struct erofs_dir_context *ctx,
+ void *dentry_blk, unsigned int lblk,
+ unsigned int next_nameoff, unsigned int maxsize,
+ bool fsck)
+{
+ struct erofs_dirent *de = dentry_blk;
+ const struct erofs_dirent *end = dentry_blk + next_nameoff;
+ const char *prev_name = NULL;
+ const char *errmsg;
+ unsigned int prev_namelen = 0;
+ int ret = 0;
+ bool silent = false;
+
+ while (de < end) {
+ const char *de_name;
+ unsigned int de_namelen;
+ unsigned int nameoff;
+
+ nameoff = le16_to_cpu(de->nameoff);
+ de_name = (char *)dentry_blk + nameoff;
+
+ /* the last dirent check */
+ if (de + 1 >= end)
+ de_namelen = strnlen(de_name, maxsize - nameoff);
+ else
+ de_namelen = le16_to_cpu(de[1].nameoff) - nameoff;
+
+ ctx->de_nid = le64_to_cpu(de->nid);
+ erofs_dbg("traversed nid (%llu)", ctx->de_nid | 0ULL);
+
+ ret = -EFSCORRUPTED;
+ /* corrupted entry check */
+ if (nameoff != next_nameoff) {
+ errmsg = "bogus dirent nameoff";
+ break;
+ }
+
+ if (nameoff + de_namelen > maxsize ||
+ de_namelen > EROFS_NAME_LEN) {
+ errmsg = "bogus dirent namelen";
+ break;
+ }
+
+ if (fsck && prev_name) {
+ int cmp = strncmp(prev_name, de_name,
+ min(prev_namelen, de_namelen));
+
+ if (cmp > 0 || (cmp == 0 &&
+ prev_namelen >= de_namelen)) {
+ errmsg = "wrong dirent name order";
+ break;
+ }
+ }
+
+ if (fsck && de->file_type >= EROFS_FT_MAX) {
+ errmsg = "invalid file type %u";
+ break;
+ }
+
+ ctx->dname = de_name;
+ ctx->de_namelen = de_namelen;
+ ctx->de_ftype = de->file_type;
+ ctx->dot_dotdot = is_dot_dotdot_len(de_name, de_namelen);
+ if (ctx->dot_dotdot) {
+ switch (de_namelen) {
+ case 2:
+ if (fsck &&
+ (ctx->flags & EROFS_READDIR_DOTDOT_FOUND)) {
+ errmsg = "duplicated `..' dirent";
+ goto out;
+ }
+ ctx->flags |= EROFS_READDIR_DOTDOT_FOUND;
+ if (sbi.root_nid == ctx->dir->nid) {
+ ctx->pnid = sbi.root_nid;
+ ctx->flags |= EROFS_READDIR_VALID_PNID;
+ }
+ if (fsck &&
+ (ctx->flags & EROFS_READDIR_VALID_PNID) &&
+ ctx->de_nid != ctx->pnid) {
+ errmsg = "corrupted `..' dirent";
+ goto out;
+ }
+ break;
+ case 1:
+ if (fsck &&
+ (ctx->flags & EROFS_READDIR_DOT_FOUND)) {
+ errmsg = "duplicated `.' dirent";
+ goto out;
+ }
+
+ ctx->flags |= EROFS_READDIR_DOT_FOUND;
+ if (fsck && ctx->de_nid != ctx->dir->nid) {
+ errmsg = "corrupted `.' dirent";
+ goto out;
+ }
+ break;
+ }
+ }
+ ret = ctx->cb(ctx);
+ if (ret) {
+ silent = true;
+ break;
+ }
+ prev_name = de_name;
+ prev_namelen = de_namelen;
+ next_nameoff += de_namelen;
+ ++de;
+ }
+out:
+ if (ret && !silent)
+ erofs_err("%s @ nid %llu, lblk %u, index %lu",
+ errmsg, ctx->dir->nid | 0ULL, lblk,
+ (de - (struct erofs_dirent *)dentry_blk) | 0UL);
+ return ret;
+}
+
+int erofs_iterate_dir(struct erofs_dir_context *ctx, bool fsck)
+{
+ struct erofs_inode *dir = ctx->dir;
+ int err = 0;
+ erofs_off_t pos;
+ char buf[EROFS_BLKSIZ];
+
+ if ((dir->i_mode & S_IFMT) != S_IFDIR)
+ return -ENOTDIR;
+
+ ctx->flags &= ~EROFS_READDIR_ALL_SPECIAL_FOUND;
+ pos = 0;
+ while (pos < dir->i_size) {
+ erofs_blk_t lblk = erofs_blknr(pos);
+ erofs_off_t maxsize = min_t(erofs_off_t,
+ dir->i_size - pos, EROFS_BLKSIZ);
+ const struct erofs_dirent *de = (const void *)buf;
+ unsigned int nameoff;
+
+ err = erofs_pread(dir, buf, maxsize, pos);
+ if (err) {
+ erofs_err("I/O error occurred when reading dirents @ nid %llu, lblk %u: %d",
+ dir->nid | 0ULL, lblk, err);
+ return err;
+ }
+
+ nameoff = le16_to_cpu(de->nameoff);
+ if (nameoff < sizeof(struct erofs_dirent) ||
+ nameoff >= PAGE_SIZE) {
+ erofs_err("invalid de[0].nameoff %u @ nid %llu, lblk %u",
+ nameoff, dir->nid | 0ULL, lblk);
+ return -EFSCORRUPTED;
+ }
+ err = traverse_dirents(ctx, buf, lblk, nameoff, maxsize, fsck);
+ if (err)
+ break;
+ pos += maxsize;
+ }
+
+ if (fsck && (ctx->flags & EROFS_READDIR_ALL_SPECIAL_FOUND) !=
+ EROFS_READDIR_ALL_SPECIAL_FOUND) {
+ erofs_err("`.' or `..' dirent is missing @ nid %llu",
+ dir->nid | 0ULL);
+ return -EFSCORRUPTED;
+ }
+ return err;
+}