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 }