#!/bin/ksh 
#
#ident	"@(#)flar.sh	1.2	01/01/18 SMI"
#
# Copyright (c) 2000 by Sun Microsystems, Inc.
# All rights reserved.
#

   ######################################################
   #                                                    #
   # flar -- Utility Functions for Flash Archives       #
   #                                                    #
   ######################################################

TEXTDOMAIN=SUNW_INSTALL_FLASH
export TEXTDOMAIN

##############################################################
# Function print_error_and_exit prints a formatted message
# to stderr prefixed with ERROR: and new-lined, and exits 1.
##############################################################

print_error_and_exit ()
{
	typeset format=$1
	shift
	typeset values=$*

	printf "$(gettext 'ERROR:') ${format}\n" $values >&2

	exit 1
}

##############################################################
# Function print_message prints a formatted message to stderr
##############################################################

print_message ()
{
	typeset format=$1
	shift
	typeset values=$*

	printf "${format}\n" $values >&2
}

##############################################
# Function print_usage_and_exit does just that
##############################################

print_usage_and_exit ()
{
	# Remember to update the usage in flarcreate if you update this one
	typeset myname=`basename $0`
	print_message "$(gettext "Usage:")"
	if [[ $subcommand_selected -eq 0 || $do_create -eq 1 ]] ; then
		print_message "$myname $(gettext 'create -n name [-R root] [-H] [-S] [-c] [-t [-p posn] [-b blocksize]]')"
		print_message "             $(gettext '[-i date] [-u section [-d path ]] [-U key=value] [-m master]')"
		print_message "             $(gettext '[-f [ list_file | - ] [-F]]')"
		print_message "             $(gettext '[-a author] [-e descr | -E descr_file] [-T type] [-x exclude]')"
		print_message "             $(gettext 'archive')"
	fi
	if [[ $subcommand_selected -eq 0 ]] ; then
		print_message ""
	fi

	if [[ $subcommand_selected -eq 0 || $do_combine -eq 1 ]] ; then
		print_message "$myname $(gettext 'combine [-d dir] [-u section] [-t [-p posn] [-b blocksize]] archive')"
	fi
	if [[ $subcommand_selected -eq 0 ]] ; then
		print_message ""
	fi

	if [[ $subcommand_selected -eq 0 || $do_split -eq 1 ]] ; then
		print_message "$myname $(gettext 'split   [-d dir] [-u section] [-f] [-S sect]')"
		print_message "             $(gettext '[-t [-p posn] [-b blocksize]] archive')"
	fi
	if [[ $subcommand_selected -eq 0 ]] ; then
		print_message ""
	fi

	if [[ $subcommand_selected -eq 0 || $do_info -eq 1 ]] ; then
		print_message "$myname $(gettext 'info    [-l] [-k keyword] [-t [-p posn] [-b blocksize]] archive')"
	fi

	exit 1
}

##############################################################
# Function print_error_and_usage prints a formatted message to stderr
# with ERROR: and calls print_usage_and_exit
##############################################################

print_error_and_usage ()
{
	typeset format=$1
	shift
	typeset values=$*

	printf "$(gettext 'ERROR:') ${format}\n" $values >&2
	print_usage_and_exit
}

###################################################
# retrieves a line from stdin, safely, without
# using ksh's internal read function, which
# spins indefinitely on binary lines.
###################################################
get_line ()
{
    typeset tmp_line=`/usr/bin/awk "{print $1} {exit}"`
    if [ $? != 0 ] ; then
	return 1
    fi
    echo "$tmp_line"
    return 0
}

##############################################################
# Functions dial(), start_dial(), stop_dial(), and cleanup()
# implement a spinner of |, /. -, and \. dial() runs in the
# background. Use of traps is criticalcal to avoid spinning after
# user has Control-C'd.
##############################################################

dial()
{
	typeset state=0
	trap - EXIT INT # Set trap to ignore

	while [[ 1 -eq 1 ]] ; do
		case $state in
		    0)
			echo "|\b\c"   # top-bottom
			state=1
			;;
		    1)
			echo "/\b\c"   # upper right, lower left
			state=2
			;;
		    2)
			echo "-\b\c"   # left-right
			state=3
			;;
		    3)
			echo "\\" "\b\b\c" # upper left, lower right
			state=0 
			;;
		esac

		sleep 1
	done
}
start_dial()
{
	[[ $QUIET = $YES ]] && return
	dial &       # Start in background
	DIALPID=$!   # Get pid, for killing
	trap "cleanup 1" EXIT INT  # Set trap
}
stop_dial()
{
	[[ $QUIET = $YES ]] &&  return
	trap "" EXIT INT  
	kill $DIALPID >/dev/null 2>&1
	DIALPID=
	echo " \b\c"
}
cleanup()
{
	typeset exitcode=$1

	if [[ ! -z "$DIALPID" ]] ; then
		kill $DIALPID
	fi
	exit $exitcode
}

###################################################
# Function remove_from_list removes an element from
# a list.  If the element was not in the list,
# the original list (minus any surrounding whitespace)
# is returned on stdout.
#
# $1 = space-separated list
# $2 = element to remove
###################################################
remove_from_list()
{
    typeset newlist
    for i in $1 ; do
	if [[ $i != $2 ]] ; then
	    newlist="$newlist $i"
	fi
    done
    newlist=${newlist# }
    newlist=${newlist% }
    [ -n "$newlist" ] && {
	echo "$newlist"
    }
}

###################################################
# Function in_list examines list for existance
# of an element
#
# $1 = space-separated list
# $2 = element to look for
# returns zero if found, non-zero if not
###################################################
in_list()
{
    for i in $1 ; do
	if [[ $i = $2 ]] ; then
	    return 0
	fi
    done
    return 1
}

###################################################
# Function file_read_or_stop checks for readability
###################################################

file_read_or_stop()
{
	typeset input_file=$1

	if [[ -r $input_file ]]; then
		return 0
	else
		print_error_and_exit "$(gettext "File %s is not readable.")" $input_file
	fi
}

###################################################
# Function file_write_or_stop checks for writability
###################################################

file_write_or_stop ()
{
	typeset output_file=$1

	[[ -e $output_file ]] && rm -rf $output_file
	touch $output_file
	if [[ -w $output_file ]]; then
		rm -rf $output_file
		return 0
	else
		print_error_and_exit "$(gettext "File %s could not be created for writing.")" $output_file 
	fi
}

##################################################################
# Function get_compression sees what, if any, compression
# Prints answer, so run under command execution
##################################################################

get_compression ()
{
	typeset ident=$1
	typeset line=$(grep "files_compressed_method" $ident)

	print ${line##*=}
}

###################################################
# Function gen_archive does -c processing
# Puts all information to standard output for caller
###################################################

gen_archive ()
{
	typeset result
	typeset command

	cat $use_directory/$cookie
	[[ $? -ne 0 ]] && return 1
	[[ $DEBUG -ge 1 ]] && print_message "Wrote cookie."
	
	# remove cookie and archive (if in there) from list of sections
	section_list=`remove_from_list "$section_list" $cookie`
	section_list=`remove_from_list "$section_list" $archive`

	# Put each identification section
	for section in $section_list
	do
		print "section_begin="$section
		cat $use_directory/$section
		print "section_end="$section
	done
	# Put archive contents
	print "section_begin="$archive
	if [[ -d $use_directory/$archive ]]; then
		cd $use_directory/$archive
		if [[ $(get_compression ../$identification) = "compress" ]]; then
			find . -print | cpio -oc 2>/dev/null | compress -c
		else
			find . -print | cpio -oc 2>/dev/null	
		fi
		result=$?
		cd $current_dir
	else
		dd <$use_directory/$archive 2>/dev/null
		result=$?
	fi
	[[ $result -ne 0 ]] && return 1
	[[ $DEBUG -ge 1 ]] && print_message "Wrote archive."
	return 0
}

###################################################
# Function gen_split does -s processing
# Receives archive as standard input from caller
###################################################

gen_split ()
{
	typeset old_ident=
	typeset result
	typeset done
	typeset section_done
	typeset key
	typeset val
	typeset process_archive=$NO
	# Read and copy cookie
	input_line=`get_line`
	if [[ $? -ne 0 ]]; then
		print_error_and_exit "$(gettext "Did not succeed in reading input file.")"
	fi
	if [[ ${input_line%%-[0-9]*.[0-9]*} != "FlAsH-aRcHiVe" ]] then
		print_error_and_exit "$(gettext "File %s is not an archive file.")" $archive_file
	fi
	# Validate cookie 
	in_list "$section_list" $cookie && {
		print $input_line >$use_directory/$cookie
		[[ $DEBUG -ge 1 ]] && print_message "Extracted %s." \"$cookie\"
		section_list=`remove_from_list "$section_list" $cookie`
	}

	# remove archive from list, since it's assumed we'll work on it
	# later and we don't want to think of it as an unknown section
	if in_list "$section_list" $archive ; then
	    section_list=`remove_from_list "$section_list" $archive`
	    process_archive=$YES

	    # force ident section to be extracted, so that we know
	    # what the compression method is for extracting the
	    # archive later on
	    section_list="$identification $section_list"
	fi

	done=$NO
	while [ $done != $YES ]
	do

		# Read until past section_begin
		success=0
		read input_line
		key=${input_line%%=*}
		val=${input_line##*=}
		
		# we should always be at the start of a section here
		if [ "$key" != "section_begin" ] ; then
			print_error_and_exit "$(gettext "Invalid data between section")"
		fi
		section_name=$val

		# if we're at the archive, don't process any more sections.
		# if the section list isn't empty, warn about ignored sections
		if [[ "$section_name" = "$archive" ]] ; then
		    if [ -n "$section_list" -a "$section_list" != $archive ] ; then
			print_message "$(gettext "WARNING: ignoring non-existant sections: %s")" $section_list
		    fi
		    done=$YES
		    continue
		fi
		
		# skip past this section if not in the section list,
		# if we are doing custom lists
		if [ $custom_list == $YES ] && ! in_list "$section_list" $section_name ; then
		    # Copy lines until detecting section_end
		    success=0
		    while read input_line
		    do
			    if [[ $input_line = "section_end=$section_name" ]]; then
				    success=1
				    break
			    fi
			    # if we read all the way to the archive, something bad happened
			    if [[ $input_line = "section_begin=$archive" ]] ; then
				print_error_and_exit "$(gettext "section %s has no end")" $section_name
			    fi
		    done
		    if [[ $success = 0 ]] ; then
			# if we got here, we read to the end of the file before
			# seeing the end of the section we were reading; this is bad
			print_error_and_exit "$(gettext "EOF before end of section %s")" $section_name
		    else
			# else we finished with the section.  Go back and start
			# on the next section.
			continue
		    fi
		else
			# we are about to process this section.  Remove it
			# from the list first.
			section_list=`remove_from_list "$section_list" $section_name`
		fi
		
		section_done=$NO
		# read entire section, dumping its contents to disk, until the end of
		# the section
		while [ $section_done != $YES ] ; do
		    # the following awk scripts reads lines from stdin until:
		    # a) the end of the current section (success, exit level 0),
		    # b) the beginning of the archive section is reached (error, exit level 1),
		    # c) the end of the file is reached, error, exit level 2
		    awk " BEGIN                            { status = 2 }  \
		          /^section_begin=$archive\$/      { status = 1; exit status } ; \
			  /^section_end=$section_name\$/   { status = 0; exit status } ; \
		                                           { print } ; \
			  END                              { exit status } \
		    " > $use_directory/$section_name

		    if [ $? != 0 ] ; then
			# if we read all the way to the archive, or fell off the end of
		        # the file, something bad happened
			print_error_and_exit "$(gettext "section %s has no end")" $section_name
			# never reached
		    fi
		    
		    # if we get here, we successfully read to the end of the current
		    # section, so let's fall out of the loop
		    section_done=$YES
		done

		# print out something if we just extracted the ident section
		if [[ $section_name = $identification ]] ; then
		    if [[ $DEBUG -ge 1 ]] ; then
			print_message "Extracted identification section"
		    fi
		fi
	done

	# Copy remaining archive file to "archive", if it's in the list
	# to process
	if [[ $process_archive = $YES ]] ; then
	    if [[ $arch_cpio = $YES ]]; then
		# Copy files as cpio output to $archive *directory*
		mkdir $use_directory/$archive
		if [[ $? -ne 0 ]]; then
		    print_error_and_exit "$(gettext "Could not make directory %s.")" $use_directory/$archive
		fi
		cd $use_directory/$archive
		if [[ $(get_compression ../$identification) = "compress" ]]; then
		    zcat | cpio -icd 2>/dev/null
		else
		    cpio -icd 2>/dev/null
		fi
		result=$?
		cd $current_dir
	    else
		# Copy files as what they are to $archive *file*
		dd >$use_directory/$archive 2>/dev/null
		result=$?
	    fi
	    [[ $result -ne 0 ]] && return 1
	    [[ $DEBUG -ge 1 ]] && print_message "Extracted %s." $archive
	fi
	return 0 
}

###################################################
# Prints a list of the files in the archive
# coming in through stdin.
###################################################

print_list ()
{
	typeset compression=$NO

	# Read ident
	success=0
	in_line=`get_line`
	if [[ $? -ne 0 ]]; then
		print_error_and_exit "$(gettext "Could not read cookie from %s.")" $archive_file
	fi
	if [[ ${in_line%%-[0-9]*.[0-9]*} != "FlAsH-aRcHiVe" ]] then
		print_error_and_exit "$(gettext "File %s is not an archive file.")" $archive_file
	fi
	while read input_line
	do
		if [[ $input_line = "section_begin=identification" ]]; then
			success=1
			break
		fi
	done

	if [[ $success -eq 0 ]]; then
		print_error_and_exit "$(gettext "EOF on %s before finding ident section.")" $archive_file
	fi

	success=0
	while read input_line
	do
		if [[ $input_line = "section_end=identification" ]]; then
			success=1
			break
		elif [[ $input_line = "files_compressed_method=compress" ]]; then
			compression=$YES
		fi
	done

	if [[ $success -eq 0 ]]; then
		print_error_and_exit "$(gettext "EOF on %s before finding end of ident section.")" $archive_file
	fi

	success=0
	while read input_line
	do
		if [[ $input_line = "section_begin=archive" ]]; then
			success=1
			break
		fi
	done

	if [[ $success -eq 0 ]]; then
		print_error_and_exit "$(gettext "EOF on %s before finding archive section")" $archive_file
	fi
	
	success=0
	
	pipeline_cmd="cpio -ict"

	if [[ $compression = $YES ]] ; then
	    pipeline_cmd="uncompress -c | $pipeline_cmd"
	fi

	eval $pipeline_cmd 2> /dev/null
	if [ $? = 0 ] ; then
		success=1;
	fi

	if [[ $success -eq 0 ]]; then
		print_error_and_exit "$(gettext "EOF on %s before finding end of archive section")" $archive_file
	fi

}

###################################################
# prints indentification from stdin
# from its caller. Also, validates the cookie.
###################################################
print_info ()
{
	typeset in_line=
	typeset temp=

	in_line=`get_line`
	if [[ $? -ne 0 ]]; then
		print_error_and_exit "$(gettext "Could not read cookie from %s.")" $archive_file
	fi
	if [[ ${in_line%%-[0-9]*.[0-9]*} != "FlAsH-aRcHiVe" ]] then
		print_error_and_exit "$(gettext "File %s is not an archive file.")" $archive_file
	else
		# Version information extracted for possible future use
		temp=${in_line#FlAsH-aRcHiVe-}
		major_version=${temp%.[0-9]*}
		minor_version=${temp#[0-9]*.}
	fi
	read in_line
	if [[ $in_line != "section_begin=identification" ]]; then
		print_error_and_exit "$(gettext "File %s does not contain identification section.")" $archive_file
	fi 
		
	while [ 1 ] ; do
		read in_line
		if [[ $in_line = "section_end=identification" ]]; then
			# Finished; how to exit?
			if [[ ! -z $keyword_value ]]; then
				# Didn't find specified value, if we got here
				exit 2
			else
				exit 0
			fi
		else
			# If -k option, see if we have found it
			if [[ ! -z $keyword_value ]]; then
				if [[ ${in_line%%=*} = $keyword_value ]]; then
					print $in_line
					exit 0
				fi
			else # Not -k, so print each line
				print $in_line
			fi
		fi
	done
}


###################################################
# gen_info processes -i, and expects standard input
# from its caller. Also, validates the cookie.
###################################################

gen_info ()
{
	if [ "$info_list" = $YES ] ; then
	    print_list
	else
	    print_info
	fi
}

#########################################################
# Execution starts here.
# Miscellaneous intializations and beginning-of-job steps
#########################################################

NO="No"
YES="Yes"
current_dir=$(pwd)

if [[ $# -lt 1 ]]; then
	print_usage_and_exit
fi

####################################################
# Initialize defaults and parse the invoking command
####################################################

cookie="cookie"
identification="identification"
archive="archive"
subcommand_selected=0
do_combine=0
do_split=0
do_info=0
do_create=0
gen_which=
custom_list=$NO
keyword_value=
info_list=$NO
arch_cpio=$NO
master_section_list="$cookie $identification $archive"
section_list="$master_section_list"
use_directory=$(pwd)
tape_position=
tape_usage=$NO
tape_blocksize=
tape_block_default="64k"
DEBUG=0
QUIET=$NO

# Look for a leading keyword
case "$1" in
	combine|-c)
		do_combine=1
		;;
	create)
		do_create=1
		;;
	info|-i)
		do_info=1
		;;
	split|-s)
		do_split=1
		;;
	*)
		print_usage_and_exit
		;;
esac
shift
subcommand_selected=1

# If they specified create, switch over to flarcreate.
if [[ $do_create -eq 1 ]] ; then
	flarcreate=/usr/sbin/flarcreate
	if [[ -x $flarcreate ]] ; then
		exec $flarcreate "$@"
	else
		print_error_and_exit \
		    "$(gettext "Unable to find flarcreate as $flarcreate")"
	fi
fi

# Process the arguments
while getopts ":b:d:fk:lp:qS:tu:v" opt; do
	case $opt in
		b  ) tape_blocksize=$OPTARG ;;
		d  ) use_directory=$OPTARG ;;
		f  ) arch_cpio=$YES ;;
		k  ) keyword_value=$OPTARG ;;
		l  ) info_list=$YES ;;
		p  ) tape_position=$OPTARG ;;
		q  ) QUIET=$YES ;;     # PRIVATE
		t  ) tape_usage=$YES ;;
		u  ) section_list="$section_list $OPTARG" ; custom_list=$YES ;;
		S  ) section_list="$OPTARG" ; custom_list=$YES ;;
		v  ) DEBUG=1 ;;        # PRIVATE
		\? ) print_error_and_usage "$(gettext "Option -%s is invalid.")" $OPTARG ;;
		\: ) print_error_and_usage "$(gettext "Option -%s has no value.")" $OPTARG ;;
		*  ) print_usage_and_exit ;;
	esac
done
shift $(($OPTIND - 1))

# Isolate outfile parameter - it should be the only argument left
if [[ $# -gt 1 ]] ; then
	print_usage_and_exit
elif [[ $# -eq 0 ]] ; then
	print_error_and_usage "$(gettext "Archive file name must be supplied.")"
fi
archive_file=$1

###############################################
# Make format, dependency, and existence checks
###############################################

# Checking common to all options
if [[ -z $archive_file ]]; then
	if [[ $tape_usage = $NO ]]; then
		print_error_and_usage "$(gettext "Path name for archive must be provided.")"
	else
		print_error_and_usage "$(gettext "Tape drive name for archive must be provided.")"
	fi
fi
if [[ $tape_usage = $NO ]]; then
	if [[ $archive_file = ${archive_file#/} ]]; then
		archive_file=$(pwd)/$archive_file
	fi
fi
if [[ ! -z $tape_position ]]; then
	if [[ $tape_usage = $NO ]]; then
		print_error_and_usage \
			"$(gettext "Option -p is invalid in the absence of option -t.")"
	else
		# $tape_usage = $YES
		if [[ $tape_position != +([0-9]*) ]]; then
			print_error_and_usage \
				"$(gettext "Tape position %s must be a number.")" $tape_position
		fi
	fi
fi
if [[ ! -z $tape_blocksize && $tape_usage = $NO ]]; then
	print_error_and_usage \
		"$(gettext "Option -b is invalid in the absence of option -t.")"
fi
if [[ $tape_usage =  $YES ]]; then
	tape_blocksize=${tape_blocksize:-$tape_block_default}
	if [[ $tape_blocksize != +([0-9])?(@(k|b|w)) ]]; then
		print_error_and_usage \
			"$(gettext "Tape blocksize %s must be number[k|b|w].")" $tape_blocksize
	fi
fi

# Checking for -c option
if [[ $do_combine -eq 1 ]]; then
	if [[ ! -d $use_directory ]]; then
		print_error_and_exit "$(gettext "Directory %s is not accessible.")" $use_directory
	fi
	if [[ $arch_cpio = $YES ]]; then
		print_error_and_usage "$(gettext "Option -f is not relevant to -c option.")"
	fi
	if [[ ! -z $keyword_value ]]; then
		print_error_and_usage "$(gettext "Option -k is not relevant to -c option.")"
	fi

	file_read_or_stop $use_directory/$cookie
	for section in $section_list
	do
		file_read_or_stop $use_directory/$section
	done
	file_read_or_stop $use_directory/$archive

	if [[ $tape_usage = $NO ]]; then
		rm -f $archive_file
		touch $archive_file
		if [[ ! -w $archive_file ]]; then
			print_error_and_exit "$(gettext "File %s is not writable.")" $archive_file
		fi
	fi

# Checking for -s option
elif [[ $do_split -eq 1 ]]; then
	gen_which="gen_split"
	if [[ $arch_cpio = $YES ]] ; then
	    ! in_list "$section_list" $archive && {
		print_error_and_usage \
- 			"$(gettext "Option -f is not relevant when archive is not produced.")"
	    }
	fi
	if [[ ! -z $keyword_value ]]; then
		print_error_and_usage "$(gettext "Option -k is not relevant to -s option.")"
	fi
	if [[ ! -d $use_directory ]]; then
		print_error_and_exit \
			"$(gettext "Directory %s is not accessible.")" $use_directory
	fi

	file_write_or_stop $use_directory/$cookie
	for section in $section_list
	do
		file_write_or_stop $use_directory/$section
	done
	file_write_or_stop $use_directory/$archive

	if [[ $tape_usage = $NO && ! -r $archive_file ]]; then
		print_error_and_exit "$(gettext "File %s is not readable.")" $archive_file
	fi

# Checking for -i option
elif [[ $do_info -eq 1 ]]; then
	gen_which="gen_info"
	if [ "$custom_list" = $YES -o $arch_cpio = $YES -o \
	      $use_directory != $(pwd) ]; then
		print_error_and_usage \
			"$(gettext "Options -d, -f, -S, and -u are not relevant to option -i.")"
	fi

	if [[ $tape_usage = $NO && ! -r $archive_file ]]; then
		print_error_and_exit "$(gettext "File %s is not readable.")" $archive_file
	fi

fi
[[ $DEBUG -ge 1 ]] && print_message "Checks completed."

######################################
# Position magnetic tape, if requested
######################################

if [[ $tape_usage = $YES && ! -z $tape_position ]]; then
	print_message "$(gettext "Positioning tape drive...")"
	start_dial

	mt -f $archive_file asf $tape_position
	if [[ $? -gt 0 ]]; then
		print_error_and_exit \
			"$(gettext "Unable to move tape %s to position %d.")" $archive_file $tape_position 
	fi

	stop_dial
	[[ $DEBUG -ge 1 ]] && print_message "Tape positioned."
fi

#########################################
# Perform the requested option
#########################################

# Process -c
if [[ $do_combine -eq 1 ]]; then
	if [[ $tape_usage = $NO ]]; then
		gen_archive >> $archive_file
	else
		gen_archive | dd of=$archive_file obs=$tape_blocksize 2>/dev/null
	fi
	status=$?

	if [[ $status -ne 0 ]]; then
		print_error_and_exit "$(gettext "Unable to write archive file %s.")" $archive_file
	fi
fi

# Process -s and -i
if [[ $do_split -eq 1 || $do_info -eq 1 ]]; then
	if [[ $tape_usage = $NO ]]; then
		$gen_which < $archive_file
	else
		dd if=$archive_file ibs=$tape_blocksize 2>/dev/null | $gen_which
	fi
	status=$?

	if [[ $status -ne 0 ]]; then
		print_error_and_exit \
			"$(gettext "Unable to process archive file %s.")" $archive_file
	fi
fi

#########################################
# Perform any EOJ processing, and exit
#########################################

exit 0
