File: /usr/src/linux/fs/adfs/dir.c

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