Avoid using the inode number as the indirect disc address, even though these currently have the same value. Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
		
			
				
	
	
		
			458 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			458 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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,
 | |
| };
 |