blob: e6b9283e6e645fa6751e0b247c7dac6e29cd39c6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+ OR Apache-2.0
#include <stdlib.h>
#include <sys/stat.h>
#include "erofs/print.h"
#include "erofs/dir.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 (!S_ISDIR(dir->i_mode))
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 >= EROFS_BLKSIZ) {
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;
}
#define EROFS_PATHNAME_FOUND 1
struct erofs_get_pathname_context {
struct erofs_dir_context ctx;
erofs_nid_t target_nid;
char *buf;
size_t size;
size_t pos;
};
static int erofs_get_pathname_iter(struct erofs_dir_context *ctx)
{
int ret;
struct erofs_get_pathname_context *pathctx = (void *)ctx;
const char *dname = ctx->dname;
size_t len = ctx->de_namelen;
size_t pos = pathctx->pos;
if (ctx->dot_dotdot)
return 0;
if (ctx->de_nid == pathctx->target_nid) {
if (pos + len + 2 > pathctx->size) {
erofs_err("get_pathname buffer not large enough: len %zd, size %zd",
pos + len + 2, pathctx->size);
return -ERANGE;
}
pathctx->buf[pos++] = '/';
strncpy(pathctx->buf + pos, dname, len);
pathctx->buf[pos + len] = '\0';
return EROFS_PATHNAME_FOUND;
}
if (ctx->de_ftype == EROFS_FT_DIR || ctx->de_ftype == EROFS_FT_UNKNOWN) {
struct erofs_inode dir = { .nid = ctx->de_nid };
ret = erofs_read_inode_from_disk(&dir);
if (ret) {
erofs_err("read inode failed @ nid %llu", dir.nid | 0ULL);
return ret;
}
if (S_ISDIR(dir.i_mode)) {
ctx->dir = &dir;
pathctx->pos = pos + len + 1;
ret = erofs_iterate_dir(ctx, false);
pathctx->pos = pos;
if (ret == EROFS_PATHNAME_FOUND) {
pathctx->buf[pos++] = '/';
strncpy(pathctx->buf + pos, dname, len);
}
return ret;
} else if (ctx->de_ftype == EROFS_FT_DIR) {
erofs_err("i_mode and file_type are inconsistent @ nid %llu",
dir.nid | 0ULL);
}
}
return 0;
}
int erofs_get_pathname(erofs_nid_t nid, char *buf, size_t size)
{
int ret;
struct erofs_inode root = { .nid = sbi.root_nid };
struct erofs_get_pathname_context pathctx = {
.ctx.flags = 0,
.ctx.dir = &root,
.ctx.cb = erofs_get_pathname_iter,
.target_nid = nid,
.buf = buf,
.size = size,
.pos = 0,
};
if (nid == root.nid) {
if (size < 2) {
erofs_err("get_pathname buffer not large enough: len 2, size %zd",
size);
return -ERANGE;
}
buf[0] = '/';
buf[1] = '\0';
return 0;
}
ret = erofs_read_inode_from_disk(&root);
if (ret) {
erofs_err("read inode failed @ nid %llu", root.nid | 0ULL);
return ret;
}
ret = erofs_iterate_dir(&pathctx.ctx, false);
if (ret == EROFS_PATHNAME_FOUND)
return 0;
if (!ret)
return -ENOENT;
return ret;
}