1 /* $NetBSD: kern_fileassoc.c,v 1.10 2006/09/08 13:57:38 blymn 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.10 2006/09/08 13:57:38 blymn 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 50 static struct fileassoc_hash_entry * 51 fileassoc_file_lookup(struct vnode *, fhandle_t *); 52 static struct fileassoc_hash_entry * 53 fileassoc_file_add(struct vnode *, fhandle_t *); 54 55 /* 56 * Hook entry. 57 * Includes the hook name for identification and private hook clear callback. 58 */ 59 struct 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. */ 65 struct 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 71 LIST_HEAD(fileassoc_hashhead, fileassoc_hash_entry); 72 73 struct 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 82 struct fileassoc_hook fileassoc_hooks[FILEASSOC_NHOOKS]; 83 int fileassoc_nhooks; 84 85 /* Global list of hash tables, one per device. */ 86 LIST_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 */ 99 void 100 fileassoc_init(void) 101 { 102 memset(fileassoc_hooks, 0, sizeof(fileassoc_hooks)); 103 fileassoc_nhooks = 0; 104 } 105 106 /* 107 * Register a new hook. 108 */ 109 fileassoc_t 110 fileassoc_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 */ 132 int 133 fileassoc_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 */ 149 static struct fileassoc_table * 150 fileassoc_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 */ 167 static struct fileassoc_hash_entry * 168 fileassoc_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 return (e); 202 } 203 204 if (hint == NULL) 205 vfs_composefh_free(th); 206 207 return (NULL); 208 } 209 210 /* 211 * Return hook data associated with a vnode. 212 */ 213 void * 214 fileassoc_lookup(struct vnode *vp, fileassoc_t id) 215 { 216 struct fileassoc_hash_entry *mhe; 217 218 mhe = fileassoc_file_lookup(vp, NULL); 219 if (mhe == NULL) 220 return (NULL); 221 222 return (mhe->hooks[id]); 223 } 224 225 /* 226 * Create a new fileassoc table. 227 */ 228 int 229 fileassoc_table_add(struct mount *mp, size_t size) 230 { 231 struct fileassoc_table *tbl; 232 233 /* Check for existing table for device. */ 234 if (fileassoc_table_lookup(mp) != NULL) 235 return (EEXIST); 236 237 /* Allocate and initialize a Veriexec hash table. */ 238 tbl = malloc(sizeof(*tbl), M_TEMP, M_WAITOK | M_ZERO); 239 tbl->hash_size = size; 240 tbl->tbl_mntpt = mp; 241 tbl->hash_tbl = hashinit(size, HASH_LIST, M_TEMP, 242 M_WAITOK | M_ZERO, &tbl->hash_mask); 243 244 LIST_INSERT_HEAD(&fileassoc_tables, tbl, hash_list); 245 246 return (0); 247 } 248 249 /* 250 * Delete a table. 251 */ 252 int 253 fileassoc_table_delete(struct mount *mp) 254 { 255 struct fileassoc_table *tbl; 256 struct fileassoc_hashhead *hh; 257 u_long i; 258 int j; 259 260 tbl = fileassoc_table_lookup(mp); 261 if (tbl == NULL) 262 return (EEXIST); 263 264 /* Remove all entries from the table and lists */ 265 hh = tbl->hash_tbl; 266 for (i = 0; i < tbl->hash_size; i++) { 267 struct fileassoc_hash_entry *mhe; 268 269 while (LIST_FIRST(&hh[i]) != NULL) { 270 mhe = LIST_FIRST(&hh[i]); 271 LIST_REMOVE(mhe, entries); 272 273 for (j = 0; j < fileassoc_nhooks; j++) 274 if (fileassoc_hooks[j].hook_cleanup_cb != NULL) 275 (fileassoc_hooks[j].hook_cleanup_cb) 276 (mhe->hooks[j], 277 FILEASSOC_CLEANUP_FILE); 278 279 vfs_composefh_free(mhe->handle); 280 free(mhe, M_TEMP); 281 } 282 } 283 284 for (j = 0; j < fileassoc_nhooks; j++) 285 if (fileassoc_hooks[j].hook_cleanup_cb != NULL) 286 (fileassoc_hooks[j].hook_cleanup_cb)(tbl->tables[j], 287 FILEASSOC_CLEANUP_TABLE); 288 289 /* Remove hash table and sysctl node */ 290 hashdone(tbl->hash_tbl, M_TEMP); 291 LIST_REMOVE(tbl, hash_list); 292 293 return (0); 294 } 295 296 /* 297 * Run a callback for each hook entry in a table. 298 */ 299 int 300 fileassoc_table_run(struct mount *mp, fileassoc_t id, fileassoc_cb_t cb) 301 { 302 struct fileassoc_table *tbl; 303 struct fileassoc_hashhead *hh; 304 u_long i; 305 306 tbl = fileassoc_table_lookup(mp); 307 if (tbl == NULL) 308 return (EEXIST); 309 310 hh = tbl->hash_tbl; 311 for (i = 0; i < tbl->hash_size; i++) { 312 struct fileassoc_hash_entry *mhe; 313 314 LIST_FOREACH(mhe, &hh[i], entries) { 315 if (mhe->hooks[id] != NULL) 316 cb(mhe->hooks[id]); 317 } 318 } 319 320 return (0); 321 } 322 323 /* 324 * Clear a table for a given hook. 325 */ 326 int 327 fileassoc_table_clear(struct mount *mp, fileassoc_t id) 328 { 329 struct fileassoc_table *tbl; 330 struct fileassoc_hashhead *hh; 331 fileassoc_cleanup_cb_t cleanup_cb; 332 u_long i; 333 334 tbl = fileassoc_table_lookup(mp); 335 if (tbl == NULL) 336 return (EEXIST); 337 338 cleanup_cb = fileassoc_hooks[id].hook_cleanup_cb; 339 340 hh = tbl->hash_tbl; 341 for (i = 0; i < tbl->hash_size; i++) { 342 struct fileassoc_hash_entry *mhe; 343 344 LIST_FOREACH(mhe, &hh[i], entries) { 345 if ((mhe->hooks[id] != NULL) && cleanup_cb != NULL) 346 cleanup_cb(mhe->hooks[id], 347 FILEASSOC_CLEANUP_FILE); 348 349 mhe->hooks[id] = NULL; 350 } 351 } 352 353 if ((tbl->tables[id] != NULL) && cleanup_cb != NULL) 354 cleanup_cb(tbl->tables[id], FILEASSOC_CLEANUP_TABLE); 355 356 tbl->tables[id] = NULL; 357 358 return (0); 359 } 360 361 /* 362 * Add hook-specific data on a fileassoc table. 363 */ 364 int 365 fileassoc_tabledata_add(struct mount *mp, fileassoc_t id, void *data) 366 { 367 struct fileassoc_table *tbl; 368 369 tbl = fileassoc_table_lookup(mp); 370 if (tbl == NULL) 371 return (EFAULT); 372 373 tbl->tables[id] = data; 374 375 return (0); 376 } 377 378 /* 379 * Clear hook-specific data on a fileassoc table. 380 */ 381 int 382 fileassoc_tabledata_clear(struct mount *mp, fileassoc_t id) 383 { 384 struct fileassoc_table *tbl; 385 386 tbl = fileassoc_table_lookup(mp); 387 if (tbl == NULL) 388 return (EFAULT); 389 390 tbl->tables[id] = NULL; 391 392 return (0); 393 } 394 395 /* 396 * Retrieve hook-specific data from a fileassoc table. 397 */ 398 void * 399 fileassoc_tabledata_lookup(struct mount *mp, fileassoc_t id) 400 { 401 struct fileassoc_table *tbl; 402 403 tbl = fileassoc_table_lookup(mp); 404 if (tbl == NULL) 405 return (NULL); 406 407 return (tbl->tables[id]); 408 } 409 410 /* 411 * Add a file entry to a table. 412 */ 413 static struct fileassoc_hash_entry * 414 fileassoc_file_add(struct vnode *vp, fhandle_t *hint) 415 { 416 struct fileassoc_table *tbl; 417 struct fileassoc_hashhead *vhh; 418 struct fileassoc_hash_entry *e; 419 size_t indx; 420 fhandle_t *th; 421 int error; 422 423 if (hint == 0) { 424 error = vfs_composefh_alloc(vp, &th); 425 if (error) 426 return (NULL); 427 } else 428 th = hint; 429 430 e = fileassoc_file_lookup(vp, th); 431 if (e != NULL) { 432 if (hint == NULL) 433 vfs_composefh_free(th); 434 435 return (e); 436 } 437 438 tbl = fileassoc_table_lookup(vp->v_mount); 439 if (tbl == NULL) { 440 if (hint == NULL) 441 vfs_composefh_free(th); 442 443 return (NULL); 444 } 445 446 indx = FILEASSOC_HASH(tbl, th); 447 vhh = &(tbl->hash_tbl[indx]); 448 449 e = malloc(sizeof(*e), M_TEMP, M_WAITOK | M_ZERO); 450 e->handle = th; 451 LIST_INSERT_HEAD(vhh, e, entries); 452 453 return (e); 454 } 455 456 /* 457 * Delete a file entry from a table. 458 */ 459 int 460 fileassoc_file_delete(struct vnode *vp) 461 { 462 struct fileassoc_hash_entry *mhe; 463 int i; 464 465 mhe = fileassoc_file_lookup(vp, NULL); 466 if (mhe == NULL) 467 return (ENOENT); 468 469 LIST_REMOVE(mhe, entries); 470 471 for (i = 0; i < fileassoc_nhooks; i++) 472 if (fileassoc_hooks[i].hook_cleanup_cb != NULL) 473 (fileassoc_hooks[i].hook_cleanup_cb)(mhe->hooks[i], 474 FILEASSOC_CLEANUP_FILE); 475 476 free(mhe, M_TEMP); 477 478 return (0); 479 } 480 481 /* 482 * Add a hook to a vnode. 483 */ 484 int 485 fileassoc_add(struct vnode *vp, fileassoc_t id, void *data) 486 { 487 struct fileassoc_hash_entry *e; 488 489 e = fileassoc_file_lookup(vp, NULL); 490 if (e == NULL) { 491 e = fileassoc_file_add(vp, NULL); 492 if (e == NULL) 493 return (ENOTDIR); 494 } 495 496 if (e->hooks[id] != NULL) 497 return (EEXIST); 498 499 e->hooks[id] = data; 500 501 return (0); 502 } 503 504 /* 505 * Clear a hook from a vnode. 506 */ 507 int 508 fileassoc_clear(struct vnode *vp, fileassoc_t id) 509 { 510 struct fileassoc_hash_entry *mhe; 511 fileassoc_cleanup_cb_t cleanup_cb; 512 513 mhe = fileassoc_file_lookup(vp, NULL); 514 if (mhe == NULL) 515 return (ENOENT); 516 517 cleanup_cb = fileassoc_hooks[id].hook_cleanup_cb; 518 if ((mhe->hooks[id] != NULL) && cleanup_cb != NULL) 519 cleanup_cb(mhe->hooks[id], FILEASSOC_CLEANUP_FILE); 520 521 mhe->hooks[id] = NULL; 522 523 return (0); 524 } 525