1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2013 Damian Bogel. All rights reserved. 14 */ 15 16 /* 17 * Filesystem disturber pseudo-device driver. 18 */ 19 20 #include <sys/conf.h> 21 #include <sys/ddi.h> 22 #include <sys/file.h> 23 #include <sys/fsd.h> 24 #include <sys/fsh.h> 25 #include <sys/kmem.h> 26 #include <sys/ksynch.h> 27 #include <sys/list.h> 28 #include <sys/mkdev.h> 29 #include <sys/refstr.h> 30 #include <sys/stat.h> 31 #include <sys/sunddi.h> 32 #include <sys/sysmacros.h> 33 #include <sys/types.h> 34 35 /* 36 * TODO: 37 * - add checking if a file descriptor passed by the client is indeed 38 * a mountpoint (we'd like to avoid disturbing / instead of an 39 * unmounted filesystem) 40 */ 41 /* 42 * fsd - filesystem disturber 43 * 44 * 1. Abstract 45 * Filesystem disturber is a pseudo-device driver used to inject pathological 46 * behaviour into vfs calls. It is NOT a fuzzer. That kind of behaviour 47 * should be expected and correctly handled by software. A simple example of 48 * such behaviour is read() reading less bytes than it was requested. It's 49 * well documented and every read() caller should check the return value of 50 * this function before proceeding. 51 * 52 * 2. Features 53 * * per-vfs injections 54 * * injection installing on every newly mounted vfs (that's called an 55 * omnipresent disturber) 56 * 57 * 3. Usage 58 * fsd_t is a structure which contains all the parameters for the disturbers. 59 * This structure is shared by all hooks on a vfs_t. 60 * 61 * fsd_info_t is filled out by a call to ioctl() and it provides basic 62 * information about fsd's current status. 63 * 64 * fsd_dis_t is passed to ioctl() when a request to disturb a filesystem is 65 * made. It's just a descriptor of a representative file and an fsd_t structure. 66 * 67 * fsd_fs_t is a structure filled out by ioctl() call when the client requests a 68 * full list of disturbers installed in the system. 69 * 70 * fsd_ioc_t is an union for different ioctl() commands. 71 * 72 * ioctl() commands: 73 * FSD_ENABLE: 74 * ioctl(fd, FSD_ENABLE); 75 * Enables the fsd. When fsd is enabled, any attemps to detach the driver 76 * will fail. 77 * 78 * FSD_DISABLE: 79 * ioctl(fd, FSD_DISABLE); 80 * Disables the fsd. 81 * 82 * FSD_GET_PARAM: 83 * ioctl(fd, FSD_GET_PARAM, ioc); 84 * Get's fsd_t associated with a given filesystem. ioc is fsdioc_mnt when 85 * passed to ioctl(). fsdioc_param is the output. 86 * Errors: 87 * ENOENT - the filesystem is not being disturbed 88 * 89 * FSD_DISTURB: 90 * ioctl(fd, FSD_DISTURB, ioc); 91 * Installs a disturber on a given filesystem. If a disturber is already 92 * installed on this filesystem, it overwrites it. ioc is fsdioc_dis. 93 * Errors: 94 * EAGAIN - hook limit exceeded 95 * EBADFD - cannot open the file descriptor 96 * EINVAL - parameters are invalid 97 * 98 * FSD_DISTURB_OFF: 99 * ioctl(fd, FSD_DISTURB_OFF, ioc); 100 * Removes a disturber from a given filesystem. ioc is fsdioc_mnt 101 * Errors: 102 * EBADFD - cannot open the file descriptor 103 * ENOENT - the filesystem is not being disturbed 104 * 105 * FSD_DISTURB_OMNI: 106 * ioctl(fd, FSD_DISTURB_OMNI, ioc); 107 * Install an omnipresent disturber. It means that whenever a new vfs_t is 108 * being created, this disturber is installed on it. If an omnipresent 109 * disturber is already installed, it overwrites it. ioc is fsdioc_param 110 * Errors: 111 * EINVAL - parameters are invalid 112 * 113 * FSD_DISTURB_OMNI_OFF: 114 * ioctl(fd, FSD_DISTURB_OMNI_OFF); 115 * Removes the omnipresent disturber. That does NOT mean that filesystems 116 * which are disturbed because of the omnipresent disturber presence in the 117 * past are going to stop being disturbed after this call. 118 * 119 * FSD_GET_LIST: 120 * ioctl(fd, FSD_GET_LIST, ioc); 121 * Get's a full list of disturbers installed in the system. ioc is 122 * fsdioc_list here. This is a structure with two fields, count and listp. 123 * The count is the number of fsd_fs_t's allocated on the address that 124 * listp is pointing to. There would be at most count fsd_fs_t entries 125 * copied out to the caller. Also, count is set to the number of entries 126 * copied out. 127 * 128 * FSD_GET_INFO: 129 * ioctl(fd, FSD_GET_INFO, ioc); 130 * Get's current information about fsd. ioc is fsdioc_info here. 131 * 132 * At most one hook is installed per vfs_t, and fsd_t describes all possible 133 * disturbance methods. Multiple commands using the fsd should somehow cooperate 134 * in order not to destroy each other efforts in installing disturbers. 135 * 136 * 4. Internals 137 * When fsd_enabled is nonzero, fsd_detach() fails. 138 * 139 * fsd_attach() does the necessary mount, free callbacks installing. 140 * fsd_detach() does the removing. 141 * These callbacks are used for both installing injections on newly mounted 142 * vfs_t's (omnipresent) and cleaning up when a vfs_t is destroyed. 143 * (fsd_callback_{mount, free}) 144 * 145 * The list of currently installed hooks is kept in fsd_list. 146 * 147 * fsd installs at most one hook on a vfs_t. 148 * 149 * 5. Locking 150 * Every modification of fsd_enable, fsd_hooks, fsd_omni_param and fsd_list is 151 * protected by fsd_lock. 152 * 153 * Hooks use only the elements of fsd_list, nothing else. Before an element of 154 * fsd_list is destroyed, a hook which uses it is removed. Elements from 155 * fsd_lists are removed and destroyed in the hook remove callback 156 * (fsd_remove_cb). 157 * 158 * Because of the fact that fsd_remove_cb() could be called both in the context 159 * of the thread that executes fsh_hook_remove() or outside the fsd, we need to 160 * use fsd_rem_thread in order not to cause a deadlock. fsh_hook_remove() could 161 * be called by at most one thread inside fsd (both fsd_remove_disturber() and 162 * fsd_callback_free() hold fsd_lock while doing that). We just have to check 163 * inside fsd_remove_cb() if it was called from fsh_hook_remove() or not. We use 164 * fsd_rem_thread to determine that. 165 * 166 * fsd_int_t.fsdi_param is protected by fsd_int_t.fsdi_lock which is an rwlock. 167 */ 168 169 /* 170 * Once a set of hooks is installed on a filesystem, there's no need 171 * to bother fsh if we want to change the parameters of disturbance. 172 * Intead, we use fsd_lock to protect the fsd_int_t when it's being 173 * used or changed. 174 */ 175 typedef struct fsd_int { 176 krwlock_t fsdi_lock; /* protects fsd_param */ 177 fsd_t fsdi_param; 178 fsh_handle_t fsdi_handle; /* we use fsh's handle in fsd */ 179 vfs_t *fsdi_vfsp; 180 int fsdi_doomed; 181 list_node_t fsdi_next; 182 } fsd_int_t; 183 184 static dev_info_t *fsd_devi; 185 186 187 /* procets: fsd_enabled, fsd_omni_param, fsd_list, fsd_cb_handle */ 188 static kmutex_t fsd_lock; 189 190 static kthread_t *fsd_rem_thread; 191 static kmutex_t fsd_rem_thread_lock; 192 193 static fsd_t *fsd_omni_param; /* Argument used by fsd's mount callback. */ 194 static fsh_callback_handle_t fsd_cb_handle; 195 static int fsd_enabled; 196 197 /* 198 * List of fsd_int_t. For every vfs_t on which fsd has installed a set of hooks 199 * there exist exactly one fsd_int_t with fsdi_vfsp pointing to this vfs_t. 200 */ 201 static list_t fsd_list; 202 static int fsd_list_count; 203 static kcondvar_t fsd_cv_empty; 204 205 206 /* 207 * Although it's safe to use this kind of pseudo-random number generator, 208 * it behaves very regular when it comes to parity. Every fsd_rand() call 209 * changes the parity of the seed. That's why when a range of width 2 is set 210 * as a parameter, it's highly possible that the random value will always be 211 * the same, because fsd_rand() could be called the same number of times in a 212 * hook. 213 */ 214 static long fsd_rand_seed; 215 216 static int 217 fsd_rand() 218 { 219 fsd_rand_seed = fsd_rand_seed * 1103515245L + 12345; 220 return (fsd_rand_seed & 0x7ffffffff); 221 } 222 223 /* vnode hooks */ 224 /* 225 * A pointer to a given fsd_int_t is valid always inside fsh_hook_xxx() 226 * call, because it's valid until the hooks associated with it are removed. 227 * If a hook is removed, it cannot be executing. 228 */ 229 static int 230 fsd_hook_read(fsh_int_t *fshi, void *arg, vnode_t *vp, uio_t *uiop, 231 int ioflag, cred_t *cr, caller_context_t *ct) 232 { 233 fsd_int_t *fsdi = (fsd_int_t *)arg; 234 uint64_t count, less, less_chance; 235 236 /* 237 * It is used to keep an odd number of fsd_rand() calls in every 238 * fsd_hook_read() call. That is desired because when a range of width 239 * 2 is set as a parameter, we don't want to make it a constant. 240 * The pseudo-random number generator returns a number with different 241 * parity with every call. If this function is called in every 242 * fsd_hook_read() execution even number of times, it would always be 243 * the same % 2. 244 */ 245 (void) fsd_rand(); 246 247 ASSERT(vp->v_vfsp == fsdi->fsdi_vfsp); 248 249 rw_enter(&fsdi->fsdi_lock, RW_READER); 250 less_chance = fsdi->fsdi_param.read_less_chance; 251 less = (uint64_t)fsd_rand() % 252 (fsdi->fsdi_param.read_less_r[1] + 1 - 253 fsdi->fsdi_param.read_less_r[0]) + fsdi->fsdi_param.read_less_r[0]; 254 rw_exit(&fsdi->fsdi_lock); 255 256 count = uiop->uio_iov->iov_len; 257 if ((uint64_t)fsd_rand() % 100 < less_chance) { 258 extern size_t copyout_max_cached; 259 int ret; 260 261 if (count > less) 262 count -= less; 263 else 264 less = 0; 265 266 uiop->uio_iov->iov_len = count; 267 uiop->uio_resid = count; 268 if (count <= copyout_max_cached) 269 uiop->uio_extflg = UIO_COPY_CACHED; 270 else 271 uiop->uio_extflg = UIO_COPY_DEFAULT; 272 273 ret = fsh_next_read(fshi, vp, uiop, ioflag, cr, ct); 274 uiop->uio_resid += less; 275 return (ret); 276 } 277 278 return (fsh_next_read(fshi, vp, uiop, ioflag, cr, ct)); 279 } 280 281 282 static void 283 fsd_remove_cb(void *arg, fsh_handle_t handle) 284 { 285 _NOTE(ARGUNUSED(handle)); 286 287 fsd_int_t *fsdi = (fsd_int_t *)arg; 288 int fsd_context; 289 290 mutex_enter(&fsd_rem_thread_lock); 291 fsd_context = fsd_rem_thread == curthread; 292 mutex_exit(&fsd_rem_thread_lock); 293 294 if (!fsd_context) 295 mutex_enter(&fsd_lock); 296 297 list_remove(&fsd_list, fsdi); 298 fsd_list_count--; 299 if (fsd_list_count == 0) 300 cv_signal(&fsd_cv_empty); 301 302 rw_destroy(&fsdi->fsdi_lock); 303 kmem_free(fsdi, sizeof (*fsdi)); 304 305 if (!fsd_context) 306 mutex_exit(&fsd_lock); 307 } 308 309 /* 310 * Installs a set of hook with given parameters on a vfs_t. 311 * 312 * It is expected that fsd_lock is being held. 313 * 314 * Returns 0 on success and non-zero if hook limit exceeded. 315 */ 316 static int 317 fsd_install_disturber(vfs_t *vfsp, fsd_t *fsd) 318 { 319 fsd_int_t *fsdi; 320 321 ASSERT(MUTEX_HELD(&fsd_lock)); 322 323 for (fsdi = list_head(&fsd_list); fsdi != NULL; 324 fsdi = list_next(&fsd_list, fsdi)) { 325 if (fsdi->fsdi_vfsp == vfsp) 326 break; 327 } 328 329 if (fsdi != NULL) { 330 /* Just change the existing fsd_int_t */ 331 rw_enter(&fsdi->fsdi_lock, RW_WRITER); 332 (void) memcpy(&fsdi->fsdi_param, fsd, 333 sizeof (fsdi->fsdi_param)); 334 rw_exit(&fsdi->fsdi_lock); 335 } else { 336 fsh_t hook = { 0 }; 337 338 fsdi = kmem_zalloc(sizeof (*fsdi), KM_SLEEP); 339 fsdi->fsdi_vfsp = vfsp; 340 (void) memcpy(&fsdi->fsdi_param, fsd, 341 sizeof (fsdi->fsdi_param)); 342 rw_init(&fsdi->fsdi_lock, NULL, RW_DRIVER, NULL); 343 344 hook.arg = fsdi; 345 hook.read = fsd_hook_read; 346 hook.remove_cb = fsd_remove_cb; 347 348 /* 349 * It is safe to do so, because none of the hooks installed 350 * by fsd uses fsdi_handle nor the fsd_list. 351 */ 352 fsdi->fsdi_handle = fsh_hook_install(vfsp, &hook); 353 if (fsdi->fsdi_handle == -1) { 354 kmem_free(fsdi, sizeof (*fsdi)); 355 rw_destroy(&fsdi->fsdi_lock); 356 return (-1); 357 } 358 list_insert_head(&fsd_list, fsdi); 359 fsd_list_count++; 360 } 361 return (0); 362 } 363 364 static int 365 fsd_remove_disturber(vfs_t *vfsp) 366 { 367 fsd_int_t *fsdi; 368 369 ASSERT(MUTEX_HELD(&fsd_lock)); 370 371 for (fsdi = list_head(&fsd_list); fsdi != NULL; 372 fsdi = list_next(&fsd_list, fsdi)) { 373 if (fsdi->fsdi_vfsp == vfsp) 374 break; 375 } 376 if (fsdi == NULL || fsdi->fsdi_doomed) 377 return (ENOENT); 378 379 fsdi->fsdi_doomed = 1; 380 381 mutex_enter(&fsd_rem_thread_lock); 382 fsd_rem_thread = curthread; 383 mutex_exit(&fsd_rem_thread_lock); 384 385 ASSERT(fsh_hook_remove(fsdi->fsdi_handle) == 0); 386 387 mutex_enter(&fsd_rem_thread_lock); 388 fsd_rem_thread = NULL; 389 mutex_exit(&fsd_rem_thread_lock); 390 391 return (0); 392 } 393 394 static void 395 fsd_callback_mount(vfs_t *vfsp, void *arg) 396 { 397 _NOTE(ARGUNUSED(arg)); 398 399 int error = 0; 400 401 mutex_enter(&fsd_lock); 402 if (fsd_omni_param != NULL) 403 error = fsd_install_disturber(vfsp, fsd_omni_param); 404 mutex_exit(&fsd_lock); 405 406 if (error != 0) { 407 refstr_t *mntref; 408 409 mntref = vfs_getmntpoint(vfsp); 410 (void) cmn_err(CE_NOTE, "Installing disturber for %s failed.\n", 411 refstr_value(mntref)); 412 refstr_rele(mntref); 413 } 414 } 415 416 static void 417 fsd_callback_free(vfs_t *vfsp, void *arg) 418 { 419 _NOTE(ARGUNUSED(arg)); 420 421 fsd_int_t *fsdi; 422 423 /* 424 * Why we don't just pass fsd_int_t associated with this hook as an 425 * argument? 426 * Let's say that this callback has been just fired, but hasn't yet 427 * locked the fsd_lock. Meanwhile, in other thread, 428 * fsd_remove_disturber() is executing and the hook associated with 429 * fsd_int_t has been removed and the fsd_int_t has been destroyed. Now 430 * we go back to our free callback thread, and we try to remove an entry 431 * which does not exist. 432 */ 433 mutex_enter(&fsd_lock); 434 for (fsdi = list_head(&fsd_list); fsdi != NULL; 435 fsdi = list_next(&fsd_list, fsdi)) { 436 if (fsdi->fsdi_vfsp == vfsp) { 437 if (fsdi->fsdi_doomed) 438 continue; 439 440 fsdi->fsdi_doomed = 1; 441 /* 442 * We make such assertion, because fsd_lock is held 443 * and that means that neither fsd_remove_disturber() 444 * nor fsd_remove_cb() has removed this hook in 445 * different thread. 446 */ 447 mutex_enter(&fsd_rem_thread_lock); 448 fsd_rem_thread = curthread; 449 mutex_exit(&fsd_rem_thread_lock); 450 451 ASSERT(fsh_hook_remove(fsdi->fsdi_handle) == 0); 452 453 mutex_enter(&fsd_rem_thread_lock); 454 fsd_rem_thread = NULL; 455 mutex_exit(&fsd_rem_thread_lock); 456 457 /* 458 * Since there is at most one hook installed by fsd, 459 * we break. 460 */ 461 break; 462 } 463 } 464 /* 465 * We can't write ASSERT(fsdi != NULL) because it is possible that 466 * there was a concurrent call to fsd_remove_disturber() or 467 * fsd_detach(). 468 */ 469 mutex_exit(&fsd_lock); 470 } 471 472 473 static void 474 fsd_enable() 475 { 476 mutex_enter(&fsd_lock); 477 fsd_enabled = 1; 478 mutex_exit(&fsd_lock); 479 } 480 481 static void 482 fsd_disable() 483 { 484 mutex_enter(&fsd_lock); 485 fsd_enabled = 0; 486 mutex_exit(&fsd_lock); 487 } 488 489 490 /* Entry points */ 491 static int 492 fsd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 493 { 494 minor_t instance; 495 fsh_callback_t cb = { 0 }; 496 497 if (cmd != DDI_ATTACH) 498 return (DDI_FAILURE); 499 500 if (fsd_devi != NULL) 501 return (DDI_FAILURE); 502 503 instance = ddi_get_instance(dip); 504 if (ddi_create_minor_node(dip, "fsd", S_IFCHR, instance, 505 DDI_PSEUDO, 0) == DDI_FAILURE) 506 return (DDI_FAILURE); 507 fsd_devi = dip; 508 ddi_report_dev(fsd_devi); 509 510 list_create(&fsd_list, sizeof (fsd_int_t), 511 offsetof(fsd_int_t, fsdi_next)); 512 513 fsd_rand_seed = gethrtime(); 514 515 mutex_init(&fsd_lock, NULL, MUTEX_DRIVER, NULL); 516 mutex_init(&fsd_rem_thread_lock, NULL, MUTEX_DRIVER, NULL); 517 cv_init(&fsd_cv_empty, NULL, CV_DRIVER, NULL); 518 519 cb.fshc_mount = fsd_callback_mount; 520 cb.fshc_free = fsd_callback_free; 521 cb.fshc_arg = fsd_omni_param; 522 fsd_cb_handle = fsh_callback_install(&cb); 523 if (fsd_cb_handle == -1) { 524 /* Cleanup */ 525 list_destroy(&fsd_list); 526 cv_destroy(&fsd_cv_empty); 527 mutex_destroy(&fsd_rem_thread_lock); 528 mutex_destroy(&fsd_lock); 529 ddi_remove_minor_node(fsd_devi, NULL); 530 fsd_devi = NULL; 531 return (DDI_FAILURE); 532 } 533 534 return (DDI_SUCCESS); 535 } 536 537 /* 538 * If fsd_enable() was called and there was no subsequent fsd_disable() call, 539 * detach will fail. 540 */ 541 static int 542 fsd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 543 { 544 fsd_int_t *fsdi; 545 546 if (cmd != DDI_DETACH) 547 return (DDI_FAILURE); 548 549 ASSERT(dip == fsd_devi); 550 551 /* 552 * No need to hold fsd_lock here. Since only the hooks and callbacks 553 * might be running at this point. 554 */ 555 if (fsd_enabled) 556 return (DDI_FAILURE); 557 558 ddi_remove_minor_node(dip, NULL); 559 fsd_devi = NULL; 560 561 /* 562 * Hooks have to be removed before the callbacks. That's because without 563 * free() callbacks, we wouldn't be able to determine if a hook handle 564 * is valid. 565 */ 566 mutex_enter(&fsd_lock); 567 for (fsdi = list_head(&fsd_list); fsdi != NULL; 568 fsdi = list_next(&fsd_list, fsdi)) { 569 if (fsdi->fsdi_doomed == 0) 570 ASSERT(fsd_remove_disturber(fsdi->fsdi_vfsp) == 0); 571 } 572 573 while (fsd_list_count > 0) 574 cv_wait(&fsd_cv_empty, &fsd_lock); 575 mutex_exit(&fsd_lock); 576 cv_destroy(&fsd_cv_empty); 577 578 ASSERT(fsh_callback_remove(fsd_cb_handle) == 0); 579 if (fsd_omni_param != NULL) { 580 kmem_free(fsd_omni_param, sizeof (*fsd_omni_param)); 581 fsd_omni_param = NULL; 582 } 583 584 /* After removing the callbacks and hooks, it is safe to remove these */ 585 list_destroy(&fsd_list); 586 mutex_destroy(&fsd_rem_thread_lock); 587 mutex_destroy(&fsd_lock); 588 589 return (DDI_SUCCESS); 590 } 591 592 static int 593 fsd_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **resultp) 594 { 595 _NOTE(ARGUNUSED(dip)); 596 597 switch (infocmd) { 598 case DDI_INFO_DEVT2DEVINFO: 599 *resultp = fsd_devi; 600 return (DDI_SUCCESS); 601 case DDI_INFO_DEVT2INSTANCE: 602 *resultp = (void *)(uintptr_t)getminor((dev_t)arg); 603 return (DDI_SUCCESS); 604 default: 605 return (DDI_FAILURE); 606 } 607 } 608 609 static int 610 fsd_open(dev_t *devp, int flag, int otyp, cred_t *credp) 611 { 612 _NOTE(ARGUNUSED(devp)); 613 614 if (flag & FEXCL || flag & FNDELAY) 615 return (EINVAL); 616 617 if (otyp != OTYP_CHR) 618 return (EINVAL); 619 620 if (!(flag & FREAD && flag & FWRITE)) 621 return (EINVAL); 622 623 if (drv_priv(credp) == EPERM) 624 return (EPERM); 625 626 return (0); 627 } 628 629 static int 630 fsd_close(dev_t dev, int flag, int otyp, cred_t *credp) 631 { 632 _NOTE(ARGUNUSED(dev)); 633 _NOTE(ARGUNUSED(flag)); 634 _NOTE(ARGUNUSED(otyp)); 635 _NOTE(ARGUNUSED(credp)); 636 637 return (0); 638 } 639 640 641 /* ioctl(9E) and it's support functions */ 642 static int 643 fsd_check_param(fsd_t *fsd) 644 { 645 if (fsd->read_less_chance > 100 || 646 fsd->read_less_r[0] > fsd->read_less_r[1]) 647 return (EINVAL); 648 return (0); 649 } 650 651 static int 652 fsd_ioctl_disturb(fsd_ioc_t *ioc, int mode, int *rvalp) 653 { 654 file_t *file; 655 fsd_dis_t dis; 656 int rv; 657 658 if (ddi_copyin(&ioc->fsdioc_dis, &dis, sizeof (dis), mode)) 659 return (EFAULT); 660 661 if ((rv = fsd_check_param(&dis.fsdd_param)) != 0) { 662 *rvalp = rv; 663 return (0); 664 } 665 666 if ((file = getf((int)dis.fsdd_mnt)) == NULL) { 667 *rvalp = EBADFD; 668 return (0); 669 } 670 671 mutex_enter(&fsd_lock); 672 rv = fsd_install_disturber(file->f_vnode->v_vfsp, &dis.fsdd_param); 673 mutex_exit(&fsd_lock); 674 675 releasef((int)dis.fsdd_mnt); 676 677 if (rv != 0) 678 *rvalp = EAGAIN; 679 else 680 *rvalp = 0; 681 682 return (0); 683 } 684 685 static int 686 fsd_ioctl_get_param(fsd_ioc_t *ioc, int mode, int *rvalp) 687 { 688 file_t *file; 689 fsd_int_t *fsdi; 690 int error = 0; 691 int64_t fd; 692 vfs_t *vfsp; 693 694 if (ddi_copyin(&ioc->fsdioc_mnt, &fd, sizeof (fd), mode)) 695 return (EFAULT); 696 697 if ((file = getf((int)fd)) == NULL) { 698 *rvalp = EBADFD; 699 return (0); 700 } 701 vfsp = file->f_vnode->v_vfsp; 702 releasef((int)fd); 703 704 705 mutex_enter(&fsd_lock); 706 707 for (fsdi = list_head(&fsd_list); fsdi != NULL; 708 fsdi = list_next(&fsd_list, fsdi)) { 709 if (fsdi->fsdi_vfsp == vfsp) 710 break; 711 } 712 if (fsdi == NULL) { 713 *rvalp = ENOENT; 714 mutex_exit(&fsd_lock); 715 return (0); 716 } 717 rw_enter(&fsdi->fsdi_lock, RW_READER); 718 error = ddi_copyout(&fsdi->fsdi_param, &ioc->fsdioc_param, 719 sizeof (fsdi->fsdi_param), mode); 720 rw_exit(&fsdi->fsdi_lock); 721 722 mutex_exit(&fsd_lock); 723 724 725 if (error) { 726 return (EFAULT); 727 } else { 728 *rvalp = 0; 729 return (0); 730 } 731 } 732 733 static int 734 fsd_ioctl_get_info(fsd_ioc_t *ioc, int mode, int *rvalp) 735 { 736 fsd_info_t info; 737 738 mutex_enter(&fsd_lock); 739 info.fsdinf_enabled = fsd_enabled; 740 info.fsdinf_count = fsd_list_count; 741 info.fsdinf_omni_on = fsd_omni_param != NULL; 742 if (info.fsdinf_omni_on) 743 (void) memcpy(&info.fsdinf_omni_param, fsd_omni_param, 744 sizeof (info.fsdinf_omni_param)); 745 mutex_exit(&fsd_lock); 746 747 if (ddi_copyout(&info, &ioc->fsdioc_info, sizeof (info), mode)) 748 return (EFAULT); 749 750 *rvalp = 0; 751 return (0); 752 } 753 754 static int 755 fsd_ioctl_get_list(fsd_ioc_t *ioc, int mode, int *rvalp) 756 { 757 fsd_int_t *fsdi; 758 fsd_fs_t *fsdfs_list; 759 int i; 760 int ret = 0; 761 int64_t ioc_list_count; 762 763 *rvalp = 0; 764 765 /* Get data */ 766 if (ddi_copyin(&ioc->fsdioc_list.count, &ioc_list_count, 767 sizeof (ioc_list_count), mode)) 768 return (EFAULT); 769 if (ddi_copyin(&ioc->fsdioc_list.listp, &fsdfs_list, 770 sizeof (fsdfs_list), mode)) 771 return (EFAULT); 772 773 774 mutex_enter(&fsd_lock); 775 if (ioc_list_count > fsd_list_count) 776 ioc_list_count = fsd_list_count; 777 778 /* Copyout */ 779 if (ddi_copyout(&ioc_list_count, &ioc->fsdioc_list.count, 780 sizeof (ioc_list_count), mode)) { 781 ret = EFAULT; 782 goto out; 783 } 784 for (fsdi = list_head(&fsd_list), i = 0; 785 fsdi != NULL && i < ioc_list_count; 786 fsdi = list_next(&fsd_list, fsdi), i++) { 787 refstr_t *mntstr = vfs_getmntpoint(fsdi->fsdi_vfsp); 788 int len = strlen(refstr_value(mntstr)); 789 790 rw_enter(&fsdi->fsdi_lock, RW_READER); 791 if (ddi_copyout(refstr_value(mntstr), fsdfs_list[i].fsdf_name, 792 len + 1, mode) || 793 ddi_copyout(&fsdi->fsdi_param, &fsdfs_list[i].fsdf_param, 794 sizeof (fsdi->fsdi_param), mode)) { 795 ret = EFAULT; 796 } 797 rw_exit(&fsdi->fsdi_lock); 798 refstr_rele(mntstr); 799 800 if (ret != 0) 801 break; 802 } 803 804 805 out: 806 mutex_exit(&fsd_lock); 807 return (ret); 808 } 809 810 static int 811 fsd_ioctl_disturb_off(fsd_ioc_t *ioc, int mode, int *rvalp) 812 { 813 file_t *file; 814 int64_t fd; 815 816 if (ddi_copyin(&ioc->fsdioc_mnt, &fd, sizeof (fd), mode)) 817 return (EFAULT); 818 819 if ((file = getf((int)fd)) == NULL) { 820 *rvalp = EBADFD; 821 return (0); 822 } 823 824 mutex_enter(&fsd_lock); 825 *rvalp = fsd_remove_disturber(file->f_vnode->v_vfsp); 826 releasef((int)fd); 827 mutex_exit(&fsd_lock); 828 829 return (0); 830 } 831 832 static int 833 fsd_ioctl_disturb_omni(fsd_ioc_t *ioc, int mode, int *rvalp) 834 { 835 fsd_t fsd; 836 int rv; 837 838 if (ddi_copyin(&ioc->fsdioc_param, &fsd, sizeof (fsd), mode)) 839 return (EFAULT); 840 841 if ((rv = fsd_check_param(&fsd)) != 0) { 842 *rvalp = rv; 843 return (0); 844 } 845 846 mutex_enter(&fsd_lock); 847 if (fsd_omni_param == NULL) 848 fsd_omni_param = (fsd_t *)kmem_alloc(sizeof (*fsd_omni_param), 849 KM_SLEEP); 850 (void) memcpy(fsd_omni_param, &fsd, sizeof (*fsd_omni_param)); 851 mutex_exit(&fsd_lock); 852 853 *rvalp = 0; 854 return (0); 855 } 856 857 858 static int 859 fsd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, 860 int *rvalp) 861 { 862 _NOTE(ARGUNUSED(dev)); 863 _NOTE(ARGUNUSED(credp)); 864 865 if (!fsd_enabled && cmd != FSD_ENABLE) { 866 *rvalp = ENOTACTIVE; 867 return (0); 868 } 869 870 switch (cmd) { 871 case FSD_ENABLE: 872 fsd_enable(); 873 *rvalp = 0; 874 return (0); 875 876 case FSD_DISABLE: 877 fsd_disable(); 878 *rvalp = 0; 879 return (0); 880 881 case FSD_GET_PARAM: 882 return (fsd_ioctl_get_param((fsd_ioc_t *)arg, mode, rvalp)); 883 884 case FSD_DISTURB: 885 return (fsd_ioctl_disturb((fsd_ioc_t *)arg, mode, rvalp)); 886 887 case FSD_DISTURB_OFF: 888 return (fsd_ioctl_disturb_off((fsd_ioc_t *)arg, mode, rvalp)); 889 890 case FSD_DISTURB_OMNI: 891 return (fsd_ioctl_disturb_omni((fsd_ioc_t *)arg, mode, rvalp)); 892 893 case FSD_DISTURB_OMNI_OFF: 894 mutex_enter(&fsd_lock); 895 if (fsd_omni_param != NULL) 896 kmem_free(fsd_omni_param, sizeof (*fsd_omni_param)); 897 fsd_omni_param = NULL; 898 mutex_exit(&fsd_lock); 899 900 *rvalp = 0; 901 return (0); 902 903 case FSD_GET_LIST: 904 return (fsd_ioctl_get_list((fsd_ioc_t *)arg, mode, rvalp)); 905 906 case FSD_GET_INFO: 907 return (fsd_ioctl_get_info((fsd_ioc_t *)arg, mode, rvalp)); 908 909 default: 910 return (ENOTTY); 911 } 912 } 913 914 static struct cb_ops cb_ops = { 915 fsd_open, /* open(9E) */ 916 fsd_close, /* close(9E) */ 917 nodev, /* strategy(9E) */ 918 nodev, /* print(9E) */ 919 nodev, /* dump(9E) */ 920 nodev, /* read(9E) */ 921 nodev, /* write(9E) */ 922 fsd_ioctl, /* ioctl(9E) */ 923 nodev, /* devmap(9E) */ 924 nodev, /* mmap(9E) */ 925 nodev, /* segmap(9E) */ 926 nochpoll, /* chpoll(9E) */ 927 ddi_prop_op, /* prop_op(9E) */ 928 NULL, /* streamtab(9E) */ 929 D_MP | D_64BIT, /* cb_flag(9E) */ 930 CB_REV, /* cb_rev(9E) */ 931 nodev, /* aread(9E) */ 932 nodev, /* awrite(9E) */ 933 }; 934 935 static struct dev_ops dev_ops = { 936 DEVO_REV, /* driver build version */ 937 0, /* reference count */ 938 fsd_getinfo, /* getinfo */ 939 nulldev, 940 nulldev, /* probe */ 941 fsd_attach, /* attach */ 942 fsd_detach, /* detach */ 943 nodev, 944 &cb_ops, /* cb_ops */ 945 NULL, /* bus_ops */ 946 NULL, /* power */ 947 ddi_quiesce_not_needed, /* quiesce */ 948 }; 949 950 static struct modldrv modldrv = { 951 &mod_driverops, "Filesystem disturber", &dev_ops 952 }; 953 954 static struct modlinkage modlinkage = { 955 MODREV_1, &modldrv, NULL 956 }; 957 958 int 959 _init(void) 960 { 961 return (mod_install(&modlinkage)); 962 } 963 964 int 965 _info(struct modinfo *modinfop) 966 { 967 return (mod_info(&modlinkage, modinfop)); 968 } 969 970 int 971 _fini(void) 972 { 973 return (mod_remove(&modlinkage)); 974 }