kern_fileassoc.c revision 1.11
1/* $NetBSD: kern_fileassoc.c,v 1.11 2006/10/27 22:17:09 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.11 2006/10/27 22:17:09 elad Exp $"); 35 36#include <sys/param.h> 37#include <sys/mount.h> 38#include <sys/queue.h> 39#include <sys/malloc.h> 40#include <sys/vnode.h> 41#include <sys/namei.h> 42#include <sys/exec.h> 43#include <sys/proc.h> 44#include <sys/inttypes.h> 45#include <sys/errno.h> 46#include <sys/fileassoc.h> 47#include <sys/hash.h> 48#include <sys/fstypes.h> 49 50static struct fileassoc_hash_entry * 51fileassoc_file_lookup(struct vnode *, fhandle_t *); 52static struct fileassoc_hash_entry * 53fileassoc_file_add(struct vnode *, fhandle_t *); 54 55/* 56 * Hook entry. 57 * Includes the hook name for identification and private hook clear callback. 58 */ 59struct fileassoc_hook { 60 const char *hook_name; /* Hook name. */ 61 fileassoc_cleanup_cb_t hook_cleanup_cb; /* Hook clear callback. */ 62}; 63 64/* An entry in the per-device hash table. */ 65struct fileassoc_hash_entry { 66 fhandle_t *handle; /* File handle */ 67 void *hooks[FILEASSOC_NHOOKS]; /* Hooks. */ 68 LIST_ENTRY(fileassoc_hash_entry) entries; /* List pointer. */ 69}; 70 71LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry); 72 73struct fileassoc_table { 74 struct fileassoc_hashhead *hash_tbl; 75 size_t hash_size; /* Number of slots. */ 76 struct mount *tbl_mntpt; 77 u_long hash_mask; 78 void *tables[FILEASSOC_NHOOKS]; 79 LIST_ENTRY(fileassoc_table) hash_list; /* List pointer. */ 80}; 81 82struct fileassoc_hook fileassoc_hooks[FILEASSOC_NHOOKS]; 83int fileassoc_nhooks; 84 85/* Global list of hash tables, one per device. */ 86LIST_HEAD(, fileassoc_table) fileassoc_tables; 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 96/* 97 * Initialize the fileassoc subsystem. 98 */ 99void 100fileassoc_init(void) 101{ 102 memset(fileassoc_hooks, 0, sizeof(fileassoc_hooks)); 103 fileassoc_nhooks = 0; 104} 105 106/* 107 * Register a new hook. 108 */ 109fileassoc_t 110fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb) 111{ 112 int i; 113 114 if (fileassoc_nhooks >= FILEASSOC_NHOOKS) 115 return (-1); 116 117 for (i = 0; i < FILEASSOC_NHOOKS; i++) 118 if (fileassoc_hooks[i].hook_name == NULL) 119 break; 120 121 fileassoc_hooks[i].hook_name = name; 122 fileassoc_hooks[i].hook_cleanup_cb = cleanup_cb; 123 124 fileassoc_nhooks++; 125 126 return (i); 127} 128 129/* 130 * Deregister a hook. 131 */ 132int 133fileassoc_deregister(fileassoc_t id) 134{ 135 if (id < 0 || id >= FILEASSOC_NHOOKS) 136 return (EINVAL); 137 138 fileassoc_hooks[id].hook_name = NULL; 139 fileassoc_hooks[id].hook_cleanup_cb = NULL; 140 141 fileassoc_nhooks--; 142 143 return (0); 144} 145 146/* 147 * Get the hash table for the specified device. 148 */ 149static struct fileassoc_table * 150fileassoc_table_lookup(struct mount *mp) 151{ 152 struct fileassoc_table *tbl; 153 154 LIST_FOREACH(tbl, &fileassoc_tables, hash_list) { 155 if (tbl->tbl_mntpt == mp) 156 return (tbl); 157 } 158 159 return (NULL); 160} 161 162/* 163 * Perform a lookup on a hash table. If hint is non-zero then use the value 164 * of the hint as the identifier instead of performing a lookup for the 165 * fileid. 166 */ 167static struct fileassoc_hash_entry * 168fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint) 169{ 170 struct fileassoc_table *tbl; 171 struct fileassoc_hashhead *tble; 172 struct fileassoc_hash_entry *e; 173 size_t indx; 174 fhandle_t *th; 175 int error; 176 177 if (hint == NULL) { 178 error = vfs_composefh_alloc(vp, &th); 179 if (error) 180 return (NULL); 181 } else 182 th = hint; 183 184 tbl = fileassoc_table_lookup(vp->v_mount); 185 if (tbl == NULL) { 186 if (hint == NULL) 187 vfs_composefh_free(th); 188 189 return (NULL); 190 } 191 192 indx = FILEASSOC_HASH(tbl, th); 193 tble = &(tbl->hash_tbl[indx]); 194 195 LIST_FOREACH(e, tble, entries) { 196 if ((e != NULL) && 197 ((FHANDLE_FILEID(e->handle)->fid_len == 198 FHANDLE_FILEID(th)->fid_len)) && 199 (memcmp(FHANDLE_FILEID(e->handle), FHANDLE_FILEID(th), 200 (FHANDLE_FILEID(th))->fid_len) == 0)) { 201 if (hint == NULL) 202 vfs_composefh_free(th); 203 204 return (e); 205 } 206 } 207 208 if (hint == NULL) 209 vfs_composefh_free(th); 210 211 return (NULL); 212} 213 214/* 215 * Return hook data associated with a vnode. 216 */ 217void * 218fileassoc_lookup(struct vnode *vp, fileassoc_t id) 219{ 220 struct fileassoc_hash_entry *mhe; 221 222 mhe = fileassoc_file_lookup(vp, NULL); 223 if (mhe == NULL) 224 return (NULL); 225 226 return (mhe->hooks[id]); 227} 228 229/* 230 * Create a new fileassoc table. 231 */ 232int 233fileassoc_table_add(struct mount *mp, size_t size) 234{ 235 struct fileassoc_table *tbl; 236 237 /* Check for existing table for device. */ 238 if (fileassoc_table_lookup(mp) != NULL) 239 return (EEXIST); 240 241 /* Allocate and initialize a Veriexec hash table. */ 242 tbl = malloc(sizeof(*tbl), M_TEMP, M_WAITOK | M_ZERO); 243 tbl->hash_size = size; 244 tbl->tbl_mntpt = mp; 245 tbl->hash_tbl = hashinit(size, HASH_LIST, M_TEMP, 246 M_WAITOK | M_ZERO, &tbl->hash_mask); 247 248 LIST_INSERT_HEAD(&fileassoc_tables, tbl, hash_list); 249 250 return (0); 251} 252 253/* 254 * Delete a table. 255 */ 256int 257fileassoc_table_delete(struct mount *mp) 258{ 259 struct fileassoc_table *tbl; 260 struct fileassoc_hashhead *hh; 261 u_long i; 262 int j; 263 264 tbl = fileassoc_table_lookup(mp); 265 if (tbl == NULL) 266 return (EEXIST); 267 268 /* Remove all entries from the table and lists */ 269 hh = tbl->hash_tbl; 270 for (i = 0; i < tbl->hash_size; i++) { 271 struct fileassoc_hash_entry *mhe; 272 273 while (LIST_FIRST(&hh[i]) != NULL) { 274 mhe = LIST_FIRST(&hh[i]); 275 LIST_REMOVE(mhe, entries); 276 277 for (j = 0; j < fileassoc_nhooks; j++) 278 if (fileassoc_hooks[j].hook_cleanup_cb != NULL) 279 (fileassoc_hooks[j].hook_cleanup_cb) 280 (mhe->hooks[j], 281 FILEASSOC_CLEANUP_FILE); 282 283 vfs_composefh_free(mhe->handle); 284 free(mhe, M_TEMP); 285 } 286 } 287 288 for (j = 0; j < fileassoc_nhooks; j++) 289 if (fileassoc_hooks[j].hook_cleanup_cb != NULL) 290 (fileassoc_hooks[j].hook_cleanup_cb)(tbl->tables[j], 291 FILEASSOC_CLEANUP_TABLE); 292 293 /* Remove hash table and sysctl node */ 294 hashdone(tbl->hash_tbl, M_TEMP); 295 LIST_REMOVE(tbl, hash_list); 296 297 return (0); 298} 299 300/* 301 * Run a callback for each hook entry in a table. 302 */ 303int 304fileassoc_table_run(struct mount *mp, fileassoc_t id, fileassoc_cb_t cb) 305{ 306 struct fileassoc_table *tbl; 307 struct fileassoc_hashhead *hh; 308 u_long i; 309 310 tbl = fileassoc_table_lookup(mp); 311 if (tbl == NULL) 312 return (EEXIST); 313 314 hh = tbl->hash_tbl; 315 for (i = 0; i < tbl->hash_size; i++) { 316 struct fileassoc_hash_entry *mhe; 317 318 LIST_FOREACH(mhe, &hh[i], entries) { 319 if (mhe->hooks[id] != NULL) 320 cb(mhe->hooks[id]); 321 } 322 } 323 324 return (0); 325} 326 327/* 328 * Clear a table for a given hook. 329 */ 330int 331fileassoc_table_clear(struct mount *mp, fileassoc_t id) 332{ 333 struct fileassoc_table *tbl; 334 struct fileassoc_hashhead *hh; 335 fileassoc_cleanup_cb_t cleanup_cb; 336 u_long i; 337 338 tbl = fileassoc_table_lookup(mp); 339 if (tbl == NULL) 340 return (EEXIST); 341 342 cleanup_cb = fileassoc_hooks[id].hook_cleanup_cb; 343 344 hh = tbl->hash_tbl; 345 for (i = 0; i < tbl->hash_size; i++) { 346 struct fileassoc_hash_entry *mhe; 347 348 LIST_FOREACH(mhe, &hh[i], entries) { 349 if ((mhe->hooks[id] != NULL) && cleanup_cb != NULL) 350 cleanup_cb(mhe->hooks[id], 351 FILEASSOC_CLEANUP_FILE); 352 353 mhe->hooks[id] = NULL; 354 } 355 } 356 357 if ((tbl->tables[id] != NULL) && cleanup_cb != NULL) 358 cleanup_cb(tbl->tables[id], FILEASSOC_CLEANUP_TABLE); 359 360 tbl->tables[id] = NULL; 361 362 return (0); 363} 364 365/* 366 * Add hook-specific data on a fileassoc table. 367 */ 368int 369fileassoc_tabledata_add(struct mount *mp, fileassoc_t id, void *data) 370{ 371 struct fileassoc_table *tbl; 372 373 tbl = fileassoc_table_lookup(mp); 374 if (tbl == NULL) 375 return (EFAULT); 376 377 tbl->tables[id] = data; 378 379 return (0); 380} 381 382/* 383 * Clear hook-specific data on a fileassoc table. 384 */ 385int 386fileassoc_tabledata_clear(struct mount *mp, fileassoc_t id) 387{ 388 struct fileassoc_table *tbl; 389 390 tbl = fileassoc_table_lookup(mp); 391 if (tbl == NULL) 392 return (EFAULT); 393 394 tbl->tables[id] = NULL; 395 396 return (0); 397} 398 399/* 400 * Retrieve hook-specific data from a fileassoc table. 401 */ 402void * 403fileassoc_tabledata_lookup(struct mount *mp, fileassoc_t id) 404{ 405 struct fileassoc_table *tbl; 406 407 tbl = fileassoc_table_lookup(mp); 408 if (tbl == NULL) 409 return (NULL); 410 411 return (tbl->tables[id]); 412} 413 414/* 415 * Add a file entry to a table. 416 */ 417static struct fileassoc_hash_entry * 418fileassoc_file_add(struct vnode *vp, fhandle_t *hint) 419{ 420 struct fileassoc_table *tbl; 421 struct fileassoc_hashhead *vhh; 422 struct fileassoc_hash_entry *e; 423 size_t indx; 424 fhandle_t *th; 425 int error; 426 427 if (hint == 0) { 428 error = vfs_composefh_alloc(vp, &th); 429 if (error) 430 return (NULL); 431 } else 432 th = hint; 433 434 e = fileassoc_file_lookup(vp, th); 435 if (e != NULL) { 436 if (hint == NULL) 437 vfs_composefh_free(th); 438 439 return (e); 440 } 441 442 tbl = fileassoc_table_lookup(vp->v_mount); 443 if (tbl == NULL) { 444 if (hint == NULL) 445 vfs_composefh_free(th); 446 447 return (NULL); 448 } 449 450 indx = FILEASSOC_HASH(tbl, th); 451 vhh = &(tbl->hash_tbl[indx]); 452 453 e = malloc(sizeof(*e), M_TEMP, M_WAITOK | M_ZERO); 454 e->handle = th; 455 LIST_INSERT_HEAD(vhh, e, entries); 456 457 return (e); 458} 459 460/* 461 * Delete a file entry from a table. 462 */ 463int 464fileassoc_file_delete(struct vnode *vp) 465{ 466 struct fileassoc_hash_entry *mhe; 467 int i; 468 469 mhe = fileassoc_file_lookup(vp, NULL); 470 if (mhe == NULL) 471 return (ENOENT); 472 473 LIST_REMOVE(mhe, entries); 474 475 for (i = 0; i < fileassoc_nhooks; i++) 476 if (fileassoc_hooks[i].hook_cleanup_cb != NULL) 477 (fileassoc_hooks[i].hook_cleanup_cb)(mhe->hooks[i], 478 FILEASSOC_CLEANUP_FILE); 479 480 free(mhe, M_TEMP); 481 482 return (0); 483} 484 485/* 486 * Add a hook to a vnode. 487 */ 488int 489fileassoc_add(struct vnode *vp, fileassoc_t id, void *data) 490{ 491 struct fileassoc_hash_entry *e; 492 493 e = fileassoc_file_lookup(vp, NULL); 494 if (e == NULL) { 495 e = fileassoc_file_add(vp, NULL); 496 if (e == NULL) 497 return (ENOTDIR); 498 } 499 500 if (e->hooks[id] != NULL) 501 return (EEXIST); 502 503 e->hooks[id] = data; 504 505 return (0); 506} 507 508/* 509 * Clear a hook from a vnode. 510 */ 511int 512fileassoc_clear(struct vnode *vp, fileassoc_t id) 513{ 514 struct fileassoc_hash_entry *mhe; 515 fileassoc_cleanup_cb_t cleanup_cb; 516 517 mhe = fileassoc_file_lookup(vp, NULL); 518 if (mhe == NULL) 519 return (ENOENT); 520 521 cleanup_cb = fileassoc_hooks[id].hook_cleanup_cb; 522 if ((mhe->hooks[id] != NULL) && cleanup_cb != NULL) 523 cleanup_cb(mhe->hooks[id], FILEASSOC_CLEANUP_FILE); 524 525 mhe->hooks[id] = NULL; 526 527 return (0); 528} 529