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