kern_fileassoc.c revision 1.30
1/* $NetBSD: kern_fileassoc.c,v 1.30 2008/01/02 11:48:49 ad 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.30 2008/01/02 11:48:49 ad 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
54static struct fileassoc_hash_entry *
55fileassoc_file_lookup(struct vnode *, fhandle_t *);
56static struct fileassoc_hash_entry *
57fileassoc_file_add(struct vnode *, fhandle_t *);
58static struct fileassoc_table *fileassoc_table_resize(struct fileassoc_table *);
59
60static specificdata_domain_t fileassoc_domain;
61static specificdata_key_t fileassoc_mountspecific_key;
62static ONCE_DECL(control);
63
64/*
65 * Hook entry.
66 * Includes the hook name for identification and private hook clear callback.
67 */
68struct 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
75static LIST_HEAD(, fileassoc) fileassoc_list;
76
77/* An entry in the per-mount hash table. */
78struct 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
85LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry);
86
87struct 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
103static void *
104file_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
111static void
112file_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
120static void
121file_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
134static void
135file_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
149static void
150table_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 */
175static int
176fileassoc_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 */
193int
194fileassoc_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 */
222int
223fileassoc_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 */
236static struct fileassoc_table *
237fileassoc_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 */
253static struct fileassoc_hash_entry *
254fileassoc_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 */
297void *
298fileassoc_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
309static struct fileassoc_table *
310fileassoc_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 */
365static struct fileassoc_table *
366fileassoc_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 */
391int
392fileassoc_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 */
409int
410fileassoc_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 */
440int
441fileassoc_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 */
467static struct fileassoc_hash_entry *
468fileassoc_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 */
525int
526fileassoc_file_delete(struct vnode *vp)
527{
528	struct fileassoc_table *tbl;
529	struct fileassoc_hash_entry *mhe;
530
531	KERNEL_LOCK(1, NULL);
532
533	mhe = fileassoc_file_lookup(vp, NULL);
534	if (mhe == NULL) {
535		KERNEL_UNLOCK_ONE(NULL);
536		return (ENOENT);
537	}
538
539	file_free(mhe);
540
541	tbl = fileassoc_table_lookup(vp->v_mount);
542	--(tbl->hash_used); /* XXX gc? */
543
544	KERNEL_UNLOCK_ONE(NULL);
545
546	return (0);
547}
548
549/*
550 * Add a hook to a vnode.
551 */
552int
553fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
554{
555	struct fileassoc_hash_entry *e;
556	void *olddata;
557
558	e = fileassoc_file_lookup(vp, NULL);
559	if (e == NULL) {
560		e = fileassoc_file_add(vp, NULL);
561		if (e == NULL)
562			return (ENOTDIR);
563	}
564
565	olddata = file_getdata(e, assoc);
566	if (olddata != NULL)
567		return (EEXIST);
568
569	file_setdata(e, assoc, data);
570
571	e->nassocs++;
572
573	return (0);
574}
575
576/*
577 * Clear a hook from a vnode.
578 */
579int
580fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
581{
582	struct fileassoc_hash_entry *mhe;
583
584	mhe = fileassoc_file_lookup(vp, NULL);
585	if (mhe == NULL)
586		return (ENOENT);
587
588	file_cleanup(mhe, assoc);
589	file_setdata(mhe, assoc, NULL);
590
591	--(mhe->nassocs); /* XXX gc? */
592
593	return (0);
594}
595