xref: /netbsd-src/sys/kern/kern_fileassoc.c (revision 9db8c230ae5d91331db658b0aefefa68d293fd6c)
1 /* $NetBSD: kern_fileassoc.c,v 1.38 2023/12/28 12:49:06 hannken 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.38 2023/12/28 12:49:06 hannken 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/errno.h>
40 #include <sys/fileassoc.h>
41 #include <sys/specificdata.h>
42 #include <sys/hash.h>
43 #include <sys/kmem.h>
44 #include <sys/once.h>
45 #include <sys/mutex.h>
46 #include <sys/xcall.h>
47 
48 #define	FILEASSOC_INITIAL_TABLESIZE	128
49 
50 static specificdata_domain_t fileassoc_domain = NULL;
51 static specificdata_key_t fileassoc_mountspecific_key;
52 static ONCE_DECL(control);
53 
54 /*
55  * Assoc entry.
56  * Includes the assoc name for identification and private clear callback.
57  */
58 struct fileassoc {
59 	LIST_ENTRY(fileassoc) assoc_list;
60 	const char *assoc_name;				/* Name. */
61 	fileassoc_cleanup_cb_t assoc_cleanup_cb;	/* Clear callback. */
62 	specificdata_key_t assoc_key;
63 };
64 
65 static LIST_HEAD(, fileassoc) fileassoc_list;
66 
67 /* An entry in the per-mount hash table. */
68 struct fileassoc_file {
69 	fhandle_t *faf_handle;				/* File handle */
70 	specificdata_reference faf_data;		/* Assoc data. */
71 	u_int faf_nassocs;				/* # of assocs. */
72 	LIST_ENTRY(fileassoc_file) faf_list;		/* List pointer. */
73 };
74 
75 LIST_HEAD(fileassoc_hash_entry, fileassoc_file);
76 
77 struct fileassoc_table {
78 	struct fileassoc_hash_entry *tbl_hash;
79 	u_long tbl_mask;				/* Hash table mask. */
80 	size_t tbl_nslots;				/* Number of slots. */
81 	size_t tbl_nused;				/* # of used slots. */
82 	specificdata_reference tbl_data;
83 };
84 
85 /*
86  * Hashing function: Takes a number modulus the mask to give back an
87  * index into the hash table.
88  */
89 #define FILEASSOC_HASH(tbl, handle)	\
90 	(hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \
91 	 & ((tbl)->tbl_mask))
92 
93 /*
94  * Global usage counting.  This is bad for parallelism of updates, but
95  * good for avoiding calls to fileassoc when it's not in use.  Unclear
96  * if parallelism of updates matters much.  If you want to improve
97  * fileassoc(9) update performance, feel free to rip this out as long
98  * as you don't cause the fast paths to take any global locks or incur
99  * memory barriers when fileassoc(9) is not in use.
100  */
101 static struct {
102 	kmutex_t lock;
103 	uint64_t nassocs;
104 	volatile bool inuse;
105 } fileassoc_global __cacheline_aligned;
106 
107 static void
fileassoc_incuse(void)108 fileassoc_incuse(void)
109 {
110 
111 	mutex_enter(&fileassoc_global.lock);
112 	if (fileassoc_global.nassocs++ == 0) {
113 		KASSERT(!fileassoc_global.inuse);
114 		atomic_store_relaxed(&fileassoc_global.inuse, true);
115 		xc_barrier(0);
116 	}
117 	mutex_exit(&fileassoc_global.lock);
118 }
119 
120 static void
fileassoc_decuse(void)121 fileassoc_decuse(void)
122 {
123 
124 	mutex_enter(&fileassoc_global.lock);
125 	KASSERT(fileassoc_global.nassocs > 0);
126 	KASSERT(fileassoc_global.inuse);
127 	if (--fileassoc_global.nassocs == 0)
128 		atomic_store_relaxed(&fileassoc_global.inuse, false);
129 	mutex_exit(&fileassoc_global.lock);
130 }
131 
132 static bool
fileassoc_inuse(void)133 fileassoc_inuse(void)
134 {
135 
136 	return __predict_false(atomic_load_relaxed(&fileassoc_global.inuse));
137 }
138 
139 static void *
file_getdata(struct fileassoc_file * faf,const struct fileassoc * assoc)140 file_getdata(struct fileassoc_file *faf, const struct fileassoc *assoc)
141 {
142 
143 	return specificdata_getspecific(fileassoc_domain, &faf->faf_data,
144 	    assoc->assoc_key);
145 }
146 
147 static void
file_setdata(struct fileassoc_file * faf,const struct fileassoc * assoc,void * data)148 file_setdata(struct fileassoc_file *faf, const struct fileassoc *assoc,
149     void *data)
150 {
151 
152 	specificdata_setspecific(fileassoc_domain, &faf->faf_data,
153 	    assoc->assoc_key, data);
154 }
155 
156 static void
file_cleanup(struct fileassoc_file * faf,const struct fileassoc * assoc)157 file_cleanup(struct fileassoc_file *faf, const struct fileassoc *assoc)
158 {
159 	fileassoc_cleanup_cb_t cb;
160 	void *data;
161 
162 	cb = assoc->assoc_cleanup_cb;
163 	if (cb == NULL) {
164 		return;
165 	}
166 	data = file_getdata(faf, assoc);
167 	(*cb)(data);
168 }
169 
170 static void
file_free(struct fileassoc_file * faf)171 file_free(struct fileassoc_file *faf)
172 {
173 	struct fileassoc *assoc;
174 
175 	LIST_REMOVE(faf, faf_list);
176 
177 	LIST_FOREACH(assoc, &fileassoc_list, assoc_list) {
178 		file_cleanup(faf, assoc);
179 		fileassoc_decuse();
180 	}
181 	vfs_composefh_free(faf->faf_handle);
182 	specificdata_fini(fileassoc_domain, &faf->faf_data);
183 	kmem_free(faf, sizeof(*faf));
184 }
185 
186 static void
table_dtor(void * v)187 table_dtor(void *v)
188 {
189 	struct fileassoc_table *tbl = v;
190 	u_long i;
191 
192 	/* Remove all entries from the table and lists */
193 	for (i = 0; i < tbl->tbl_nslots; i++) {
194 		struct fileassoc_file *faf;
195 
196 		while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
197 			file_free(faf);
198 		}
199 	}
200 
201 	/* Remove hash table and sysctl node */
202 	hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
203 	specificdata_fini(fileassoc_domain, &tbl->tbl_data);
204 	kmem_free(tbl, sizeof(*tbl));
205 }
206 
207 /*
208  * Initialize the fileassoc subsystem.
209  */
210 static int
fileassoc_init(void)211 fileassoc_init(void)
212 {
213 	int error;
214 
215 	error = mount_specific_key_create(&fileassoc_mountspecific_key,
216 	    table_dtor);
217 	if (error) {
218 		return error;
219 	}
220 	fileassoc_domain = specificdata_domain_create();
221 
222 	mutex_init(&fileassoc_global.lock, MUTEX_DEFAULT, IPL_NONE);
223 
224 	return 0;
225 }
226 
227 /*
228  * Register a new assoc.
229  */
230 int
fileassoc_register(const char * name,fileassoc_cleanup_cb_t cleanup_cb,fileassoc_t * result)231 fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
232     fileassoc_t *result)
233 {
234 	int error;
235 	specificdata_key_t key;
236 	struct fileassoc *assoc;
237 
238 	error = RUN_ONCE(&control, fileassoc_init);
239 	if (error) {
240 		return error;
241 	}
242 	error = specificdata_key_create(fileassoc_domain, &key, NULL);
243 	if (error) {
244 		return error;
245 	}
246 	assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
247 	assoc->assoc_name = name;
248 	assoc->assoc_cleanup_cb = cleanup_cb;
249 	assoc->assoc_key = key;
250 
251 	LIST_INSERT_HEAD(&fileassoc_list, assoc, assoc_list);
252 
253 	*result = assoc;
254 
255 	return 0;
256 }
257 
258 /*
259  * Deregister an assoc.
260  */
261 int
fileassoc_deregister(fileassoc_t assoc)262 fileassoc_deregister(fileassoc_t assoc)
263 {
264 
265 	LIST_REMOVE(assoc, assoc_list);
266 	specificdata_key_delete(fileassoc_domain, assoc->assoc_key);
267 	kmem_free(assoc, sizeof(*assoc));
268 
269 	return 0;
270 }
271 
272 /*
273  * Get the hash table for the specified device.
274  */
275 static struct fileassoc_table *
fileassoc_table_lookup(struct mount * mp)276 fileassoc_table_lookup(struct mount *mp)
277 {
278 	int error;
279 
280 	if (!fileassoc_inuse())
281 		return NULL;
282 
283 	error = RUN_ONCE(&control, fileassoc_init);
284 	if (error) {
285 		return NULL;
286 	}
287 	return mount_getspecific(mp, fileassoc_mountspecific_key);
288 }
289 
290 /*
291  * Perform a lookup on a hash table.  If hint is non-zero then use the value
292  * of the hint as the identifier instead of performing a lookup for the
293  * fileid.
294  */
295 static struct fileassoc_file *
fileassoc_file_lookup(struct vnode * vp,fhandle_t * hint)296 fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
297 {
298 	struct fileassoc_table *tbl;
299 	struct fileassoc_hash_entry *hash_entry;
300 	struct fileassoc_file *faf;
301 	size_t indx;
302 	fhandle_t *th;
303 	int error;
304 
305 	tbl = fileassoc_table_lookup(vp->v_mount);
306 	if (tbl == NULL) {
307 		return NULL;
308 	}
309 
310 	if (hint == NULL) {
311 		error = vfs_composefh_alloc(vp, &th);
312 		if (error)
313 			return (NULL);
314 	} else {
315 		th = hint;
316 	}
317 
318 	indx = FILEASSOC_HASH(tbl, th);
319 	hash_entry = &(tbl->tbl_hash[indx]);
320 
321 	LIST_FOREACH(faf, hash_entry, faf_list) {
322 		if (((FHANDLE_FILEID(faf->faf_handle)->fid_len ==
323 		     FHANDLE_FILEID(th)->fid_len)) &&
324 		    (memcmp(FHANDLE_FILEID(faf->faf_handle), FHANDLE_FILEID(th),
325 			   (FHANDLE_FILEID(th))->fid_len) == 0)) {
326 			break;
327 		}
328 	}
329 
330 	if (hint == NULL)
331 		vfs_composefh_free(th);
332 
333 	return faf;
334 }
335 
336 /*
337  * Return assoc data associated with a vnode.
338  */
339 void *
fileassoc_lookup(struct vnode * vp,fileassoc_t assoc)340 fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
341 {
342 	struct fileassoc_file *faf;
343 
344 	faf = fileassoc_file_lookup(vp, NULL);
345 	if (faf == NULL)
346 		return (NULL);
347 
348 	return file_getdata(faf, assoc);
349 }
350 
351 static struct fileassoc_table *
fileassoc_table_resize(struct fileassoc_table * tbl)352 fileassoc_table_resize(struct fileassoc_table *tbl)
353 {
354 	struct fileassoc_table *newtbl;
355 	u_long i;
356 
357 	/*
358 	 * Allocate a new table. Like the condition in fileassoc_file_add(),
359 	 * this is also temporary -- just double the number of slots.
360 	 */
361 	newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
362 	newtbl->tbl_nslots = (tbl->tbl_nslots * 2);
363 	if (newtbl->tbl_nslots < tbl->tbl_nslots)
364 		newtbl->tbl_nslots = tbl->tbl_nslots;
365 	newtbl->tbl_hash = hashinit(newtbl->tbl_nslots, HASH_LIST,
366 	    true, &newtbl->tbl_mask);
367 	newtbl->tbl_nused = 0;
368 	specificdata_init(fileassoc_domain, &newtbl->tbl_data);
369 
370 	/* XXX we need to make sure nothing uses fileassoc here! */
371 
372 	for (i = 0; i < tbl->tbl_nslots; i++) {
373 		struct fileassoc_file *faf;
374 
375 		while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
376 			struct fileassoc_hash_entry *hash_entry;
377 			size_t indx;
378 
379 			LIST_REMOVE(faf, faf_list);
380 
381 			indx = FILEASSOC_HASH(newtbl, faf->faf_handle);
382 			hash_entry = &(newtbl->tbl_hash[indx]);
383 
384 			LIST_INSERT_HEAD(hash_entry, faf, faf_list);
385 
386 			newtbl->tbl_nused++;
387 		}
388 	}
389 
390 	if (tbl->tbl_nused != newtbl->tbl_nused)
391 		panic("fileassoc_table_resize: inconsistency detected! "
392 		    "needed %zu entries, got %zu", tbl->tbl_nused,
393 		    newtbl->tbl_nused);
394 
395 	hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
396 	specificdata_fini(fileassoc_domain, &tbl->tbl_data);
397 	kmem_free(tbl, sizeof(*tbl));
398 
399 	return (newtbl);
400 }
401 
402 /*
403  * Create a new fileassoc table.
404  */
405 static struct fileassoc_table *
fileassoc_table_add(struct mount * mp)406 fileassoc_table_add(struct mount *mp)
407 {
408 	struct fileassoc_table *tbl;
409 
410 	/* Check for existing table for device. */
411 	tbl = fileassoc_table_lookup(mp);
412 	if (tbl != NULL)
413 		return (tbl);
414 
415 	/* Allocate and initialize a table. */
416 	tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
417 	tbl->tbl_nslots = FILEASSOC_INITIAL_TABLESIZE;
418 	tbl->tbl_hash = hashinit(tbl->tbl_nslots, HASH_LIST, true,
419 	    &tbl->tbl_mask);
420 	tbl->tbl_nused = 0;
421 	specificdata_init(fileassoc_domain, &tbl->tbl_data);
422 
423 	mount_setspecific(mp, fileassoc_mountspecific_key, tbl);
424 
425 	return (tbl);
426 }
427 
428 /*
429  * Delete a table.
430  */
431 int
fileassoc_table_delete(struct mount * mp)432 fileassoc_table_delete(struct mount *mp)
433 {
434 	struct fileassoc_table *tbl;
435 
436 	tbl = fileassoc_table_lookup(mp);
437 	if (tbl == NULL)
438 		return (EEXIST);
439 
440 	mount_setspecific(mp, fileassoc_mountspecific_key, NULL);
441 	table_dtor(tbl);
442 
443 	return (0);
444 }
445 
446 /*
447  * Run a callback for each assoc in a table.
448  */
449 int
fileassoc_table_run(struct mount * mp,fileassoc_t assoc,fileassoc_cb_t cb,void * cookie)450 fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
451     void *cookie)
452 {
453 	struct fileassoc_table *tbl;
454 	u_long i;
455 
456 	tbl = fileassoc_table_lookup(mp);
457 	if (tbl == NULL)
458 		return (EEXIST);
459 
460 	for (i = 0; i < tbl->tbl_nslots; i++) {
461 		struct fileassoc_file *faf;
462 
463 		LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
464 			void *data;
465 
466 			data = file_getdata(faf, assoc);
467 			if (data != NULL)
468 				cb(data, cookie);
469 		}
470 	}
471 
472 	return (0);
473 }
474 
475 /*
476  * Clear a table for a given assoc.
477  */
478 int
fileassoc_table_clear(struct mount * mp,fileassoc_t assoc)479 fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
480 {
481 	struct fileassoc_table *tbl;
482 	u_long i;
483 
484 	tbl = fileassoc_table_lookup(mp);
485 	if (tbl == NULL)
486 		return (EEXIST);
487 
488 	for (i = 0; i < tbl->tbl_nslots; i++) {
489 		struct fileassoc_file *faf;
490 
491 		LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
492 			file_cleanup(faf, assoc);
493 			file_setdata(faf, assoc, NULL);
494 			/* XXX missing faf->faf_nassocs--? */
495 			fileassoc_decuse();
496 		}
497 	}
498 
499 	return (0);
500 }
501 
502 /*
503  * Add a file entry to a table.
504  */
505 static struct fileassoc_file *
fileassoc_file_add(struct vnode * vp,fhandle_t * hint)506 fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
507 {
508 	struct fileassoc_table *tbl;
509 	struct fileassoc_hash_entry *hash_entry;
510 	struct fileassoc_file *faf;
511 	size_t indx;
512 	fhandle_t *th;
513 	int error;
514 
515 	if (hint == NULL) {
516 		error = vfs_composefh_alloc(vp, &th);
517 		if (error)
518 			return (NULL);
519 	} else
520 		th = hint;
521 
522 	faf = fileassoc_file_lookup(vp, th);
523 	if (faf != NULL) {
524 		if (hint == NULL)
525 			vfs_composefh_free(th);
526 
527 		return (faf);
528 	}
529 
530 	tbl = fileassoc_table_lookup(vp->v_mount);
531 	if (tbl == NULL) {
532 		tbl = fileassoc_table_add(vp->v_mount);
533 	}
534 
535 	indx = FILEASSOC_HASH(tbl, th);
536 	hash_entry = &(tbl->tbl_hash[indx]);
537 
538 	faf = kmem_zalloc(sizeof(*faf), KM_SLEEP);
539 	faf->faf_handle = th;
540 	specificdata_init(fileassoc_domain, &faf->faf_data);
541 	LIST_INSERT_HEAD(hash_entry, faf, faf_list);
542 
543 	/*
544 	 * This decides when we need to resize the table. For now,
545 	 * resize it whenever we "filled" up the number of slots it
546 	 * has. That's not really true unless of course we had zero
547 	 * collisions. Think positive! :)
548 	 */
549 	if (++(tbl->tbl_nused) == tbl->tbl_nslots) {
550 		struct fileassoc_table *newtbl;
551 
552 		newtbl = fileassoc_table_resize(tbl);
553 		mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
554 		    newtbl);
555 	}
556 
557 	return (faf);
558 }
559 
560 /*
561  * Delete a file entry from a table.
562  */
563 int
fileassoc_file_delete(struct vnode * vp)564 fileassoc_file_delete(struct vnode *vp)
565 {
566 	struct fileassoc_table *tbl;
567 	struct fileassoc_file *faf;
568 
569 	if (!fileassoc_inuse())
570 		return ENOENT;
571 
572 	KERNEL_LOCK(1, NULL);
573 
574 	faf = fileassoc_file_lookup(vp, NULL);
575 	if (faf == NULL) {
576 		KERNEL_UNLOCK_ONE(NULL);
577 		return (ENOENT);
578 	}
579 
580 	file_free(faf);
581 
582 	tbl = fileassoc_table_lookup(vp->v_mount);
583 	KASSERT(tbl != NULL);
584 	--(tbl->tbl_nused); /* XXX gc? */
585 
586 	KERNEL_UNLOCK_ONE(NULL);
587 
588 	return (0);
589 }
590 
591 /*
592  * Add an assoc to a vnode.
593  */
594 int
fileassoc_add(struct vnode * vp,fileassoc_t assoc,void * data)595 fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
596 {
597 	struct fileassoc_file *faf;
598 	void *olddata;
599 
600 	faf = fileassoc_file_lookup(vp, NULL);
601 	if (faf == NULL) {
602 		faf = fileassoc_file_add(vp, NULL);
603 		if (faf == NULL)
604 			return (ENOTDIR);
605 	}
606 
607 	olddata = file_getdata(faf, assoc);
608 	if (olddata != NULL)
609 		return (EEXIST);
610 
611 	fileassoc_incuse();
612 
613 	file_setdata(faf, assoc, data);
614 
615 	faf->faf_nassocs++;
616 
617 	return (0);
618 }
619 
620 /*
621  * Clear an assoc from a vnode.
622  */
623 int
fileassoc_clear(struct vnode * vp,fileassoc_t assoc)624 fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
625 {
626 	struct fileassoc_file *faf;
627 
628 	faf = fileassoc_file_lookup(vp, NULL);
629 	if (faf == NULL)
630 		return (ENOENT);
631 
632 	file_cleanup(faf, assoc);
633 	file_setdata(faf, assoc, NULL);
634 
635 	--(faf->faf_nassocs); /* XXX gc? */
636 
637 	fileassoc_decuse();
638 
639 	return (0);
640 }
641