kern_fileassoc.c revision 1.18
1/* $NetBSD: kern_fileassoc.c,v 1.18 2006/12/26 08:55:32 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. 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.18 2006/12/26 08:55:32 elad 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 * 101file_getdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc) 102{ 103 104 return specificdata_getspecific(fileassoc_domain, &e->data, 105 assoc->key); 106} 107 108static void 109file_setdata(struct fileassoc_hash_entry *e, const struct fileassoc *assoc, 110 void *data) 111{ 112 113 specificdata_setspecific(fileassoc_domain, &e->data, assoc->key, 114 data); 115} 116 117static void 118file_cleanup(struct fileassoc_hash_entry *e, 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 = file_getdata(e, assoc); 128 (*cb)(data); 129} 130 131static void 132file_free(struct fileassoc_hash_entry *e) 133{ 134 struct fileassoc *assoc; 135 136 LIST_REMOVE(e, entries); 137 138 LIST_FOREACH(assoc, &fileassoc_list, list) { 139 file_cleanup(e, assoc); 140 } 141 vfs_composefh_free(e->handle); 142 specificdata_fini(fileassoc_domain, &e->data); 143 kmem_free(e, sizeof(*e)); 144} 145 146static void 147table_dtor(void *vp) 148{ 149 struct fileassoc_table *tbl = vp; 150 struct fileassoc_hashhead *hh; 151 u_long i; 152 153 /* Remove all entries from the table and lists */ 154 hh = tbl->hash_tbl; 155 for (i = 0; i < tbl->hash_size; i++) { 156 struct fileassoc_hash_entry *mhe; 157 158 while ((mhe = LIST_FIRST(&hh[i])) != NULL) { 159 file_free(mhe); 160 } 161 } 162 163 /* Remove hash table and sysctl node */ 164 hashdone(tbl->hash_tbl, M_TEMP); 165 specificdata_fini(fileassoc_domain, &tbl->data); 166 kmem_free(tbl, sizeof(*tbl)); 167} 168 169/* 170 * Initialize the fileassoc subsystem. 171 */ 172static int 173fileassoc_init(void) 174{ 175 int error; 176 177 error = mount_specific_key_create(&fileassoc_mountspecific_key, 178 table_dtor); 179 if (error) { 180 return error; 181 } 182 fileassoc_domain = specificdata_domain_create(); 183 184 return 0; 185} 186 187/* 188 * Register a new hook. 189 */ 190int 191fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb, 192 fileassoc_t *result) 193{ 194 int error; 195 specificdata_key_t key; 196 struct fileassoc *assoc; 197 static ONCE_DECL(control); 198 199 error = RUN_ONCE(&control, fileassoc_init); 200 if (error) { 201 return error; 202 } 203 error = specificdata_key_create(fileassoc_domain, &key, NULL); 204 if (error) { 205 return error; 206 } 207 assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP); 208 assoc->name = name; 209 assoc->cleanup_cb = cleanup_cb; 210 assoc->key = key; 211 LIST_INSERT_HEAD(&fileassoc_list, assoc, list); 212 *result = assoc; 213 214 return 0; 215} 216 217/* 218 * Deregister a hook. 219 */ 220int 221fileassoc_deregister(fileassoc_t assoc) 222{ 223 224 LIST_REMOVE(assoc, list); 225 kmem_free(assoc, sizeof(*assoc)); 226 227 return 0; 228} 229 230/* 231 * Get the hash table for the specified device. 232 */ 233static struct fileassoc_table * 234fileassoc_table_lookup(struct mount *mp) 235{ 236 237 return mount_getspecific(mp, fileassoc_mountspecific_key); 238} 239 240/* 241 * Perform a lookup on a hash table. If hint is non-zero then use the value 242 * of the hint as the identifier instead of performing a lookup for the 243 * fileid. 244 */ 245static struct fileassoc_hash_entry * 246fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint) 247{ 248 struct fileassoc_table *tbl; 249 struct fileassoc_hashhead *tble; 250 struct fileassoc_hash_entry *e; 251 size_t indx; 252 fhandle_t *th; 253 int error; 254 255 tbl = fileassoc_table_lookup(vp->v_mount); 256 if (tbl == NULL) { 257 return NULL; 258 } 259 260 if (hint == NULL) { 261 error = vfs_composefh_alloc(vp, &th); 262 if (error) 263 return (NULL); 264 } else { 265 th = hint; 266 } 267 268 indx = FILEASSOC_HASH(tbl, th); 269 tble = &(tbl->hash_tbl[indx]); 270 271 LIST_FOREACH(e, tble, entries) { 272 if (((FHANDLE_FILEID(e->handle)->fid_len == 273 FHANDLE_FILEID(th)->fid_len)) && 274 (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th), 275 (FHANDLE_FILEID(th))->fid_len) == 0)) { 276 break; 277 } 278 } 279 280 if (hint == NULL) 281 vfs_composefh_free(th); 282 283 return e; 284} 285 286/* 287 * Return hook data associated with a vnode. 288 */ 289void * 290fileassoc_lookup(struct vnode *vp, fileassoc_t assoc) 291{ 292 struct fileassoc_hash_entry *mhe; 293 294 mhe = fileassoc_file_lookup(vp, NULL); 295 if (mhe == NULL) 296 return (NULL); 297 298 return file_getdata(mhe, assoc); 299} 300 301/* 302 * Create a new fileassoc table. 303 */ 304int 305fileassoc_table_add(struct mount *mp, size_t size) 306{ 307 struct fileassoc_table *tbl; 308 309 /* Check for existing table for device. */ 310 if (fileassoc_table_lookup(mp) != NULL) 311 return (EEXIST); 312 313 /* Allocate and initialize a table. */ 314 tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP); 315 tbl->hash_size = size; 316 tbl->hash_tbl = hashinit(size, HASH_LIST, M_TEMP, 317 M_WAITOK | M_ZERO, &tbl->hash_mask); 318 specificdata_init(fileassoc_domain, &tbl->data); 319 320 mount_setspecific(mp, fileassoc_mountspecific_key, tbl); 321 322 return (0); 323} 324 325/* 326 * Delete a table. 327 */ 328int 329fileassoc_table_delete(struct mount *mp) 330{ 331 struct fileassoc_table *tbl; 332 333 tbl = fileassoc_table_lookup(mp); 334 if (tbl == NULL) 335 return (EEXIST); 336 337 mount_setspecific(mp, fileassoc_mountspecific_key, NULL); 338 table_dtor(tbl); 339 340 return (0); 341} 342 343/* 344 * Run a callback for each hook entry in a table. 345 */ 346int 347fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb) 348{ 349 struct fileassoc_table *tbl; 350 struct fileassoc_hashhead *hh; 351 u_long i; 352 353 tbl = fileassoc_table_lookup(mp); 354 if (tbl == NULL) 355 return (EEXIST); 356 357 hh = tbl->hash_tbl; 358 for (i = 0; i < tbl->hash_size; i++) { 359 struct fileassoc_hash_entry *mhe; 360 361 LIST_FOREACH(mhe, &hh[i], entries) { 362 void *data; 363 364 data = file_getdata(mhe, assoc); 365 if (data != NULL) 366 cb(data); 367 } 368 } 369 370 return (0); 371} 372 373/* 374 * Clear a table for a given hook. 375 */ 376int 377fileassoc_table_clear(struct mount *mp, fileassoc_t assoc) 378{ 379 struct fileassoc_table *tbl; 380 struct fileassoc_hashhead *hh; 381 u_long i; 382 383 tbl = fileassoc_table_lookup(mp); 384 if (tbl == NULL) 385 return (EEXIST); 386 387 hh = tbl->hash_tbl; 388 for (i = 0; i < tbl->hash_size; i++) { 389 struct fileassoc_hash_entry *mhe; 390 391 LIST_FOREACH(mhe, &hh[i], entries) { 392 file_cleanup(mhe, assoc); 393 file_setdata(mhe, assoc, NULL); 394 } 395 } 396 397 return (0); 398} 399 400/* 401 * Add a file entry to a table. 402 */ 403static struct fileassoc_hash_entry * 404fileassoc_file_add(struct vnode *vp, fhandle_t *hint) 405{ 406 struct fileassoc_table *tbl; 407 struct fileassoc_hashhead *vhh; 408 struct fileassoc_hash_entry *e; 409 size_t indx; 410 fhandle_t *th; 411 int error; 412 413 if (hint == NULL) { 414 error = vfs_composefh_alloc(vp, &th); 415 if (error) 416 return (NULL); 417 } else 418 th = hint; 419 420 e = fileassoc_file_lookup(vp, th); 421 if (e != NULL) { 422 if (hint == NULL) 423 vfs_composefh_free(th); 424 425 return (e); 426 } 427 428 tbl = fileassoc_table_lookup(vp->v_mount); 429 if (tbl == NULL) { 430 if (hint == NULL) 431 vfs_composefh_free(th); 432 433 return (NULL); 434 } 435 436 indx = FILEASSOC_HASH(tbl, th); 437 vhh = &(tbl->hash_tbl[indx]); 438 439 e = kmem_zalloc(sizeof(*e), KM_SLEEP); 440 e->handle = th; 441 specificdata_init(fileassoc_domain, &e->data); 442 LIST_INSERT_HEAD(vhh, e, entries); 443 444 return (e); 445} 446 447/* 448 * Delete a file entry from a table. 449 */ 450int 451fileassoc_file_delete(struct vnode *vp) 452{ 453 struct fileassoc_hash_entry *mhe; 454 455 mhe = fileassoc_file_lookup(vp, NULL); 456 if (mhe == NULL) 457 return (ENOENT); 458 459 file_free(mhe); 460 461 return (0); 462} 463 464/* 465 * Add a hook to a vnode. 466 */ 467int 468fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data) 469{ 470 struct fileassoc_hash_entry *e; 471 void *olddata; 472 473 e = fileassoc_file_lookup(vp, NULL); 474 if (e == NULL) { 475 e = fileassoc_file_add(vp, NULL); 476 if (e == NULL) 477 return (ENOTDIR); 478 } 479 480 olddata = file_getdata(e, assoc); 481 if (olddata != NULL) 482 return (EEXIST); 483 484 file_setdata(e, assoc, data); 485 486 return (0); 487} 488 489/* 490 * Clear a hook from a vnode. 491 */ 492int 493fileassoc_clear(struct vnode *vp, fileassoc_t assoc) 494{ 495 struct fileassoc_hash_entry *mhe; 496 497 mhe = fileassoc_file_lookup(vp, NULL); 498 if (mhe == NULL) 499 return (ENOENT); 500 501 file_cleanup(mhe, assoc); 502 file_setdata(mhe, assoc, NULL); 503 504 return (0); 505} 506