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