Print this page
Update from fsd_sep3 webrev to fsd_sep9
*** 135,145 ****
*
* 4. Internals
* When fsd_enabled is nonzero, fsd_detach() fails.
*
* These mount callback is used for installing injections on newly mounted
! * vfs_t's (omnipresent).
*
* The list of currently installed hooks is kept in fsd_list.
*
* fsd installs at most one hook on a vfs_t.
*
--- 135,145 ----
*
* 4. Internals
* When fsd_enabled is nonzero, fsd_detach() fails.
*
* These mount callback is used for installing injections on newly mounted
! * vfs_t's (omnipresent). The free callback is used for cleaning up.
*
* The list of currently installed hooks is kept in fsd_list.
*
* fsd installs at most one hook on a vfs_t.
*
*** 159,169 ****
* (fsd_remove_cb).
*
* Because of the fact that fsd_remove_cb() could be called both in the context
* of the thread that executes fsh_hook_remove() or outside the fsd, we need to
* use fsd_rem_thread in order not to cause a deadlock. fsh_hook_remove() could
! * be called by at most one thread inside fsd (fsd_remove_disturber() holds
* fsd_lock). We just have to check inside fsd_remove_cb() if it was called
* from fsh_hook_remove() or not. We use fsd_rem_thread to determine that.
*
* fsd_int_t.fsdi_param is protected by fsd_int_t.fsdi_lock which is an rwlock.
*/
--- 159,169 ----
* (fsd_remove_cb).
*
* Because of the fact that fsd_remove_cb() could be called both in the context
* of the thread that executes fsh_hook_remove() or outside the fsd, we need to
* use fsd_rem_thread in order not to cause a deadlock. fsh_hook_remove() could
! * be called by at most one thread inside fsd (fsd_disturber_remove() holds
* fsd_lock). We just have to check inside fsd_remove_cb() if it was called
* from fsh_hook_remove() or not. We use fsd_rem_thread to determine that.
*
* fsd_int_t.fsdi_param is protected by fsd_int_t.fsdi_lock which is an rwlock.
*/
*** 178,188 ****
krwlock_t fsdi_lock; /* protects fsd_param */
fsd_t fsdi_param;
fsh_handle_t fsdi_handle; /* we use fsh's handle in fsd */
vfs_t *fsdi_vfsp;
int fsdi_doomed;
! list_node_t fsdi_next;
} fsd_int_t;
static dev_info_t *fsd_devi;
--- 178,188 ----
krwlock_t fsdi_lock; /* protects fsd_param */
fsd_t fsdi_param;
fsh_handle_t fsdi_handle; /* we use fsh's handle in fsd */
vfs_t *fsdi_vfsp;
int fsdi_doomed;
! list_node_t fsdi_node;
} fsd_int_t;
static dev_info_t *fsd_devi;
*** 230,292 ****
/*
* A pointer to a given fsd_int_t is valid always inside fsh_hook_xxx()
* call, because it's valid until the hooks associated with it are removed.
* If a hook is removed, it cannot be executing.
*/
! static int
! fsd_hook_read(fsh_int_t *fshi, void *arg, vnode_t *vp, uio_t *uiop,
! int ioflag, cred_t *cr, caller_context_t *ct)
{
fsd_int_t *fsdi = (fsd_int_t *)arg;
! uint64_t count, less, less_chance;
/*
* It is used to keep an odd number of fsd_rand() calls in every
! * fsd_hook_read() call. That is desired because when a range of width
! * 2 is set as a parameter, we don't want to make it a constant.
* The pseudo-random number generator returns a number with different
* parity with every call. If this function is called in every
! * fsd_hook_read() execution even number of times, it would always be
! * the same % 2.
*/
(void) fsd_rand();
! ASSERT(vp->v_vfsp == fsdi->fsdi_vfsp);
rw_enter(&fsdi->fsdi_lock, RW_READER);
less_chance = fsdi->fsdi_param.read_less_chance;
- less = (uint64_t)fsd_rand() %
- (fsdi->fsdi_param.read_less_r[1] + 1 -
- fsdi->fsdi_param.read_less_r[0]) + fsdi->fsdi_param.read_less_r[0];
rw_exit(&fsdi->fsdi_lock);
- count = uiop->uio_iov->iov_len;
if ((uint64_t)fsd_rand() % 100 < less_chance) {
extern size_t copyout_max_cached;
! int ret;
! if (count > less)
count -= less;
! else
! less = 0;
! uiop->uio_iov->iov_len = count;
! uiop->uio_resid = count;
if (count <= copyout_max_cached)
! uiop->uio_extflg = UIO_COPY_CACHED;
else
! uiop->uio_extflg = UIO_COPY_DEFAULT;
!
! ret = fsh_next_read(fshi, vp, uiop, ioflag, cr, ct);
! uiop->uio_resid += less;
! return (ret);
}
-
- return (fsh_next_read(fshi, vp, uiop, ioflag, cr, ct));
}
static void
fsd_remove_cb(void *arg, fsh_handle_t handle)
{
_NOTE(ARGUNUSED(handle));
--- 230,315 ----
/*
* A pointer to a given fsd_int_t is valid always inside fsh_hook_xxx()
* call, because it's valid until the hooks associated with it are removed.
* If a hook is removed, it cannot be executing.
*/
! static void
! fsd_hook_pre_read(void *arg, void **instancep, vnode_t **vpp, uio_t **uiopp,
! int *ioflagp, cred_t **crp, caller_context_t **ctp)
{
+ _NOTE(ARGUNUSED(ioflagp));
+ _NOTE(ARGUNUSED(crp));
+ _NOTE(ARGUNUSED(ctp));
+
fsd_int_t *fsdi = (fsd_int_t *)arg;
! uint64_t less_chance;
/*
* It is used to keep an odd number of fsd_rand() calls in every
! * fsd_hook_pre_read() call. That is desired because when a range of
! * width 2 is set as a parameter, we don't want to make it a constant.
* The pseudo-random number generator returns a number with different
* parity with every call. If this function is called in every
! * fsd_hook_pre_read() execution even number of times, it would always
! * be the same % 2.
*/
(void) fsd_rand();
! ASSERT((*vpp)->v_vfsp == fsdi->fsdi_vfsp);
rw_enter(&fsdi->fsdi_lock, RW_READER);
less_chance = fsdi->fsdi_param.read_less_chance;
rw_exit(&fsdi->fsdi_lock);
if ((uint64_t)fsd_rand() % 100 < less_chance) {
extern size_t copyout_max_cached;
! uint64_t r[2];
! uint64_t count, less;
! count = (*uiopp)->uio_iov->iov_len;
! r[0] = fsdi->fsdi_param.read_less_r[0];
! r[1] = fsdi->fsdi_param.read_less_r[1];
! less = (uint64_t)fsd_rand() % (r[1] + 1 - r[0]) + r[0];
!
! if (count > less) {
count -= less;
! *instancep = kmem_alloc(sizeof (uint64_t), KM_SLEEP);
! *(*(uint64_t **)instancep) = less;
! } else {
! *instancep = NULL;
! return;
! }
! (*uiopp)->uio_iov->iov_len = count;
! (*uiopp)->uio_resid = count;
if (count <= copyout_max_cached)
! (*uiopp)->uio_extflg = UIO_COPY_CACHED;
else
! (*uiopp)->uio_extflg = UIO_COPY_DEFAULT;
! } else {
! *instancep = NULL;
}
}
+ static int
+ fsd_hook_post_read(int ret, void *arg, void *instance, vnode_t *vp,
+ uio_t *uiop, int oflag, cred_t *cr, caller_context_t *ct)
+ {
+ _NOTE(ARGUNUSED(arg));
+ _NOTE(ARGUNUSED(vp));
+ _NOTE(ARGUNUSED(oflag));
+ _NOTE(ARGUNUSED(cr));
+ _NOTE(ARGUNUSED(ct));
+ if (instance != NULL) {
+ uint64_t *lessp = instance;
+ uiop->uio_resid += *lessp;
+ kmem_free(lessp, sizeof (*lessp));
+ }
+ return (ret);
+ }
+
static void
fsd_remove_cb(void *arg, fsh_handle_t handle)
{
_NOTE(ARGUNUSED(handle));
*** 322,332 ****
* It is expected that fsd_lock is being held.
*
* Returns 0 on success and non-zero if hook limit exceeded.
*/
static int
! fsd_install_disturber(vfs_t *vfsp, fsd_t *fsd)
{
fsd_int_t *fsdi;
ASSERT(MUTEX_HELD(&fsd_lock));
--- 345,355 ----
* It is expected that fsd_lock is being held.
*
* Returns 0 on success and non-zero if hook limit exceeded.
*/
static int
! fsd_disturber_install(vfs_t *vfsp, fsd_t *fsd)
{
fsd_int_t *fsdi;
ASSERT(MUTEX_HELD(&fsd_lock));
*** 350,360 ****
(void) memcpy(&fsdi->fsdi_param, fsd,
sizeof (fsdi->fsdi_param));
rw_init(&fsdi->fsdi_lock, NULL, RW_DRIVER, NULL);
hook.arg = fsdi;
! hook.read = fsd_hook_read;
hook.remove_cb = fsd_remove_cb;
/*
* It is safe to do so, because none of the hooks installed
* by fsd uses fsdi_handle nor the fsd_list.
--- 373,384 ----
(void) memcpy(&fsdi->fsdi_param, fsd,
sizeof (fsdi->fsdi_param));
rw_init(&fsdi->fsdi_lock, NULL, RW_DRIVER, NULL);
hook.arg = fsdi;
! hook.pre_read = fsd_hook_pre_read;
! hook.post_read = fsd_hook_post_read;
hook.remove_cb = fsd_remove_cb;
/*
* It is safe to do so, because none of the hooks installed
* by fsd uses fsdi_handle nor the fsd_list.
*** 370,380 ****
}
return (0);
}
static int
! fsd_remove_disturber(vfs_t *vfsp)
{
fsd_int_t *fsdi;
ASSERT(MUTEX_HELD(&fsd_lock));
--- 394,404 ----
}
return (0);
}
static int
! fsd_disturber_remove(vfs_t *vfsp)
{
fsd_int_t *fsdi;
ASSERT(MUTEX_HELD(&fsd_lock));
*** 400,418 ****
return (0);
}
static void
! fsd_callback_mount(vfs_t *vfsp, void *arg)
{
_NOTE(ARGUNUSED(arg));
int error = 0;
mutex_enter(&fsd_lock);
if (fsd_omni_param != NULL)
! error = fsd_install_disturber(vfsp, fsd_omni_param);
mutex_exit(&fsd_lock);
if (error != 0) {
refstr_t *mntref;
--- 424,442 ----
return (0);
}
static void
! fsd_mount_callback(vfs_t *vfsp, void *arg)
{
_NOTE(ARGUNUSED(arg));
int error = 0;
mutex_enter(&fsd_lock);
if (fsd_omni_param != NULL)
! error = fsd_disturber_install(vfsp, fsd_omni_param);
mutex_exit(&fsd_lock);
if (error != 0) {
refstr_t *mntref;
*** 421,431 ****
--- 445,508 ----
refstr_value(mntref));
refstr_rele(mntref);
}
}
+ /*
+ * Although, we might delete the fsd_free_callback(), it would make the whole
+ * proces less clear. There's a time window between firing free callbacks and
+ * freeing the vfs_t in fsd_disturber_remove() could be called. fsh can
+ * deal with invalid handles (until there is no collision), but we'd like to
+ * have a nice assertion instead.
+ */
static void
+ fsd_free_callback(vfs_t *vfsp, void *arg)
+ {
+ _NOTE(ARGUNUSED(arg));
+
+ fsd_int_t *fsdi;
+
+ mutex_enter(&fsd_lock);
+ for (fsdi = list_head(&fsd_list); fsdi != NULL;
+ fsdi = list_next(&fsd_list, fsdi)) {
+ if (fsdi->fsdi_vfsp == vfsp) {
+ if (fsdi->fsdi_doomed)
+ continue;
+
+ fsdi->fsdi_doomed = 1;
+ /*
+ * We make such assertion, because fsd_lock is held
+ * and that means that neither fsd_disturber_remove()
+ * nor fsd_remove_cb() has removed this hook in
+ * different thread.
+ */
+ mutex_enter(&fsd_rem_thread_lock);
+ fsd_rem_thread = curthread;
+ mutex_exit(&fsd_rem_thread_lock);
+
+ ASSERT(fsh_hook_remove(fsdi->fsdi_handle) == 0);
+
+ mutex_enter(&fsd_rem_thread_lock);
+ fsd_rem_thread = NULL;
+ mutex_exit(&fsd_rem_thread_lock);
+
+ /*
+ * Since there is at most one hook installed by fsd,
+ * we break.
+ */
+ break;
+ }
+ }
+ /*
+ * We can't write ASSERT(fsdi != NULL) because it is possible that
+ * there was a concurrent call to fsd_disturber_remove() or
+ * fsd_detach().
+ */
+ mutex_exit(&fsd_lock);
+ }
+
+ static void
fsd_enable()
{
mutex_enter(&fsd_lock);
fsd_enabled = 1;
mutex_exit(&fsd_lock);
*** 459,477 ****
return (DDI_FAILURE);
fsd_devi = dip;
ddi_report_dev(fsd_devi);
list_create(&fsd_list, sizeof (fsd_int_t),
! offsetof(fsd_int_t, fsdi_next));
fsd_rand_seed = gethrtime();
mutex_init(&fsd_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&fsd_rem_thread_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&fsd_cv_empty, NULL, CV_DRIVER, NULL);
! cb.fshc_mount = fsd_callback_mount;
cb.fshc_arg = fsd_omni_param;
fsd_cb_handle = fsh_callback_install(&cb);
if (fsd_cb_handle == -1) {
/* Cleanup */
list_destroy(&fsd_list);
--- 536,555 ----
return (DDI_FAILURE);
fsd_devi = dip;
ddi_report_dev(fsd_devi);
list_create(&fsd_list, sizeof (fsd_int_t),
! offsetof(fsd_int_t, fsdi_node));
fsd_rand_seed = gethrtime();
mutex_init(&fsd_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&fsd_rem_thread_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&fsd_cv_empty, NULL, CV_DRIVER, NULL);
! cb.fshc_mount = fsd_mount_callback;
! cb.fshc_free = fsd_free_callback;
cb.fshc_arg = fsd_omni_param;
fsd_cb_handle = fsh_callback_install(&cb);
if (fsd_cb_handle == -1) {
/* Cleanup */
list_destroy(&fsd_list);
*** 508,520 ****
return (DDI_FAILURE);
ddi_remove_minor_node(dip, NULL);
fsd_devi = NULL;
mutex_enter(&fsd_lock);
fsd_detaching = 1;
! while ((fsdi = list_remove_head(&fsd_list)) != NULL)
if (fsdi->fsdi_doomed == 0) {
fsdi->fsdi_doomed = 1;
mutex_enter(&fsd_rem_thread_lock);
fsd_rem_thread = curthread;
--- 586,623 ----
return (DDI_FAILURE);
ddi_remove_minor_node(dip, NULL);
fsd_devi = NULL;
+ /*
+ * 1. Remove the hooks.
+ * 2. Remove the callbacks.
+ *
+ * This order has to be preserved, because of the fact that
+ * fsd_free_callback() is the last stop before a vfs_t is destroyed.
+ * Without it, this might happen:
+ * vfs_free() fsd_detach()
+ * 1. Handle for the hook is
+ * invalidated.
+ * 2. Fired fsd_remove_cb().
+ * 3. fsd_remove_cb() hasn't yet fsd_lock is acquired.
+ * acquired the fsd_lock.
+ * 4 Waiting for fsd_lock. That ASSERT(fsh_hook_remove(..) == 0);
+ * means that the hook hasn't failed, because the handle is
+ * been removed from fsd_hooks already invalid.
+ * fsd_hooks yet.
+ *
+ * The ASSERT() here is nice and without a good reason, we don't want
+ * to get rid of it.
+ */
mutex_enter(&fsd_lock);
+ /*
+ * After we set fsd_detaching to 1, hook remove callback (fsd_remove_cb)
+ * won't try to remove entries from fsd_list.
+ */
fsd_detaching = 1;
! while ((fsdi = list_remove_head(&fsd_list)) != NULL) {
if (fsdi->fsdi_doomed == 0) {
fsdi->fsdi_doomed = 1;
mutex_enter(&fsd_rem_thread_lock);
fsd_rem_thread = curthread;
*** 528,537 ****
--- 631,641 ----
mutex_enter(&fsd_rem_thread_lock);
fsd_rem_thread = NULL;
mutex_exit(&fsd_rem_thread_lock);
}
+ }
while (fsd_list_count > 0)
cv_wait(&fsd_cv_empty, &fsd_lock);
mutex_exit(&fsd_lock);
cv_destroy(&fsd_cv_empty);
*** 628,638 ****
*rvalp = EBADFD;
return (0);
}
mutex_enter(&fsd_lock);
! rv = fsd_install_disturber(file->f_vnode->v_vfsp, &dis.fsdd_param);
mutex_exit(&fsd_lock);
releasef((int)dis.fsdd_mnt);
if (rv != 0)
--- 732,742 ----
*rvalp = EBADFD;
return (0);
}
mutex_enter(&fsd_lock);
! rv = fsd_disturber_install(file->f_vnode->v_vfsp, &dis.fsdd_param);
mutex_exit(&fsd_lock);
releasef((int)dis.fsdd_mnt);
if (rv != 0)
*** 781,791 ****
*rvalp = EBADFD;
return (0);
}
mutex_enter(&fsd_lock);
! *rvalp = fsd_remove_disturber(file->f_vnode->v_vfsp);
releasef((int)fd);
mutex_exit(&fsd_lock);
return (0);
}
--- 885,895 ----
*rvalp = EBADFD;
return (0);
}
mutex_enter(&fsd_lock);
! *rvalp = fsd_disturber_remove(file->f_vnode->v_vfsp);
releasef((int)fd);
mutex_exit(&fsd_lock);
return (0);
}
*** 821,831 ****
int *rvalp)
{
_NOTE(ARGUNUSED(dev));
_NOTE(ARGUNUSED(credp));
! if (!fsd_enabled && cmd != FSD_ENABLE) {
*rvalp = ENOTACTIVE;
return (0);
}
switch (cmd) {
--- 925,941 ----
int *rvalp)
{
_NOTE(ARGUNUSED(dev));
_NOTE(ARGUNUSED(credp));
! int enabled;
!
! mutex_enter(&fsd_lock);
! enabled = fsd_enabled;
! mutex_exit(&fsd_lock);
!
! if (!enabled && cmd != FSD_ENABLE) {
*rvalp = ENOTACTIVE;
return (0);
}
switch (cmd) {