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