#!/usr/bin/ksh -p
#
#
# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.
#

#
# Copyright (c) 2016 by Delphix. All rights reserved.
# Copyright 2019 Joyent, Inc.
#

. $STF_SUITE/include/libtest.shlib
. $STF_SUITE/tests/functional/snapshot/snapshot.cfg

#
# DESCRIPTION:
#
# This test ensures that the following race condition does not
# take place:
#
# 1] A sync thread inserts a new entry in the deadlist of a
#    snapshot. The dle_bpobj at that entry currently is the
#    empty bpobj (our sentinel), so we close it and we are
#    about to reopen it. (see dle_enqueue())
#
# 2] At the same time a thread executing an administrative
#    command that uses dsl_deadlist_space_range() is about
#    to dereference that same bpobj that was just closed
#    and therefore is NULL.
#
# 3] The sync thread loses the race and we dereference the
#    NULL pointer in the kernel.
#
# STRATEGY:
#
# 1. Setup a folder and create a bunch of test files. Take a
#    snapshot right after you create a new test file.
# 2. Start DTrace in the background to put a delay in the
#    sync thread after it closes the empty bpobj and before
#    it reopens it. The dtrace process is set to exit when this
#    script exits.
# 3. Start a process in the backgroud that runs zfs-destroy
#    dry-runs in an infinite loop. The idea is to keep calling
#    dsl_deadlist_space_range().
# 4. Go ahead and start removing the test files. This should
#    start populating the deadlist of each snapshot with
#    entries and go through the dle_enqueue() target code.
# 5. Kill the 'zfs destroy' loop and clean up the dataset.
#

verify_runnable "both"


DLDS="dl_race"

function cleanup
{
	log_must kill -9 $DLOOP_PID
	log_must zfs destroy -fR $TESTPOOL/$TESTFS/$DLDS
}

function setup
{
	log_must zfs create $TESTPOOL/$TESTFS/$DLDS
	for i in {1..50}; do
		log_must mkfile 1m /$TESTDIR/$DLDS/dl_test_file$i
		log_must zfs snapshot $TESTPOOL/$TESTFS/$DLDS@snap${i}
	done
}

function destroy_nv_loop
{
	while true; do
		log_must zfs destroy -nv $TESTPOOL/$TESTFS/$DLDS@snap1%snap50
	done
}

log_onexit cleanup

setup
log_must sync

log_must dtrace -p "$PPID" -qwn "fbt::bpobj_decr_empty:entry { chill(500000000); }" &
sleep 1

destroy_nv_loop &
DLOOP_PID="$!"
sleep 1

for i in {1..50}; do
	log_must rm /$TESTDIR/$DLDS/dl_test_file$i
done
log_must sync

log_pass "There should be no race condition when an administrative command" \
    " attempts to read a deadlist's entries while a sync" \
    " thread is manipulating it."
