kern_fileassoc.c revision 1.31
1/* $NetBSD: kern_fileassoc.c,v 1.31 2008/05/05 17:11:17 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.31 2008/05/05 17:11:17 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/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 struct fileassoc_hash_entry *
54fileassoc_file_lookup(struct vnode *, fhandle_t *);
55static struct fileassoc_hash_entry *
56fileassoc_file_add(struct vnode *, fhandle_t *);
57static struct fileassoc_table *fileassoc_table_resize(struct fileassoc_table *);
58
59static specificdata_domain_t fileassoc_domain;
60static specificdata_key_t fileassoc_mountspecific_key;
61static ONCE_DECL(control);
62
63/*
64 * Hook entry.
65 * Includes the hook name for identification and private hook clear callback.
66 */
67struct fileassoc {
68	LIST_ENTRY(fileassoc) list;
69	const char *name;			/* name. */
70	fileassoc_cleanup_cb_t cleanup_cb;	/* clear callback. */
71	specificdata_key_t key;
72};
73
74static LIST_HEAD(, fileassoc) fileassoc_list;
75
76/* An entry in the per-mount hash table. */
77struct fileassoc_hash_entry {
78	fhandle_t *handle;				/* File handle */
79	specificdata_reference data;			/* Hooks. */
80	u_int nassocs;					/* # of hooks. */
81	LIST_ENTRY(fileassoc_hash_entry) entries;	/* List pointer. */
82};
83
84LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry);
85
86struct fileassoc_table {
87	struct fileassoc_hashhead *hash_tbl;
88	size_t hash_size;				/* Number of slots. */
89	u_long hash_mask;
90	size_t hash_used;				/* # of used slots. */
91	specificdata_reference data;
92};
93
94/*
95 * Hashing function: Takes a number modulus the mask to give back an
96 * index into the hash table.
97 */
98#define FILEASSOC_HASH(tbl, handle)	\
99	(hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \
100	 & ((tbl)->hash_mask))
101
102static void *
103file_getdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc)
104{
105
106	return specificdata_getspecific(fileassoc_domain, &e->data,
107	    assoc->key);
108}
109
110static void
111file_setdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc,
112    void *data)
113{
114
115	specificdata_setspecific(fileassoc_domain, &e->data, assoc->key,
116	    data);
117}
118
119static void
120file_cleanup(struct fileassoc_hash_entry *e, const struct fileassoc *assoc)
121{
122	fileassoc_cleanup_cb_t cb;
123	void *data;
124
125	cb = assoc->cleanup_cb;
126	if (cb == NULL) {
127		return;
128	}
129	data = file_getdata(e, assoc);
130	(*cb)(data);
131}
132
133static void
134file_free(struct fileassoc_hash_entry *e)
135{
136	struct fileassoc *assoc;
137
138	LIST_REMOVE(e, entries);
139
140	LIST_FOREACH(assoc, &fileassoc_list, list) {
141		file_cleanup(e, assoc);
142	}
143	vfs_composefh_free(e->handle);
144	specificdata_fini(fileassoc_domain, &e->data);
145	kmem_free(e, sizeof(*e));
146}
147
148static void
149table_dtor(void *vp)
150{
151	struct fileassoc_table *tbl = vp;
152	struct fileassoc_hashhead *hh;
153	u_long i;
154
155	/* Remove all entries from the table and lists */
156	hh = tbl->hash_tbl;
157	for (i = 0; i < tbl->hash_size; i++) {
158		struct fileassoc_hash_entry *mhe;
159
160		while ((mhe = LIST_FIRST(&hh[i])) != NULL) {
161			file_free(mhe);
162		}
163	}
164
165	/* Remove hash table and sysctl node */
166	hashdone(tbl->hash_tbl, HASH_LIST, tbl->hash_mask);
167	specificdata_fini(fileassoc_domain, &tbl->data);
168	kmem_free(tbl, sizeof(*tbl));
169}
170
171/*
172 * Initialize the fileassoc subsystem.
173 */
174static int
175fileassoc_init(void)
176{
177	int error;
178
179	error = mount_specific_key_create(&fileassoc_mountspecific_key,
180	    table_dtor);
181	if (error) {
182		return error;
183	}
184	fileassoc_domain = specificdata_domain_create();
185
186	return 0;
187}
188
189/*
190 * Register a new hook.
191 */
192int
193fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
194    fileassoc_t *result)
195{
196	int error;
197	specificdata_key_t key;
198	struct fileassoc *assoc;
199
200	error = RUN_ONCE(&control, fileassoc_init);
201	if (error) {
202		return error;
203	}
204	error = specificdata_key_create(fileassoc_domain, &key, NULL);
205	if (error) {
206		return error;
207	}
208	assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
209	assoc->name = name;
210	assoc->cleanup_cb = cleanup_cb;
211	assoc->key = key;
212	LIST_INSERT_HEAD(&fileassoc_list, assoc, list);
213	*result = assoc;
214
215	return 0;
216}
217
218/*
219 * Deregister a hook.
220 */
221int
222fileassoc_deregister(fileassoc_t assoc)
223{
224
225	LIST_REMOVE(assoc, list);
226	specificdata_key_delete(fileassoc_domain, assoc->key);
227	kmem_free(assoc, sizeof(*assoc));
228
229	return 0;
230}
231
232/*
233 * Get the hash table for the specified device.
234 */
235static struct fileassoc_table *
236fileassoc_table_lookup(struct mount *mp)
237{
238	int error;
239
240	error = RUN_ONCE(&control, fileassoc_init);
241	if (error) {
242		return NULL;
243	}
244	return mount_getspecific(mp, fileassoc_mountspecific_key);
245}
246
247/*
248 * Perform a lookup on a hash table.  If hint is non-zero then use the value
249 * of the hint as the identifier instead of performing a lookup for the
250 * fileid.
251 */
252static struct fileassoc_hash_entry *
253fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
254{
255	struct fileassoc_table *tbl;
256	struct fileassoc_hashhead *tble;
257	struct fileassoc_hash_entry *e;
258	size_t indx;
259	fhandle_t *th;
260	int error;
261
262	tbl = fileassoc_table_lookup(vp->v_mount);
263	if (tbl == NULL) {
264		return NULL;
265	}
266
267	if (hint == NULL) {
268		error = vfs_composefh_alloc(vp, &th);
269		if (error)
270			return (NULL);
271	} else {
272		th = hint;
273	}
274
275	indx = FILEASSOC_HASH(tbl, th);
276	tble = &(tbl->hash_tbl[indx]);
277
278	LIST_FOREACH(e, tble, entries) {
279		if (((FHANDLE_FILEID(e->handle)->fid_len ==
280		     FHANDLE_FILEID(th)->fid_len)) &&
281		    (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th),
282			   (FHANDLE_FILEID(th))->fid_len) == 0)) {
283			break;
284		}
285	}
286
287	if (hint == NULL)
288		vfs_composefh_free(th);
289
290	return e;
291}
292
293/*
294 * Return hook data associated with a vnode.
295 */
296void *
297fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
298{
299        struct fileassoc_hash_entry *mhe;
300
301        mhe = fileassoc_file_lookup(vp, NULL);
302        if (mhe == NULL)
303                return (NULL);
304
305        return file_getdata(mhe, assoc);
306}
307
308static struct fileassoc_table *
309fileassoc_table_resize(struct fileassoc_table *tbl)
310{
311	struct fileassoc_table *newtbl;
312	struct fileassoc_hashhead *hh;
313	u_long i;
314
315	/*
316	 * Allocate a new table. Like the condition in fileassoc_file_add(),
317	 * this is also temporary -- just double the number of slots.
318	 */
319	newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
320	newtbl->hash_size = (tbl->hash_size * 2);
321	if (newtbl->hash_size < tbl->hash_size)
322		newtbl->hash_size = tbl->hash_size;
323	newtbl->hash_tbl = hashinit(newtbl->hash_size, HASH_LIST,
324	    true, &newtbl->hash_mask);
325	newtbl->hash_used = 0;
326	specificdata_init(fileassoc_domain, &newtbl->data);
327
328	/* XXX we need to make sure nothing uses fileassoc here! */
329
330	hh = tbl->hash_tbl;
331	for (i = 0; i < tbl->hash_size; i++) {
332		struct fileassoc_hash_entry *mhe;
333
334		while ((mhe = LIST_FIRST(&hh[i])) != NULL) {
335			struct fileassoc_hashhead *vhh;
336			size_t indx;
337
338			LIST_REMOVE(mhe, entries);
339
340			indx = FILEASSOC_HASH(newtbl, mhe->handle);
341			vhh = &(newtbl->hash_tbl[indx]);
342
343			LIST_INSERT_HEAD(vhh, mhe, entries);
344
345			newtbl->hash_used++;
346		}
347	}
348
349	if (tbl->hash_used != newtbl->hash_used)
350		panic("fileassoc_table_resize: inconsistency detected! "
351		    "needed %zu entries, got %zu", tbl->hash_used,
352		    newtbl->hash_used);
353
354	hashdone(tbl->hash_tbl, HASH_LIST, tbl->hash_mask);
355	specificdata_fini(fileassoc_domain, &tbl->data);
356	kmem_free(tbl, sizeof(*tbl));
357
358	return (newtbl);
359}
360
361/*
362 * Create a new fileassoc table.
363 */
364static struct fileassoc_table *
365fileassoc_table_add(struct mount *mp)
366{
367	struct fileassoc_table *tbl;
368
369	/* Check for existing table for device. */
370	tbl = fileassoc_table_lookup(mp);
371	if (tbl != NULL)
372		return (tbl);
373
374	/* Allocate and initialize a table. */
375	tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
376	tbl->hash_size = FILEASSOC_INITIAL_TABLESIZE;
377	tbl->hash_tbl = hashinit(tbl->hash_size, HASH_LIST, true,
378	    &tbl->hash_mask);
379	tbl->hash_used = 0;
380	specificdata_init(fileassoc_domain, &tbl->data);
381
382	mount_setspecific(mp, fileassoc_mountspecific_key, tbl);
383
384	return (tbl);
385}
386
387/*
388 * Delete a table.
389 */
390int
391fileassoc_table_delete(struct mount *mp)
392{
393	struct fileassoc_table *tbl;
394
395	tbl = fileassoc_table_lookup(mp);
396	if (tbl == NULL)
397		return (EEXIST);
398
399	mount_setspecific(mp, fileassoc_mountspecific_key, NULL);
400	table_dtor(tbl);
401
402	return (0);
403}
404
405/*
406 * Run a callback for each hook entry in a table.
407 */
408int
409fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
410    void *cookie)
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, cookie);
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	KERNEL_LOCK(1, NULL);
531
532	mhe = fileassoc_file_lookup(vp, NULL);
533	if (mhe == NULL) {
534		KERNEL_UNLOCK_ONE(NULL);
535		return (ENOENT);
536	}
537
538	file_free(mhe);
539
540	tbl = fileassoc_table_lookup(vp->v_mount);
541	--(tbl->hash_used); /* XXX gc? */
542
543	KERNEL_UNLOCK_ONE(NULL);
544
545	return (0);
546}
547
548/*
549 * Add a hook to a vnode.
550 */
551int
552fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
553{
554	struct fileassoc_hash_entry *e;
555	void *olddata;
556
557	e = fileassoc_file_lookup(vp, NULL);
558	if (e == NULL) {
559		e = fileassoc_file_add(vp, NULL);
560		if (e == NULL)
561			return (ENOTDIR);
562	}
563
564	olddata = file_getdata(e, assoc);
565	if (olddata != NULL)
566		return (EEXIST);
567
568	file_setdata(e, assoc, data);
569
570	e->nassocs++;
571
572	return (0);
573}
574
575/*
576 * Clear a hook from a vnode.
577 */
578int
579fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
580{
581	struct fileassoc_hash_entry *mhe;
582
583	mhe = fileassoc_file_lookup(vp, NULL);
584	if (mhe == NULL)
585		return (ENOENT);
586
587	file_cleanup(mhe, assoc);
588	file_setdata(mhe, assoc, NULL);
589
590	--(mhe->nassocs); /* XXX gc? */
591
592	return (0);
593}
594