162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2017-2018 HUAWEI, Inc.
462306a36Sopenharmony_ci *             https://www.huawei.com/
562306a36Sopenharmony_ci * Copyright (C) 2022, Alibaba Cloud
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include "internal.h"
862306a36Sopenharmony_ci
962306a36Sopenharmony_cistatic int erofs_fill_dentries(struct inode *dir, struct dir_context *ctx,
1062306a36Sopenharmony_ci			       void *dentry_blk, struct erofs_dirent *de,
1162306a36Sopenharmony_ci			       unsigned int nameoff, unsigned int maxsize)
1262306a36Sopenharmony_ci{
1362306a36Sopenharmony_ci	const struct erofs_dirent *end = dentry_blk + nameoff;
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci	while (de < end) {
1662306a36Sopenharmony_ci		const char *de_name;
1762306a36Sopenharmony_ci		unsigned int de_namelen;
1862306a36Sopenharmony_ci		unsigned char d_type;
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci		d_type = fs_ftype_to_dtype(de->file_type);
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci		nameoff = le16_to_cpu(de->nameoff);
2362306a36Sopenharmony_ci		de_name = (char *)dentry_blk + nameoff;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci		/* the last dirent in the block? */
2662306a36Sopenharmony_ci		if (de + 1 >= end)
2762306a36Sopenharmony_ci			de_namelen = strnlen(de_name, maxsize - nameoff);
2862306a36Sopenharmony_ci		else
2962306a36Sopenharmony_ci			de_namelen = le16_to_cpu(de[1].nameoff) - nameoff;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci		/* a corrupted entry is found */
3262306a36Sopenharmony_ci		if (nameoff + de_namelen > maxsize ||
3362306a36Sopenharmony_ci		    de_namelen > EROFS_NAME_LEN) {
3462306a36Sopenharmony_ci			erofs_err(dir->i_sb, "bogus dirent @ nid %llu",
3562306a36Sopenharmony_ci				  EROFS_I(dir)->nid);
3662306a36Sopenharmony_ci			DBG_BUGON(1);
3762306a36Sopenharmony_ci			return -EFSCORRUPTED;
3862306a36Sopenharmony_ci		}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci		if (!dir_emit(ctx, de_name, de_namelen,
4162306a36Sopenharmony_ci			      le64_to_cpu(de->nid), d_type))
4262306a36Sopenharmony_ci			return 1;
4362306a36Sopenharmony_ci		++de;
4462306a36Sopenharmony_ci		ctx->pos += sizeof(struct erofs_dirent);
4562306a36Sopenharmony_ci	}
4662306a36Sopenharmony_ci	return 0;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic int erofs_readdir(struct file *f, struct dir_context *ctx)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct inode *dir = file_inode(f);
5262306a36Sopenharmony_ci	struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
5362306a36Sopenharmony_ci	struct super_block *sb = dir->i_sb;
5462306a36Sopenharmony_ci	unsigned long bsz = sb->s_blocksize;
5562306a36Sopenharmony_ci	const size_t dirsize = i_size_read(dir);
5662306a36Sopenharmony_ci	unsigned int i = erofs_blknr(sb, ctx->pos);
5762306a36Sopenharmony_ci	unsigned int ofs = erofs_blkoff(sb, ctx->pos);
5862306a36Sopenharmony_ci	int err = 0;
5962306a36Sopenharmony_ci	bool initial = true;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	buf.inode = dir;
6262306a36Sopenharmony_ci	while (ctx->pos < dirsize) {
6362306a36Sopenharmony_ci		struct erofs_dirent *de;
6462306a36Sopenharmony_ci		unsigned int nameoff, maxsize;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci		de = erofs_bread(&buf, i, EROFS_KMAP);
6762306a36Sopenharmony_ci		if (IS_ERR(de)) {
6862306a36Sopenharmony_ci			erofs_err(sb, "fail to readdir of logical block %u of nid %llu",
6962306a36Sopenharmony_ci				  i, EROFS_I(dir)->nid);
7062306a36Sopenharmony_ci			err = PTR_ERR(de);
7162306a36Sopenharmony_ci			break;
7262306a36Sopenharmony_ci		}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		nameoff = le16_to_cpu(de->nameoff);
7562306a36Sopenharmony_ci		if (nameoff < sizeof(struct erofs_dirent) || nameoff >= bsz) {
7662306a36Sopenharmony_ci			erofs_err(sb, "invalid de[0].nameoff %u @ nid %llu",
7762306a36Sopenharmony_ci				  nameoff, EROFS_I(dir)->nid);
7862306a36Sopenharmony_ci			err = -EFSCORRUPTED;
7962306a36Sopenharmony_ci			break;
8062306a36Sopenharmony_ci		}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		maxsize = min_t(unsigned int, dirsize - ctx->pos + ofs, bsz);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci		/* search dirents at the arbitrary position */
8562306a36Sopenharmony_ci		if (initial) {
8662306a36Sopenharmony_ci			initial = false;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci			ofs = roundup(ofs, sizeof(struct erofs_dirent));
8962306a36Sopenharmony_ci			ctx->pos = erofs_pos(sb, i) + ofs;
9062306a36Sopenharmony_ci			if (ofs >= nameoff)
9162306a36Sopenharmony_ci				goto skip_this;
9262306a36Sopenharmony_ci		}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci		err = erofs_fill_dentries(dir, ctx, de, (void *)de + ofs,
9562306a36Sopenharmony_ci					  nameoff, maxsize);
9662306a36Sopenharmony_ci		if (err)
9762306a36Sopenharmony_ci			break;
9862306a36Sopenharmony_ciskip_this:
9962306a36Sopenharmony_ci		ctx->pos = erofs_pos(sb, i) + maxsize;
10062306a36Sopenharmony_ci		++i;
10162306a36Sopenharmony_ci		ofs = 0;
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci	erofs_put_metabuf(&buf);
10462306a36Sopenharmony_ci	return err < 0 ? err : 0;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ciconst struct file_operations erofs_dir_fops = {
10862306a36Sopenharmony_ci	.llseek		= generic_file_llseek,
10962306a36Sopenharmony_ci	.read		= generic_read_dir,
11062306a36Sopenharmony_ci	.iterate_shared	= erofs_readdir,
11162306a36Sopenharmony_ci};
112