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