#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2018 Red Hat Inc.  All Rights Reserved.
#
# FS QA Test 478
#
# Test OFD lock. fcntl F_OFD_SETLK to set lock, then F_OFD_GETLK
# to verify we are being given correct advice by kernel.
#
# OFD lock combines POSIX lock and BSD flock:
#   + does not share between threads
#   + byte granularity
#            (both tested by LTP/fcntl3{4,6})
#   + only release automatically after all open fd closed
#
# This test target the third one and expand a little bit.
#
# The basic idea is one setlk routine setting locks via fcntl
# *_SETLK, followed by operations like clone, dup then close fd;
# another routine getlk getting locks via fcntl *_GETLK.
#
# Firstly in setlk routine process P0, place a lock L0 on an
# opened testfile, then
#
#   + clone() a child P1 to close the fd then tell getlk to go,
#     parent P0 wait getlk done then close fd.
# or
#   + dup() fd to a newfd then close newfd then tell getlk to go,
#     then wait getlk done then close fd.
#
# In getlk process P2, do fcntl *_GETLK with lock L1 after get
# notified by setlk routine.
#
# In the end, getlk routine check the returned struct flock.l_type
# to see if the lock mechanism works fine.
#
# When testing with clone,
#    + CLONE_FILES set, close releases all locks;
#    + CLONE_FILES not set, locks remain in P0;
#
# If L0 is a POSIX lock,
#   + it is not inherited into P1
#   + it is released after dup & close
#
# If L0 is a OFD lock,
#   + it is inherited into P1
#   + it is not released after dup & close
#
#  setlk routine:			 * getlk routine:
#    start				 *   start
#      |				 *     |
#   open file				 *  open file
#      |				 *     |
#   init sem				 *     |
#      |				 *     |
#  wait init sem done			 * wait init sem done
#      |				 *     |
#    setlk L0                            *     |
#      |				 *     |
#      |---------clone()--------|	 *     |
#      |                        |	 *     |
#      |(child P1)   (parent P0)|	 *     | (P2)
#      |                        |	 *     |
#      |                   close fd	 *     |
#      |                        |	 *     |
#      |                 set sem0=0	 *  wait sem0==0
#      |                        |	 *     |
#      |                        |	 *   getlk L1
#      |                        |	 *     |
#   wait sem1==0                |    	 *  set sem1=0
#      |                        |	 *     |
#     exit                wait child 	 *     |
#                               |	 *  check result
#                           cleanup  	 *     |
#                               |	 *     |
#                             exit	 *    exit
#
# We can test combainations of:
#	+ shared or exclusive lock
#	+ these locks are conflicting or not
#	+ one OFD lock and one POSIX lock
#	+ that open testfile RDONLY or RDWR
#	+ clone with CLONE_FILES or not
#	+ dup and close newfd
#
. ./common/preamble
_begin_fstest auto quick

# Import common functions.
. ./common/filter

# Modify as appropriate.
_require_test
_require_ofd_locks

# prepare a 4k testfile in TEST_DIR
$XFS_IO_PROG -f -c "pwrite -S 0xFF 0 4096" \
	$TEST_DIR/testfile >> $seqres.full 2>&1

mk_sem()
{
	SEMID=$(ipcmk -S 2 | cut -d ":" -f 2 | tr -d '[:space:]')
	if [ -z "$SEMID" ]; then
		echo "ipcmk failed"
		exit 1
	fi
	SEMKEY=$(ipcs -s | grep $SEMID | cut -d " " -f 1)
	if [ -z "$SEMKEY" ]; then
		echo "ipcs failed"
		exit 1
	fi
}

rm_sem()
{
	ipcrm -s $SEMID 2>/dev/null
}

do_test()
{
	local soptions
	local goptions
	# print options and getlk output for debug
	echo $* >> $seqres.full 2>&1
	mk_sem
	soptions="$1 -K $SEMKEY"
	goptions="$2 -K $SEMKEY"
	# -s : do setlk
	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
	# -g : do getlk
	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
		tee -a $seqres.full
	wait $!
	rm_sem

	mk_sem
	# add -F to clone with CLONE_FILES
	soptions="$1 -F -K $SEMKEY"
	goptions="$2 -K $SEMKEY"
	# with -F, new locks are always file to place
	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
		tee -a $seqres.full
	wait $!
	rm_sem

	mk_sem
	# add -d to dup and close
	soptions="$1 -d -K $SEMKEY"
	goptions="$2 -K $SEMKEY"
	$here/src/t_ofd_locks $soptions $TEST_DIR/testfile &
	$here/src/t_ofd_locks $goptions $TEST_DIR/testfile | \
		tee -a $seqres.full
	wait $!
	rm_sem
}

# Always setlk at range [0,9], getlk at range [0,9] [5,24] or [20,29].
# To open file RDONLY or RDWR should not break the locks.
# POSIX locks should be released after closed fd, so it wont conflict
# with other locks in tests

# -P : operate posix lock
# -w : operate on F_WRLCK
# -r : operate on F_RDLCK
# -R : open file RDONLY
# -W : open file RDWR
# -o : file offset where the lock starts
# -l : lock length
# -F : clone with CLONE_FILES in setlk
# -d : dup and close in setlk

# setlk wrlck [0,9], getlk wrlck [0,9], expect
#    + wrlck when CLONE_FILES not set
#    + unlck when CLONE_FILES set
#    + wrlck when dup & close
do_test "-s -w -o 0 -l 10 -W" "-g -w -o 0 -l 10 -W" "wrlck" "unlck" "wrlck"
# setlk wrlck [0,9], getlk posix wrlck [5,24]
do_test "-s -w -o 0 -l 10 -W" "-g -w -o 5 -l 20 -W -P" "wrlck" "unlck" "wrlck"
# setlk wrlck [0,9], getlk wrlck [20,29]
do_test "-s -w -o 0 -l 10 -W" "-g -w -o 20 -l 10 -W" "unlck" "unlck" "unlck"
# setlk posix wrlck [0,9], getlk wrlck [5,24]
do_test "-s -w -o 0 -l 10 -W -P" "-g -w -o 5 -l 20 -W" "wrlck" "unlck" "unlck"
# setlk posix wrlck [0,9], getlk wrlck [20,29]
do_test "-s -w -o 0 -l 10 -W -P" "-g -w -o 20 -l 10 -W" "unlck" "unlck" "unlck"

# setlk wrlck [0,9], getlk rdlck [0,9]
do_test "-s -w -o 0 -l 10 -W" "-g -r -o 0 -l 10 -W" "wrlck" "unlck" "wrlck"
# setlk wrlck [0,9], getlk posix rdlck [5,24]
do_test "-s -w -o 0 -l 10" "-g -r -o 5 -l 20 -P" "wrlck" "unlck" "wrlck"
# setlk wrlck [0,9], getlk rdlck [20,29]
do_test "-s -w -o 0 -l 10" "-g -r -o 20 -l 10" "unlck" "unlck" "unlck"
# setlk posix wrlck [0,9], getlk rdlck [5,24]
do_test "-s -w -o 0 -l 10 -P" "-g -r -o 5 -l 20" "wrlck" "unlck" "unlck"
# setlk posix wrlck [0,9], getlk rdlck [20,29]
do_test "-s -w -o 0 -l 10 -P" "-g -r -o 20 -l 10" "unlck" "unlck" "unlck"

# setlk rdlck [0,9], getlk wrlck [0,9], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -w -o 0 -l 10 -R" "rdlck" "unlck" "rdlck"
# setlk rdlck [0,9], getlk wrlck [5,24], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -w -o 5 -l 20 -R -P" "rdlck" "unlck" "rdlck"
# setlk posix rdlck [0,9], getlk wrlck [5,24], open RDONLY
do_test "-s -r -o 0 -l 10 -R -P" "-g -w -o 5 -l 20 -R" "rdlck" "unlck" "unlck"

# setlk rdlck [0,9], getlk wrlck [0,9]
do_test "-s -r -o 0 -l 10" "-g -w -o 0 -l 10" "rdlck" "unlck" "rdlck"
# setlk rdlck [0,9], getlk posix wrlck [5,24]
do_test "-s -r -o 0 -l 10" "-g -w -o 5 -l 20 -P" "rdlck" "unlck" "rdlck"
# setlk posix rdlck [0,9], getlk wrlck [5,24]
do_test "-s -r -o 0 -l 10 -P" "-g -w -o 5 -l 20" "rdlck" "unlck" "unlck"

# setlk rdlck [0,9], getlk wrlck [20,29], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -w -o 20 -l 10 -R" "unlck" "unlck" "unlck"
# setlk posix rdlck [0,9], getlk wrlck [20,29], open RDONLY
do_test "-s -r -o 0 -l 10 -R -P" "-g -w -o 20 -l 10 -R" "unlck" "unlck" "unlck"
# setlk rdlck [0,9], getlk wrlck [20,29]
do_test "-s -r -o 0 -l 10" "-g -w -o 20 -l 10" "unlck" "unlck" "unlck"
# setlk posix rdlck [0,9], getlk wrlck [20,29]
do_test "-s -r -o 0 -l 10 -P" "-g -w -o 20 -l 10" "unlck" "unlck" "unlck"

# setlk rdlck [0,9], getlk rdlck [0,9], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -r -o 0 -l 10 -R" "unlck" "unlck" "unlck"
# setlk rdlck [0,9], getlk posix rdlck [0,9], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -r -o 0 -l 10 -R -P" "unlck" "unlck" "unlck"
# setlk posix rdlck [0,9], getlk rdlck [0,9], open RDONLY
do_test "-s -r -o 0 -l 10 -R -P" "-g -r -o 0 -l 10 -R" "unlck" "unlck" "unlck"
# setlk rdlck [0,9], getlk rdlck [0,9]
do_test "-s -r -o 0 -l 10" "-g -r -o 0 -l 10" "unlck" "unlck" "unlck"
# setlk posix rdlck [0,9], getlk rdlck [0,9]
do_test "-s -r -o 0 -l 10 -P" "-g -r -o 0 -l 10" "unlck" "unlck" "unlck"

# setlk rdlck [0,9], getlk rdlck [20,29], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -r -o 20 -l 10 -R" "unlck" "unlck" "unlck"
# setlk rdlck [0,9], getlk posix rdlck [20,29], open RDONLY
do_test "-s -r -o 0 -l 10 -R" "-g -r -o 20 -l 10 -R -P" "unlck" "unlck" "unlck"
# setlk posix rdlck [0,9], getlk rdlck [20,29], open RDONLY
do_test "-s -r -o 0 -l 10 -R -P" "-g -r -o 20 -l 10 -R" "unlck" "unlck" "unlck"
# setlk rdlck [0,9], getlk rdlck [20,29]
do_test "-s -r -o 0 -l 10" "-g -r -o 20 -l 10" "unlck" "unlck" "unlck"
# setlk posix rdlck [0,9], getlk rdlck [20,29]
do_test "-s -r -o 0 -l 10 -P" "-g -r -o 20 -l 10" "unlck" "unlck" "unlck"

# success, all done
status=0
exit
