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