kern_fileassoc.c revision 1.33 1 /* $NetBSD: kern_fileassoc.c,v 1.33 2009/12/25 20:05:43 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.33 2009/12/25 20:05:43 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/errno.h>
40 #include <sys/fileassoc.h>
41 #include <sys/specificdata.h>
42 #include <sys/hash.h>
43 #include <sys/kmem.h>
44 #include <sys/once.h>
45
46 #define FILEASSOC_INITIAL_TABLESIZE 128
47
48 static specificdata_domain_t fileassoc_domain;
49 static specificdata_key_t fileassoc_mountspecific_key;
50 static ONCE_DECL(control);
51
52 /*
53 * Assoc entry.
54 * Includes the assoc name for identification and private clear callback.
55 */
56 struct fileassoc {
57 LIST_ENTRY(fileassoc) assoc_list;
58 const char *assoc_name; /* Name. */
59 fileassoc_cleanup_cb_t assoc_cleanup_cb; /* Clear callback. */
60 specificdata_key_t assoc_key;
61 };
62
63 static LIST_HEAD(, fileassoc) fileassoc_list;
64 static kmutex_t fileassoc_list_lock;
65
66 /* An entry in the per-mount hash table. */
67 struct fileassoc_file {
68 fhandle_t *faf_handle; /* File handle */
69 specificdata_reference faf_data; /* Assoc data. */
70 u_int faf_nassocs; /* # of assocs. */
71 LIST_ENTRY(fileassoc_file) faf_list; /* List pointer. */
72 };
73
74 LIST_HEAD(fileassoc_hash_entry, fileassoc_file);
75
76 struct fileassoc_table {
77 struct fileassoc_hash_entry *tbl_hash;
78 u_long tbl_mask; /* Hash table mask. */
79 size_t tbl_nslots; /* Number of slots. */
80 size_t tbl_nused; /* # of used slots. */
81 specificdata_reference tbl_data;
82 };
83
84 /*
85 * Hashing function: Takes a number modulus the mask to give back an
86 * index into the hash table.
87 */
88 #define FILEASSOC_HASH(tbl, handle) \
89 (hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \
90 & ((tbl)->tbl_mask))
91
92 static void *
93 file_getdata(struct fileassoc_file *faf, const struct fileassoc *assoc)
94 {
95
96 return specificdata_getspecific(fileassoc_domain, &faf->faf_data,
97 assoc->assoc_key);
98 }
99
100 static void
101 file_setdata(struct fileassoc_file *faf, const struct fileassoc *assoc,
102 void *data)
103 {
104
105 specificdata_setspecific(fileassoc_domain, &faf->faf_data,
106 assoc->assoc_key, data);
107 }
108
109 static void
110 file_cleanup(struct fileassoc_file *faf, const struct fileassoc *assoc)
111 {
112 fileassoc_cleanup_cb_t cb;
113 void *data;
114
115 cb = assoc->assoc_cleanup_cb;
116 if (cb == NULL) {
117 return;
118 }
119 data = file_getdata(faf, assoc);
120 (*cb)(data);
121 }
122
123 static void
124 file_free(struct fileassoc_file *faf)
125 {
126 struct fileassoc *assoc;
127
128 LIST_REMOVE(faf, faf_list);
129
130 LIST_FOREACH(assoc, &fileassoc_list, assoc_list) {
131 file_cleanup(faf, assoc);
132 }
133 vfs_composefh_free(faf->faf_handle);
134 specificdata_fini(fileassoc_domain, &faf->faf_data);
135 kmem_free(faf, sizeof(*faf));
136 }
137
138 static void
139 table_dtor(void *v)
140 {
141 struct fileassoc_table *tbl = v;
142 u_long i;
143
144 /* Remove all entries from the table and lists */
145 for (i = 0; i < tbl->tbl_nslots; i++) {
146 struct fileassoc_file *faf;
147
148 while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
149 file_free(faf);
150 }
151 }
152
153 /* Remove hash table and sysctl node */
154 hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
155 specificdata_fini(fileassoc_domain, &tbl->tbl_data);
156 kmem_free(tbl, sizeof(*tbl));
157 }
158
159 /*
160 * Initialize the fileassoc subsystem.
161 */
162 static int
163 fileassoc_init(void)
164 {
165 int error;
166
167 error = mount_specific_key_create(&fileassoc_mountspecific_key,
168 table_dtor);
169 if (error) {
170 return error;
171 }
172 fileassoc_domain = specificdata_domain_create();
173
174 mutex_init(&fileassoc_list_lock, MUTEX_DEFAULT, IPL_NONE);
175
176 return 0;
177 }
178
179 /*
180 * Register a new assoc.
181 */
182 int
183 fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
184 fileassoc_t *result)
185 {
186 int error;
187 specificdata_key_t key;
188 struct fileassoc *assoc;
189
190 error = RUN_ONCE(&control, fileassoc_init);
191 if (error) {
192 return error;
193 }
194 error = specificdata_key_create(fileassoc_domain, &key, NULL);
195 if (error) {
196 return error;
197 }
198 assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
199 assoc->assoc_name = name;
200 assoc->assoc_cleanup_cb = cleanup_cb;
201 assoc->assoc_key = key;
202
203 mutex_enter(&fileassoc_list_lock);
204 LIST_INSERT_HEAD(&fileassoc_list, assoc, assoc_list);
205 mutex_exit(&fileassoc_list_lock);
206
207 *result = assoc;
208
209 return 0;
210 }
211
212 /*
213 * Deregister an assoc.
214 */
215 int
216 fileassoc_deregister(fileassoc_t assoc)
217 {
218
219 LIST_REMOVE(assoc, assoc_list);
220 specificdata_key_delete(fileassoc_domain, assoc->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_file *
247 fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
248 {
249 struct fileassoc_table *tbl;
250 struct fileassoc_hash_entry *hash_entry;
251 struct fileassoc_file *faf;
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 hash_entry = &(tbl->tbl_hash[indx]);
271
272 LIST_FOREACH(faf, hash_entry, faf_list) {
273 if (((FHANDLE_FILEID(faf->faf_handle)->fid_len ==
274 FHANDLE_FILEID(th)->fid_len)) &&
275 (memcmp(FHANDLE_FILEID(faf->faf_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 faf;
285 }
286
287 /*
288 * Return assoc data associated with a vnode.
289 */
290 void *
291 fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
292 {
293 struct fileassoc_file *faf;
294
295 faf = fileassoc_file_lookup(vp, NULL);
296 if (faf == NULL)
297 return (NULL);
298
299 return file_getdata(faf, assoc);
300 }
301
302 static struct fileassoc_table *
303 fileassoc_table_resize(struct fileassoc_table *tbl)
304 {
305 struct fileassoc_table *newtbl;
306 u_long i;
307
308 /*
309 * Allocate a new table. Like the condition in fileassoc_file_add(),
310 * this is also temporary -- just double the number of slots.
311 */
312 newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
313 newtbl->tbl_nslots = (tbl->tbl_nslots * 2);
314 if (newtbl->tbl_nslots < tbl->tbl_nslots)
315 newtbl->tbl_nslots = tbl->tbl_nslots;
316 newtbl->tbl_hash = hashinit(newtbl->tbl_nslots, HASH_LIST,
317 true, &newtbl->tbl_mask);
318 newtbl->tbl_nused = 0;
319 specificdata_init(fileassoc_domain, &newtbl->tbl_data);
320
321 /* XXX we need to make sure nothing uses fileassoc here! */
322
323 for (i = 0; i < tbl->tbl_nslots; i++) {
324 struct fileassoc_file *faf;
325
326 while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
327 struct fileassoc_hash_entry *hash_entry;
328 size_t indx;
329
330 LIST_REMOVE(faf, faf_list);
331
332 indx = FILEASSOC_HASH(newtbl, faf->faf_handle);
333 hash_entry = &(newtbl->tbl_hash[indx]);
334
335 LIST_INSERT_HEAD(hash_entry, faf, faf_list);
336
337 newtbl->tbl_nused++;
338 }
339 }
340
341 if (tbl->tbl_nused != newtbl->tbl_nused)
342 panic("fileassoc_table_resize: inconsistency detected! "
343 "needed %zu entries, got %zu", tbl->tbl_nused,
344 newtbl->tbl_nused);
345
346 hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
347 specificdata_fini(fileassoc_domain, &tbl->tbl_data);
348 kmem_free(tbl, sizeof(*tbl));
349
350 return (newtbl);
351 }
352
353 /*
354 * Create a new fileassoc table.
355 */
356 static struct fileassoc_table *
357 fileassoc_table_add(struct mount *mp)
358 {
359 struct fileassoc_table *tbl;
360
361 /* Check for existing table for device. */
362 tbl = fileassoc_table_lookup(mp);
363 if (tbl != NULL)
364 return (tbl);
365
366 /* Allocate and initialize a table. */
367 tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
368 tbl->tbl_nslots = FILEASSOC_INITIAL_TABLESIZE;
369 tbl->tbl_hash = hashinit(tbl->tbl_nslots, HASH_LIST, true,
370 &tbl->tbl_mask);
371 tbl->tbl_nused = 0;
372 specificdata_init(fileassoc_domain, &tbl->tbl_data);
373
374 mount_setspecific(mp, fileassoc_mountspecific_key, tbl);
375
376 return (tbl);
377 }
378
379 /*
380 * Delete a table.
381 */
382 int
383 fileassoc_table_delete(struct mount *mp)
384 {
385 struct fileassoc_table *tbl;
386
387 tbl = fileassoc_table_lookup(mp);
388 if (tbl == NULL)
389 return (EEXIST);
390
391 mount_setspecific(mp, fileassoc_mountspecific_key, NULL);
392 table_dtor(tbl);
393
394 return (0);
395 }
396
397 /*
398 * Run a callback for each assoc in a table.
399 */
400 int
401 fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
402 void *cookie)
403 {
404 struct fileassoc_table *tbl;
405 u_long i;
406
407 tbl = fileassoc_table_lookup(mp);
408 if (tbl == NULL)
409 return (EEXIST);
410
411 for (i = 0; i < tbl->tbl_nslots; i++) {
412 struct fileassoc_file *faf;
413
414 LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
415 void *data;
416
417 data = file_getdata(faf, assoc);
418 if (data != NULL)
419 cb(data, cookie);
420 }
421 }
422
423 return (0);
424 }
425
426 /*
427 * Clear a table for a given assoc.
428 */
429 int
430 fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
431 {
432 struct fileassoc_table *tbl;
433 u_long i;
434
435 tbl = fileassoc_table_lookup(mp);
436 if (tbl == NULL)
437 return (EEXIST);
438
439 for (i = 0; i < tbl->tbl_nslots; i++) {
440 struct fileassoc_file *faf;
441
442 LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
443 file_cleanup(faf, assoc);
444 file_setdata(faf, assoc, NULL);
445 }
446 }
447
448 return (0);
449 }
450
451 /*
452 * Add a file entry to a table.
453 */
454 static struct fileassoc_file *
455 fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
456 {
457 struct fileassoc_table *tbl;
458 struct fileassoc_hash_entry *hash_entry;
459 struct fileassoc_file *faf;
460 size_t indx;
461 fhandle_t *th;
462 int error;
463
464 if (hint == NULL) {
465 error = vfs_composefh_alloc(vp, &th);
466 if (error)
467 return (NULL);
468 } else
469 th = hint;
470
471 faf = fileassoc_file_lookup(vp, th);
472 if (faf != NULL) {
473 if (hint == NULL)
474 vfs_composefh_free(th);
475
476 return (faf);
477 }
478
479 tbl = fileassoc_table_lookup(vp->v_mount);
480 if (tbl == NULL) {
481 tbl = fileassoc_table_add(vp->v_mount);
482 }
483
484 indx = FILEASSOC_HASH(tbl, th);
485 hash_entry = &(tbl->tbl_hash[indx]);
486
487 faf = kmem_zalloc(sizeof(*faf), KM_SLEEP);
488 faf->faf_handle = th;
489 specificdata_init(fileassoc_domain, &faf->faf_data);
490 LIST_INSERT_HEAD(hash_entry, faf, faf_list);
491
492 /*
493 * This decides when we need to resize the table. For now,
494 * resize it whenever we "filled" up the number of slots it
495 * has. That's not really true unless of course we had zero
496 * collisions. Think positive! :)
497 */
498 if (++(tbl->tbl_nused) == tbl->tbl_nslots) {
499 struct fileassoc_table *newtbl;
500
501 newtbl = fileassoc_table_resize(tbl);
502 mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
503 newtbl);
504 }
505
506 return (faf);
507 }
508
509 /*
510 * Delete a file entry from a table.
511 */
512 int
513 fileassoc_file_delete(struct vnode *vp)
514 {
515 struct fileassoc_table *tbl;
516 struct fileassoc_file *faf;
517
518 KERNEL_LOCK(1, NULL);
519
520 faf = fileassoc_file_lookup(vp, NULL);
521 if (faf == NULL) {
522 KERNEL_UNLOCK_ONE(NULL);
523 return (ENOENT);
524 }
525
526 file_free(faf);
527
528 tbl = fileassoc_table_lookup(vp->v_mount);
529 --(tbl->tbl_nused); /* XXX gc? */
530
531 KERNEL_UNLOCK_ONE(NULL);
532
533 return (0);
534 }
535
536 /*
537 * Add an assoc to a vnode.
538 */
539 int
540 fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
541 {
542 struct fileassoc_file *faf;
543 void *olddata;
544
545 faf = fileassoc_file_lookup(vp, NULL);
546 if (faf == NULL) {
547 faf = fileassoc_file_add(vp, NULL);
548 if (faf == NULL)
549 return (ENOTDIR);
550 }
551
552 olddata = file_getdata(faf, assoc);
553 if (olddata != NULL)
554 return (EEXIST);
555
556 file_setdata(faf, assoc, data);
557
558 faf->faf_nassocs++;
559
560 return (0);
561 }
562
563 /*
564 * Clear an assoc from a vnode.
565 */
566 int
567 fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
568 {
569 struct fileassoc_file *faf;
570
571 faf = fileassoc_file_lookup(vp, NULL);
572 if (faf == NULL)
573 return (ENOENT);
574
575 file_cleanup(faf, assoc);
576 file_setdata(faf, assoc, NULL);
577
578 --(faf->faf_nassocs); /* XXX gc? */
579
580 return (0);
581 }
582