kern_fileassoc.c revision 1.14
1/* $NetBSD: kern_fileassoc.c,v 1.14 2006/12/11 15:24:28 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.14 2006/12/11 15:24:28 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; 61 62/* 63 * Hook entry. 64 * Includes the hook name for identification and private hook clear callback. 65 */ 66struct fileassoc { 67 LIST_ENTRY(fileassoc) list; 68 const char *name; /* name. */ 69 fileassoc_cleanup_cb_t cleanup_cb; /* clear callback. */ 70 specificdata_key_t key; 71}; 72 73static LIST_HEAD(, fileassoc) fileassoc_list; 74 75/* An entry in the per-device hash table. */ 76struct fileassoc_hash_entry { 77 fhandle_t *handle; /* File handle */ 78 specificdata_reference data; /* Hooks. */ 79 LIST_ENTRY(fileassoc_hash_entry) entries; /* List pointer. */ 80}; 81 82LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry); 83 84struct fileassoc_table { 85 struct fileassoc_hashhead *hash_tbl; 86 size_t hash_size; /* Number of slots. */ 87 struct mount *tbl_mntpt; 88 u_long hash_mask; 89 specificdata_reference data; 90 LIST_ENTRY(fileassoc_table) hash_list; /* List pointer. */ 91}; 92 93/* Global list of hash tables, one per device. */ 94LIST_HEAD(, fileassoc_table) fileassoc_tables; 95 96/* 97 * Hashing function: Takes a number modulus the mask to give back an 98 * index into the hash table. 99 */ 100#define FILEASSOC_HASH(tbl, handle) \ 101 (hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \ 102 & ((tbl)->hash_mask)) 103 104static void * 105table_getdata(struct fileassoc_table *tbl, const struct fileassoc *assoc) 106{ 107 108 return specificdata_getspecific(fileassoc_domain, &tbl->data, 109 assoc->key); 110} 111 112static void 113table_setdata(struct fileassoc_table *tbl, const struct fileassoc *assoc, 114 void *data) 115{ 116 117 specificdata_setspecific(fileassoc_domain, &tbl->data, assoc->key, 118 data); 119} 120 121static void 122table_cleanup(struct fileassoc_table *tbl, const struct fileassoc *assoc) 123{ 124 fileassoc_cleanup_cb_t cb; 125 void *data; 126 127 cb = assoc->cleanup_cb; 128 if (cb == NULL) { 129 return; 130 } 131 data = table_getdata(tbl, assoc); 132 (*cb)(data, FILEASSOC_CLEANUP_TABLE); 133} 134 135static void * 136file_getdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc) 137{ 138 139 return specificdata_getspecific(fileassoc_domain, &e->data, 140 assoc->key); 141} 142 143static void 144file_setdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc, 145 void *data) 146{ 147 148 specificdata_setspecific(fileassoc_domain, &e->data, assoc->key, 149 data); 150} 151 152static void 153file_cleanup(struct fileassoc_hash_entry *e, const struct fileassoc *assoc) 154{ 155 fileassoc_cleanup_cb_t cb; 156 void *data; 157 158 cb = assoc->cleanup_cb; 159 if (cb == NULL) { 160 return; 161 } 162 data = file_getdata(e, assoc); 163 (*cb)(data, FILEASSOC_CLEANUP_FILE); 164} 165 166static void 167file_free(struct fileassoc_hash_entry *e) 168{ 169 struct fileassoc *assoc; 170 171 LIST_REMOVE(e, entries); 172 173 LIST_FOREACH(assoc, &fileassoc_list, list) { 174 file_cleanup(e, assoc); 175 } 176 vfs_composefh_free(e->handle); 177 specificdata_fini(fileassoc_domain, &e->data); 178 kmem_free(e, sizeof(*e)); 179} 180 181/* 182 * Initialize the fileassoc subsystem. 183 */ 184static int 185fileassoc_init(void) 186{ 187 188 fileassoc_domain = specificdata_domain_create(); 189 190 return 0; 191} 192 193/* 194 * Register a new hook. 195 */ 196int 197fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb, 198 fileassoc_t *result) 199{ 200 int error; 201 specificdata_key_t key; 202 struct fileassoc *assoc; 203 static ONCE_DECL(control); 204 205 RUN_ONCE(&control, fileassoc_init); 206 error = specificdata_key_create(fileassoc_domain, &key, NULL); 207 if (error) { 208 return error; 209 } 210 assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP); 211 assoc->name = name; 212 assoc->cleanup_cb = cleanup_cb; 213 assoc->key = key; 214 LIST_INSERT_HEAD(&fileassoc_list, assoc, list); 215 *result = assoc; 216 217 return 0; 218} 219 220/* 221 * Deregister a hook. 222 */ 223int 224fileassoc_deregister(fileassoc_t assoc) 225{ 226 227 LIST_REMOVE(assoc, list); 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 struct fileassoc_table *tbl; 240 241 LIST_FOREACH(tbl, &fileassoc_tables, hash_list) { 242 if (tbl->tbl_mntpt == mp) 243 return (tbl); 244 } 245 246 return (NULL); 247} 248 249/* 250 * Perform a lookup on a hash table. If hint is non-zero then use the value 251 * of the hint as the identifier instead of performing a lookup for the 252 * fileid. 253 */ 254static struct fileassoc_hash_entry * 255fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint) 256{ 257 struct fileassoc_table *tbl; 258 struct fileassoc_hashhead *tble; 259 struct fileassoc_hash_entry *e; 260 size_t indx; 261 fhandle_t *th; 262 int error; 263 264 if (hint == NULL) { 265 error = vfs_composefh_alloc(vp, &th); 266 if (error) 267 return (NULL); 268 } else 269 th = hint; 270 271 tbl = fileassoc_table_lookup(vp->v_mount); 272 if (tbl == NULL) { 273 if (hint == NULL) 274 vfs_composefh_free(th); 275 276 return (NULL); 277 } 278 279 indx = FILEASSOC_HASH(tbl, th); 280 tble = &(tbl->hash_tbl[indx]); 281 282 LIST_FOREACH(e, tble, entries) { 283 if ((e != NULL) && 284 ((FHANDLE_FILEID(e->handle)->fid_len == 285 FHANDLE_FILEID(th)->fid_len)) && 286 (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th), 287 (FHANDLE_FILEID(th))->fid_len) == 0)) { 288 if (hint == NULL) 289 vfs_composefh_free(th); 290 291 return (e); 292 } 293 } 294 295 if (hint == NULL) 296 vfs_composefh_free(th); 297 298 return (NULL); 299} 300 301/* 302 * Return hook data associated with a vnode. 303 */ 304void * 305fileassoc_lookup(struct vnode *vp, fileassoc_t assoc) 306{ 307 struct fileassoc_hash_entry *mhe; 308 309 mhe = fileassoc_file_lookup(vp, NULL); 310 if (mhe == NULL) 311 return (NULL); 312 313 return file_getdata(mhe, assoc); 314} 315 316/* 317 * Create a new fileassoc table. 318 */ 319int 320fileassoc_table_add(struct mount *mp, size_t size) 321{ 322 struct fileassoc_table *tbl; 323 324 /* Check for existing table for device. */ 325 if (fileassoc_table_lookup(mp) != NULL) 326 return (EEXIST); 327 328 /* Allocate and initialize a Veriexec hash table. */ 329 tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP); 330 tbl->hash_size = size; 331 tbl->tbl_mntpt = mp; 332 tbl->hash_tbl = hashinit(size, HASH_LIST, M_TEMP, 333 M_WAITOK | M_ZERO, &tbl->hash_mask); 334 specificdata_init(fileassoc_domain, &tbl->data); 335 336 LIST_INSERT_HEAD(&fileassoc_tables, tbl, hash_list); 337 338 return (0); 339} 340 341/* 342 * Delete a table. 343 */ 344int 345fileassoc_table_delete(struct mount *mp) 346{ 347 const struct fileassoc *assoc; 348 struct fileassoc_table *tbl; 349 struct fileassoc_hashhead *hh; 350 u_long i; 351 352 tbl = fileassoc_table_lookup(mp); 353 if (tbl == NULL) 354 return (EEXIST); 355 356 /* Remove all entries from the table and lists */ 357 hh = tbl->hash_tbl; 358 for (i = 0; i < tbl->hash_size; i++) { 359 struct fileassoc_hash_entry *mhe; 360 361 while ((mhe = LIST_FIRST(&hh[i])) != NULL) { 362 file_free(mhe); 363 } 364 } 365 366 LIST_FOREACH(assoc, &fileassoc_list, list) { 367 table_cleanup(tbl, assoc); 368 } 369 370 /* Remove hash table and sysctl node */ 371 hashdone(tbl->hash_tbl, M_TEMP); 372 LIST_REMOVE(tbl, hash_list); 373 specificdata_fini(fileassoc_domain, &tbl->data); 374 kmem_free(tbl, sizeof(*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