| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *  linux/fs/adfs/dir.c | 
 | 3 |  * | 
 | 4 |  *  Copyright (C) 1999-2000 Russell King | 
 | 5 |  * | 
 | 6 |  * This program is free software; you can redistribute it and/or modify | 
 | 7 |  * it under the terms of the GNU General Public License version 2 as | 
 | 8 |  * published by the Free Software Foundation. | 
 | 9 |  * | 
 | 10 |  *  Common directory handling for ADFS | 
 | 11 |  */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 12 | #include <linux/smp_lock.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 13 | #include "adfs.h" | 
 | 14 |  | 
 | 15 | /* | 
 | 16 |  * For future.  This should probably be per-directory. | 
 | 17 |  */ | 
 | 18 | static DEFINE_RWLOCK(adfs_dir_lock); | 
 | 19 |  | 
 | 20 | static int | 
 | 21 | adfs_readdir(struct file *filp, void *dirent, filldir_t filldir) | 
 | 22 | { | 
| Josef Sipek | 514653e | 2006-12-08 02:36:50 -0800 | [diff] [blame] | 23 | 	struct inode *inode = filp->f_path.dentry->d_inode; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 24 | 	struct super_block *sb = inode->i_sb; | 
 | 25 | 	struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | 
 | 26 | 	struct object_info obj; | 
 | 27 | 	struct adfs_dir dir; | 
 | 28 | 	int ret = 0; | 
 | 29 |  | 
 | 30 | 	lock_kernel();	 | 
 | 31 |  | 
 | 32 | 	if (filp->f_pos >> 32) | 
 | 33 | 		goto out; | 
 | 34 |  | 
 | 35 | 	ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); | 
 | 36 | 	if (ret) | 
 | 37 | 		goto out; | 
 | 38 |  | 
 | 39 | 	switch ((unsigned long)filp->f_pos) { | 
 | 40 | 	case 0: | 
 | 41 | 		if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR) < 0) | 
 | 42 | 			goto free_out; | 
 | 43 | 		filp->f_pos += 1; | 
 | 44 |  | 
 | 45 | 	case 1: | 
 | 46 | 		if (filldir(dirent, "..", 2, 1, dir.parent_id, DT_DIR) < 0) | 
 | 47 | 			goto free_out; | 
 | 48 | 		filp->f_pos += 1; | 
 | 49 |  | 
 | 50 | 	default: | 
 | 51 | 		break; | 
 | 52 | 	} | 
 | 53 |  | 
 | 54 | 	read_lock(&adfs_dir_lock); | 
 | 55 |  | 
 | 56 | 	ret = ops->setpos(&dir, filp->f_pos - 2); | 
 | 57 | 	if (ret) | 
 | 58 | 		goto unlock_out; | 
 | 59 | 	while (ops->getnext(&dir, &obj) == 0) { | 
 | 60 | 		if (filldir(dirent, obj.name, obj.name_len, | 
 | 61 | 			    filp->f_pos, obj.file_id, DT_UNKNOWN) < 0) | 
 | 62 | 			goto unlock_out; | 
 | 63 | 		filp->f_pos += 1; | 
 | 64 | 	} | 
 | 65 |  | 
 | 66 | unlock_out: | 
 | 67 | 	read_unlock(&adfs_dir_lock); | 
 | 68 |  | 
 | 69 | free_out: | 
 | 70 | 	ops->free(&dir); | 
 | 71 |  | 
 | 72 | out: | 
 | 73 | 	unlock_kernel(); | 
 | 74 | 	return ret; | 
 | 75 | } | 
 | 76 |  | 
 | 77 | int | 
| Al Viro | ffdc906 | 2009-06-08 00:44:42 -0400 | [diff] [blame] | 78 | adfs_dir_update(struct super_block *sb, struct object_info *obj, int wait) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 79 | { | 
 | 80 | 	int ret = -EINVAL; | 
 | 81 | #ifdef CONFIG_ADFS_FS_RW | 
 | 82 | 	struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | 
 | 83 | 	struct adfs_dir dir; | 
 | 84 |  | 
 | 85 | 	printk(KERN_INFO "adfs_dir_update: object %06X in dir %06X\n", | 
 | 86 | 		 obj->file_id, obj->parent_id); | 
 | 87 |  | 
 | 88 | 	if (!ops->update) { | 
 | 89 | 		ret = -EINVAL; | 
 | 90 | 		goto out; | 
 | 91 | 	} | 
 | 92 |  | 
 | 93 | 	ret = ops->read(sb, obj->parent_id, 0, &dir); | 
 | 94 | 	if (ret) | 
 | 95 | 		goto out; | 
 | 96 |  | 
 | 97 | 	write_lock(&adfs_dir_lock); | 
 | 98 | 	ret = ops->update(&dir, obj); | 
 | 99 | 	write_unlock(&adfs_dir_lock); | 
 | 100 |  | 
| Al Viro | ffdc906 | 2009-06-08 00:44:42 -0400 | [diff] [blame] | 101 | 	if (wait) { | 
 | 102 | 		int err = ops->sync(&dir); | 
 | 103 | 		if (!ret) | 
 | 104 | 			ret = err; | 
 | 105 | 	} | 
 | 106 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 107 | 	ops->free(&dir); | 
 | 108 | out: | 
 | 109 | #endif | 
 | 110 | 	return ret; | 
 | 111 | } | 
 | 112 |  | 
 | 113 | static int | 
 | 114 | adfs_match(struct qstr *name, struct object_info *obj) | 
 | 115 | { | 
 | 116 | 	int i; | 
 | 117 |  | 
 | 118 | 	if (name->len != obj->name_len) | 
 | 119 | 		return 0; | 
 | 120 |  | 
 | 121 | 	for (i = 0; i < name->len; i++) { | 
 | 122 | 		char c1, c2; | 
 | 123 |  | 
 | 124 | 		c1 = name->name[i]; | 
 | 125 | 		c2 = obj->name[i]; | 
 | 126 |  | 
 | 127 | 		if (c1 >= 'A' && c1 <= 'Z') | 
 | 128 | 			c1 += 'a' - 'A'; | 
 | 129 | 		if (c2 >= 'A' && c2 <= 'Z') | 
 | 130 | 			c2 += 'a' - 'A'; | 
 | 131 |  | 
 | 132 | 		if (c1 != c2) | 
 | 133 | 			return 0; | 
 | 134 | 	} | 
 | 135 | 	return 1; | 
 | 136 | } | 
 | 137 |  | 
 | 138 | static int | 
 | 139 | adfs_dir_lookup_byname(struct inode *inode, struct qstr *name, struct object_info *obj) | 
 | 140 | { | 
 | 141 | 	struct super_block *sb = inode->i_sb; | 
 | 142 | 	struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir; | 
 | 143 | 	struct adfs_dir dir; | 
 | 144 | 	int ret; | 
 | 145 |  | 
 | 146 | 	ret = ops->read(sb, inode->i_ino, inode->i_size, &dir); | 
 | 147 | 	if (ret) | 
 | 148 | 		goto out; | 
 | 149 |  | 
 | 150 | 	if (ADFS_I(inode)->parent_id != dir.parent_id) { | 
 | 151 | 		adfs_error(sb, "parent directory changed under me! (%lx but got %lx)\n", | 
 | 152 | 			   ADFS_I(inode)->parent_id, dir.parent_id); | 
 | 153 | 		ret = -EIO; | 
 | 154 | 		goto free_out; | 
 | 155 | 	} | 
 | 156 |  | 
 | 157 | 	obj->parent_id = inode->i_ino; | 
 | 158 |  | 
 | 159 | 	/* | 
 | 160 | 	 * '.' is handled by reserved_lookup() in fs/namei.c | 
 | 161 | 	 */ | 
 | 162 | 	if (name->len == 2 && name->name[0] == '.' && name->name[1] == '.') { | 
 | 163 | 		/* | 
 | 164 | 		 * Currently unable to fill in the rest of 'obj', | 
 | 165 | 		 * but this is better than nothing.  We need to | 
 | 166 | 		 * ascend one level to find it's parent. | 
 | 167 | 		 */ | 
 | 168 | 		obj->name_len = 0; | 
 | 169 | 		obj->file_id  = obj->parent_id; | 
 | 170 | 		goto free_out; | 
 | 171 | 	} | 
 | 172 |  | 
 | 173 | 	read_lock(&adfs_dir_lock); | 
 | 174 |  | 
 | 175 | 	ret = ops->setpos(&dir, 0); | 
 | 176 | 	if (ret) | 
 | 177 | 		goto unlock_out; | 
 | 178 |  | 
 | 179 | 	ret = -ENOENT; | 
 | 180 | 	while (ops->getnext(&dir, obj) == 0) { | 
 | 181 | 		if (adfs_match(name, obj)) { | 
 | 182 | 			ret = 0; | 
 | 183 | 			break; | 
 | 184 | 		} | 
 | 185 | 	} | 
 | 186 |  | 
 | 187 | unlock_out: | 
 | 188 | 	read_unlock(&adfs_dir_lock); | 
 | 189 |  | 
 | 190 | free_out: | 
 | 191 | 	ops->free(&dir); | 
 | 192 | out: | 
 | 193 | 	return ret; | 
 | 194 | } | 
 | 195 |  | 
| Arjan van de Ven | 4b6f5d2 | 2006-03-28 01:56:42 -0800 | [diff] [blame] | 196 | const struct file_operations adfs_dir_operations = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 197 | 	.read		= generic_read_dir, | 
| Al Viro | 59af158 | 2008-08-24 07:24:41 -0400 | [diff] [blame] | 198 | 	.llseek		= generic_file_llseek, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 199 | 	.readdir	= adfs_readdir, | 
| Al Viro | ffdc906 | 2009-06-08 00:44:42 -0400 | [diff] [blame] | 200 | 	.fsync		= simple_fsync, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 201 | }; | 
 | 202 |  | 
 | 203 | static int | 
 | 204 | adfs_hash(struct dentry *parent, struct qstr *qstr) | 
 | 205 | { | 
 | 206 | 	const unsigned int name_len = ADFS_SB(parent->d_sb)->s_namelen; | 
 | 207 | 	const unsigned char *name; | 
 | 208 | 	unsigned long hash; | 
 | 209 | 	int i; | 
 | 210 |  | 
 | 211 | 	if (qstr->len < name_len) | 
 | 212 | 		return 0; | 
 | 213 |  | 
 | 214 | 	/* | 
 | 215 | 	 * Truncate the name in place, avoids | 
 | 216 | 	 * having to define a compare function. | 
 | 217 | 	 */ | 
 | 218 | 	qstr->len = i = name_len; | 
 | 219 | 	name = qstr->name; | 
 | 220 | 	hash = init_name_hash(); | 
 | 221 | 	while (i--) { | 
 | 222 | 		char c; | 
 | 223 |  | 
 | 224 | 		c = *name++; | 
 | 225 | 		if (c >= 'A' && c <= 'Z') | 
 | 226 | 			c += 'a' - 'A'; | 
 | 227 |  | 
 | 228 | 		hash = partial_name_hash(c, hash); | 
 | 229 | 	} | 
 | 230 | 	qstr->hash = end_name_hash(hash); | 
 | 231 |  | 
 | 232 | 	return 0; | 
 | 233 | } | 
 | 234 |  | 
 | 235 | /* | 
 | 236 |  * Compare two names, taking note of the name length | 
 | 237 |  * requirements of the underlying filesystem. | 
 | 238 |  */ | 
 | 239 | static int | 
 | 240 | adfs_compare(struct dentry *parent, struct qstr *entry, struct qstr *name) | 
 | 241 | { | 
 | 242 | 	int i; | 
 | 243 |  | 
 | 244 | 	if (entry->len != name->len) | 
 | 245 | 		return 1; | 
 | 246 |  | 
 | 247 | 	for (i = 0; i < name->len; i++) { | 
 | 248 | 		char a, b; | 
 | 249 |  | 
 | 250 | 		a = entry->name[i]; | 
 | 251 | 		b = name->name[i]; | 
 | 252 |  | 
 | 253 | 		if (a >= 'A' && a <= 'Z') | 
 | 254 | 			a += 'a' - 'A'; | 
 | 255 | 		if (b >= 'A' && b <= 'Z') | 
 | 256 | 			b += 'a' - 'A'; | 
 | 257 |  | 
 | 258 | 		if (a != b) | 
 | 259 | 			return 1; | 
 | 260 | 	} | 
 | 261 | 	return 0; | 
 | 262 | } | 
 | 263 |  | 
| Al Viro | e16404e | 2009-02-20 05:55:13 +0000 | [diff] [blame] | 264 | const struct dentry_operations adfs_dentry_operations = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 265 | 	.d_hash		= adfs_hash, | 
 | 266 | 	.d_compare	= adfs_compare, | 
 | 267 | }; | 
 | 268 |  | 
 | 269 | static struct dentry * | 
 | 270 | adfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd) | 
 | 271 | { | 
 | 272 | 	struct inode *inode = NULL; | 
 | 273 | 	struct object_info obj; | 
 | 274 | 	int error; | 
 | 275 |  | 
 | 276 | 	dentry->d_op = &adfs_dentry_operations;	 | 
 | 277 | 	lock_kernel(); | 
 | 278 | 	error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj); | 
 | 279 | 	if (error == 0) { | 
 | 280 | 		error = -EACCES; | 
 | 281 | 		/* | 
 | 282 | 		 * This only returns NULL if get_empty_inode | 
 | 283 | 		 * fails. | 
 | 284 | 		 */ | 
 | 285 | 		inode = adfs_iget(dir->i_sb, &obj); | 
 | 286 | 		if (inode) | 
 | 287 | 			error = 0; | 
 | 288 | 	} | 
 | 289 | 	unlock_kernel(); | 
 | 290 | 	d_add(dentry, inode); | 
 | 291 | 	return ERR_PTR(error); | 
 | 292 | } | 
 | 293 |  | 
 | 294 | /* | 
 | 295 |  * directories can handle most operations... | 
 | 296 |  */ | 
| Arjan van de Ven | 754661f | 2007-02-12 00:55:38 -0800 | [diff] [blame] | 297 | const struct inode_operations adfs_dir_inode_operations = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 298 | 	.lookup		= adfs_lookup, | 
 | 299 | 	.setattr	= adfs_notify_change, | 
 | 300 | }; |