brintos

brintos / linux-shallow public Read only

0
0
Text · 9.5 KiB · 77fbd19 Raw
458 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 *  linux/fs/adfs/dir.c4 *5 *  Copyright (C) 1999-2000 Russell King6 *7 *  Common directory handling for ADFS8 */9#include <linux/slab.h>10#include "adfs.h"11 12/*13 * For future.  This should probably be per-directory.14 */15static DECLARE_RWSEM(adfs_dir_rwsem);16 17int adfs_dir_copyfrom(void *dst, struct adfs_dir *dir, unsigned int offset,18		      size_t len)19{20	struct super_block *sb = dir->sb;21	unsigned int index, remain;22 23	index = offset >> sb->s_blocksize_bits;24	offset &= sb->s_blocksize - 1;25	remain = sb->s_blocksize - offset;26	if (index + (remain < len) >= dir->nr_buffers)27		return -EINVAL;28 29	if (remain < len) {30		memcpy(dst, dir->bhs[index]->b_data + offset, remain);31		dst += remain;32		len -= remain;33		index += 1;34		offset = 0;35	}36 37	memcpy(dst, dir->bhs[index]->b_data + offset, len);38 39	return 0;40}41 42int adfs_dir_copyto(struct adfs_dir *dir, unsigned int offset, const void *src,43		    size_t len)44{45	struct super_block *sb = dir->sb;46	unsigned int index, remain;47 48	index = offset >> sb->s_blocksize_bits;49	offset &= sb->s_blocksize - 1;50	remain = sb->s_blocksize - offset;51	if (index + (remain < len) >= dir->nr_buffers)52		return -EINVAL;53 54	if (remain < len) {55		memcpy(dir->bhs[index]->b_data + offset, src, remain);56		src += remain;57		len -= remain;58		index += 1;59		offset = 0;60	}61 62	memcpy(dir->bhs[index]->b_data + offset, src, len);63 64	return 0;65}66 67static void __adfs_dir_cleanup(struct adfs_dir *dir)68{69	dir->nr_buffers = 0;70 71	if (dir->bhs != dir->bh)72		kfree(dir->bhs);73	dir->bhs = NULL;74	dir->sb = NULL;75}76 77void adfs_dir_relse(struct adfs_dir *dir)78{79	unsigned int i;80 81	for (i = 0; i < dir->nr_buffers; i++)82		brelse(dir->bhs[i]);83 84	__adfs_dir_cleanup(dir);85}86 87static void adfs_dir_forget(struct adfs_dir *dir)88{89	unsigned int i;90 91	for (i = 0; i < dir->nr_buffers; i++)92		bforget(dir->bhs[i]);93 94	__adfs_dir_cleanup(dir);95}96 97int adfs_dir_read_buffers(struct super_block *sb, u32 indaddr,98			  unsigned int size, struct adfs_dir *dir)99{100	struct buffer_head **bhs;101	unsigned int i, num;102	int block;103 104	num = ALIGN(size, sb->s_blocksize) >> sb->s_blocksize_bits;105	if (num > ARRAY_SIZE(dir->bh)) {106		/* We only allow one extension */107		if (dir->bhs != dir->bh)108			return -EINVAL;109 110		bhs = kcalloc(num, sizeof(*bhs), GFP_KERNEL);111		if (!bhs)112			return -ENOMEM;113 114		if (dir->nr_buffers)115			memcpy(bhs, dir->bhs, dir->nr_buffers * sizeof(*bhs));116 117		dir->bhs = bhs;118	}119 120	for (i = dir->nr_buffers; i < num; i++) {121		block = __adfs_block_map(sb, indaddr, i);122		if (!block) {123			adfs_error(sb, "dir %06x has a hole at offset %u",124				   indaddr, i);125			goto error;126		}127 128		dir->bhs[i] = sb_bread(sb, block);129		if (!dir->bhs[i]) {130			adfs_error(sb,131				   "dir %06x failed read at offset %u, mapped block 0x%08x",132				   indaddr, i, block);133			goto error;134		}135 136		dir->nr_buffers++;137	}138	return 0;139 140error:141	adfs_dir_relse(dir);142 143	return -EIO;144}145 146static int adfs_dir_read(struct super_block *sb, u32 indaddr,147			 unsigned int size, struct adfs_dir *dir)148{149	dir->sb = sb;150	dir->bhs = dir->bh;151	dir->nr_buffers = 0;152 153	return ADFS_SB(sb)->s_dir->read(sb, indaddr, size, dir);154}155 156static int adfs_dir_read_inode(struct super_block *sb, struct inode *inode,157			       struct adfs_dir *dir)158{159	int ret;160 161	ret = adfs_dir_read(sb, ADFS_I(inode)->indaddr, inode->i_size, dir);162	if (ret)163		return ret;164 165	if (ADFS_I(inode)->parent_id != dir->parent_id) {166		adfs_error(sb,167			   "parent directory id changed under me! (%06x but got %06x)\n",168			   ADFS_I(inode)->parent_id, dir->parent_id);169		adfs_dir_relse(dir);170		ret = -EIO;171	}172 173	return ret;174}175 176static void adfs_dir_mark_dirty(struct adfs_dir *dir)177{178	unsigned int i;179 180	/* Mark the buffers dirty */181	for (i = 0; i < dir->nr_buffers; i++)182		mark_buffer_dirty(dir->bhs[i]);183}184 185static int adfs_dir_sync(struct adfs_dir *dir)186{187	int err = 0;188	int i;189 190	for (i = dir->nr_buffers - 1; i >= 0; i--) {191		struct buffer_head *bh = dir->bhs[i];192		sync_dirty_buffer(bh);193		if (buffer_req(bh) && !buffer_uptodate(bh))194			err = -EIO;195	}196 197	return err;198}199 200void adfs_object_fixup(struct adfs_dir *dir, struct object_info *obj)201{202	unsigned int dots, i;203 204	/*205	 * RISC OS allows the use of '/' in directory entry names, so we need206	 * to fix these up.  '/' is typically used for FAT compatibility to207	 * represent '.', so do the same conversion here.  In any case, '.'208	 * will never be in a RISC OS name since it is used as the pathname209	 * separator.  Handle the case where we may generate a '.' or '..'210	 * name, replacing the first character with '^' (the RISC OS "parent211	 * directory" character.)212	 */213	for (i = dots = 0; i < obj->name_len; i++)214		if (obj->name[i] == '/') {215			obj->name[i] = '.';216			dots++;217		}218 219	if (obj->name_len <= 2 && dots == obj->name_len)220		obj->name[0] = '^';221 222	/*223	 * If the object is a file, and the user requested the ,xyz hex224	 * filetype suffix to the name, check the filetype and append.225	 */226	if (!(obj->attr & ADFS_NDA_DIRECTORY) && ADFS_SB(dir->sb)->s_ftsuffix) {227		u16 filetype = adfs_filetype(obj->loadaddr);228 229		if (filetype != ADFS_FILETYPE_NONE) {230			obj->name[obj->name_len++] = ',';231			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 8);232			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 4);233			obj->name[obj->name_len++] = hex_asc_lo(filetype >> 0);234		}235	}236}237 238static int adfs_iterate(struct file *file, struct dir_context *ctx)239{240	struct inode *inode = file_inode(file);241	struct super_block *sb = inode->i_sb;242	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;243	struct adfs_dir dir;244	int ret;245 246	down_read(&adfs_dir_rwsem);247	ret = adfs_dir_read_inode(sb, inode, &dir);248	if (ret)249		goto unlock;250 251	if (ctx->pos == 0) {252		if (!dir_emit_dot(file, ctx))253			goto unlock_relse;254		ctx->pos = 1;255	}256	if (ctx->pos == 1) {257		if (!dir_emit(ctx, "..", 2, dir.parent_id, DT_DIR))258			goto unlock_relse;259		ctx->pos = 2;260	}261 262	ret = ops->iterate(&dir, ctx);263 264unlock_relse:265	up_read(&adfs_dir_rwsem);266	adfs_dir_relse(&dir);267	return ret;268 269unlock:270	up_read(&adfs_dir_rwsem);271	return ret;272}273 274int275adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait)276{277	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;278	struct adfs_dir dir;279	int ret;280 281	if (!IS_ENABLED(CONFIG_ADFS_FS_RW))282		return -EINVAL;283 284	if (!ops->update)285		return -EINVAL;286 287	down_write(&adfs_dir_rwsem);288	ret = adfs_dir_read(sb, obj->parent_id, 0, &dir);289	if (ret)290		goto unlock;291 292	ret = ops->update(&dir, obj);293	if (ret)294		goto forget;295 296	ret = ops->commit(&dir);297	if (ret)298		goto forget;299	up_write(&adfs_dir_rwsem);300 301	adfs_dir_mark_dirty(&dir);302 303	if (wait)304		ret = adfs_dir_sync(&dir);305 306	adfs_dir_relse(&dir);307	return ret;308 309	/*310	 * If the updated failed because the entry wasn't found, we can311	 * just release the buffers. If it was any other error, forget312	 * the dirtied buffers so they aren't written back to the media.313	 */314forget:315	if (ret == -ENOENT)316		adfs_dir_relse(&dir);317	else318		adfs_dir_forget(&dir);319unlock:320	up_write(&adfs_dir_rwsem);321 322	return ret;323}324 325static unsigned char adfs_tolower(unsigned char c)326{327	if (c >= 'A' && c <= 'Z')328		c += 'a' - 'A';329	return c;330}331 332static int __adfs_compare(const unsigned char *qstr, u32 qlen,333			  const char *str, u32 len)334{335	u32 i;336 337	if (qlen != len)338		return 1;339 340	for (i = 0; i < qlen; i++)341		if (adfs_tolower(qstr[i]) != adfs_tolower(str[i]))342			return 1;343 344	return 0;345}346 347static int adfs_dir_lookup_byname(struct inode *inode, const struct qstr *qstr,348				  struct object_info *obj)349{350	struct super_block *sb = inode->i_sb;351	const struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;352	const unsigned char *name;353	struct adfs_dir dir;354	u32 name_len;355	int ret;356 357	down_read(&adfs_dir_rwsem);358	ret = adfs_dir_read_inode(sb, inode, &dir);359	if (ret)360		goto unlock;361 362	ret = ops->setpos(&dir, 0);363	if (ret)364		goto unlock_relse;365 366	ret = -ENOENT;367	name = qstr->name;368	name_len = qstr->len;369	while (ops->getnext(&dir, obj) == 0) {370		if (!__adfs_compare(name, name_len, obj->name, obj->name_len)) {371			ret = 0;372			break;373		}374	}375	obj->parent_id = ADFS_I(inode)->indaddr;376 377unlock_relse:378	up_read(&adfs_dir_rwsem);379	adfs_dir_relse(&dir);380	return ret;381 382unlock:383	up_read(&adfs_dir_rwsem);384	return ret;385}386 387const struct file_operations adfs_dir_operations = {388	.read		= generic_read_dir,389	.llseek		= generic_file_llseek,390	.iterate_shared	= adfs_iterate,391	.fsync		= generic_file_fsync,392};393 394static int395adfs_hash(const struct dentry *parent, struct qstr *qstr)396{397	const unsigned char *name;398	unsigned long hash;399	u32 len;400 401	if (qstr->len > ADFS_SB(parent->d_sb)->s_namelen)402		return -ENAMETOOLONG;403 404	len = qstr->len;405	name = qstr->name;406	hash = init_name_hash(parent);407	while (len--)408		hash = partial_name_hash(adfs_tolower(*name++), hash);409	qstr->hash = end_name_hash(hash);410 411	return 0;412}413 414/*415 * Compare two names, taking note of the name length416 * requirements of the underlying filesystem.417 */418static int adfs_compare(const struct dentry *dentry, unsigned int len,419			const char *str, const struct qstr *qstr)420{421	return __adfs_compare(qstr->name, qstr->len, str, len);422}423 424const struct dentry_operations adfs_dentry_operations = {425	.d_hash		= adfs_hash,426	.d_compare	= adfs_compare,427};428 429static struct dentry *430adfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)431{432	struct inode *inode = NULL;433	struct object_info obj;434	int error;435 436	error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);437	if (error == 0) {438		/*439		 * This only returns NULL if get_empty_inode440		 * fails.441		 */442		inode = adfs_iget(dir->i_sb, &obj);443		if (!inode)444			inode = ERR_PTR(-EACCES);445	} else if (error != -ENOENT) {446		inode = ERR_PTR(error);447	}448	return d_splice_alias(inode, dentry);449}450 451/*452 * directories can handle most operations...453 */454const struct inode_operations adfs_dir_inode_operations = {455	.lookup		= adfs_lookup,456	.setattr	= adfs_notify_change,457};458