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@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
53static specificdata_domain_t fileassoc_domain;
54static specificdata_key_t fileassoc_mountspecific_key;
55static ONCE_DECL(control);
56
57/*
58 * Hook entry.
59 * Includes the hook name for identification and private hook clear callback.
60 */
61struct 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
68static LIST_HEAD(, fileassoc) fileassoc_list;
69
70/* An entry in the per-mount hash table. */
71struct 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
78LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry);
79
80struct 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
96static void *
97file_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
104static void
105file_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
113static void
114file_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
127static void
128file_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
142static void
143table_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 */
168static int
169fileassoc_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 */
186int
187fileassoc_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 */
215int
216fileassoc_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 */
229static struct fileassoc_table *
230fileassoc_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 */
246static struct fileassoc_hash_entry *
247fileassoc_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 */
290void *
291fileassoc_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
302static struct fileassoc_table *
303fileassoc_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 */
358static struct fileassoc_table *
359fileassoc_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 */
384int
385fileassoc_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 */
402int
403fileassoc_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 */
433int
434fileassoc_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 */
460static struct fileassoc_hash_entry *
461fileassoc_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 */
518int
519fileassoc_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 */
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