162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  linux/fs/affs/dir.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  (c) 1996  Hans-Joachim Widmaier - Rewritten
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  (C) 1993  Ray Burr - Modified for Amiga FFS filesystem.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci *  (C) 1992  Eric Youngdale Modified for ISO 9660 filesystem.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci *  (C) 1991  Linus Torvalds - minix filesystem
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci *  affs directory handling functions
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/iversion.h>
1862306a36Sopenharmony_ci#include "affs.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic int affs_readdir(struct file *, struct dir_context *);
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ciconst struct file_operations affs_dir_operations = {
2362306a36Sopenharmony_ci	.read		= generic_read_dir,
2462306a36Sopenharmony_ci	.llseek		= generic_file_llseek,
2562306a36Sopenharmony_ci	.iterate_shared	= affs_readdir,
2662306a36Sopenharmony_ci	.fsync		= affs_file_fsync,
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * directories can handle most operations...
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_ciconst struct inode_operations affs_dir_inode_operations = {
3362306a36Sopenharmony_ci	.create		= affs_create,
3462306a36Sopenharmony_ci	.lookup		= affs_lookup,
3562306a36Sopenharmony_ci	.link		= affs_link,
3662306a36Sopenharmony_ci	.unlink		= affs_unlink,
3762306a36Sopenharmony_ci	.symlink	= affs_symlink,
3862306a36Sopenharmony_ci	.mkdir		= affs_mkdir,
3962306a36Sopenharmony_ci	.rmdir		= affs_rmdir,
4062306a36Sopenharmony_ci	.rename		= affs_rename2,
4162306a36Sopenharmony_ci	.setattr	= affs_notify_change,
4262306a36Sopenharmony_ci};
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int
4562306a36Sopenharmony_ciaffs_readdir(struct file *file, struct dir_context *ctx)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	struct inode		*inode = file_inode(file);
4862306a36Sopenharmony_ci	struct super_block	*sb = inode->i_sb;
4962306a36Sopenharmony_ci	struct buffer_head	*dir_bh = NULL;
5062306a36Sopenharmony_ci	struct buffer_head	*fh_bh = NULL;
5162306a36Sopenharmony_ci	unsigned char		*name;
5262306a36Sopenharmony_ci	int			 namelen;
5362306a36Sopenharmony_ci	u32			 i;
5462306a36Sopenharmony_ci	int			 hash_pos;
5562306a36Sopenharmony_ci	int			 chain_pos;
5662306a36Sopenharmony_ci	u32			 ino;
5762306a36Sopenharmony_ci	int			 error = 0;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	pr_debug("%s(ino=%lu,f_pos=%llx)\n", __func__, inode->i_ino, ctx->pos);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	if (ctx->pos < 2) {
6262306a36Sopenharmony_ci		file->private_data = (void *)0;
6362306a36Sopenharmony_ci		if (!dir_emit_dots(file, ctx))
6462306a36Sopenharmony_ci			return 0;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	affs_lock_dir(inode);
6862306a36Sopenharmony_ci	chain_pos = (ctx->pos - 2) & 0xffff;
6962306a36Sopenharmony_ci	hash_pos  = (ctx->pos - 2) >> 16;
7062306a36Sopenharmony_ci	if (chain_pos == 0xffff) {
7162306a36Sopenharmony_ci		affs_warning(sb, "readdir", "More than 65535 entries in chain");
7262306a36Sopenharmony_ci		chain_pos = 0;
7362306a36Sopenharmony_ci		hash_pos++;
7462306a36Sopenharmony_ci		ctx->pos = ((hash_pos << 16) | chain_pos) + 2;
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci	dir_bh = affs_bread(sb, inode->i_ino);
7762306a36Sopenharmony_ci	if (!dir_bh)
7862306a36Sopenharmony_ci		goto out_unlock_dir;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* If the directory hasn't changed since the last call to readdir(),
8162306a36Sopenharmony_ci	 * we can jump directly to where we left off.
8262306a36Sopenharmony_ci	 */
8362306a36Sopenharmony_ci	ino = (u32)(long)file->private_data;
8462306a36Sopenharmony_ci	if (ino && inode_eq_iversion(inode, file->f_version)) {
8562306a36Sopenharmony_ci		pr_debug("readdir() left off=%d\n", ino);
8662306a36Sopenharmony_ci		goto inside;
8762306a36Sopenharmony_ci	}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	ino = be32_to_cpu(AFFS_HEAD(dir_bh)->table[hash_pos]);
9062306a36Sopenharmony_ci	for (i = 0; ino && i < chain_pos; i++) {
9162306a36Sopenharmony_ci		fh_bh = affs_bread(sb, ino);
9262306a36Sopenharmony_ci		if (!fh_bh) {
9362306a36Sopenharmony_ci			affs_error(sb, "readdir","Cannot read block %d", i);
9462306a36Sopenharmony_ci			error = -EIO;
9562306a36Sopenharmony_ci			goto out_brelse_dir;
9662306a36Sopenharmony_ci		}
9762306a36Sopenharmony_ci		ino = be32_to_cpu(AFFS_TAIL(sb, fh_bh)->hash_chain);
9862306a36Sopenharmony_ci		affs_brelse(fh_bh);
9962306a36Sopenharmony_ci		fh_bh = NULL;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci	if (ino)
10262306a36Sopenharmony_ci		goto inside;
10362306a36Sopenharmony_ci	hash_pos++;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	for (; hash_pos < AFFS_SB(sb)->s_hashsize; hash_pos++) {
10662306a36Sopenharmony_ci		ino = be32_to_cpu(AFFS_HEAD(dir_bh)->table[hash_pos]);
10762306a36Sopenharmony_ci		if (!ino)
10862306a36Sopenharmony_ci			continue;
10962306a36Sopenharmony_ci		ctx->pos = (hash_pos << 16) + 2;
11062306a36Sopenharmony_ciinside:
11162306a36Sopenharmony_ci		do {
11262306a36Sopenharmony_ci			fh_bh = affs_bread(sb, ino);
11362306a36Sopenharmony_ci			if (!fh_bh) {
11462306a36Sopenharmony_ci				affs_error(sb, "readdir",
11562306a36Sopenharmony_ci					   "Cannot read block %d", ino);
11662306a36Sopenharmony_ci				break;
11762306a36Sopenharmony_ci			}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci			namelen = min(AFFS_TAIL(sb, fh_bh)->name[0],
12062306a36Sopenharmony_ci				      (u8)AFFSNAMEMAX);
12162306a36Sopenharmony_ci			name = AFFS_TAIL(sb, fh_bh)->name + 1;
12262306a36Sopenharmony_ci			pr_debug("readdir(): dir_emit(\"%.*s\", ino=%u), hash=%d, f_pos=%llx\n",
12362306a36Sopenharmony_ci				 namelen, name, ino, hash_pos, ctx->pos);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci			if (!dir_emit(ctx, name, namelen, ino, DT_UNKNOWN))
12662306a36Sopenharmony_ci				goto done;
12762306a36Sopenharmony_ci			ctx->pos++;
12862306a36Sopenharmony_ci			ino = be32_to_cpu(AFFS_TAIL(sb, fh_bh)->hash_chain);
12962306a36Sopenharmony_ci			affs_brelse(fh_bh);
13062306a36Sopenharmony_ci			fh_bh = NULL;
13162306a36Sopenharmony_ci		} while (ino);
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_cidone:
13462306a36Sopenharmony_ci	file->f_version = inode_query_iversion(inode);
13562306a36Sopenharmony_ci	file->private_data = (void *)(long)ino;
13662306a36Sopenharmony_ci	affs_brelse(fh_bh);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ciout_brelse_dir:
13962306a36Sopenharmony_ci	affs_brelse(dir_bh);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ciout_unlock_dir:
14262306a36Sopenharmony_ci	affs_unlock_dir(inode);
14362306a36Sopenharmony_ci	return error;
14462306a36Sopenharmony_ci}
145