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

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

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 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] [-x exclude]')"
	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 checks whether $2 is dirname of $1
##############################################

in_or_under()
{
	typeset head="$1"
	typeset dir="$2"

	head=$(echo $head |sed 's:/*$::')

	expr X$dir : "^X${head}/" >/dev/null 2>&1
	if [[ $? != 0 && "$dir" != "$head" ]] ; then
		return 1
	else
		return 0
	fi
}

##########################################
# Function makes root directory relative
##########################################

make_root_relative()
{
	typeset file="$1"

	file=${file#${root_directory}}

	if [[ -z "$file" ]] ; then
		echo .
	elif expr "X$file" : "X/" >/dev/null 2>&1 ; then
		echo ".${file}"
	else
		echo "./${file}"
	fi
}

#################################################
# Functions test if fstype parameter is ufs or
# vxfs, which are neccesarily local
#################################################

is_local_fstype()
{
	typeset fstype="$1"

	if [[ $fstype = "ufs" || $fstype = "vxfs" || $fstype = "pcfs" ]] ; then
		return 0
	else
		return 1
	fi
}
on_local_fs()
{
	typeset file="$1"

	is_local_fstype $(df -n $file |awk '{print $3;}')
}	

#############################################
# Function find_interesting_filesystems
#############################################
#
# Description: Find the filesystems that contain files to be included in the
# archive.  The following rules govern filesystem selection:
#		1. If $root_directory is set (isn't /), the filesystem must
#		   be mounted under $root_directory.
#		2. The filesystem must be neither the exclude directory
#		   (if any), nor under it.
#		3. The filesystem must not be under the control of vold
#		4. The filesystem must be local.  We assume only filesystems
#		   of type `ufs' or `vxfs' are local.
#
# This function sets the global array int_fs and the global
# variable int_fs_num.  The latter holds the number of entries
# in the former.
#
# It also adds semi-interesting mount points to semiint_fs
# (and updates the corresponding counter semiint_fs_num).
# Semi-interesting mount points are mount points of non-local
# filesystems.

find_interesting_filesystems()
{
	typeset source=
	typeset mountpt=
	typeset fstype=
	typeset junk=
	typeset vold_mount=

	int_fs_num=0

	# If an alternate root has been specified that is not the root of
	# a filesystem, the top of the filesystem tree headded by the
	# alternate root won't be included.  We need to find the name of
	# the filesystem containing the alternate root so we can explicitly
	# make sure said filesystem is saved as interesting.
	if [[ $root_directory != "/" ]] ; then
		root_parent=$(df -n $root_directory |awk '{print $1;}')
		[[ $DEBUG -ge 1 ]] && echo "root_parent $root_parent"
	fi

	# If an alternate root has been specified, we need to find the
	# filesystem that contains the alternate root.  This filesystem is
	# interesting, and would not have been caught if the alternate root
	# wasn't the root 

	# Look for the mountpoint owned by vold (if any)
	while read source mountpt fstype junk ; do
		if expr $source : \
		    "^`uname -n`:vold(pid[0-9]*)$" >/dev/null 2>&1 ; then
			vold_mount="$mountpt"
			break
		fi
	done </etc/mnttab

	# Find the interesting filesystems.
	while read source mountpt fstype junk ; do
		# Skip filesystems not in the tree mounted by $root_directory
		# (if $root_directory is set)
		if [[ $root_directory != "/" ]] ; then
			
			in_or_under $root_directory $mountpt
			if [[ $? != 0 && $mountpt != "$root_parent" ]] ; then
				[[ $DEBUG -ge 1 ]] &&
				    echo "excluding non-root $mountpt"
				continue
			fi
		fi

		# Skip the exclude directory and anything under it
		if [[ ! -z $exclude_path ]] ; then
			if in_or_under $exclude_path $mountpt ; then
				[[ $DEBUG -ge 1 ]] &&
				    echo "excluding exclude $mountpt"
				continue
			fi
		fi

		# Skip filesystems whose source begins with $vold_mount (if set)
		if [[ ! -z $vold_mount ]] ; then
			if expr $source : \
			    "^${vold_mount}/" >/dev/null 2>&1 ; then
				[[ $DEBUG -ge 1 ]] &&
				    echo "excluding vol mounted $mountpt"
				continue
			fi
		fi

		# Skip the vold mount point too
		if [[ -n "$vold_mount" && $mountpt = $vold_mount ]] ; then
			[[ $DEBUG -ge 1 ]] &&
			    echo "excluding vold $mountpt"
			continue
		fi

		# Local filesystems only.  Since we don't know the filesystem
		# types for all remote filesystems, we'll punt and include only
		# filesystem types that we know are local
		is_local_fstype $fstype
		if [[ $? != 0 ]] ; then
			# This is a semi-interesting mount point.  It's not
			# a separate local filesystem that we want to put in
			# the archive.  Unfortunately, find was designed such
			# that the following won't pick up mount points:
			#	find . -print -mount
			# That means we won't get mountpoints for non-local
			# filesystems.  Here we save filesystems that would
			# have been printed if find did print mount points.
			save_semiinteresting_mount_point $mountpt

			[[ $DEBUG -ge 1 ]] &&
			    echo "excluding non-local $mountpt"
			continue
		fi

		set -A int_fs ${int_fs[*]} $mountpt
		int_fs_num=$((int_fs_num + 1))

		[[ $DEBUG -ge 1 ]] && echo accepted $mountpt $int_fs_num

	done </etc/mnttab

	# Sort the filesystems so we can put them in the archive in the proper
	# order.  The array needs to be sorted such that parent filesystems
	# always come before their children.
	set -A int_fs $(print_filesystems |sort)

	if [[ $DEBUG -ge 1 ]] ; then
		echo "Filesystems found:"
		print_filesystems
	fi
}

#############################################
# Function save_semiinteresting_mount_point
#############################################

save_semiinteresting_mount_point()
{
	typeset mountpt="$1"
	typeset last=
	typeset fstype=
	typeset found=

	# First, head up the path.  We want to find the place where $mountpt
	# actually mounts into the filesystem.  For example, say we get
	# /net/host/path.  /net/host and /net/host/path are magic things
	# conjured into existence by the automounter.  We want the highest
	# point in the path that is still governed by the automounter.
	#
	# This function assumes / is a local filesystem
	#
	while : ; do
		fstype=$(df -n $mountpt |awk '{print $3;}')
		if is_local_fstype $fstype ; then
			# We're one past the top.  $last has the actual
			# mount point.
			tosave=$last
			break
		else
			# Move one further up
			last=$mountpt
			mountpt=$(dirname $mountpt)
		fi
	done

	# Save this mount point if we haven't already seen it
	found=0
	for fname in ${semiint_fs[*]} ; do
		if [[ $fname = $tosave ]] ; then
			found=1
			break
		fi
	done

	if [[ $found -eq 0 ]] ; then
		set -A semiint_fs ${semiint_fs[*]} $tosave

		[[ $DEBUG -ge 1 ]] && echo semi-interesting $tosave
	fi
}

#############################################################
# Function	print_filesystems prints the filesystems in the
# filesystem array, one per line.
#############################################################

print_filesystems()
{
	typeset num=

	num=0
	while [ $num -lt $int_fs_num ] ; do
		echo ${int_fs[$num]}
		num=$(($num + 1))
	done
}

########################################################
# Function file_in_fs determines wither the $1 file
# is in the $2 filesystem. Returns 0 no, 1 yes.
########################################################

file_in_fs()
{
	typeset fs="$1"
	typeset file="$2"
	typeset match=
	typeset num=

	match=$(df -n $file |awk '{print $1;}')
	if [[ "$match" = "$fs" ]] ; then
		return 0
	else
		return 1
	fi
}

####################################################
# Function gen_filenames generates file names from
# the filesystem array. Used as start of pipe.
####################################################

gen_filenames()
{
	typeset num=

	num=0
	while [[ $num -lt $int_fs_num ]] ; do
		eval \$PIPE_$num
		num=$(($num + 1))
	done

	for fname in ${semiint_fs[*]} ; do
		make_root_relative $fname
	done

	# now generate file list filenames, if we
	# are doing that sort of thing
	if [[ "X$file_list" != "X" ]] ; then
	    eval $LIST_PIPE
	fi

}

######################################################
# 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

	cd $root_directory
	if [[ $compress_action = $YES ]] ; then
		archive_size=$(gen_filenames |cpio -oc 2>/dev/null | \
					compress -c | wc -c |awk '{print $1}')
	else
		archive_size=$(gen_filenames |cpio -oc 2>/dev/null | \
					wc -c |awk '{print $1}')
	fi
	status=$?
	cd $current_dir

	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

	cat <<-EOF
		files_archived_method=cpio
		creation_date=$creation_date
		creation_master=$creation_master
		content_name=$content_name
	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"
	typeset gen_cmd="gen_filenames |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()
{
	cd $root_directory

	gen_ident_and_cookie
	gen_archive_files

	status=$?

	cd $current_dir

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

#######################################################
# Function determine_filesystems determines which filesystems
# will be searched when generating filenames of files to be
# included in the archive.  This function builds a
# rather large "find" command line that, when executed,
# produces the filename list of files that should be included
# in the archive.
#######################################################
determine_filesystems()
{
    print_message "$(gettext "Determining which filesystems will be included in the archive...")"

    find_interesting_filesystems
    if [[ $int_fs_num = 0 ]] ; then
	print_error_and_exit "$(gettext "Could not find any local filesystems to include.")"
    fi

    # Build the pipeline for each filesystem
    num=0
    while [[ $num -lt $int_fs_num ]] ; do
	# The common beginning stuff
	PIPE="find"

	# If we're on an alternate root, and this is the filesystem that heads
	# the alternate root, we need to start the find at the alternate root
	# itself
	if [[ $root_directory != "/" && $root_parent = ${int_fs[$num]} ]] ; then
		PIPE="$PIPE ."
	else
		PIPE="$PIPE $(make_root_relative ${int_fs[$num]})"
	fi

	# More common stuff - exclude files we can't cpio		
	PIPE="$PIPE ! -type D ! -type s"

	# Exclude the archive file if we're not using tape and the archive
	# file is on this filesystem
	if [[ $tape_usage = $NO ]] && on_local_fs $archive_outfile ; then
		if file_in_fs ${int_fs[$num]} $archive_outfile ; then
			PIPE="$PIPE ! -inum $archive_inode"
		fi
	fi

	# If we're using an exclude path and it's on this filesystem,
	# exclude it
	if [[ ! -z $exclude_path ]] ; then
		if file_in_fs ${int_fs[$num]} $exclude_path ; then
			PIPE="$PIPE ( -inum $exclude_inode "
			PIPE="$PIPE -prune -o -print )"
		else
			PIPE="$PIPE -print"
		fi
	else
		PIPE="$PIPE -print"
	fi

	# The common trailing stuff
	PIPE="$PIPE ! -mount -prune"

	# Save the pipeline
	eval PIPE_$num=\""$PIPE"\"

	[[ $DEBUG -ge 1 ]] && echo "Filesystem: ${int_fs[$num]}, Command: $PIPE"

	num=$(($num + 1))
    done
}

#######################################################
# Function determine_filelist constructs a rather
# large command that, when executed, produces
# a list of files to be included in the archive,
# *based on the file list given on the command line*
#######################################################
determine_filelist() {

    # Build the pipeline for the file list

    # If file list is not coming from stdin, then we must cat it first
    if [[ $file_list != "-" ]] ; then
	    LIST_PIPE="cat $file_list | "
    fi

    # collapse multiple /'s into a single /
    LIST_PIPE="$LIST_PIPE sed -n -e 's:[/]\{1,\}:/:g'"

    # For relative filenames, append the current root
    # directory.
    LIST_PIPE="$LIST_PIPE -e 's:^[^/]:'${root_directory}'/&:'"

    # again, collapse multiple /'s into a single /
    LIST_PIPE="$LIST_PIPE -e 's:[/]\{1,\}:/:g'"


    # exclude any files lying within the exclude directory (if any)
    if [[ ! -z $exclude_path ]] ; then
	LIST_PIPE="$LIST_PIPE -e '\!^'${exclude_path}'! d'"
    fi

    if [[ $root_directory = "/" ]] ; then
	# if root directory is /, then all files are now valid,
	# since they are guaranteed to begin with /, so just
	# print them all.
	LIST_PIPE="$LIST_PIPE -e p"
    else
	# only print those files who lie within the alternate
	# root. if no root directory prefix is found, discard the
	# line.
	LIST_PIPE="$LIST_PIPE -e 's:^'${root_directory}'/:./:p'"
    fi

    [[ $DEBUG -ge 1 ]] && echo "File list command: $LIST_PIPE"
}

#####################################
# 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"

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="/"
content_type=
archive_outfile=
getopt_error=$NO
DEBUG=0
QUIET=$NO
COMPUTEHASH=

while getopts ":a:b:cd:e:E:f:FHi:m:n:p:qR:StT:u:U:vx:" opt; do
	case $opt in
		a  ) content_author=$OPTARG ;;
		b  ) tape_blocksize=$OPTARG ;;
		c  ) compress_action=$YES ;;
		d  ) use_directory=$OPTARG ;;
		e  ) content_description=$OPTARG ;;
		E  ) content_description_path=$OPTARG ;;
		f  ) file_list=$OPTARG ;;
		F  ) exclusive_file_list=$YES ;;
		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  ) exclude_path=$OPTARG ;;
		\? ) 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 -D and -f 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_blockize:-$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
	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

if [[ $exclusive_file_list = $YES && -z "$file_list" ]] ; then
    print_error_and_usage "$(gettext "Option -F invalid in the absense of option -f")"
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

if [ $exclusive_file_list != $YES ] ; then
    determine_filesystems
fi
  
if [ -n "{file_list}" ] ; then
    determine_filelist
fi
  
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 write archive.")"
fi

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

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