[PATCH] Test for sb_getblk return value
[linux-2.6] / fs / dcookies.c
1 /*
2  * dcookies.c
3  *
4  * Copyright 2002 John Levon <levon@movementarian.org>
5  *
6  * Persistent cookie-path mappings. These are used by
7  * profilers to convert a per-task EIP value into something
8  * non-transitory that can be processed at a later date.
9  * This is done by locking the dentry/vfsmnt pair in the
10  * kernel until released by the tasks needing the persistent
11  * objects. The tag is simply an unsigned long that refers
12  * to the pair and can be looked up from userspace.
13  */
14
15 #include <linux/config.h>
16 #include <linux/syscalls.h>
17 #include <linux/module.h>
18 #include <linux/slab.h>
19 #include <linux/list.h>
20 #include <linux/mount.h>
21 #include <linux/dcache.h>
22 #include <linux/mm.h>
23 #include <linux/errno.h>
24 #include <linux/dcookies.h>
25 #include <asm/uaccess.h>
26
27 /* The dcookies are allocated from a kmem_cache and
28  * hashed onto a small number of lists. None of the
29  * code here is particularly performance critical
30  */
31 struct dcookie_struct {
32         struct dentry * dentry;
33         struct vfsmount * vfsmnt;
34         struct list_head hash_list;
35 };
36
37 static LIST_HEAD(dcookie_users);
38 static DECLARE_MUTEX(dcookie_sem);
39 static kmem_cache_t * dcookie_cache;
40 static struct list_head * dcookie_hashtable;
41 static size_t hash_size;
42
43 static inline int is_live(void)
44 {
45         return !(list_empty(&dcookie_users));
46 }
47
48
49 /* The dentry is locked, its address will do for the cookie */
50 static inline unsigned long dcookie_value(struct dcookie_struct * dcs)
51 {
52         return (unsigned long)dcs->dentry;
53 }
54
55
56 static size_t dcookie_hash(unsigned long dcookie)
57 {
58         return (dcookie >> L1_CACHE_SHIFT) & (hash_size - 1);
59 }
60
61
62 static struct dcookie_struct * find_dcookie(unsigned long dcookie)
63 {
64         struct dcookie_struct *found = NULL;
65         struct dcookie_struct * dcs;
66         struct list_head * pos;
67         struct list_head * list;
68
69         list = dcookie_hashtable + dcookie_hash(dcookie);
70
71         list_for_each(pos, list) {
72                 dcs = list_entry(pos, struct dcookie_struct, hash_list);
73                 if (dcookie_value(dcs) == dcookie) {
74                         found = dcs;
75                         break;
76                 }
77         }
78
79         return found;
80 }
81
82
83 static void hash_dcookie(struct dcookie_struct * dcs)
84 {
85         struct list_head * list = dcookie_hashtable + dcookie_hash(dcookie_value(dcs));
86         list_add(&dcs->hash_list, list);
87 }
88
89
90 static struct dcookie_struct * alloc_dcookie(struct dentry * dentry,
91         struct vfsmount * vfsmnt)
92 {
93         struct dcookie_struct * dcs = kmem_cache_alloc(dcookie_cache, GFP_KERNEL);
94         if (!dcs)
95                 return NULL;
96
97         dentry->d_cookie = dcs;
98
99         dcs->dentry = dget(dentry);
100         dcs->vfsmnt = mntget(vfsmnt);
101         hash_dcookie(dcs);
102
103         return dcs;
104 }
105
106
107 /* This is the main kernel-side routine that retrieves the cookie
108  * value for a dentry/vfsmnt pair.
109  */
110 int get_dcookie(struct dentry * dentry, struct vfsmount * vfsmnt,
111         unsigned long * cookie)
112 {
113         int err = 0;
114         struct dcookie_struct * dcs;
115
116         down(&dcookie_sem);
117
118         if (!is_live()) {
119                 err = -EINVAL;
120                 goto out;
121         }
122
123         dcs = dentry->d_cookie;
124
125         if (!dcs)
126                 dcs = alloc_dcookie(dentry, vfsmnt);
127
128         if (!dcs) {
129                 err = -ENOMEM;
130                 goto out;
131         }
132
133         *cookie = dcookie_value(dcs);
134
135 out:
136         up(&dcookie_sem);
137         return err;
138 }
139
140
141 /* And here is where the userspace process can look up the cookie value
142  * to retrieve the path.
143  */
144 asmlinkage long sys_lookup_dcookie(u64 cookie64, char __user * buf, size_t len)
145 {
146         unsigned long cookie = (unsigned long)cookie64;
147         int err = -EINVAL;
148         char * kbuf;
149         char * path;
150         size_t pathlen;
151         struct dcookie_struct * dcs;
152
153         /* we could leak path information to users
154          * without dir read permission without this
155          */
156         if (!capable(CAP_SYS_ADMIN))
157                 return -EPERM;
158
159         down(&dcookie_sem);
160
161         if (!is_live()) {
162                 err = -EINVAL;
163                 goto out;
164         }
165
166         if (!(dcs = find_dcookie(cookie)))
167                 goto out;
168
169         err = -ENOMEM;
170         kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
171         if (!kbuf)
172                 goto out;
173
174         /* FIXME: (deleted) ? */
175         path = d_path(dcs->dentry, dcs->vfsmnt, kbuf, PAGE_SIZE);
176
177         if (IS_ERR(path)) {
178                 err = PTR_ERR(path);
179                 goto out_free;
180         }
181
182         err = -ERANGE;
183  
184         pathlen = kbuf + PAGE_SIZE - path;
185         if (pathlen <= len) {
186                 err = pathlen;
187                 if (copy_to_user(buf, path, pathlen))
188                         err = -EFAULT;
189         }
190
191 out_free:
192         kfree(kbuf);
193 out:
194         up(&dcookie_sem);
195         return err;
196 }
197
198
199 static int dcookie_init(void)
200 {
201         struct list_head * d;
202         unsigned int i, hash_bits;
203         int err = -ENOMEM;
204
205         dcookie_cache = kmem_cache_create("dcookie_cache",
206                 sizeof(struct dcookie_struct),
207                 0, 0, NULL, NULL);
208
209         if (!dcookie_cache)
210                 goto out;
211
212         dcookie_hashtable = kmalloc(PAGE_SIZE, GFP_KERNEL);
213         if (!dcookie_hashtable)
214                 goto out_kmem;
215
216         err = 0;
217
218         /*
219          * Find the power-of-two list-heads that can fit into the allocation..
220          * We don't guarantee that "sizeof(struct list_head)" is necessarily
221          * a power-of-two.
222          */
223         hash_size = PAGE_SIZE / sizeof(struct list_head);
224         hash_bits = 0;
225         do {
226                 hash_bits++;
227         } while ((hash_size >> hash_bits) != 0);
228         hash_bits--;
229
230         /*
231          * Re-calculate the actual number of entries and the mask
232          * from the number of bits we can fit.
233          */
234         hash_size = 1UL << hash_bits;
235
236         /* And initialize the newly allocated array */
237         d = dcookie_hashtable;
238         i = hash_size;
239         do {
240                 INIT_LIST_HEAD(d);
241                 d++;
242                 i--;
243         } while (i);
244
245 out:
246         return err;
247 out_kmem:
248         kmem_cache_destroy(dcookie_cache);
249         goto out;
250 }
251
252
253 static void free_dcookie(struct dcookie_struct * dcs)
254 {
255         dcs->dentry->d_cookie = NULL;
256         dput(dcs->dentry);
257         mntput(dcs->vfsmnt);
258         kmem_cache_free(dcookie_cache, dcs);
259 }
260
261
262 static void dcookie_exit(void)
263 {
264         struct list_head * list;
265         struct list_head * pos;
266         struct list_head * pos2;
267         struct dcookie_struct * dcs;
268         size_t i;
269
270         for (i = 0; i < hash_size; ++i) {
271                 list = dcookie_hashtable + i;
272                 list_for_each_safe(pos, pos2, list) {
273                         dcs = list_entry(pos, struct dcookie_struct, hash_list);
274                         list_del(&dcs->hash_list);
275                         free_dcookie(dcs);
276                 }
277         }
278
279         kfree(dcookie_hashtable);
280         kmem_cache_destroy(dcookie_cache);
281 }
282
283
284 struct dcookie_user {
285         struct list_head next;
286 };
287  
288 struct dcookie_user * dcookie_register(void)
289 {
290         struct dcookie_user * user;
291
292         down(&dcookie_sem);
293
294         user = kmalloc(sizeof(struct dcookie_user), GFP_KERNEL);
295         if (!user)
296                 goto out;
297
298         if (!is_live() && dcookie_init())
299                 goto out_free;
300
301         list_add(&user->next, &dcookie_users);
302
303 out:
304         up(&dcookie_sem);
305         return user;
306 out_free:
307         kfree(user);
308         user = NULL;
309         goto out;
310 }
311
312
313 void dcookie_unregister(struct dcookie_user * user)
314 {
315         down(&dcookie_sem);
316
317         list_del(&user->next);
318         kfree(user);
319
320         if (!is_live())
321                 dcookie_exit();
322
323         up(&dcookie_sem);
324 }
325
326 EXPORT_SYMBOL_GPL(dcookie_register);
327 EXPORT_SYMBOL_GPL(dcookie_unregister);
328 EXPORT_SYMBOL_GPL(get_dcookie);