kern_fileassoc.c revision 1.16
1/* $NetBSD: kern_fileassoc.c,v 1.16 2006/12/14 09:24:54 yamt 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. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by Elad Efrat. 18 * 4. The name of the author may not be used to endorse or promote products 19 * derived from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33#include <sys/cdefs.h> 34__KERNEL_RCSID(0, "$NetBSD: kern_fileassoc.c,v 1.16 2006/12/14 09:24:54 yamt Exp $"); 35 36#include "opt_fileassoc.h" 37 38#include <sys/param.h> 39#include <sys/mount.h> 40#include <sys/queue.h> 41#include <sys/malloc.h> 42#include <sys/vnode.h> 43#include <sys/namei.h> 44#include <sys/exec.h> 45#include <sys/proc.h> 46#include <sys/inttypes.h> 47#include <sys/errno.h> 48#include <sys/fileassoc.h> 49#include <sys/specificdata.h> 50#include <sys/hash.h> 51#include <sys/fstypes.h> 52#include <sys/kmem.h> 53#include <sys/once.h> 54 55static struct fileassoc_hash_entry * 56fileassoc_file_lookup(struct vnode *, fhandle_t *); 57static struct fileassoc_hash_entry * 58fileassoc_file_add(struct vnode *, fhandle_t *); 59 60static specificdata_domain_t fileassoc_domain; 61static specificdata_key_t fileassoc_mountspecific_key; 62 63/* 64 * Hook entry. 65 * Includes the hook name for identification and private hook clear callback. 66 */ 67struct fileassoc { 68 LIST_ENTRY(fileassoc) list; 69 const char *name; /* name. */ 70 fileassoc_cleanup_cb_t cleanup_cb; /* clear callback. */ 71 specificdata_key_t key; 72}; 73 74static LIST_HEAD(, fileassoc) fileassoc_list; 75 76/* An entry in the per-device hash table. */ 77struct fileassoc_hash_entry { 78 fhandle_t *handle; /* File handle */ 79 specificdata_reference data; /* Hooks. */ 80 LIST_ENTRY(fileassoc_hash_entry) entries; /* List pointer. */ 81}; 82 83LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry); 84 85struct fileassoc_table { 86 struct fileassoc_hashhead *hash_tbl; 87 size_t hash_size; /* Number of slots. */ 88 u_long hash_mask; 89 specificdata_reference data; 90}; 91 92/* 93 * Hashing function: Takes a number modulus the mask to give back an 94 * index into the hash table. 95 */ 96#define FILEASSOC_HASH(tbl, handle) \ 97 (hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \ 98 & ((tbl)->hash_mask)) 99 100static void * 101table_getdata(struct fileassoc_table *tbl, const struct fileassoc *assoc) 102{ 103 104 return specificdata_getspecific(fileassoc_domain, &tbl->data, 105 assoc->key); 106} 107 108static void 109table_setdata(struct fileassoc_table *tbl, const struct fileassoc *assoc, 110 void *data) 111{ 112 113 specificdata_setspecific(fileassoc_domain, &tbl->data, assoc->key, 114 data); 115} 116 117static void 118table_cleanup(struct fileassoc_table *tbl, const struct fileassoc *assoc) 119{ 120 fileassoc_cleanup_cb_t cb; 121 void *data; 122 123 cb = assoc->cleanup_cb; 124 if (cb == NULL) { 125 return; 126 } 127 data = table_getdata(tbl, assoc); 128 (*cb)(data, FILEASSOC_CLEANUP_TABLE); 129} 130 131static void * 132file_getdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc) 133{ 134 135 return specificdata_getspecific(fileassoc_domain, &e->data, 136 assoc->key); 137} 138 139static void 140file_setdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc, 141 void *data) 142{ 143 144 specificdata_setspecific(fileassoc_domain, &e->data, assoc->key, 145 data); 146} 147 148static void 149file_cleanup(struct fileassoc_hash_entry *e, const struct fileassoc *assoc) 150{ 151 fileassoc_cleanup_cb_t cb; 152 void *data; 153 154 cb = assoc->cleanup_cb; 155 if (cb == NULL) { 156 return; 157 } 158 data = file_getdata(e, assoc); 159 (*cb)(data, FILEASSOC_CLEANUP_FILE); 160} 161 162static void 163file_free(struct fileassoc_hash_entry *e) 164{ 165 struct fileassoc *assoc; 166 167 LIST_REMOVE(e, entries); 168 169 LIST_FOREACH(assoc, &fileassoc_list, list) { 170 file_cleanup(e, assoc); 171 } 172 vfs_composefh_free(e->handle); 173 specificdata_fini(fileassoc_domain, &e->data); 174 kmem_free(e, sizeof(*e)); 175} 176 177static void 178table_dtor(void *vp) 179{ 180 struct fileassoc_table *tbl = vp; 181 const struct fileassoc *assoc; 182 struct fileassoc_hashhead *hh; 183 u_long i; 184 185 /* Remove all entries from the table and lists */ 186 hh = tbl->hash_tbl; 187 for (i = 0; i < tbl->hash_size; i++) { 188 struct fileassoc_hash_entry *mhe; 189 190 while ((mhe = LIST_FIRST(&hh[i])) != NULL) { 191 file_free(mhe); 192 } 193 } 194 195 LIST_FOREACH(assoc, &fileassoc_list, list) { 196 table_cleanup(tbl, assoc); 197 } 198 199 /* Remove hash table and sysctl node */ 200 hashdone(tbl->hash_tbl, M_TEMP); 201 specificdata_fini(fileassoc_domain, &tbl->data); 202 kmem_free(tbl, sizeof(*tbl)); 203} 204 205/* 206 * Initialize the fileassoc subsystem. 207 */ 208static int 209fileassoc_init(void) 210{ 211 int error; 212 213 error = mount_specific_key_create(&fileassoc_mountspecific_key, 214 table_dtor); 215 if (error) { 216 return error; 217 } 218 fileassoc_domain = specificdata_domain_create(); 219 220 return 0; 221} 222 223/* 224 * Register a new hook. 225 */ 226int 227fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb, 228 fileassoc_t *result) 229{ 230 int error; 231 specificdata_key_t key; 232 struct fileassoc *assoc; 233 static ONCE_DECL(control); 234 235 error = RUN_ONCE(&control, fileassoc_init); 236 if (error) { 237 return error; 238 } 239 error = specificdata_key_create(fileassoc_domain, &key, NULL); 240 if (error) { 241 return error; 242 } 243 assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP); 244 assoc->name = name; 245 assoc->cleanup_cb = cleanup_cb; 246 assoc->key = key; 247 LIST_INSERT_HEAD(&fileassoc_list, assoc, list); 248 *result = assoc; 249 250 return 0; 251} 252 253/* 254 * Deregister a hook. 255 */ 256int 257fileassoc_deregister(fileassoc_t assoc) 258{ 259 260 LIST_REMOVE(assoc, list); 261 kmem_free(assoc, sizeof(*assoc)); 262 263 return 0; 264} 265 266/* 267 * Get the hash table for the specified device. 268 */ 269static struct fileassoc_table * 270fileassoc_table_lookup(struct mount *mp) 271{ 272 273 return mount_getspecific(mp, fileassoc_mountspecific_key); 274} 275 276/* 277 * Perform a lookup on a hash table. If hint is non-zero then use the value 278 * of the hint as the identifier instead of performing a lookup for the 279 * fileid. 280 */ 281static struct fileassoc_hash_entry * 282fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint) 283{ 284 struct fileassoc_table *tbl; 285 struct fileassoc_hashhead *tble; 286 struct fileassoc_hash_entry *e; 287 size_t indx; 288 fhandle_t *th; 289 int error; 290 291 tbl = fileassoc_table_lookup(vp->v_mount); 292 if (tbl == NULL) { 293 return NULL; 294 } 295 296 if (hint == NULL) { 297 error = vfs_composefh_alloc(vp, &th); 298 if (error) 299 return (NULL); 300 } else { 301 th = hint; 302 } 303 304 indx = FILEASSOC_HASH(tbl, th); 305 tble = &(tbl->hash_tbl[indx]); 306 307 LIST_FOREACH(e, tble, entries) { 308 if (((FHANDLE_FILEID(e->handle)->fid_len == 309 FHANDLE_FILEID(th)->fid_len)) && 310 (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th), 311 (FHANDLE_FILEID(th))->fid_len) == 0)) { 312 break; 313 } 314 } 315 316 if (hint == NULL) 317 vfs_composefh_free(th); 318 319 return e; 320} 321 322/* 323 * Return hook data associated with a vnode. 324 */ 325void * 326fileassoc_lookup(struct vnode *vp, fileassoc_t assoc) 327{ 328 struct fileassoc_hash_entry *mhe; 329 330 mhe = fileassoc_file_lookup(vp, NULL); 331 if (mhe == NULL) 332 return (NULL); 333 334 return file_getdata(mhe, assoc); 335} 336 337/* 338 * Create a new fileassoc table. 339 */ 340int 341fileassoc_table_add(struct mount *mp, size_t size) 342{ 343 struct fileassoc_table *tbl; 344 345 /* Check for existing table for device. */ 346 if (fileassoc_table_lookup(mp) != NULL) 347 return (EEXIST); 348 349 /* Allocate and initialize a Veriexec hash table. */ 350 tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP); 351 tbl->hash_size = size; 352 tbl->hash_tbl = hashinit(size, HASH_LIST, M_TEMP, 353 M_WAITOK | M_ZERO, &tbl->hash_mask); 354 specificdata_init(fileassoc_domain, &tbl->data); 355 356 mount_setspecific(mp, fileassoc_mountspecific_key, tbl); 357 358 return (0); 359} 360 361/* 362 * Delete a table. 363 */ 364int 365fileassoc_table_delete(struct mount *mp) 366{ 367 struct fileassoc_table *tbl; 368 369 tbl = fileassoc_table_lookup(mp); 370 if (tbl == NULL) 371 return (EEXIST); 372 373 mount_setspecific(mp, fileassoc_mountspecific_key, NULL); 374 table_dtor(tbl); 375 376 return (0); 377} 378 379/* 380 * Run a callback for each hook entry in a table. 381 */ 382int 383fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb) 384{ 385 struct fileassoc_table *tbl; 386 struct fileassoc_hashhead *hh; 387 u_long i; 388 389 tbl = fileassoc_table_lookup(mp); 390 if (tbl == NULL) 391 return (EEXIST); 392 393 hh = tbl->hash_tbl; 394 for (i = 0; i < tbl->hash_size; i++) { 395 struct fileassoc_hash_entry *mhe; 396 397 LIST_FOREACH(mhe, &hh[i], entries) { 398 void *data; 399 400 data = file_getdata(mhe, assoc); 401 if (data != NULL) 402 cb(data); 403 } 404 } 405 406 return (0); 407} 408 409/* 410 * Clear a table for a given hook. 411 */ 412int 413fileassoc_table_clear(struct mount *mp, fileassoc_t assoc) 414{ 415 struct fileassoc_table *tbl; 416 struct fileassoc_hashhead *hh; 417 u_long i; 418 419 tbl = fileassoc_table_lookup(mp); 420 if (tbl == NULL) 421 return (EEXIST); 422 423 hh = tbl->hash_tbl; 424 for (i = 0; i < tbl->hash_size; i++) { 425 struct fileassoc_hash_entry *mhe; 426 427 LIST_FOREACH(mhe, &hh[i], entries) { 428 file_cleanup(mhe, assoc); 429 file_setdata(mhe, assoc, NULL); 430 } 431 } 432 433 table_cleanup(tbl, assoc); 434 table_setdata(tbl, assoc, NULL); 435 436 return (0); 437} 438 439/* 440 * Add hook-specific data on a fileassoc table. 441 */ 442int 443fileassoc_tabledata_add(struct mount *mp, fileassoc_t assoc, void *data) 444{ 445 struct fileassoc_table *tbl; 446 447 tbl = fileassoc_table_lookup(mp); 448 if (tbl == NULL) 449 return (EFAULT); 450 451 table_setdata(tbl, assoc, data); 452 453 return (0); 454} 455 456/* 457 * Clear hook-specific data on a fileassoc table. 458 */ 459int 460fileassoc_tabledata_clear(struct mount *mp, fileassoc_t assoc) 461{ 462 463 return fileassoc_tabledata_add(mp, assoc, NULL); 464} 465 466/* 467 * Retrieve hook-specific data from a fileassoc table. 468 */ 469void * 470fileassoc_tabledata_lookup(struct mount *mp, fileassoc_t assoc) 471{ 472 struct fileassoc_table *tbl; 473 474 tbl = fileassoc_table_lookup(mp); 475 if (tbl == NULL) 476 return (NULL); 477 478 return table_getdata(tbl, assoc); 479} 480 481/* 482 * Add a file entry to a table. 483 */ 484static struct fileassoc_hash_entry * 485fileassoc_file_add(struct vnode *vp, fhandle_t *hint) 486{ 487 struct fileassoc_table *tbl; 488 struct fileassoc_hashhead *vhh; 489 struct fileassoc_hash_entry *e; 490 size_t indx; 491 fhandle_t *th; 492 int error; 493 494 if (hint == NULL) { 495 error = vfs_composefh_alloc(vp, &th); 496 if (error) 497 return (NULL); 498 } else 499 th = hint; 500 501 e = fileassoc_file_lookup(vp, th); 502 if (e != NULL) { 503 if (hint == NULL) 504 vfs_composefh_free(th); 505 506 return (e); 507 } 508 509 tbl = fileassoc_table_lookup(vp->v_mount); 510 if (tbl == NULL) { 511 if (hint == NULL) 512 vfs_composefh_free(th); 513 514 return (NULL); 515 } 516 517 indx = FILEASSOC_HASH(tbl, th); 518 vhh = &(tbl->hash_tbl[indx]); 519 520 e = kmem_zalloc(sizeof(*e), KM_SLEEP); 521 e->handle = th; 522 specificdata_init(fileassoc_domain, &e->data); 523 LIST_INSERT_HEAD(vhh, e, entries); 524 525 return (e); 526} 527 528/* 529 * Delete a file entry from a table. 530 */ 531int 532fileassoc_file_delete(struct vnode *vp) 533{ 534 struct fileassoc_hash_entry *mhe; 535 536 mhe = fileassoc_file_lookup(vp, NULL); 537 if (mhe == NULL) 538 return (ENOENT); 539 540 file_free(mhe); 541 542 return (0); 543} 544 545/* 546 * Add a hook to a vnode. 547 */ 548int 549fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data) 550{ 551 struct fileassoc_hash_entry *e; 552 void *olddata; 553 554 e = fileassoc_file_lookup(vp, NULL); 555 if (e == NULL) { 556 e = fileassoc_file_add(vp, NULL); 557 if (e == NULL) 558 return (ENOTDIR); 559 } 560 561 olddata = file_getdata(e, assoc); 562 if (olddata != NULL) 563 return (EEXIST); 564 565 file_setdata(e, assoc, data); 566 567 return (0); 568} 569 570/* 571 * Clear a hook from a vnode. 572 */ 573int 574fileassoc_clear(struct vnode *vp, fileassoc_t assoc) 575{ 576 struct fileassoc_hash_entry *mhe; 577 578 mhe = fileassoc_file_lookup(vp, NULL); 579 if (mhe == NULL) 580 return (ENOENT); 581 582 file_cleanup(mhe, assoc); 583 file_setdata(mhe, assoc, NULL); 584 585 return (0); 586} 587