xref: /netbsd-src/sys/kern/kern_fileassoc.c (revision 865c57e0098351fba0d2d2a97b33e7e0270e62c6)
1 /* $NetBSD: kern_fileassoc.c,v 1.37 2023/08/02 07:11:31 riastradh 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.37 2023/08/02 07:11:31 riastradh 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
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
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
133 fileassoc_inuse(void)
134 {
135 
136 	return __predict_false(atomic_load_relaxed(&fileassoc_global.inuse));
137 }
138 
139 static void *
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
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
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
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
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
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 	return 0;
223 }
224 
225 /*
226  * Register a new assoc.
227  */
228 int
229 fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
230     fileassoc_t *result)
231 {
232 	int error;
233 	specificdata_key_t key;
234 	struct fileassoc *assoc;
235 
236 	error = RUN_ONCE(&control, fileassoc_init);
237 	if (error) {
238 		return error;
239 	}
240 	error = specificdata_key_create(fileassoc_domain, &key, NULL);
241 	if (error) {
242 		return error;
243 	}
244 	assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
245 	assoc->assoc_name = name;
246 	assoc->assoc_cleanup_cb = cleanup_cb;
247 	assoc->assoc_key = key;
248 
249 	LIST_INSERT_HEAD(&fileassoc_list, assoc, assoc_list);
250 
251 	*result = assoc;
252 
253 	return 0;
254 }
255 
256 /*
257  * Deregister an assoc.
258  */
259 int
260 fileassoc_deregister(fileassoc_t assoc)
261 {
262 
263 	LIST_REMOVE(assoc, assoc_list);
264 	specificdata_key_delete(fileassoc_domain, assoc->assoc_key);
265 	kmem_free(assoc, sizeof(*assoc));
266 
267 	return 0;
268 }
269 
270 /*
271  * Get the hash table for the specified device.
272  */
273 static struct fileassoc_table *
274 fileassoc_table_lookup(struct mount *mp)
275 {
276 	int error;
277 
278 	if (!fileassoc_inuse())
279 		return NULL;
280 
281 	error = RUN_ONCE(&control, fileassoc_init);
282 	if (error) {
283 		return NULL;
284 	}
285 	return mount_getspecific(mp, fileassoc_mountspecific_key);
286 }
287 
288 /*
289  * Perform a lookup on a hash table.  If hint is non-zero then use the value
290  * of the hint as the identifier instead of performing a lookup for the
291  * fileid.
292  */
293 static struct fileassoc_file *
294 fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
295 {
296 	struct fileassoc_table *tbl;
297 	struct fileassoc_hash_entry *hash_entry;
298 	struct fileassoc_file *faf;
299 	size_t indx;
300 	fhandle_t *th;
301 	int error;
302 
303 	tbl = fileassoc_table_lookup(vp->v_mount);
304 	if (tbl == NULL) {
305 		return NULL;
306 	}
307 
308 	if (hint == NULL) {
309 		error = vfs_composefh_alloc(vp, &th);
310 		if (error)
311 			return (NULL);
312 	} else {
313 		th = hint;
314 	}
315 
316 	indx = FILEASSOC_HASH(tbl, th);
317 	hash_entry = &(tbl->tbl_hash[indx]);
318 
319 	LIST_FOREACH(faf, hash_entry, faf_list) {
320 		if (((FHANDLE_FILEID(faf->faf_handle)->fid_len ==
321 		     FHANDLE_FILEID(th)->fid_len)) &&
322 		    (memcmp(FHANDLE_FILEID(faf->faf_handle), FHANDLE_FILEID(th),
323 			   (FHANDLE_FILEID(th))->fid_len) == 0)) {
324 			break;
325 		}
326 	}
327 
328 	if (hint == NULL)
329 		vfs_composefh_free(th);
330 
331 	return faf;
332 }
333 
334 /*
335  * Return assoc data associated with a vnode.
336  */
337 void *
338 fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
339 {
340 	struct fileassoc_file *faf;
341 
342 	faf = fileassoc_file_lookup(vp, NULL);
343 	if (faf == NULL)
344 		return (NULL);
345 
346 	return file_getdata(faf, assoc);
347 }
348 
349 static struct fileassoc_table *
350 fileassoc_table_resize(struct fileassoc_table *tbl)
351 {
352 	struct fileassoc_table *newtbl;
353 	u_long i;
354 
355 	/*
356 	 * Allocate a new table. Like the condition in fileassoc_file_add(),
357 	 * this is also temporary -- just double the number of slots.
358 	 */
359 	newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
360 	newtbl->tbl_nslots = (tbl->tbl_nslots * 2);
361 	if (newtbl->tbl_nslots < tbl->tbl_nslots)
362 		newtbl->tbl_nslots = tbl->tbl_nslots;
363 	newtbl->tbl_hash = hashinit(newtbl->tbl_nslots, HASH_LIST,
364 	    true, &newtbl->tbl_mask);
365 	newtbl->tbl_nused = 0;
366 	specificdata_init(fileassoc_domain, &newtbl->tbl_data);
367 
368 	/* XXX we need to make sure nothing uses fileassoc here! */
369 
370 	for (i = 0; i < tbl->tbl_nslots; i++) {
371 		struct fileassoc_file *faf;
372 
373 		while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
374 			struct fileassoc_hash_entry *hash_entry;
375 			size_t indx;
376 
377 			LIST_REMOVE(faf, faf_list);
378 
379 			indx = FILEASSOC_HASH(newtbl, faf->faf_handle);
380 			hash_entry = &(newtbl->tbl_hash[indx]);
381 
382 			LIST_INSERT_HEAD(hash_entry, faf, faf_list);
383 
384 			newtbl->tbl_nused++;
385 		}
386 	}
387 
388 	if (tbl->tbl_nused != newtbl->tbl_nused)
389 		panic("fileassoc_table_resize: inconsistency detected! "
390 		    "needed %zu entries, got %zu", tbl->tbl_nused,
391 		    newtbl->tbl_nused);
392 
393 	hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
394 	specificdata_fini(fileassoc_domain, &tbl->tbl_data);
395 	kmem_free(tbl, sizeof(*tbl));
396 
397 	return (newtbl);
398 }
399 
400 /*
401  * Create a new fileassoc table.
402  */
403 static struct fileassoc_table *
404 fileassoc_table_add(struct mount *mp)
405 {
406 	struct fileassoc_table *tbl;
407 
408 	/* Check for existing table for device. */
409 	tbl = fileassoc_table_lookup(mp);
410 	if (tbl != NULL)
411 		return (tbl);
412 
413 	/* Allocate and initialize a table. */
414 	tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
415 	tbl->tbl_nslots = FILEASSOC_INITIAL_TABLESIZE;
416 	tbl->tbl_hash = hashinit(tbl->tbl_nslots, HASH_LIST, true,
417 	    &tbl->tbl_mask);
418 	tbl->tbl_nused = 0;
419 	specificdata_init(fileassoc_domain, &tbl->tbl_data);
420 
421 	mount_setspecific(mp, fileassoc_mountspecific_key, tbl);
422 
423 	return (tbl);
424 }
425 
426 /*
427  * Delete a table.
428  */
429 int
430 fileassoc_table_delete(struct mount *mp)
431 {
432 	struct fileassoc_table *tbl;
433 
434 	tbl = fileassoc_table_lookup(mp);
435 	if (tbl == NULL)
436 		return (EEXIST);
437 
438 	mount_setspecific(mp, fileassoc_mountspecific_key, NULL);
439 	table_dtor(tbl);
440 
441 	return (0);
442 }
443 
444 /*
445  * Run a callback for each assoc in a table.
446  */
447 int
448 fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
449     void *cookie)
450 {
451 	struct fileassoc_table *tbl;
452 	u_long i;
453 
454 	tbl = fileassoc_table_lookup(mp);
455 	if (tbl == NULL)
456 		return (EEXIST);
457 
458 	for (i = 0; i < tbl->tbl_nslots; i++) {
459 		struct fileassoc_file *faf;
460 
461 		LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
462 			void *data;
463 
464 			data = file_getdata(faf, assoc);
465 			if (data != NULL)
466 				cb(data, cookie);
467 		}
468 	}
469 
470 	return (0);
471 }
472 
473 /*
474  * Clear a table for a given assoc.
475  */
476 int
477 fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
478 {
479 	struct fileassoc_table *tbl;
480 	u_long i;
481 
482 	tbl = fileassoc_table_lookup(mp);
483 	if (tbl == NULL)
484 		return (EEXIST);
485 
486 	for (i = 0; i < tbl->tbl_nslots; i++) {
487 		struct fileassoc_file *faf;
488 
489 		LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
490 			file_cleanup(faf, assoc);
491 			file_setdata(faf, assoc, NULL);
492 			/* XXX missing faf->faf_nassocs--? */
493 			fileassoc_decuse();
494 		}
495 	}
496 
497 	return (0);
498 }
499 
500 /*
501  * Add a file entry to a table.
502  */
503 static struct fileassoc_file *
504 fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
505 {
506 	struct fileassoc_table *tbl;
507 	struct fileassoc_hash_entry *hash_entry;
508 	struct fileassoc_file *faf;
509 	size_t indx;
510 	fhandle_t *th;
511 	int error;
512 
513 	if (hint == NULL) {
514 		error = vfs_composefh_alloc(vp, &th);
515 		if (error)
516 			return (NULL);
517 	} else
518 		th = hint;
519 
520 	faf = fileassoc_file_lookup(vp, th);
521 	if (faf != NULL) {
522 		if (hint == NULL)
523 			vfs_composefh_free(th);
524 
525 		return (faf);
526 	}
527 
528 	tbl = fileassoc_table_lookup(vp->v_mount);
529 	if (tbl == NULL) {
530 		tbl = fileassoc_table_add(vp->v_mount);
531 	}
532 
533 	indx = FILEASSOC_HASH(tbl, th);
534 	hash_entry = &(tbl->tbl_hash[indx]);
535 
536 	faf = kmem_zalloc(sizeof(*faf), KM_SLEEP);
537 	faf->faf_handle = th;
538 	specificdata_init(fileassoc_domain, &faf->faf_data);
539 	LIST_INSERT_HEAD(hash_entry, faf, faf_list);
540 
541 	/*
542 	 * This decides when we need to resize the table. For now,
543 	 * resize it whenever we "filled" up the number of slots it
544 	 * has. That's not really true unless of course we had zero
545 	 * collisions. Think positive! :)
546 	 */
547 	if (++(tbl->tbl_nused) == tbl->tbl_nslots) {
548 		struct fileassoc_table *newtbl;
549 
550 		newtbl = fileassoc_table_resize(tbl);
551 		mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
552 		    newtbl);
553 	}
554 
555 	return (faf);
556 }
557 
558 /*
559  * Delete a file entry from a table.
560  */
561 int
562 fileassoc_file_delete(struct vnode *vp)
563 {
564 	struct fileassoc_table *tbl;
565 	struct fileassoc_file *faf;
566 
567 	if (!fileassoc_inuse())
568 		return ENOENT;
569 
570 	KERNEL_LOCK(1, NULL);
571 
572 	faf = fileassoc_file_lookup(vp, NULL);
573 	if (faf == NULL) {
574 		KERNEL_UNLOCK_ONE(NULL);
575 		return (ENOENT);
576 	}
577 
578 	file_free(faf);
579 
580 	tbl = fileassoc_table_lookup(vp->v_mount);
581 	KASSERT(tbl != NULL);
582 	--(tbl->tbl_nused); /* XXX gc? */
583 
584 	KERNEL_UNLOCK_ONE(NULL);
585 
586 	return (0);
587 }
588 
589 /*
590  * Add an assoc to a vnode.
591  */
592 int
593 fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
594 {
595 	struct fileassoc_file *faf;
596 	void *olddata;
597 
598 	faf = fileassoc_file_lookup(vp, NULL);
599 	if (faf == NULL) {
600 		faf = fileassoc_file_add(vp, NULL);
601 		if (faf == NULL)
602 			return (ENOTDIR);
603 	}
604 
605 	olddata = file_getdata(faf, assoc);
606 	if (olddata != NULL)
607 		return (EEXIST);
608 
609 	fileassoc_incuse();
610 
611 	file_setdata(faf, assoc, data);
612 
613 	faf->faf_nassocs++;
614 
615 	return (0);
616 }
617 
618 /*
619  * Clear an assoc from a vnode.
620  */
621 int
622 fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
623 {
624 	struct fileassoc_file *faf;
625 
626 	faf = fileassoc_file_lookup(vp, NULL);
627 	if (faf == NULL)
628 		return (ENOENT);
629 
630 	file_cleanup(faf, assoc);
631 	file_setdata(faf, assoc, NULL);
632 
633 	--(faf->faf_nassocs); /* XXX gc? */
634 
635 	fileassoc_decuse();
636 
637 	return (0);
638 }
639