kern_fileassoc.c revision 1.25
1/* $NetBSD: kern_fileassoc.c,v 1.25 2007/03/09 05:51:50 yamt 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.25 2007/03/09 05:51:50 yamt 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{
412	struct fileassoc_table *tbl;
413	struct fileassoc_hashhead *hh;
414	u_long i;
415
416	tbl = fileassoc_table_lookup(mp);
417	if (tbl == NULL)
418		return (EEXIST);
419
420	hh = tbl->hash_tbl;
421	for (i = 0; i < tbl->hash_size; i++) {
422		struct fileassoc_hash_entry *mhe;
423
424		LIST_FOREACH(mhe, &hh[i], entries) {
425			void *data;
426
427			data = file_getdata(mhe, assoc);
428			if (data != NULL)
429				cb(data);
430		}
431	}
432
433	return (0);
434}
435
436/*
437 * Clear a table for a given hook.
438 */
439int
440fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
441{
442	struct fileassoc_table *tbl;
443	struct fileassoc_hashhead *hh;
444	u_long i;
445
446	tbl = fileassoc_table_lookup(mp);
447	if (tbl == NULL)
448		return (EEXIST);
449
450	hh = tbl->hash_tbl;
451	for (i = 0; i < tbl->hash_size; i++) {
452		struct fileassoc_hash_entry *mhe;
453
454		LIST_FOREACH(mhe, &hh[i], entries) {
455			file_cleanup(mhe, assoc);
456			file_setdata(mhe, assoc, NULL);
457		}
458	}
459
460	return (0);
461}
462
463/*
464 * Add a file entry to a table.
465 */
466static struct fileassoc_hash_entry *
467fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
468{
469	struct fileassoc_table *tbl;
470	struct fileassoc_hashhead *vhh;
471	struct fileassoc_hash_entry *e;
472	size_t indx;
473	fhandle_t *th;
474	int error;
475
476	if (hint == NULL) {
477		error = vfs_composefh_alloc(vp, &th);
478		if (error)
479			return (NULL);
480	} else
481		th = hint;
482
483	e = fileassoc_file_lookup(vp, th);
484	if (e != NULL) {
485		if (hint == NULL)
486			vfs_composefh_free(th);
487
488		return (e);
489	}
490
491	tbl = fileassoc_table_lookup(vp->v_mount);
492	if (tbl == NULL) {
493		tbl = fileassoc_table_add(vp->v_mount);
494	}
495
496	indx = FILEASSOC_HASH(tbl, th);
497	vhh = &(tbl->hash_tbl[indx]);
498
499	e = kmem_zalloc(sizeof(*e), KM_SLEEP);
500	e->handle = th;
501	specificdata_init(fileassoc_domain, &e->data);
502	LIST_INSERT_HEAD(vhh, e, entries);
503
504	/*
505	 * This decides when we need to resize the table. For now,
506	 * resize it whenever we "filled" up the number of slots it
507	 * has. That's not really true unless of course we had zero
508	 * collisions. Think positive! :)
509	 */
510	if (++(tbl->hash_used) == tbl->hash_size) {
511		struct fileassoc_table *newtbl;
512
513		newtbl = fileassoc_table_resize(tbl);
514		mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
515		    newtbl);
516	}
517
518	return (e);
519}
520
521/*
522 * Delete a file entry from a table.
523 */
524int
525fileassoc_file_delete(struct vnode *vp)
526{
527	struct fileassoc_table *tbl;
528	struct fileassoc_hash_entry *mhe;
529
530	mhe = fileassoc_file_lookup(vp, NULL);
531	if (mhe == NULL)
532		return (ENOENT);
533
534	file_free(mhe);
535
536	tbl = fileassoc_table_lookup(vp->v_mount);
537	--(tbl->hash_used); /* XXX gc? */
538
539	return (0);
540}
541
542/*
543 * Add a hook to a vnode.
544 */
545int
546fileassoc_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 */
572int
573fileassoc_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