#!/bin/bash
# Public domain
# Copyright (C) 2005, 2006 Vitaly Lipatov <lav@etersoft.ru>
# Copyright (C) 2005, 2006 Pavel Vainerman <pv@etersoft.ru>
# Copyright (C) 2006 Kruchinin Danyil <asgard@etersoft.ru>
# Copyright (C) 2007, 2012 Vitaly Lipatov <lav@etersoft.ru>
#
# Автоматическое тестирование блокировок файлов.
# В качестве параметра скрипту можно задать каталог, в котором производить тестирование
# При запуске под root в этом каталоге создаются домашние каталоги двух пользователей
# и производится совместное тестирование.
# При запуске под пользователем проверка производится в одиночном режиме, в текущем каталоге,
# или в указанном.
#
# =======================================
# Алгоритм работы:
# 1) Создаётся среда выполнения (cм. create_test_environment())
#	- основная часть системы развёртывается в ROOTDIR
#	- Для запуска теста используется SHAREDIR
#	- создаётся группа WGROUP для пользователей
#	- создаётся два пользователя USER1 и USER2 c домашними каталогоами в ROOTDIR/USER
# 	- для каждого пользователя запускатеся wine для генерирования структуры каталогов
#	- В общий каталог SHAREDIR помещаются тестовые программы
#	- у каждого пользователя создаётся диск t:, указывающий на этот каталог
#	WARNING:
#		pipe-ы для обмена между процессами создаются в /tmp (всегда).
#		Возможно стоит доработать тестирующую программу, чтобы можно было менять.
#
#	2) Запуск теста (см. run_test() )
#		- тестовая программа PROGNAME запускается под пользователем USER1 
#			в режиме "slave" (в фоновом режиме). 
#		(Для запуска slave генерируется вспомогательный скрипт winelocktest-run.sh)
#			Warning: После запуска "slave" приходится менять права на pipe-ы и lock-файлы,
#					которые создаётся slave. 
#					Чтобы их мог прочитать процесс запущенный под другим пользователем(USER2)
#		
#		- под пользователем USER2 программа запускается в режиме "master"
#			и выводит результат теста в файл RESULT
#		- "slave" останавливается
#		- происходит сравнение эталона и результатов (с выводом на экран)
#
#	3) Свёртывание (см. remove_test_environment() )
#		- удаляются пользователи из системы
#		- удаляется группа из системы
#		- удаляется каталог ROOTDIR
#		- удаляются временные файлы созданные (или скопированные) в SHAREDIR
#	-------------------------
#	TODO: 
#		- нет проверки "неудачного" выполнения команд "развертывания"
#               - перехватывать сигналы выхода (через trap) и всегда корректно выходить
# ---------------------------------------
umask 0002

prefix=/usr
exec_prefix=/usr
datadir=/usr/share/wine

SHAREDIR=`pwd`

if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then
	echo "winelocktest - test for correct file locking in Unix/Wine"
	echo "Usage: [testdir] [homedir]"
	echo " testdir - dir for check locking (current dir by default)"
	echo " homedir - dir for temporary home dir of test users (under root only)"
	exit 0
fi

if [ -d "$1" ] ; then
	SHAREDIR=$1
	shift
fi

ROOTDIR=/tmp/winelocktest				# каталог, в котором создаётся тестовая среда
if [ -d "$1" ] ; then
	ROOTDIR=$1/winelocktest
	shift
fi

#echo "Please login as root, we will create users in your system..."
# Use ~/.wine by default.
[ -z "$WINEPREFIX" ] && export WINEPREFIX=${HOME}/.wine

test -z "$WINELOADER" && WINELOADER=wine-glibc

CROOT=$WINEPREFIX/dosdevices/c:
#if [ -d "$1" ] ; then
#	CURDIR="$1"
#	shift
#else
	CURDIR=$($WINELOADER winepath -w "`pwd`")
#fi



# Указываем файл
#if [ -n "$1" ] ; then
#	TESTFILE="$1"
#fi


PROGNAME="winelocktest-linux.exe.so"
TESTPROG="/usr/lib/wine/$PROGNAME"	# проверяющая программа
USER1=winelock-user1
USER2=winelock-user2
WGROUP=winelock-group				# группа для пользователей
RESULT=result.txt		# результат теста
STANDART=${datadir}/lock-standard.txt	# эталон
test -z "$UID" && UID=`id -u`
UNAME=`uname`
[ "$UNAME" = "FreeBSD" ] && PWP="pw" || PWP=""
CREATETEST=

# ---------------------------------------

remove_test_environment()
{
	[ -z "$CREATETEST" ] && return
	[ -n "$DEBUGMODE" ] && return
	#echo "Run log for server user part:"
	#cat ${ROOTDIR}/$USER1/.wine/wine.log
	echo "Removing test environment(ROOTDIR=${ROOTDIR})..."
	$PWP userdel $USER1 -r
	$PWP userdel $USER2 -r
	$PWP groupdel $WGROUP
	rm -f ${SHAREDIR}/${PROGNAME}
    	rm -f ${SHAREDIR}/lockfile.*
        rm -f /tmp/multitest-*
	rm -f ${SHAREDIR}/${RESULT}

	rm -f ${ROOTDIR}/*
	rmdir ${ROOTDIR}

}

failed()
{
	[ -n "$DEBUGMODE" ] && return
	echo "FAILED: $1"
	remove_test_environment
	exit 1
}


create_test_environment()
{
	CREATETEST=1
	echo "Creating test environment in ${ROOTDIR}, shared dir is ${SHAREDIR}"
	mkdir -p ${ROOTDIR} && chmod 'a+rw' ${ROOTDIR} || failed "mkdir"

	$PWP groupadd $WGROUP || failed "groupadd"
	$PWP useradd $USER1 -m -d ${ROOTDIR}/$USER1 -G $WGROUP || failed "useradd $USER1"
	$PWP useradd $USER2 -m -d ${ROOTDIR}/$USER2 -G $WGROUP || failed "useradd $USER2"

	# Creating correct WINE tree
	for i in $USER1 $USER2 ; do
		echo "Create wine environment for $i"
		su - $i -c "unset DISPLAY ; $WINELOADER winepath ; rm -f ~/.wine/dosdevices/t: ; ln -s ${SHAREDIR} ~/.wine/dosdevices/t: ; wineserver -k"
	done

	#cp -f $RUNPROG ${ROOTDIR}/tmp/
#	cp -f $TESTPROG ${ROOTDIR}/ || failed "copy $TESTPORG"
	cp -f $TESTPROG ${SHAREDIR}/${PROGNAME} || failed "copy $TESTPORG"
	#rm -f /tmp/winelocktest.log > /dev/null
	#cp -f $STANDART ${ROOTDIR}/
	
	# генерирование вспомогательного скрипта
	cat >${ROOTDIR}/winelocktest-run.sh << EOF
#!/bin/sh
$WINELOADER ${PROGNAME} \$1 >~/.wine/winelocktest.log 2>~/.wine/winelocktest.log &
echo \$!
EOF
	chmod 'a+x' ${ROOTDIR}/winelocktest-run.sh || failed "chmod"
}

check_result()
{
	grep -v "^Info" "${SHAREDIR}/$RESULT" | grep -v "Start" | sed -e "s|  u|  l|g" | sed -e "s|  F|  l|g" >"${SHAREDIR}/$RESULT.1"
	if diff "${SHAREDIR}/$RESULT.1" "$STANDART" >/dev/null; then
		echo
		echo "WINE lock test PASSED for $SHAREDIR!"
		echo
		rm -f "${SHAREDIR}/${RESULT}.1"
		return 0
	fi
	echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
	echo "WINE LOCK TEST FAILED for $SHAREDIR! (see table above)"
	echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
	rm -f "${SHAREDIR}/${RESULT}.1"
	return 1
}

run_test()
{
	local PIDFILE

	# Check DISPLAY
	#xdpyinfo >/dev/null || failed "Please run with correct DISPLAY"

	# allow X access for subusers
	#XHOST=/usr/bin/xhost
	#if [ -x "$XHOST" ] ; then
	#	xhost +local:
	#	xhost +localhost
	#fi
	PIDFILE=${ROOTDIR}/server.pid

	# test permissions for write to sharedir
	su - $USER1 -c "touch $SHAREDIR/$USER1.tmp && rm -f $SHAREDIR/$USER1.tmp" || failed "Permission denied for write($USER1) to ${SHAREDIR}"
	su - $USER2 -c "touch $SHAREDIR/$USER2.tmp && rm -f $SHAREDIR/$USER2.tmp" || failed "Permission denied for write($USER2) to ${SHAREDIR}"

	echo -n "Run server... "
		
	su - $USER1 -c "unset DISPLAY ; cd ~/.wine/dosdevices/t: && sh ${ROOTDIR}/winelocktest-run.sh -s $TESTFILE > ${PIDFILE}"
		
	sleep 1
	SPID=$( cat ${PIDFILE} )
	[ -z "$SPID" ] && failed "server not started"
	echo "started with PID $SPID"
	
	echo "Wait for one second..."
	sleep 1
	# Minor bug fixed :)	
#	chown $USER1:$WGROUP ${ROOTDIR}/multitest* || failed
#	chmod 'a+rw' ${ROOTDIR}/lockfile* || failed
#	chmod 'a+rw' ${ROOTDIR}/*pipe* || failed
	# TODO: why tmp??
	if ! ps ax | grep "$SPID" >/dev/null ; then
		failed "server part is not started correctly"
	fi
	chown $USER1:$WGROUP /tmp/multitest* || failed "common files is not created by server"
	chmod 'a+rw' /tmp/*pipe* || failed
	chmod 'a+rw' ${SHAREDIR}/lockfile* || failed

	#sleep 2
	su - $USER2 -c "export DISPLAY=$DISPLAY ; cd ~/.wine/dosdevices/t: && $WINELOADER $PROGNAME -m $TESTFILE | grep -v 'Start test' | tee $RESULT"
	#sed "s|Compiled for Linux. Sizeof(struct flock):16||" -i ${ROOTDIR}/$RESULT 
	kill $SPID
	rm -f ${PIDFILE}
	
	sleep 3 # пауза для успешного завершения wineserver

	check_result || return 1
}


Sfailed()
{
	echo "FAILED: $1"
	exit 1
}


# --------------------------------------------------------------------------------------------------
if [ "$UID" = "0" ]; then
	create_test_environment
	sleep 2

	run_test
	RETVAL=$?

	sleep 2
	remove_test_environment
	exit $RETVAL
fi

[ -z "$CURDIR" ] && Sfailed "Use inside Wine's drive"
echo
echo "Check $CURDIR in single user mode..."

# Case sensitivity check
#cd $CURDIR
rm -f winelock.TeST winelock.TEST
touch winelock.TeST || Sfailed "Can't create file in $CURDIR (`pwd`)"
sleep 1 # or we can get false positive
if stat winelock.TEST 2>/dev/null >/dev/null ; then
	# possible broken case sensitivity
	if ! cat winelock.TEST 2>/dev/null >dev/null ; then
		Sfailed "Broken disabled case sensitivity. Set case sensitive = yes in smb.conf. See also http://bugs.etersoft.ru/show_bug.cgi?id=2822."
	else
		echo "Note: This filesystem in case insensitivity mode. It is recommended to set case sensitive = yes in smb.conf. See also http://bugs.etersoft.ru/show_bug.cgi?id=2822"
	fi
else
	echo "Checked: This filesystem in usual, case sensitivity mode"
	rm -f winelock.*
	touch winelock.TeST || Sfailed "Can't create file in $CURDIR (`pwd`)"
	if stat winelock.TEST 2>/dev/null >/dev/null ; then
		Sfailed "Flapping case sensitivity. Set case sensitive = yes in smb.conf. See also http://bugs.etersoft.ru/show_bug.cgi?id=2822."
	fi
fi
rm -f winelock.TeST

#cd $CROOT || failed
test -r "$CROOT/windows/command/winelocktest.exe.so" || ln -sf ${TESTPROG} "$CROOT/windows/command/winelocktest.exe.so"
SHAREDIR=$(pwd)

# save results only on master
if [ "$1" = "-m" ] || [ -z "$1" ] ; then
	$WINELOADER "$CROOT/windows/command/winelocktest.exe.so" $@ 2>&1 | tee "$SHAREDIR/$RESULT"
	check_result
	rm -f "$SHAREDIR/$RESULT"
else
	$WINELOADER "$CROOT/windows/command/winelocktest.exe.so" $@
fi
exit $?
