kern_fileassoc.c revision 1.11
1/* $NetBSD: kern_fileassoc.c,v 1.11 2006/10/27 22:17:09 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. All advertising materials mentioning features or use of this software
16 *    must display the following acknowledgement:
17 *      This product includes software developed by Elad Efrat.
18 * 4. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include <sys/cdefs.h>
34__KERNEL_RCSID(0, "$NetBSD: kern_fileassoc.c,v 1.11 2006/10/27 22:17:09 elad Exp $");
35
36#include <sys/param.h>
37#include <sys/mount.h>
38#include <sys/queue.h>
39#include <sys/malloc.h>
40#include <sys/vnode.h>
41#include <sys/namei.h>
42#include <sys/exec.h>
43#include <sys/proc.h>
44#include <sys/inttypes.h>
45#include <sys/errno.h>
46#include <sys/fileassoc.h>
47#include <sys/hash.h>
48#include <sys/fstypes.h>
49
50static struct fileassoc_hash_entry *
51fileassoc_file_lookup(struct vnode *, fhandle_t *);
52static struct fileassoc_hash_entry *
53fileassoc_file_add(struct vnode *, fhandle_t *);
54
55/*
56 * Hook entry.
57 * Includes the hook name for identification and private hook clear callback.
58 */
59struct fileassoc_hook {
60	const char *hook_name;			/* Hook name. */
61	fileassoc_cleanup_cb_t hook_cleanup_cb;	/* Hook clear callback. */
62};
63
64/* An entry in the per-device hash table. */
65struct fileassoc_hash_entry {
66	fhandle_t *handle;				/* File handle */
67	void *hooks[FILEASSOC_NHOOKS];			/* Hooks. */
68	LIST_ENTRY(fileassoc_hash_entry) entries;	/* List pointer. */
69};
70
71LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry);
72
73struct fileassoc_table {
74	struct fileassoc_hashhead *hash_tbl;
75	size_t hash_size;				/* Number of slots. */
76	struct mount *tbl_mntpt;
77	u_long hash_mask;
78	void *tables[FILEASSOC_NHOOKS];
79	LIST_ENTRY(fileassoc_table) hash_list;		/* List pointer. */
80};
81
82struct fileassoc_hook fileassoc_hooks[FILEASSOC_NHOOKS];
83int fileassoc_nhooks;
84
85/* Global list of hash tables, one per device. */
86LIST_HEAD(, fileassoc_table) fileassoc_tables;
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
96/*
97 * Initialize the fileassoc subsystem.
98 */
99void
100fileassoc_init(void)
101{
102	memset(fileassoc_hooks, 0, sizeof(fileassoc_hooks));
103	fileassoc_nhooks = 0;
104}
105
106/*
107 * Register a new hook.
108 */
109fileassoc_t
110fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb)
111{
112	int i;
113
114	if (fileassoc_nhooks >= FILEASSOC_NHOOKS)
115		return (-1);
116
117	for (i = 0; i < FILEASSOC_NHOOKS; i++)
118		if (fileassoc_hooks[i].hook_name == NULL)
119			break;
120
121	fileassoc_hooks[i].hook_name = name;
122	fileassoc_hooks[i].hook_cleanup_cb = cleanup_cb;
123
124	fileassoc_nhooks++;
125
126	return (i);
127}
128
129/*
130 * Deregister a hook.
131 */
132int
133fileassoc_deregister(fileassoc_t id)
134{
135	if (id < 0 || id >= FILEASSOC_NHOOKS)
136		return (EINVAL);
137
138	fileassoc_hooks[id].hook_name = NULL;
139	fileassoc_hooks[id].hook_cleanup_cb = NULL;
140
141	fileassoc_nhooks--;
142
143	return (0);
144}
145
146/*
147 * Get the hash table for the specified device.
148 */
149static struct fileassoc_table *
150fileassoc_table_lookup(struct mount *mp)
151{
152	struct fileassoc_table *tbl;
153
154	LIST_FOREACH(tbl, &fileassoc_tables, hash_list) {
155		if (tbl->tbl_mntpt == mp)
156			return (tbl);
157	}
158
159	return (NULL);
160}
161
162/*
163 * Perform a lookup on a hash table.  If hint is non-zero then use the value
164 * of the hint as the identifier instead of performing a lookup for the
165 * fileid.
166 */
167static struct fileassoc_hash_entry *
168fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
169{
170	struct fileassoc_table *tbl;
171	struct fileassoc_hashhead *tble;
172	struct fileassoc_hash_entry *e;
173	size_t indx;
174	fhandle_t *th;
175	int error;
176
177	if (hint == NULL) {
178		error = vfs_composefh_alloc(vp, &th);
179		if (error)
180			return (NULL);
181	} else
182		th = hint;
183
184	tbl = fileassoc_table_lookup(vp->v_mount);
185	if (tbl == NULL) {
186		if (hint == NULL)
187			vfs_composefh_free(th);
188
189		return (NULL);
190	}
191
192	indx = FILEASSOC_HASH(tbl, th);
193	tble = &(tbl->hash_tbl[indx]);
194
195	LIST_FOREACH(e, tble, entries) {
196		if ((e != NULL) &&
197		    ((FHANDLE_FILEID(e->handle)->fid_len ==
198		     FHANDLE_FILEID(th)->fid_len)) &&
199		    (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th),
200			   (FHANDLE_FILEID(th))->fid_len) == 0)) {
201			if (hint == NULL)
202				vfs_composefh_free(th);
203
204			return (e);
205		}
206	}
207
208	if (hint == NULL)
209		vfs_composefh_free(th);
210
211	return (NULL);
212}
213
214/*
215 * Return hook data associated with a vnode.
216 */
217void *
218fileassoc_lookup(struct vnode *vp, fileassoc_t id)
219{
220        struct fileassoc_hash_entry *mhe;
221
222        mhe = fileassoc_file_lookup(vp, NULL);
223        if (mhe == NULL)
224                return (NULL);
225
226        return (mhe->hooks[id]);
227}
228
229/*
230 * Create a new fileassoc table.
231 */
232int
233fileassoc_table_add(struct mount *mp, size_t size)
234{
235	struct fileassoc_table *tbl;
236
237	/* Check for existing table for device. */
238	if (fileassoc_table_lookup(mp) != NULL)
239		return (EEXIST);
240
241	/* Allocate and initialize a Veriexec hash table. */
242	tbl = malloc(sizeof(*tbl), M_TEMP, M_WAITOK | M_ZERO);
243	tbl->hash_size = size;
244	tbl->tbl_mntpt = mp;
245	tbl->hash_tbl = hashinit(size, HASH_LIST, M_TEMP,
246				 M_WAITOK | M_ZERO, &tbl->hash_mask);
247
248	LIST_INSERT_HEAD(&fileassoc_tables, tbl, hash_list);
249
250	return (0);
251}
252
253/*
254 * Delete a table.
255 */
256int
257fileassoc_table_delete(struct mount *mp)
258{
259	struct fileassoc_table *tbl;
260	struct fileassoc_hashhead *hh;
261	u_long i;
262	int j;
263
264	tbl = fileassoc_table_lookup(mp);
265	if (tbl == NULL)
266		return (EEXIST);
267
268	/* Remove all entries from the table and lists */
269	hh = tbl->hash_tbl;
270	for (i = 0; i < tbl->hash_size; i++) {
271		struct fileassoc_hash_entry *mhe;
272
273		while (LIST_FIRST(&hh[i]) != NULL) {
274			mhe = LIST_FIRST(&hh[i]);
275			LIST_REMOVE(mhe, entries);
276
277			for (j = 0; j < fileassoc_nhooks; j++)
278				if (fileassoc_hooks[j].hook_cleanup_cb != NULL)
279					(fileassoc_hooks[j].hook_cleanup_cb)
280					    (mhe->hooks[j],
281					    FILEASSOC_CLEANUP_FILE);
282
283			vfs_composefh_free(mhe->handle);
284			free(mhe, M_TEMP);
285		}
286	}
287
288	for (j = 0; j < fileassoc_nhooks; j++)
289		if (fileassoc_hooks[j].hook_cleanup_cb != NULL)
290			(fileassoc_hooks[j].hook_cleanup_cb)(tbl->tables[j],
291			    FILEASSOC_CLEANUP_TABLE);
292
293	/* Remove hash table and sysctl node */
294	hashdone(tbl->hash_tbl, M_TEMP);
295	LIST_REMOVE(tbl, hash_list);
296
297	return (0);
298}
299
300/*
301 * Run a callback for each hook entry in a table.
302 */
303int
304fileassoc_table_run(struct mount *mp, fileassoc_t id, fileassoc_cb_t cb)
305{
306	struct fileassoc_table *tbl;
307	struct fileassoc_hashhead *hh;
308	u_long i;
309
310	tbl = fileassoc_table_lookup(mp);
311	if (tbl == NULL)
312		return (EEXIST);
313
314	hh = tbl->hash_tbl;
315	for (i = 0; i < tbl->hash_size; i++) {
316		struct fileassoc_hash_entry *mhe;
317
318		LIST_FOREACH(mhe, &hh[i], entries) {
319			if (mhe->hooks[id] != NULL)
320				cb(mhe->hooks[id]);
321		}
322	}
323
324	return (0);
325}
326
327/*
328 * Clear a table for a given hook.
329 */
330int
331fileassoc_table_clear(struct mount *mp, fileassoc_t id)
332{
333	struct fileassoc_table *tbl;
334	struct fileassoc_hashhead *hh;
335	fileassoc_cleanup_cb_t cleanup_cb;
336	u_long i;
337
338	tbl = fileassoc_table_lookup(mp);
339	if (tbl == NULL)
340		return (EEXIST);
341
342	cleanup_cb = fileassoc_hooks[id].hook_cleanup_cb;
343
344	hh = tbl->hash_tbl;
345	for (i = 0; i < tbl->hash_size; i++) {
346		struct fileassoc_hash_entry *mhe;
347
348		LIST_FOREACH(mhe, &hh[i], entries) {
349			if ((mhe->hooks[id] != NULL) && cleanup_cb != NULL)
350				cleanup_cb(mhe->hooks[id],
351				    FILEASSOC_CLEANUP_FILE);
352
353			mhe->hooks[id] = NULL;
354		}
355	}
356
357	if ((tbl->tables[id] != NULL) && cleanup_cb != NULL)
358		cleanup_cb(tbl->tables[id], FILEASSOC_CLEANUP_TABLE);
359
360	tbl->tables[id] = NULL;
361
362	return (0);
363}
364
365/*
366 * Add hook-specific data on a fileassoc table.
367 */
368int
369fileassoc_tabledata_add(struct mount *mp, fileassoc_t id, void *data)
370{
371	struct fileassoc_table *tbl;
372
373	tbl = fileassoc_table_lookup(mp);
374	if (tbl == NULL)
375		return (EFAULT);
376
377	tbl->tables[id] = data;
378
379	return (0);
380}
381
382/*
383 * Clear hook-specific data on a fileassoc table.
384 */
385int
386fileassoc_tabledata_clear(struct mount *mp, fileassoc_t id)
387{
388	struct fileassoc_table *tbl;
389
390	tbl = fileassoc_table_lookup(mp);
391	if (tbl == NULL)
392		return (EFAULT);
393
394	tbl->tables[id] = NULL;
395
396	return (0);
397}
398
399/*
400 * Retrieve hook-specific data from a fileassoc table.
401 */
402void *
403fileassoc_tabledata_lookup(struct mount *mp, fileassoc_t id)
404{
405	struct fileassoc_table *tbl;
406
407	tbl = fileassoc_table_lookup(mp);
408	if (tbl == NULL)
409		return (NULL);
410
411	return (tbl->tables[id]);
412}
413
414/*
415 * Add a file entry to a table.
416 */
417static struct fileassoc_hash_entry *
418fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
419{
420	struct fileassoc_table *tbl;
421	struct fileassoc_hashhead *vhh;
422	struct fileassoc_hash_entry *e;
423	size_t indx;
424	fhandle_t *th;
425	int error;
426
427	if (hint == 0) {
428		error = vfs_composefh_alloc(vp, &th);
429		if (error)
430			return (NULL);
431	} else
432		th = hint;
433
434	e = fileassoc_file_lookup(vp, th);
435	if (e != NULL) {
436		if (hint == NULL)
437			vfs_composefh_free(th);
438
439		return (e);
440	}
441
442	tbl = fileassoc_table_lookup(vp->v_mount);
443	if (tbl == NULL) {
444		if (hint == NULL)
445			vfs_composefh_free(th);
446
447		return (NULL);
448	}
449
450	indx = FILEASSOC_HASH(tbl, th);
451	vhh = &(tbl->hash_tbl[indx]);
452
453	e = malloc(sizeof(*e), M_TEMP, M_WAITOK | M_ZERO);
454	e->handle = th;
455	LIST_INSERT_HEAD(vhh, e, entries);
456
457	return (e);
458}
459
460/*
461 * Delete a file entry from a table.
462 */
463int
464fileassoc_file_delete(struct vnode *vp)
465{
466	struct fileassoc_hash_entry *mhe;
467	int i;
468
469	mhe = fileassoc_file_lookup(vp, NULL);
470	if (mhe == NULL)
471		return (ENOENT);
472
473	LIST_REMOVE(mhe, entries);
474
475	for (i = 0; i < fileassoc_nhooks; i++)
476		if (fileassoc_hooks[i].hook_cleanup_cb != NULL)
477			(fileassoc_hooks[i].hook_cleanup_cb)(mhe->hooks[i],
478			    FILEASSOC_CLEANUP_FILE);
479
480	free(mhe, M_TEMP);
481
482	return (0);
483}
484
485/*
486 * Add a hook to a vnode.
487 */
488int
489fileassoc_add(struct vnode *vp, fileassoc_t id, void *data)
490{
491	struct fileassoc_hash_entry *e;
492
493	e = fileassoc_file_lookup(vp, NULL);
494	if (e == NULL) {
495		e = fileassoc_file_add(vp, NULL);
496		if (e == NULL)
497			return (ENOTDIR);
498	}
499
500	if (e->hooks[id] != NULL)
501		return (EEXIST);
502
503	e->hooks[id] = data;
504
505	return (0);
506}
507
508/*
509 * Clear a hook from a vnode.
510 */
511int
512fileassoc_clear(struct vnode *vp, fileassoc_t id)
513{
514	struct fileassoc_hash_entry *mhe;
515	fileassoc_cleanup_cb_t cleanup_cb;
516
517	mhe = fileassoc_file_lookup(vp, NULL);
518	if (mhe == NULL)
519		return (ENOENT);
520
521	cleanup_cb = fileassoc_hooks[id].hook_cleanup_cb;
522	if ((mhe->hooks[id] != NULL) && cleanup_cb != NULL)
523		cleanup_cb(mhe->hooks[id], FILEASSOC_CLEANUP_FILE);
524
525	mhe->hooks[id] = NULL;
526
527	return (0);
528}
529