| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  *  linux/fs/adfs/dir.c | 
 |  * | 
 |  *  Copyright (C) 1999-2000 Russell King | 
 |  * | 
 |  *  Common directory handling for ADFS | 
 |  */ | 
 | #include <linux/slab.h> | 
 | #include "adfs.h" | 
 |  | 
 | /* | 
 |  * For future.  This should probably be per-directory. | 
 |  */ | 
 | static DECLARE_RWSEM(adfs_dir_rwsem); | 
 |  | 
 | int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset, | 
 | 		      size_t len) | 
 | { | 
 | 	struct super_block *sb = dir->sb; | 
 | 	unsigned int index, remain; | 
 |  | 
 | 	index = offset >> sb->s_blocksize_bits; | 
 | 	offset &= sb->s_blocksize - 1; | 
 | 	remain = sb->s_blocksize - offset; | 
 | 	if (index + (remain < len) >= dir->nr_buffers) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (remain < len) { | 
 | 		memcpy(dst, dir->bhs[index]->b_data + offset, remain); | 
 | 		dst += remain; | 
 | 		len -= remain; | 
 | 		index += 1; | 
 | 		offset = 0; | 
 | 	} | 
 |  | 
 | 	memcpy(dst, dir->bhs[index]->b_data + offset, len); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src, | 
 | 		    size_t len) | 
 | { | 
 | 	struct super_block *sb = dir->sb; | 
 | 	unsigned int index, remain; | 
 |  | 
 | 	index = offset >> sb->s_blocksize_bits; | 
 | 	offset &= sb->s_blocksize - 1; | 
 | 	remain = sb->s_blocksize - offset; | 
 | 	if (index + (remain < len) >= dir->nr_buffers) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (remain < len) { | 
 | 		memcpy(dir->bhs[index]->b_data + offset, src, remain); | 
 | 		src += remain; | 
 | 		len -= remain; | 
 | 		index += 1; | 
 | 		offset = 0; | 
 | 	} | 
 |  | 
 | 	memcpy(dir->bhs[index]->b_data + offset, src, len); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void __adfs_dir_cleanup(struct adfs_dir *dir) | 
 | { | 
 | 	dir->nr_buffers = 0; | 
 |  | 
 | 	if (dir->bhs != dir->bh) | 
 | 		kfree(dir->bhs); | 
 | 	dir->bhs = NULL; | 
 | 	dir->sb = NULL; | 
 | } | 
 |  | 
 | void adfs_dir_relse(struct adfs_dir *dir) | 
 | { | 
 | 	unsigned int i; | 
 |  | 
 | 	for (i = 0; i < dir->nr_buffers; i++) | 
 | 		brelse(dir->bhs[i]); | 
 |  | 
 | 	__adfs_dir_cleanup(dir); | 
 | } | 
 |  | 
 | static void adfs_dir_forget(struct adfs_dir *dir) | 
 | { | 
 | 	unsigned int i; | 
 |  | 
 | 	for (i = 0; i < dir->nr_buffers; i++) | 
 | 		bforget(dir->bhs[i]); | 
 |  | 
 | 	__adfs_dir_cleanup(dir); | 
 | } | 
 |  | 
 | int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr, | 
 | 			  unsigned int size, struct adfs_dir *dir) | 
 | { | 
 | 	struct buffer_head **bhs; | 
 | 	unsigned int i, num; | 
 | 	int block; | 
 |  | 
 | 	num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits; | 
 | 	if (num > ARRAY_SIZE(dir->bh)) { | 
 | 		/* We only allow one extension */ | 
 | 		if (dir->bhs != dir->bh) | 
 | 			return -EINVAL; | 
 |  | 
 | 		bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL); | 
 | 		if (!bhs) | 
 | 			return -ENOMEM; | 
 |  | 
 | 		if (dir->nr_buffers) | 
 | 			memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs)); | 
 |  | 
 | 		dir->bhs = bhs; | 
 | 	} | 
 |  | 
 | 	for (i = dir->nr_buffers; i < num; i++) { | 
 | 		block = __adfs_block_map(sb, indaddr, i); | 
 | 		if (!block) { | 
 | 			adfs_error(sb, "dir %06x has a hole at offset %u", | 
 | 				   indaddr, i); | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		dir->bhs[i] = sb_bread(sb, block); | 
 | 		if (!dir->bhs[i]) { | 
 | 			adfs_error(sb, | 
 | 				   "dir %06x failed read at offset %u, mapped block 0x%08x", | 
 | 				   indaddr, i, block); | 
 | 			goto error; | 
 | 		} | 
 |  | 
 | 		dir->nr_buffers++; | 
 | 	} | 
 | 	return 0; | 
 |  | 
 | error: | 
 | 	adfs_dir_relse(dir); | 
 |  | 
 | 	return -EIO; | 
 | } | 
 |  | 
 | static int adfs_dir_read(struct super_block *sb, u32 indaddr, | 
 | 			 unsigned int size, struct adfs_dir *dir) | 
 | { | 
 | 	dir->sb = sb; | 
 | 	dir->bhs = dir->bh; | 
 | 	dir->nr_buffers = 0; | 
 |  | 
 | 	return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir); | 
 | } | 
 |  | 
 | static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode, | 
 | 			       struct adfs_dir *dir) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	if (ADFS_I(inode)->parent_id != dir->parent_id) { | 
 | 		adfs_error(sb, | 
 | 			   "parent directory id changed under me! (%06x but got %06x)\n", | 
 | 			   ADFS_I(inode)->parent_id, dir->parent_id); | 
 | 		adfs_dir_relse(dir); | 
 | 		ret = -EIO; | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void adfs_dir_mark_dirty(struct adfs_dir *dir) | 
 | { | 
 | 	unsigned int i; | 
 |  | 
 | 	/* Mark the buffers dirty */ | 
 | 	for (i = 0; i < dir->nr_buffers; i++) | 
 | 		mark_buffer_dirty(dir->bhs[i]); | 
 | } | 
 |  | 
 | static int adfs_dir_sync(struct adfs_dir *dir) | 
 | { | 
 | 	int err = 0; | 
 | 	int i; | 
 |  | 
 | 	for (i = dir->nr_buffers - 1; i >= 0; i--) { | 
 | 		struct buffer_head *bh = dir->bhs[i]; | 
 | 		sync_dirty_buffer(bh); | 
 | 		if (buffer_req(bh) && !buffer_uptodate(bh)) | 
 | 			err = -EIO; | 
 | 	} | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj) | 
 | { | 
 | 	unsigned int dots, i; | 
 |  | 
 | 	/* | 
 | 	 * RISC OS allows the use of '/' in directory entry names, so we need | 
 | 	 * to fix these up.  '/' is typically used for FAT compatibility to | 
 | 	 * represent '.', so do the same conversion here.  In any case, '.' | 
 | 	 * will never be in a RISC OS name since it is used as the pathname | 
 | 	 * separator.  Handle the case where we may generate a '.' or '..' | 
 | 	 * name, replacing the first character with '^' (the RISC OS "parent | 
 | 	 * directory" character.) | 
 | 	 */ | 
 | 	for (i = dots = 0; i < obj->name_len; i++) | 
 | 		if (obj->name[i] == '/') { | 
 | 			obj->name[i] = '.'; | 
 | 			dots++; | 
 | 		} | 
 |  | 
 | 	if (obj->name_len <= 2 && dots == obj->name_len) | 
 | 		obj->name[0] = '^'; | 
 |  | 
 | 	/* | 
 | 	 * If the object is a file, and the user requested the ,xyz hex | 
 | 	 * filetype suffix to the name, check the filetype and append. | 
 | 	 */ | 
 | 	if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) { | 
 | 		u16 filetype = adfs_filetype(obj->loadaddr); | 
 |  | 
 | 		if (filetype != ADFS_FILETYPE_NONE) { | 
 | 			obj->name[obj->name_len++] = ','; | 
 | 			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8); | 
 | 			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4); | 
 | 			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | static int adfs_iterate(struct file *file, struct dir_context *ctx) | 
 | { | 
 | 	struct inode *inode = file_inode(file); | 
 | 	struct super_block *sb = inode->i_sb; | 
 | 	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | 
 | 	struct adfs_dir dir; | 
 | 	int ret; | 
 |  | 
 | 	down_read(&adfs_dir_rwsem); | 
 | 	ret = adfs_dir_read_inode(sb, inode, &dir); | 
 | 	if (ret) | 
 | 		goto unlock; | 
 |  | 
 | 	if (ctx->pos == 0) { | 
 | 		if (!dir_emit_dot(file, ctx)) | 
 | 			goto unlock_relse; | 
 | 		ctx->pos = 1; | 
 | 	} | 
 | 	if (ctx->pos == 1) { | 
 | 		if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR)) | 
 | 			goto unlock_relse; | 
 | 		ctx->pos = 2; | 
 | 	} | 
 |  | 
 | 	ret = ops->iterate(&dir, ctx); | 
 |  | 
 | unlock_relse: | 
 | 	up_read(&adfs_dir_rwsem); | 
 | 	adfs_dir_relse(&dir); | 
 | 	return ret; | 
 |  | 
 | unlock: | 
 | 	up_read(&adfs_dir_rwsem); | 
 | 	return ret; | 
 | } | 
 |  | 
 | int | 
 | adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) | 
 | { | 
 | 	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | 
 | 	struct adfs_dir dir; | 
 | 	int ret; | 
 |  | 
 | 	if (!IS_ENABLED(CONFIG_ADFS_FS_RW)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!ops->update) | 
 | 		return -EINVAL; | 
 |  | 
 | 	down_write(&adfs_dir_rwsem); | 
 | 	ret = adfs_dir_read(sb, obj->parent_id, 0, &dir); | 
 | 	if (ret) | 
 | 		goto unlock; | 
 |  | 
 | 	ret = ops->update(&dir, obj); | 
 | 	if (ret) | 
 | 		goto forget; | 
 |  | 
 | 	ret = ops->commit(&dir); | 
 | 	if (ret) | 
 | 		goto forget; | 
 | 	up_write(&adfs_dir_rwsem); | 
 |  | 
 | 	adfs_dir_mark_dirty(&dir); | 
 |  | 
 | 	if (wait) | 
 | 		ret = adfs_dir_sync(&dir); | 
 |  | 
 | 	adfs_dir_relse(&dir); | 
 | 	return ret; | 
 |  | 
 | 	/* | 
 | 	 * If the updated failed because the entry wasn't found, we can | 
 | 	 * just release the buffers. If it was any other error, forget | 
 | 	 * the dirtied buffers so they aren't written back to the media. | 
 | 	 */ | 
 | forget: | 
 | 	if (ret == -ENOENT) | 
 | 		adfs_dir_relse(&dir); | 
 | 	else | 
 | 		adfs_dir_forget(&dir); | 
 | unlock: | 
 | 	up_write(&adfs_dir_rwsem); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static unsigned char adfs_tolower(unsigned char c) | 
 | { | 
 | 	if (c >= 'A' && c <= 'Z') | 
 | 		c += 'a' - 'A'; | 
 | 	return c; | 
 | } | 
 |  | 
 | static int __adfs_compare(const unsigned char *qstr, u32 qlen, | 
 | 			  const char *str, u32 len) | 
 | { | 
 | 	u32 i; | 
 |  | 
 | 	if (qlen != len) | 
 | 		return 1; | 
 |  | 
 | 	for (i = 0; i < qlen; i++) | 
 | 		if (adfs_tolower(qstr[i]) != adfs_tolower(str[i])) | 
 | 			return 1; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr, | 
 | 				  struct object_info *obj) | 
 | { | 
 | 	struct super_block *sb = inode->i_sb; | 
 | 	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | 
 | 	const unsigned char *name; | 
 | 	struct adfs_dir dir; | 
 | 	u32 name_len; | 
 | 	int ret; | 
 |  | 
 | 	down_read(&adfs_dir_rwsem); | 
 | 	ret = adfs_dir_read_inode(sb, inode, &dir); | 
 | 	if (ret) | 
 | 		goto unlock; | 
 |  | 
 | 	ret = ops->setpos(&dir, 0); | 
 | 	if (ret) | 
 | 		goto unlock_relse; | 
 |  | 
 | 	ret = -ENOENT; | 
 | 	name = qstr->name; | 
 | 	name_len = qstr->len; | 
 | 	while (ops->getnext(&dir, obj) == 0) { | 
 | 		if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) { | 
 | 			ret = 0; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	obj->parent_id = ADFS_I(inode)->indaddr; | 
 |  | 
 | unlock_relse: | 
 | 	up_read(&adfs_dir_rwsem); | 
 | 	adfs_dir_relse(&dir); | 
 | 	return ret; | 
 |  | 
 | unlock: | 
 | 	up_read(&adfs_dir_rwsem); | 
 | 	return ret; | 
 | } | 
 |  | 
 | const struct file_operations adfs_dir_operations = { | 
 | 	.read		= generic_read_dir, | 
 | 	.llseek		= generic_file_llseek, | 
 | 	.iterate_shared	= adfs_iterate, | 
 | 	.fsync		= generic_file_fsync, | 
 | }; | 
 |  | 
 | static int | 
 | adfs_hash(const struct dentry *parent, struct qstr *qstr) | 
 | { | 
 | 	const unsigned char *name; | 
 | 	unsigned long hash; | 
 | 	u32 len; | 
 |  | 
 | 	if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen) | 
 | 		return -ENAMETOOLONG; | 
 |  | 
 | 	len = qstr->len; | 
 | 	name = qstr->name; | 
 | 	hash = init_name_hash(parent); | 
 | 	while (len--) | 
 | 		hash = partial_name_hash(adfs_tolower(*name++), hash); | 
 | 	qstr->hash = end_name_hash(hash); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* | 
 |  * Compare two names, taking note of the name length | 
 |  * requirements of the underlying filesystem. | 
 |  */ | 
 | static int adfs_compare(const struct dentry *dentry, unsigned int len, | 
 | 			const char *str, const struct qstr *qstr) | 
 | { | 
 | 	return __adfs_compare(qstr->name, qstr->len, str, len); | 
 | } | 
 |  | 
 | const struct dentry_operations adfs_dentry_operations = { | 
 | 	.d_hash		= adfs_hash, | 
 | 	.d_compare	= adfs_compare, | 
 | }; | 
 |  | 
 | static struct dentry * | 
 | adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) | 
 | { | 
 | 	struct inode *inode = NULL; | 
 | 	struct object_info obj; | 
 | 	int error; | 
 |  | 
 | 	error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj); | 
 | 	if (error == 0) { | 
 | 		/* | 
 | 		 * This only returns NULL if get_empty_inode | 
 | 		 * fails. | 
 | 		 */ | 
 | 		inode = adfs_iget(dir->i_sb, &obj); | 
 | 		if (!inode) | 
 | 			inode = ERR_PTR(-EACCES); | 
 | 	} else if (error != -ENOENT) { | 
 | 		inode = ERR_PTR(error); | 
 | 	} | 
 | 	return d_splice_alias(inode, dentry); | 
 | } | 
 |  | 
 | /* | 
 |  * directories can handle most operations... | 
 |  */ | 
 | const struct inode_operations adfs_dir_inode_operations = { | 
 | 	.lookup		= adfs_lookup, | 
 | 	.setattr	= adfs_notify_change, | 
 | }; |