Home | History | Annotate | Line # | Download | only in kern
      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 (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.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
    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 	mutex_init(&fileassoc_global.lock, MUTEX_DEFAULT, IPL_NONE);
    223 
    224 	return 0;
    225 }
    226 
    227 /*
    228  * Register a new assoc.
    229  */
    230 int
    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
    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 *
    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 *
    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 *
    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 *
    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 *
    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
    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
    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
    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 *
    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
    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
    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
    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