kern_fileassoc.c revision 1.32 1 /* $NetBSD: kern_fileassoc.c,v 1.32 2009/12/25 18:51:41 elad Exp $ */
2
3 /*-
4 * Copyright (c) 2006 Elad Efrat <elad (at) NetBSD.org>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include <sys/cdefs.h>
31 __KERNEL_RCSID(0, "$NetBSD: kern_fileassoc.c,v 1.32 2009/12/25 18:51:41 elad Exp $");
32
33 #include "opt_fileassoc.h"
34
35 #include <sys/param.h>
36 #include <sys/mount.h>
37 #include <sys/queue.h>
38 #include <sys/vnode.h>
39 #include <sys/namei.h>
40 #include <sys/exec.h>
41 #include <sys/proc.h>
42 #include <sys/inttypes.h>
43 #include <sys/errno.h>
44 #include <sys/fileassoc.h>
45 #include <sys/specificdata.h>
46 #include <sys/hash.h>
47 #include <sys/fstypes.h>
48 #include <sys/kmem.h>
49 #include <sys/once.h>
50
51 #define FILEASSOC_INITIAL_TABLESIZE 128
52
53 static specificdata_domain_t fileassoc_domain;
54 static specificdata_key_t fileassoc_mountspecific_key;
55 static ONCE_DECL(control);
56
57 /*
58 * Hook entry.
59 * Includes the hook name for identification and private hook clear callback.
60 */
61 struct fileassoc {
62 LIST_ENTRY(fileassoc) list;
63 const char *name; /* name. */
64 fileassoc_cleanup_cb_t cleanup_cb; /* clear callback. */
65 specificdata_key_t key;
66 };
67
68 static LIST_HEAD(, fileassoc) fileassoc_list;
69
70 /* An entry in the per-mount hash table. */
71 struct fileassoc_hash_entry {
72 fhandle_t *handle; /* File handle */
73 specificdata_reference data; /* Hooks. */
74 u_int nassocs; /* # of hooks. */
75 LIST_ENTRY(fileassoc_hash_entry) entries; /* List pointer. */
76 };
77
78 LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry);
79
80 struct fileassoc_table {
81 struct fileassoc_hashhead *hash_tbl;
82 size_t hash_size; /* Number of slots. */
83 u_long hash_mask;
84 size_t hash_used; /* # of used slots. */
85 specificdata_reference data;
86 };
87
88 /*
89 * Hashing function: Takes a number modulus the mask to give back an
90 * index into the hash table.
91 */
92 #define FILEASSOC_HASH(tbl, handle) \
93 (hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \
94 & ((tbl)->hash_mask))
95
96 static void *
97 file_getdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc)
98 {
99
100 return specificdata_getspecific(fileassoc_domain, &e->data,
101 assoc->key);
102 }
103
104 static void
105 file_setdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc,
106 void *data)
107 {
108
109 specificdata_setspecific(fileassoc_domain, &e->data, assoc->key,
110 data);
111 }
112
113 static void
114 file_cleanup(struct fileassoc_hash_entry *e, const struct fileassoc *assoc)
115 {
116 fileassoc_cleanup_cb_t cb;
117 void *data;
118
119 cb = assoc->cleanup_cb;
120 if (cb == NULL) {
121 return;
122 }
123 data = file_getdata(e, assoc);
124 (*cb)(data);
125 }
126
127 static void
128 file_free(struct fileassoc_hash_entry *e)
129 {
130 struct fileassoc *assoc;
131
132 LIST_REMOVE(e, entries);
133
134 LIST_FOREACH(assoc, &fileassoc_list, list) {
135 file_cleanup(e, assoc);
136 }
137 vfs_composefh_free(e->handle);
138 specificdata_fini(fileassoc_domain, &e->data);
139 kmem_free(e, sizeof(*e));
140 }
141
142 static void
143 table_dtor(void *vp)
144 {
145 struct fileassoc_table *tbl = vp;
146 struct fileassoc_hashhead *hh;
147 u_long i;
148
149 /* Remove all entries from the table and lists */
150 hh = tbl->hash_tbl;
151 for (i = 0; i < tbl->hash_size; i++) {
152 struct fileassoc_hash_entry *mhe;
153
154 while ((mhe = LIST_FIRST(&hh[i])) != NULL) {
155 file_free(mhe);
156 }
157 }
158
159 /* Remove hash table and sysctl node */
160 hashdone(tbl->hash_tbl, HASH_LIST, tbl->hash_mask);
161 specificdata_fini(fileassoc_domain, &tbl->data);
162 kmem_free(tbl, sizeof(*tbl));
163 }
164
165 /*
166 * Initialize the fileassoc subsystem.
167 */
168 static int
169 fileassoc_init(void)
170 {
171 int error;
172
173 error = mount_specific_key_create(&fileassoc_mountspecific_key,
174 table_dtor);
175 if (error) {
176 return error;
177 }
178 fileassoc_domain = specificdata_domain_create();
179
180 return 0;
181 }
182
183 /*
184 * Register a new hook.
185 */
186 int
187 fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
188 fileassoc_t *result)
189 {
190 int error;
191 specificdata_key_t key;
192 struct fileassoc *assoc;
193
194 error = RUN_ONCE(&control, fileassoc_init);
195 if (error) {
196 return error;
197 }
198 error = specificdata_key_create(fileassoc_domain, &key, NULL);
199 if (error) {
200 return error;
201 }
202 assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
203 assoc->name = name;
204 assoc->cleanup_cb = cleanup_cb;
205 assoc->key = key;
206 LIST_INSERT_HEAD(&fileassoc_list, assoc, list);
207 *result = assoc;
208
209 return 0;
210 }
211
212 /*
213 * Deregister a hook.
214 */
215 int
216 fileassoc_deregister(fileassoc_t assoc)
217 {
218
219 LIST_REMOVE(assoc, list);
220 specificdata_key_delete(fileassoc_domain, assoc->key);
221 kmem_free(assoc, sizeof(*assoc));
222
223 return 0;
224 }
225
226 /*
227 * Get the hash table for the specified device.
228 */
229 static struct fileassoc_table *
230 fileassoc_table_lookup(struct mount *mp)
231 {
232 int error;
233
234 error = RUN_ONCE(&control, fileassoc_init);
235 if (error) {
236 return NULL;
237 }
238 return mount_getspecific(mp, fileassoc_mountspecific_key);
239 }
240
241 /*
242 * Perform a lookup on a hash table. If hint is non-zero then use the value
243 * of the hint as the identifier instead of performing a lookup for the
244 * fileid.
245 */
246 static struct fileassoc_hash_entry *
247 fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
248 {
249 struct fileassoc_table *tbl;
250 struct fileassoc_hashhead *tble;
251 struct fileassoc_hash_entry *e;
252 size_t indx;
253 fhandle_t *th;
254 int error;
255
256 tbl = fileassoc_table_lookup(vp->v_mount);
257 if (tbl == NULL) {
258 return NULL;
259 }
260
261 if (hint == NULL) {
262 error = vfs_composefh_alloc(vp, &th);
263 if (error)
264 return (NULL);
265 } else {
266 th = hint;
267 }
268
269 indx = FILEASSOC_HASH(tbl, th);
270 tble = &(tbl->hash_tbl[indx]);
271
272 LIST_FOREACH(e, tble, entries) {
273 if (((FHANDLE_FILEID(e->handle)->fid_len ==
274 FHANDLE_FILEID(th)->fid_len)) &&
275 (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th),
276 (FHANDLE_FILEID(th))->fid_len) == 0)) {
277 break;
278 }
279 }
280
281 if (hint == NULL)
282 vfs_composefh_free(th);
283
284 return e;
285 }
286
287 /*
288 * Return hook data associated with a vnode.
289 */
290 void *
291 fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
292 {
293 struct fileassoc_hash_entry *mhe;
294
295 mhe = fileassoc_file_lookup(vp, NULL);
296 if (mhe == NULL)
297 return (NULL);
298
299 return file_getdata(mhe, assoc);
300 }
301
302 static struct fileassoc_table *
303 fileassoc_table_resize(struct fileassoc_table *tbl)
304 {
305 struct fileassoc_table *newtbl;
306 struct fileassoc_hashhead *hh;
307 u_long i;
308
309 /*
310 * Allocate a new table. Like the condition in fileassoc_file_add(),
311 * this is also temporary -- just double the number of slots.
312 */
313 newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
314 newtbl->hash_size = (tbl->hash_size * 2);
315 if (newtbl->hash_size < tbl->hash_size)
316 newtbl->hash_size = tbl->hash_size;
317 newtbl->hash_tbl = hashinit(newtbl->hash_size, HASH_LIST,
318 true, &newtbl->hash_mask);
319 newtbl->hash_used = 0;
320 specificdata_init(fileassoc_domain, &newtbl->data);
321
322 /* XXX we need to make sure nothing uses fileassoc here! */
323
324 hh = tbl->hash_tbl;
325 for (i = 0; i < tbl->hash_size; i++) {
326 struct fileassoc_hash_entry *mhe;
327
328 while ((mhe = LIST_FIRST(&hh[i])) != NULL) {
329 struct fileassoc_hashhead *vhh;
330 size_t indx;
331
332 LIST_REMOVE(mhe, entries);
333
334 indx = FILEASSOC_HASH(newtbl, mhe->handle);
335 vhh = &(newtbl->hash_tbl[indx]);
336
337 LIST_INSERT_HEAD(vhh, mhe, entries);
338
339 newtbl->hash_used++;
340 }
341 }
342
343 if (tbl->hash_used != newtbl->hash_used)
344 panic("fileassoc_table_resize: inconsistency detected! "
345 "needed %zu entries, got %zu", tbl->hash_used,
346 newtbl->hash_used);
347
348 hashdone(tbl->hash_tbl, HASH_LIST, tbl->hash_mask);
349 specificdata_fini(fileassoc_domain, &tbl->data);
350 kmem_free(tbl, sizeof(*tbl));
351
352 return (newtbl);
353 }
354
355 /*
356 * Create a new fileassoc table.
357 */
358 static struct fileassoc_table *
359 fileassoc_table_add(struct mount *mp)
360 {
361 struct fileassoc_table *tbl;
362
363 /* Check for existing table for device. */
364 tbl = fileassoc_table_lookup(mp);
365 if (tbl != NULL)
366 return (tbl);
367
368 /* Allocate and initialize a table. */
369 tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
370 tbl->hash_size = FILEASSOC_INITIAL_TABLESIZE;
371 tbl->hash_tbl = hashinit(tbl->hash_size, HASH_LIST, true,
372 &tbl->hash_mask);
373 tbl->hash_used = 0;
374 specificdata_init(fileassoc_domain, &tbl->data);
375
376 mount_setspecific(mp, fileassoc_mountspecific_key, tbl);
377
378 return (tbl);
379 }
380
381 /*
382 * Delete a table.
383 */
384 int
385 fileassoc_table_delete(struct mount *mp)
386 {
387 struct fileassoc_table *tbl;
388
389 tbl = fileassoc_table_lookup(mp);
390 if (tbl == NULL)
391 return (EEXIST);
392
393 mount_setspecific(mp, fileassoc_mountspecific_key, NULL);
394 table_dtor(tbl);
395
396 return (0);
397 }
398
399 /*
400 * Run a callback for each hook entry in a table.
401 */
402 int
403 fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
404 void *cookie)
405 {
406 struct fileassoc_table *tbl;
407 struct fileassoc_hashhead *hh;
408 u_long i;
409
410 tbl = fileassoc_table_lookup(mp);
411 if (tbl == NULL)
412 return (EEXIST);
413
414 hh = tbl->hash_tbl;
415 for (i = 0; i < tbl->hash_size; i++) {
416 struct fileassoc_hash_entry *mhe;
417
418 LIST_FOREACH(mhe, &hh[i], entries) {
419 void *data;
420
421 data = file_getdata(mhe, assoc);
422 if (data != NULL)
423 cb(data, cookie);
424 }
425 }
426
427 return (0);
428 }
429
430 /*
431 * Clear a table for a given hook.
432 */
433 int
434 fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
435 {
436 struct fileassoc_table *tbl;
437 struct fileassoc_hashhead *hh;
438 u_long i;
439
440 tbl = fileassoc_table_lookup(mp);
441 if (tbl == NULL)
442 return (EEXIST);
443
444 hh = tbl->hash_tbl;
445 for (i = 0; i < tbl->hash_size; i++) {
446 struct fileassoc_hash_entry *mhe;
447
448 LIST_FOREACH(mhe, &hh[i], entries) {
449 file_cleanup(mhe, assoc);
450 file_setdata(mhe, assoc, NULL);
451 }
452 }
453
454 return (0);
455 }
456
457 /*
458 * Add a file entry to a table.
459 */
460 static struct fileassoc_hash_entry *
461 fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
462 {
463 struct fileassoc_table *tbl;
464 struct fileassoc_hashhead *vhh;
465 struct fileassoc_hash_entry *e;
466 size_t indx;
467 fhandle_t *th;
468 int error;
469
470 if (hint == NULL) {
471 error = vfs_composefh_alloc(vp, &th);
472 if (error)
473 return (NULL);
474 } else
475 th = hint;
476
477 e = fileassoc_file_lookup(vp, th);
478 if (e != NULL) {
479 if (hint == NULL)
480 vfs_composefh_free(th);
481
482 return (e);
483 }
484
485 tbl = fileassoc_table_lookup(vp->v_mount);
486 if (tbl == NULL) {
487 tbl = fileassoc_table_add(vp->v_mount);
488 }
489
490 indx = FILEASSOC_HASH(tbl, th);
491 vhh = &(tbl->hash_tbl[indx]);
492
493 e = kmem_zalloc(sizeof(*e), KM_SLEEP);
494 e->handle = th;
495 specificdata_init(fileassoc_domain, &e->data);
496 LIST_INSERT_HEAD(vhh, e, entries);
497
498 /*
499 * This decides when we need to resize the table. For now,
500 * resize it whenever we "filled" up the number of slots it
501 * has. That's not really true unless of course we had zero
502 * collisions. Think positive! :)
503 */
504 if (++(tbl->hash_used) == tbl->hash_size) {
505 struct fileassoc_table *newtbl;
506
507 newtbl = fileassoc_table_resize(tbl);
508 mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
509 newtbl);
510 }
511
512 return (e);
513 }
514
515 /*
516 * Delete a file entry from a table.
517 */
518 int
519 fileassoc_file_delete(struct vnode *vp)
520 {
521 struct fileassoc_table *tbl;
522 struct fileassoc_hash_entry *mhe;
523
524 KERNEL_LOCK(1, NULL);
525
526 mhe = fileassoc_file_lookup(vp, NULL);
527 if (mhe == NULL) {
528 KERNEL_UNLOCK_ONE(NULL);
529 return (ENOENT);
530 }
531
532 file_free(mhe);
533
534 tbl = fileassoc_table_lookup(vp->v_mount);
535 --(tbl->hash_used); /* XXX gc? */
536
537 KERNEL_UNLOCK_ONE(NULL);
538
539 return (0);
540 }
541
542 /*
543 * Add a hook to a vnode.
544 */
545 int
546 fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
547 {
548 struct fileassoc_hash_entry *e;
549 void *olddata;
550
551 e = fileassoc_file_lookup(vp, NULL);
552 if (e == NULL) {
553 e = fileassoc_file_add(vp, NULL);
554 if (e == NULL)
555 return (ENOTDIR);
556 }
557
558 olddata = file_getdata(e, assoc);
559 if (olddata != NULL)
560 return (EEXIST);
561
562 file_setdata(e, assoc, data);
563
564 e->nassocs++;
565
566 return (0);
567 }
568
569 /*
570 * Clear a hook from a vnode.
571 */
572 int
573 fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
574 {
575 struct fileassoc_hash_entry *mhe;
576
577 mhe = fileassoc_file_lookup(vp, NULL);
578 if (mhe == NULL)
579 return (ENOENT);
580
581 file_cleanup(mhe, assoc);
582 file_setdata(mhe, assoc, NULL);
583
584 --(mhe->nassocs); /* XXX gc? */
585
586 return (0);
587 }
588