#!/bin/ksh 
#
#ident	"@(#)flarcreate.sh	1.6	03/08/25 SMI"
#
# Copyright (c) 2000-2003 by Sun Microsystems, Inc.
# All rights reserved.
#

   ########################################
   #                                      #
   # flarcreate -- Create a Flash Archive #
   #                                      #
   ########################################

TEXTDOMAIN=SUNW_INSTALL_FLASH
export TEXTDOMAIN

# list of required utilities.  If you add any to this
# list, be sure to also add it to the $UTIL_LIST variable below!
AWK="/usr/bin/awk"
SED="/usr/bin/sed"
DF="/usr/sbin/df"
RM="/usr/bin/rm"
CPIO="/usr/bin/cpio"
COMPRESS="/usr/bin/compress"
WC="/usr/bin/wc"
CAT="/usr/bin/cat"
UNAME="/usr/bin/uname"
FIND="/usr/bin/find"
TOUCH="/usr/bin/touch"
MT="/usr/bin/mt"
DD="/usr/bin/dd"
ID="/usr/bin/id"
LS="/usr/bin/ls"

UTIL_LIST="$AWK $SED $DF $RM $CPIO $COMPRESS $WC $CAT $UNAME $FIND $TOUCH $MT $DD $ID $LS"

##############################################################
# Function find_utils makes sure required utilies exist.
##############################################################
find_utils_or_exit ()
{
    for i in $UTIL_LIST ; do
	if [ ! -x $i ] ; then
		print_error_and_exit "$(gettext "Required utility %s is not available.")" $i
	fi
    done
}

##############################################################
# 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 flar if you update this one
	typeset myname=`basename $0`
	print_message "$(gettext "Usage:")"
	print_message "$myname $(gettext '-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]')"	
	print_message "             $(gettext '[[-x exclude_dir/file][-x exclude_dir/file]...] [-X list_file]')"
	print_message "             $(gettext '[[-y include_dir/file [-y include_dir/file]...]')"	
	print_message "             $(gettext '[-z filter_list_file]')"	
	print_message "             $(gettext 'archive')"
	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
}

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

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

	while : ; 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
	if [[ -x $hash_file ]] ; then
	    ${RM} -f $hash_file
	fi
	exit $exitcode
}

#####################################################################
# Function get_platforms makes a string for the content_architectures
# identification line. It prints the string, so the caller should
# get the string as $(get_platforms).
#####################################################################

get_platforms ()
{
	typeset file="var/sadm/system/admin/.platform"
	typeset entry=
	typeset line=
	typeset values=
	
	# if there is no $root_directory, then the following argument
	# to grep will be relative to /.  Also, we aren't interested
	# in any error output from grep, all we care is that it failed,
	# so ignore stderr.
	values=$(grep "^PLATFORM_GROUP" $root_directory/$file 2> /dev/null)
	if [[ $? -ne 0 ]]; then
		print $(${UNAME} -m)
		return
	fi
	for entry in $values ; do
		[[ ! -z $line  ]] && line="${line},"
		line="${line}${entry#PLATFORM_GROUP=}"
	done 
	print $line
}

######################################################
# Function size_archive implements the sizing feature
# of flarcreate.
######################################################

size_archive()
{
	# Set up
	archive_size=0
	print_message "$(gettext "Determining the size of the archive...")"
	start_dial

	archive_size=$(gen_archive | \
			${WC} -c |${AWK} '{print $1}')
	status=$?

	stop_dial
	# Clean up
	if [[ $status != 0 || -z "$archive_size" || $archive_size -eq 0 ]]; then
		print_error_and_exit "$(gettext "Unable to find size of intended archive.")"
	fi

	# Print the size
	if [[ ${#archive_size} -gt 9 ]] ; then
		unit="GB"
		div=1073741824
	elif [[ ${#archive_size} -gt 6 ]] ; then
		unit="MB"
		div=1048576
	else
		unit="KB"
		div=1024
	fi

	intnum=$(echo $archive_size / $div |bc)
	fracnum=$(echo $archive_size / \( $div / 100 \) % 100 |bc) 

	print_message "$(gettext "The archive will be approximately %d.%02d%s.")" $intnum $fracnum $unit
}

################################################################
# Function gen_ident_and_cookie outputs the cookie and the 
# identification lines in the output archive. Also outputs any
# user-specified control sections.
################################################################

gen_ident_and_cookie()
{
	typeset keyword
	typeset section
	typeset bad_start
	typeset cookie="FlAsH-aRcHiVe-1.0"
	typeset ident_begin="section_begin=identification"
	typeset hash_key="archive_id="
	typeset hash="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

	hash_length=${#hash}
	hash_offset=`expr ${#cookie} + 1 + ${#ident_begin} + 1 + ${#hash_key}`
	hash_file=/tmp/.flarcreate.hash.$$
	${CAT} <<-EOF
		$cookie
		$ident_begin
	EOF

	if [[ $generate_hash = $YES ]] ; then
	    if [ -z "${COMPUTEHASH}" ] ; then
		print_message "$(gettext "WARNING: computehash not found; cannot generate checksums")"
		generate_hash=$NO
	    else
		${COMPUTEHASH} -n
		if [ $? != 0 ] ; then
		    print_message "$(gettext "WARNING: Hash generation only supported on Solaris 8 and later")"
		    generate_hash=$NO
		else
		    echo "${hash_key}${hash}"
		fi
	    fi
	fi

	if [[ $root_directory = "/" ]]; then
		creation_node=`uname -n`
		creation_hardware_class=`uname -m`
		creation_platform=`uname -i`
		creation_processor=`uname -p`
		creation_release=`uname -r`
		creation_os_name=`uname -s`
		creation_os_version=`uname -v`
	else
		creation_node=`${CAT} ${root_directory}/etc/nodename`
		if [[ "X$creation_node" = "X" ]]; then
		    creation_node="unknown"
		fi
		creation_hardware_class="unknown"
		creation_platform="unknown"
		creation_processor="unknown"
		creation_os_version="unknown"

		. ${root_directory}/var/sadm/system/admin/INST_RELEASE

		case $OS in
			Solaris  )
			creation_os_name=SunOS
			case $VERSION in
			    2.* ) creation_release=5.${$VERSION#*.} ;;
			    7 )   creation_release=5.7 ;;
			    8 )   creation_release=5.8 ;;
			    9 )   creation_release=5.9 ;;
			    * )   creation_release=unknown ;;
			esac
			;;
			*  )
			    creation_os_name=unknown
			    creation_release=unknown
			;;
		esac
	fi

	${CAT} <<-EOF
		files_archived_method=cpio
		creation_date=$creation_date
		creation_master=$creation_master
		content_name=$content_name
		creation_node=$creation_node
		creation_hardware_class=$creation_hardware_class
		creation_platform=$creation_platform
		creation_processor=$creation_processor
		creation_release=$creation_release
		creation_os_name=$creation_os_name
		creation_os_version=$creation_os_version
	EOF

	if [[ $compress_action = $YES ]]; then
		echo "files_compressed_method=compress"
	else
		echo "files_compressed_method=none"
	fi

	if [[ $sizing_action = $YES ]]; then
		echo "files_archived_size=$archive_size"
	fi

	#### TODO : files_unarchived_size

	if [[ ! -z $content_description ]]; then
		echo "content_description=$content_description"
	fi

	if [[ ! -z $content_type ]]; then
		echo "content_type=$content_type"
	fi

	if [[ ! -z $content_author ]]; then
		echo "content_author=$content_author"
	fi

	echo "content_architectures=$(get_platforms)"

	for keyword in $new_keywords; do
		if [[ $keyword = "${keyword#X-}" ]]; then
			print_message "$(gettext "Added keyword %s does not begin with %s.")" $keyword "X-"
		fi
		echo $keyword
	done

	echo "section_end=identification"

	# Write user-specified sections, if any
	for section in $section_list; do
		echo "section_begin=$section"
		${CAT}  $use_directory/$section
		echo "section_end=$section"
	done
}

#####################################################
# Function gen_archive files performs pipeline of
# cpio and perhaps compress, and perhaps computehash.
#####################################################

gen_archive_files()
{
	echo "section_begin=archive"
	cd / 	
	typeset gen_cmd="/usr/lib/flash/fdo filter_dir $FILTER $root_dir | \
		${CPIO} -oc"	
	if [[ $generate_hash = $YES ]] ; then
	    gen_cmd="$gen_cmd | ${COMPUTEHASH} -f $hash_file"
	fi
	if [[ $compress_action = $YES ]] ; then
	    gen_cmd="$gen_cmd | compress -c"
        fi
	
        eval ${gen_cmd}
	if [[ $? -eq 0 ]]; then
		return 0
	else
		return 1
	fi
}

#######################################################
# Function gen_archive performs the high-level control
# of flarcreate.
#######################################################

gen_archive()
{

	gen_ident_and_cookie

	gen_archive_files

	status=$?

	if [[ $status != 0 ]] ; then
		print_message "$(gettext "Unable to write archive file.")"
		return $status
	fi
}

#####################################
# Check that user is root
# (/usr/sbin/patchadd validate_uid())
#####################################

typeset -i uid
uid=$(${ID} | ${SED} 's/uid=\([0-9]*\)(.*/\1/')
if (( uid != 0 ))
then
	print_error_and_exit "$(gettext "You must be root to execute this script.")"
fi

#########################################################
# Miscellaneous intializations and beginning-of-job steps
#########################################################

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

# make sure required binaries exist
find_utils_or_exit

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

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

content_author=
compress_action=$NO
creation_date=$(date -u '+''%Y''%m''%d''%H''%M''%S')
content_description_path=
use_directory=$PWD
creation_master=$(${UNAME} -n)
content_name=
tape_position=
tape_blocksize=
file_list=
exclusive_file_list=$NO
tape_block_default="64k"
sizing_action=$YES
generate_hash=$YES
hash_offset=0
hash_length=0
hash_file=
section_list=
new_keywords=
tape_usage=$NO
exclude_path=
content_description=
root_directory="/"
old_root_directory="NON_DIFFERENTIAL"
content_type=
archive_outfile=
getopt_error=$NO
DEBUG=0
QUIET=$YES
COMPUTEHASH=
comparsion=new_dir_compare
FILTER="/tmp/flash_filter_one_.$$"

while getopts ":a:b:cCd:e:E:f:FHi:m:n:p:qR:StT:u:U:vx:X:y:z:" opt; do
	case $opt in
		a  ) content_author=$OPTARG ;;
		b  ) tape_blocksize=$OPTARG ;;
		c  ) compress_action=$YES ;;
		C  ) comparsion=full_new_dir_compare ;;	
		d  ) use_directory=$OPTARG ;;
		e  ) content_description=$OPTARG ;;
		E  ) content_description_path=$OPTARG ;;
		f  ) /usr/lib/flash/fdo do_file_list $OPTARG >> $FILTER ;; 
		F  ) echo - / >> $FILTER ;;
		i  ) creation_date=$OPTARG ;;
		m  ) creation_master=$OPTARG ;;
		n  ) content_name=$OPTARG ;;
		H  ) generate_hash=$NO ;;
		p  ) tape_position=$OPTARG ;;
		q  ) QUIET=$YES ;;            # PRIVATE
		R  ) root_directory=$OPTARG ;;
		S  ) sizing_action=$NO ;;
		t  ) tape_usage=$YES ;;
		T  ) content_type=$OPTARG ;;
		u  ) section_list="$section_list $OPTARG" ;;
		U  ) new_keywords="$new_keywords $OPTARG" ;;
		v  ) DEBUG=1 ;;               # PRIVATE
		x  ) echo - $OPTARG >> $FILTER ;; 
		X  ) /usr/lib/flash/fdo do_x_list $OPTARG >> $FILTER ;;
		y  ) echo + $OPTARG >> $FILTER ;; 
		z  ) cat $OPTARG >> $FILTER ;;	
		\? ) 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

# find a valid computehash
if [ -x /usr/sbin/computehash ] ; then
    COMPUTEHASH=/usr/sbin/computehash
elif [ -x `dirname $0`/computehash ] ; then
    _pwd=$PWD
    cd `dirname $0`
    COMPUTEHASH=`pwd`/computehash
    cd $_pwd
else
    COMPUTEHASH=
fi

# Isolate outfile parameter - it should be the only argument left
shift $(($OPTIND - 1))
if [[ $# != 1 ]] ; then
	print_usage_and_exit
fi
archive_outfile=$1

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

if [[ ! -d $root_directory ]] ; then
	print_error_and_usage "$(gettext "Directory %s does not exist or is not a directory.")" $root_directory
fi

if [[ $root_directory = ${root_directory#/} ]]; then
	print_error_and_usage "$(gettext "Root directory must have an absolute path.")"
fi

#on_local_fs $root_directory
#if [[ $? != 0 ]] ; then
#	print_error_and_exit "$(gettext "Root directory must be on a ufs or vxfs file system.")"
#fi

if [[ -z $content_name ]]; then
	print_error_and_usage "$(gettext 'Content name (option -n) must have a value.')"
fi
if [[ ${#creation_date} -ne 14  ]]; then
	print_error_and_usage "$(gettext "Creation date %s must be 14 digits long.")" $creation_date
fi
if [[ $creation_date != +([0-9]) ]]; then
	print_error_and_usage "$(gettext "Creation date %s must be numeric.")" $creation_date
fi
if [[ ! -z $content_description_path ]]; then
	if [[ ! -z $content_description  ]]; then
		print_error_and_usage "$(gettext "Options -E and -e are mutually exclusive.")"
	fi
	if [[ ! -r $content_description_path  ]]; then
		print_error_and_exit "$(gettext "Content description file %s is not available.")" $content_description_path
	fi
	content_description=$(< $content_description_path)
fi
if [[ $use_directory != $PWD  && -z $section_list ]]; then
	print_error_and_usage "$(gettext "Option -d is invalid in the absence of option -u.")"
fi
for section in $section_list; do
	if [[ ! -r $use_directory/$section ]]; then 
		print_error_and_exit "$(gettext "User section %s is not available.")" $use_directory/$section
	fi
done
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_exit "$(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_exit "$(gettext "Tape blocksize %s must be number[k|b|w].")" $tape_blocksize
	fi
	if [[ $generate_hash = $YES ]]; then
	     print_message "$(gettext 'WARNING: hash generation disabled when using tape (-t)')"
	     generate_hash=$NO
	fi
fi
		
if [[ ! -z $exclude_path ]]; then
	if [[ ! -z "$root_directory" ]] ; then
		# make exclude dir relative to alternate root
		exclude_path=$root_directory/${exclude_path#/}
	fi
	if [[ ! -d $exclude_path ]]; then
		print_error_and_exit "$(gettext "Exclusion path %s is not a directory.")" $exclude_path
	else
		# Make relative exclude path absolute.  If we have an exclude
		# path AND an alternate root, this will never happen, as the
		# resulting exclude path will always be absolute after prepending
		# the alternate root.
		if [[ $exclude_path = ${exclude_path#/} ]]; then
			exclude_path=$(pwd)/$exclude_path
		fi		
	fi

	# collapse multiple /'s into a single /
	exclude_path=`echo $exclude_path | tr -s /`
	[[ $DEBUG -ge 1 ]] && echo "Using exclude path: $exclude_path"

	exclude_inode=$(${LS} -id $exclude_path |${AWK} '{print $1}')
fi

if [[ -z $archive_outfile ]]; then
	if [[ $tape_usage = $NO ]]; then
		print_error_and_usage "$(gettext "Path name for new archive must be provided.")"
	else
		print_error_and_usage "$(gettext "Tape drive name for new archive must be provided.")"
	fi
fi
if [[ $tape_usage = $NO ]]; then
	if [[ $archive_outfile = ${archive_outfile#/} ]]; then
		archive_outfile=$(pwd)/$archive_outfile
	fi
	if [[ -e $archive_outfile ]]; then
	${RM} -f $archive_outfile
	fi
	${TOUCH} $archive_outfile
	if [[ ! -w $archive_outfile ]]; then
		print_error_and_exit "$(gettext "Archive file %s is not writable.")" $archive_outfile
	fi
	read archive_inode junk <<-EOF
		$(${LS} -i $archive_outfile)
	EOF
fi

# append current directory if filelist is relative
if [[ -n "$file_list" ]] ; then
    if [[ $file_list = ${file_list#/} ]]; then
		file_list=$(pwd)/$file_list
    fi
fi


# we can't size the archive when doing a streamed file list, since
# we can only read the file list once.
if [[ "$file_list" = "-" && $sizing_action = $YES ]] ; then
    print_message "$(gettext "WARNING: Sizing not supported when using streamed file list")"
    sizing_action=$NO
fi
 
##############################################
# Determine what filesystems to archive, and
# build the archive pipelines
##############################################
start_dial

echo - /${archive_outfile#${root_directory}} >> $FILTER
  
(${CAT} /etc/vfstab;echo mnttab;${CAT} /etc/mnttab) | /usr/lib/flash/fdo mountpoints_to_filter ${root_directory} >> $FILTER   

echo ". /cdrom" >> $FILTER
echo ". /floppy" >> $FILTER

#/usr/lib/flash/fdo contents_to_filter /var/sadm/install/contents >> $FILTER

/usr/lib/flash/fdo sort_list $root_directory $FILTER > /tmp/flash_filter_two_.$$
${RM} -f $FILTER
FILTER="/tmp/flash_filter_two_.$$"
echo current filter settings

#echo integrity check

#/usr/lib/flash/fdo integrity_check $FILTER /var/sadm/install/contents

# Exclude the archive file if we're not using tape and the archive
# file is on this filesystem

# If we're using an exclude path and it's on this filesystem,
# exclude it

stop_dial
##########################################
# If asked, find out size of archive-to-be
##########################################

if [[ $sizing_action = $YES ]]; then
	size_archive
fi

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

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

	${MT} -f $archive_outfile asf $tape_position
	if [[ $? -gt 0 ]]; then
		print_error_and_exit "$(gettext "Unable to move tape %s to position %d.")" $archive_outfile $tape_position
	fi
	stop_dial
fi

###################
# Write the archive
###################

print_message "$(gettext "Creating the archive...")"
start_dial

# Use output redirection for disk, dd for tape
if [[ $tape_usage = $NO ]]; then
	gen_archive > $archive_outfile
else
	gen_archive | ${DD} of=$archive_outfile obs=$tape_blocksize 2>/dev/null
fi
status=$?

# add hash, if we computed one.  lets hope the hash goes near the
# beginning of the file or the seek will take forever with a blocksize of 1.
if [[ $generate_hash = $YES ]] ; then
    if [ -r $hash_file -a -f $hash_file ] ; then
	${DD} if=$hash_file of=$archive_outfile bs=1 count=$hash_length \
	    seek=$hash_offset conv=notrunc >/dev/null 2>&1
	${RM} -f $hash_file
    else
	print_error_and_exit "$(gettext "Could not generate hash for %s")" $archive_outfile
    fi
fi


stop_dial

if [[ $status != 0 ]] ; then
	print_error_and_exit "$(gettext "Unable to work archive.")"
fi

#########################################
# Perform end-of-job processing, and exit
#########################################

${RM} $FILTER

print_message "$(gettext "Archive creation complete.")"
exit 0
