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