#!/bin/ksh
#=============================================================================
#  
#  semaphore - a Korn shell implementation of a counting semaphore
#  Copyright (C) 2004 Intel Corporation
#  
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the Free
#  Software Foundation; either version 2 of the License, or (at your option)
#  any later version.
#  
#  This program is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
#  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
#  for more details.
#  
#  You should have received a copy of the GNU General Public License along
#  with this program; if not, see http://www.gnu.org/copyleft/gpl.html or
#  write to:
#  
#  The Free Software Foundation, Inc.,
#  59 Temple Place
#  Suite 330, Boston, MA 02111-1307
#  USA
#  
#  Author:
#  
#  John Spurgeon
#  5200 NE Elam Young Parkway
#  Hillsboro, OR 97124
#  
#  john.p.spurgeon@intel.com
#  
#=============================================================================

print_documentation()
{
	grep -v ^#= $LICENSE_FILE | cut -d'#' -f2- >&2
	print -n "Press Enter to continue ..." >&2
	read line
	man -F semaphore
}

source_file()
{
	local files=$@

	for file in $files
	do
		if [[ -z $file ]]
		then
			print "Error in source_file: missing filename!" >&2
			exit 2
		elif [[ ! -f $file ]]
		then
			print "File $file not found!" >&2
			exit 2
		elif [[ ! -r $file ]]
		then
			print "File $file is not readable!" >&2
			exit 2
		else
			. $file
		fi
	done
	return 0
}

#*************
# MAIN PROGRAM
#*************

local FUNC_NAME=main
TRACE=":"
INFO=":"
ERROR=":"

#----------
# Constants
#----------

INFINITY=-1
WIPE_INTERVAL=30
USER=$(id | cut -d'(' -f2 | cut -d')' -f1)
GROUP=$(id | cut -d'(' -f3 | cut -d')' -f1)

#----------
# Filenames
#----------

QUEUE_DIRNAME=queue
RESOURCES_DIRNAME=resources
RESOURCES_FILENAME=num_resources
LAST_WIPE_FILENAME=last_wipe

#------------
# Directories
#------------

ROOT_DIR=/tmp/semaphores
SOURCE_DIR=/opt/local/semaphore
LIB_DIR=$SOURCE_DIR/lib
DOC_DIR=$SOURCE_DIR/doc
MANPATH=$DOC_DIR

#------
# Files
#------

P_SEMAPHORE=$LIB_DIR/P_semaphore
V_SEMAPHORE=$LIB_DIR/V_semaphore
INIT_SEMAPHORE=$LIB_DIR/Init_semaphore
QUEUE_FUNCTIONS=$LIB_DIR/queue_functions
TESTCASE_FUNCTIONS=$LIB_DIR/testcase_functions
SHARED_FUNCTIONS=$LIB_DIR/shared_functions
PRINT_FUNCTIONS=$LIB_DIR/print_functions
LICENSE_FILE=$DOC_DIR/license

#========================
# Check for liscense file
#========================

if [[ ! -f $LICENSE_FILE ]]
then
	print "$LICENSE_FILE is not readable!"
	exit 2
elif [[ ! -r $LICENSE_FILE ]]
then
	print "$LICENSE_FILE is not readable!"
	exit 2
fi

#========================
# Source Shared Functions
#========================

source_file $SHARED_FUNCTIONS

#-----------------------------------
# Local Variables and Default Values
#-----------------------------------

local I_optarg=1
local I_bool=FALSE
local P_bool=FALSE
local V_bool=FALSE
local d_bool=FALSE
local l_bool=FALSE
local o_optarg=$USER:$GROUP
local p_optarg=0755
local q_bool=FALSE
local r_bool=FALSE
local s_optarg=1
local t_optarg=$INFINITY
local u_optarg=$USER
local v_bool=FALSE
local w_bool=FALSE
local action_queue=""
local pid=$PPID
integer num_resources

#================================
# Parse the command line options.
#================================

while getopts PVI:Tdlo:p:m:qrs:t:u:vw option
do
	case $option in
		V)
			V_bool=TRUE
			action_queue="$action_queue V"
			;;
		P)
			P_bool=TRUE
			action_queue="$action_queue P"
			;;
		I)
			I_bool=TRUE
			if is_non_negative_integer $OPTARG
			then
				I_optarg=$OPTARG
			fi
			;;
		T)
			source_file $TESTCASE_FUNCTIONS
			shift $(($OPTIND-1))
			test_semaphore $@
			exit 0
			;;
		d) d_bool=TRUE ;;
		p) pid=$OPTARG ;;
		l) l_bool=TRUE ;;
		o)
			if is_ownership_string $OPTARG
			then
				o_optarg=$OPTARG
			else
				exit 2
			fi
			;;
		m)
			if is_permissions_mode $OPTARG
			then
				p_optarg=$OPTARG
			else
				exit 2
			fi
			;;
		q) q_bool=TRUE ;;
		r) r_bool=TRUE ;;
		s)
			if is_non_negative_integer $OPTARG
			then
				s_optarg=$OPTARG
			else
				print "Seconds must be non-negative!" >&2
				exit 2
			fi
			;;
		t)
			if is_non_negative_integer $OPTARG
			then
				t_optarg=$OPTARG
			else
				print "Tries must be non-negative!" >&2
				exit 2
			fi
			;;
		u) u_optarg=$OPTARG ;;
		v) v_bool=TRUE ;;
		w) w_bool=TRUE ;;
		?)
			print_documentation
			exit 0
			;;
	esac
done

OWNER_STRING=$o_optarg
PERM_STRING=$p_optarg
SLEEP_SECONDS=$s_optarg
TIMEOUT_TRIES=$t_optarg
HOME_DIR=$ROOT_DIR/$u_optarg

#==============================
# Define INFO, ERROR, and TRACE
#==============================

if [[ $v_bool = TRUE ]]
then
	INFO=""
fi

if [[ $d_bool = TRUE ]]
then
	TRACE=""
	ERROR=""
fi

#=================================
# Source additional function files
#=================================

if [[ $P_bool = TRUE ]] || [[ $I_bool = TRUE ]]
then
	source_file $INIT_SEMAPHORE
fi

if [[ $P_bool = TRUE ]]
then
	source_file $P_SEMAPHORE
fi

if [[ $V_bool = TRUE ]]
then
	source_file $V_SEMAPHORE
fi

if [[ $q_bool = TRUE ]]
then
	source_file $QUEUE_FUNCTIONS
fi

if [[ $d_bool = TRUE ]] || [[ $v_bool = TRUE ]] || [[ $l_bool = TRUE ]]
then
	source_file $PRINT_FUNCTIONS
fi
	
#==================================
# Check for command line arguments
#==================================

if [[ -z $1 ]]
then
	print_documentation
	exit 0
fi

#===========================================
# Get the operand and define some path names
#===========================================

shift $(($OPTIND-1))

if [[ -n $1 ]] \
|| ( [[ $r_bool = TRUE ]] || [[ $l_bool = TRUE ]] || [[ $w_bool = TRUE ]] ) \
|| ( [[ $I_bool = FALSE ]] && [[ $P_bool = FALSE ]] && [[ $V_bool = FALSE ]] )
then
	semaphore=$1
	SEMAPHORE_DIR=$HOME_DIR/$semaphore
	QUEUE_DIR=$SEMAPHORE_DIR/$QUEUE_DIRNAME
	RESOURCES_FILE=$SEMAPHORE_DIR/$RESOURCES_FILENAME
	RESOURCES_DIR=$SEMAPHORE_DIR/$RESOURCES_DIRNAME
else
	print "Missing semaphore name!" >&2
	exit 2
fi

#====================
# Trap for interrupts
#====================

trap "abort" 2 3

#=======================================
# Remove and/or initialize the semaphore
#=======================================

if [[ -n $semaphore ]]
then
	if [[ $r_bool = TRUE ]]
	then
		rm -rf $SEMAPHORE_DIR
		${INFO:-info "Removed semaphore $semaphore."}
	fi
	if is_initialized $semaphore
	then
		num_resources=$(get_num_resources $semaphore)
		if [[ $I_bool = TRUE ]] && (( $I_optarg != $num_resources ))
		then
			num_resources=$I_optarg
			Init_semaphore $semaphore $num_resources
		fi
	elif [[ $I_bool = TRUE ]]
	then
		num_resources=$I_optarg
		Init_semaphore $semaphore $num_resources
	elif [[ $P_bool = TRUE ]]
	then
		num_resources=1
		Init_semaphore $semaphore $num_resources
	fi
elif [[ $r_bool = TRUE ]]
then
	rm -rf $HOME_DIR
	${INFO:-info "Removed ALL semaphores for $USER."}
fi

#===========================================
# P (try) and/or V (increment) the semaphore
#===========================================

for action in $action_queue
do
	case $action in
	P)
		if [[ $q_bool = TRUE ]]
		then
			if put_in_queue
			then
				if ! wait_in_queue $semaphore $num_resources $pid $TIMEOUT_TRIES
				then
					${ERROR:-error "Error while PID $pid was waiting in queue!"}
					exit 1
				fi
			else
				${ERROR:-error "Failed to put PID $pid in the queue!"}
				exit 1
			fi
		else
			if ! keep_trying $semaphore $num_resources $pid $TIMEOUT_TRIES
			then
				exit 1
			fi
		fi
		;;
	V)
		V_semaphore $semaphore $num_resources $pid
		;;
	esac
done

#======================
# Wipe the semaphore(s)
#======================

if [[ $w_bool = TRUE ]]
then
	if [[ -n $semaphore ]]
	then
		wipe_semaphore $semaphore
	else
		for semaphore in $(get_semaphores)
		do
			wipe_semaphore $semaphore
		done
		semaphore=""
	fi
fi

#=========================================
# Print information about the semaphore(s)
#=========================================

if [[ $l_bool = TRUE ]]
then
	if [[ -n $semaphore ]]
	then
		if is_initialized $semaphore
		then
			date
			echo $HOME_DIR
			print_semaphore $semaphore $num_resources
		else
			print "Semaphore $semaphore not found in $HOME_DIR"
		fi
	else
		semaphores=$(get_semaphores)
		if [[ -n $semaphores ]]
		then
			date
			echo $HOME_DIR
			for semaphore in $semaphores
			do
				num_resources=$(get_num_resources $semaphore)
				print_semaphore $semaphore $num_resources
			done
		else
			print "No semaphores found in $HOME_DIR"
		fi
	fi
fi
