# install.tcl --
#
#	This file contains the back end of the Unix installer for TclPro.
#
# Copyright (c) 1998-2000 by Ajuba Solutions
# See the file "license.terms" for information on usage and redistribution of this file.
# 
# RCS: @(#) $Id: install.tcl,v 1.14 2001/03/06 17:08:09 andreas_kupries Exp $

namespace eval install {

    # The following variables contain the patterns that we need to
    # look for in the files we are patching for each of the given
    # compiler macros.  Their values are actually set by a Tcl
    # file generated by the genImage.tcl script (unwrapsize.tcl)
    # which is sourced by setup.tcl after this file.

    variable SHLIBPATH		{this value gets changed momentarily}
    variable TCL_LIBRARY	{this value gets changed momentarily}
    variable TCL_PACKAGE_PATH	{this value gets changed momentarily}

    variable done

    set executables {
	protclsh83
	prowish83
    }

    # The installer is soft about requiring acrobat. If you don't
    # have copies of the installer for your platform, leave them out.

    array set acrobat {
	solaris-sparc arsol302.tz
	irix-mips     arirx302.tz
	hpux-parisc   arhp302.tz
	linux-ix86    arlin302.tz
    }	

    array set acrobatInstallPrefix {
	solaris-sparc SSOLR
	irix-mips     IRIXR
	hpux-parisc   HPUXR
	linux-ix86    ILINXR
    }

if {0} {
    # No more license server
    array set serverPlatform {
	solaris-sparc licsol
	irix-mips     licsgi
	hpux-parisc   lichp
	linux-ix86    liclin
    }
}

    # The following are the names of the unzip program.
    # The funny names are copies that are put into the installer image.
    # If you platform always has an unzip installed, just put in the
    # absolute pathname to it.  Otherwise, you need to have a local
    # /tools directory with a copy of unzip, and then set up the
    # cdparts.in file to copy that unzip program into the name
    # listed below.

    array set unzipNames {
	solaris-sparc	uzpslsp
	irix-mips	uzpirmi
	hpux-parisc	uzphppa
	linux-ix86	/usr/bin/unzip
	aix-risc	uzpaix
	freebsd-ix86	/usr/local/bin/unzip
    }

    # installNames has the names of the wrapped Tclsh and Wish shells
    # that are used during installation.

    array set installNames {
	solaris-sparc	{ptsol pwsol}
	irix-mips	{ptsgi pwsgi}
	hpux-parisc	{pthp pwhp}
	linux-ix86	{ptlin pwlin}
	aix-risc	{ptaix pwaix}
	freebsd-ix86	{ptbsd pwbsd}
    }

    # This is a map from architecture tags to pretty names

    variable platforms {
	solaris-sparc	{Solaris/Sparc}
	irix-mips	{SGI IRIX}
	hpux-parisc	{HP-UX}
	linux-ix86	{Linux/Intel}
	aix-risc	{AIX}
	freebsd-ix86	{FreeBSD/Intel}
    }

    variable components {
	common       {TclPro Tools & Tcl/Tk 8.3}
	CDev         {C Development Libraries & Header Files}
	tclSource    {Tcl Sources}
	tkSource     {Tk Sources}
	incrSource   {[incrTcl] Sources}
	tclxSource   {TclX Sources}
	expectSource {Expect Sources}
    }

    # The zipfiles array maps from a tag in the parts list to which
    # zip file that file gets stored into
    # NOTE: The "common" zipfile is referenced all over the place.  Do not 
    # change the name unless you change it everywhere.  
    # NOTE2: These numbers are also reference in the cdparts.in file.

    array set zipfiles {
	common		   {tclpro.1 tclpro.2}
	solaris-sparc	   {tclpro.3}
	hpux-parisc	   {tclpro.4}
	tclSource	   {tclpro.5}
	tkSource	   {tclpro.6}
	incrSource	   {tclpro.7}
	linux-ix86         {tclpro.8}
	irix-mips          {tclpro.9}
	tclxSource	   {tclpro.10}
	expectSource	   {tclpro.11}
	cDevAll            {tclpro.12 tclpro.13}
	cDev_solaris-sparc {tclpro.14}
	cDev_hpux-parisc   {tclpro.15}
	cDev_linux-ix86    {tclpro.16}
	cDev_irix-mips     {tclpro.17}
	cDev_aix-risc      {tclpro.18}
	aix-risc           {tclpro.20}
	freebsd-ix86       {tclpro.21}
	cDev_freebsd-ix86  {tclpro.19}
    }

    variable zipSizes
    variable zipFileCount

    variable unzip ""
    variable acrobatTmpDir ""
    variable acrobatOldDir ""

    # This is the name of the shell wrapper template

    variable shellWrapperTemplate pro_wrap.sh
}

# install::getPlatforms --
#
#	Compute the set of platforms that are available to install.
#
# Arguments:
#	src		The directory that contains the installation files.
#
# Results:
#	Returns a list of alternating platform and platform descriptions.

proc install::getPlatforms {src} {
    variable platforms
    variable zipfiles

    set result {}
    foreach {plat desc} $platforms {
	if {![catch {FindFile $src $zipfiles($plat)}]} {
	    lappend result $plat $desc
	}
    }
    return $result
}

# install::getComponents --
#
#	Compute the set of components that are available to install.
#
# Arguments:
#	src		The directory that contains the installation files.
#	platforms	The list of platforms that are being installed.
#
# Results:
#	Returns a list of alternating component and component descriptions.

proc install::getComponents {src platforms} {
    variable components
    variable zipfiles

    set result {}
    foreach {comp desc} $components {
	set found 0
	switch $comp {
	    tclSource -
	    tkSource -
	    incrSource -
	    tclxSource -
	    expectSource {
		# Locate all the files necessary to install this component.  
		# If all files can be located, we can install it.
		
		foreach file $zipfiles($comp) {
		    set found 1
		    if {[catch {FindFile $src $file}]} {
			set found 0
			break
		    }
		}
	    }
	    common {
		# Locate all the files necessary to install this component.  
		# If all files can be located, we can install it.  The 
		# common files include the files in common and each platform.

		foreach file $zipfiles($comp) {
		    set found 1
		    if {[catch {FindFile $src $file}]} {
			set found 0
			break
		    }
		}
		if {$platforms == {}} {
		    set found 0
		}
		if {$found} {
		    foreach plat $platforms {
			if {[catch {FindFile $src $zipfiles($plat)}]} {
			    set found 0
			    break
			}
		    }
		}
	    }
	    CDev   {
		# We need to test for the existence of the common C Dev. file
		# and then check for the existence of each system specific C
		# Dev. file.  If all files can be located, we can install the 
		# C Dev files.
		
		foreach file $zipfiles(cDevAll) {
		    set found 1
		    if {[catch {FindFile $src $file}]} {
			set found 0
			break
		    }
		}
		if {$platforms == {}} {
		    set found 0
		}
		if {$found} {
		    foreach plat $platforms {
			if {[catch {FindFile $src $zipfiles(cDev_$plat)}]} {
			    set found 0
			    break
			}
		    }
		}
	    }
	    default {
		error "Error: bad component name \"$comp\""
	    }
	}
	if {$found} {
	    lappend result $comp $desc
	}
    }
    return $result
}

# install::saveLicenseFile --
#
#	Save the .license file.
#
# Arguments:
#	host		The server's host name.
#	port		The port the license server listens on.
#	dir		The directory to write the file to.
#
# Results:
#	None
#	Raises an error if it cannot write the file

proc install::saveLicenseFile {host port dir} {
    catch {setup::openLogFile $dir}
    catch {setup::writeLogFile "Creating License File:\n\tdir: $dir"}
    catch {setup::writeLogFile "\n\thost: $host\n\tport: $port\n"}

    if {[catch {
	lic::saveServerGlobal $host $port $dir
    } msg]} {
	catch {setup::writeLogFile "Error Creating License File: $msg"}
    }
	
    catch {setup::closeLogFile}
    return
}

# install::FindFile --
#
#	Determine the real name of an installation file given that
#	various cdrom drivers will mount things in one of several
#	different forms.
#
# Arguments:
#	dir	The installation directory.
#	file	File to find.
#
# Results:
#	Returns the real name.

proc install::FindFile {dir file} {
    set file [string tolower $file]

    set list [list $file [string toupper $file]]
    if {[file extension $file] == ""} {
	set file ${file}.
	lappend list ${file} [string toupper ${file}]
    }
    lappend list ${file}\;1 [string toupper ${file}\;1]

    foreach name $list {
	if {[file exists [file join $dir $name]]} {
	    return [file join $dir $name]
	}
    }
    error "Unable to find [file join $dir $file]"
}

# install::Unzip --
#
#	Unzip the specified file into a given directory.  Note: The log 
#	file should be opened before calling this routine.
#
# Arguments:
#	src		The installation source directory.
#	file		The zip file to install.
#	dst		The destination directory.
#	logProc		The procedure to call with logging info.
#
# Results:
#	None.

proc install::Unzip {src file dst logProc} {
    variable unzip
    variable done

    set oldDir [pwd]
    cd $dst

    if {[catch {
	set f [open "|[list $unzip -o [FindFile $src $file] -d] [glob $dst] < [file join / dev null] 2>@stdout"]
	fconfigure $f -blocking 0
	fileevent $f readable [list ::install::EventHandler $f $logProc]
	vwait ::install::done
	if {$::install::done != ""} {
	    error $::install::done
	}
    } msg]} {
	cd $oldDir
	error "Unable to extract files due to disk error."
    } else {
	cd $oldDir
	return $msg
    }
}

# install::EventHandler --
#
#	Retrieve events from the subprocess and inform the logging
#	procedure.  Note: The log file should be opened before calling 
#	this routine.
#
# Arguments:
#	chan	The pipe channel.
#	logProc	The logging procedure.
#
# Results:
#	None.

proc install::EventHandler {chan logProc} {
    variable done
    if {[gets $chan data] >= 0} {
	$logProc $data
	catch {setup::writeLogFile "$data\n"}
    }
    if {[eof $chan]} {
	if {[catch {close $chan} err]} {
	    set done $err
	} else {
	    set done ""
	}
    }    
}

# install::Patch --
#
#	Patch a binary file.  Searches for the old string and writes
#	the new string starting at the beginning of the old string.
#	Note that the new string may continue past the end of the old
#	string, so the reserved area in the file must be large enough
#	to hold the entire new string or the binary will be corrupted.
#
# Arguments:
#	file	The name of the file to patch.
#	old	A pattern to scan for in the file.
#	new	The new string to write.
#
# Results:
#	Returns 1 if the old string was found in the binary.

proc install::Patch {file old new} {
    # We need to ensure that the file is writable before trying to
    # modify it in place.
    set oldPerm [file attributes $file -permissions]
    file attributes $file -permissions 0666

    set io [open $file r+]
    fconfigure $io -translation {binary binary}
    set X [read $io [file size $file]]
    set offset [string first $old $X]
    if {$offset >= 0} {
	seek $io $offset
	puts -nonewline $io $new
	puts -nonewline $io \0x0
	catch {setup::writeLogFile "\told $old\n\tnew $new\n"}
    }
    close $io

    # Restore the old attributes now that we are done.
    file attributes $file -permissions $oldPerm
    return 1
}

# install::PatchString --
#
#	Patch a regular file.  Searches for the old string and writes
#	the new string starting at the beginning of the old string.
#
# Arguments:
#	file	The name of the file to patch.
#	args	alternating old, new values to patch.
#
# Results:
#	Returns 1 if the old string was found in the binary.

proc install::PatchString {file args} {
    if {[catch {
	set oldPerm [file attributes $file -permissions]
	file attributes $file -permissions 0666

	set in [open $file r]
	set X [read $in [file size $file]]
	close $in

	set out [open $file.new w]

	foreach {old new} $args {
	    if {[regsub -all $old $X $new X] > 0} {
		catch {setup::writeLogFile "\told $old\n\tnew $new\n"}
	    }
	}
	puts -nonewline $out $X
	close $out

	file rename -force $file.new $file

	# Restore the old attributes now that we are done.

	file attributes $file -permissions $oldPerm
    } err]} {
	catch {setup::writeLogFile $err}
	return 0
    } else {
	return 1
    }
}

# install::Truncate --
#
#	Truncate a file and copy it to the destination location.  This
#	routine is used to essentially removes the "wrapped" part of the
#	source file and place the truncated image at the destination
#	path.  We use this routine to reduce the installation image size
#	by assuming that the wrapped installation applications (PT<plat>
#	and PW<plat>) are used as prowrap input files (.in).
#
# Arguments:
#	src		Path of the file to "truncate".
#	dest		Path of file to place resulting file.
#	size		The truncation size.
#	logProc		The procedure to call with logging info.
#
# Results:
#	Returns 1 if the old string was found in the binary.

proc install::Truncate {src dest size logProc} {
    # Delete the destination file before trying to do the copy.
    # This ensures that we overwrite existing files.

    file delete -force $dest

    set inFile [open $src r]
    fconfigure $inFile -translation binary
    set outFile [open $dest w]
    fconfigure $outFile -translation binary

    $logProc ":creating $dest"
 
    fcopy $inFile $outFile -size $size

    close $outFile
    close $inFile
 
    file attributes $dest -permissions 0444

    return 1
}

# install::generateShellWrappers --
#
#	Generates patched versions of the wrapper shell scripts.
#
# Arguments:
#	dest		The directory where the installation will be placed.
#	platform	The platform being installed.
#	logProc		The procedure to call with logging info.
#
# Results:
#	Returns 1 on success, 0 on error.

proc install::generateShellWrappers {dest platform logProc} {
    variable shellWrapperTemplate
    
    # Only proceed if the wrap template exists.  If it does not exist, we
    # assume that this install bundle does not contain files to be patched.

    set file [file join $dest $platform lib $shellWrapperTemplate]
    if {![file exists $file]} {
	return
    }

    set istm [open $file r]
    set template [read $istm]
    close $istm

    # the list of wrappers to generate is determined by the list of .bin
    # files present in the bin directory; a wrapper is generated for each

    set platformDir [file join $dest $platform]
    set binDir [file join $platformDir bin]

    set cwd [pwd]
    if {[catch {
	cd $binDir
	foreach bin [glob -nocomplain *.bin] {
	    # replace the tokens in the template

	    set stem [file rootname $bin]
	    $logProc ":generating shell wrapper for $bin"
	    regsub -all {@PRODUCTHOME@} $template $platformDir tmp
	    regsub -all {@STEM@} $tmp $stem tmp
	    file delete -force $stem
	    set ostm [open $stem w]
	    puts $ostm $tmp
	    close $ostm

	    # make the script executable

	    file attributes $stem -permissions 0555
	}
	cd $cwd
    } err]} {
	set ei $::errorInfo
	set ec $::errorCode

	cd $cwd
	error $err $ei $ec
    }

    return 1
}

# install::translateFileNames --
#
#	Translate all of the platforms and components to be installed into
#	their TCLPRO.# names.
#
# Arguments:
#	platforms	A list od platforms to be installed.
#	components	A list od components to be installed.
#
# Results:
#	A complete list of TCLPRO.# files to be installed.

proc install::translateFileNames {platforms components} {
    variable zipfiles

    set files {}
    foreach comp $components {
	switch $comp {
	    tclSource -
	    tkSource -
	    incrSource -
	    tclxSource -
	    expectSource {
		eval {lappend files} $zipfiles($comp)
	    }
	    common {
		eval {lappend files} $zipfiles($comp)
		foreach plat $platforms {
		    lappend files $zipfiles($plat)
		}
	    }
	    CDev   {
		eval {lappend files} $zipfiles(cDevAll)
		foreach plat $platforms {
		    lappend files $zipfiles(cDev_$plat)
		}
	    }
	    default {
		error "Error: bad component name \"$comp\""
	    }
	}
    }
    return [concat $files]
}

# install::estimate --
#
#	Estimate the number of files that are to be installed.
#
# Arguments:
#	src		The directory that contains the installation files.
#	platforms	The platforms to install.
#	components	The components to install.
#
# Results:
#	Returns a count of the number of files being installed.

proc install::estimate {src platforms components} {
    global tclproPlatform 
    variable unzipNames
    variable unzip

    set unzip [FindFile $src $unzipNames($::tclproPlatform)]

    set count 0

    # Translate all of the sources to be installed into their TCLPRO.# names,
    # then unzip the distribution files and count the number of files.

    foreach file [install::translateFileNames $platforms $components] {
	# Count all the files in the zip
	incr count $install::zipFileCount($file)
	
	# need more information from the zip file
	set output [exec $unzip -lqq [FindFile $src $file]]

	# Count the wrapper input files again since they will have to
	# be patched.
	incr count [expr {[regsub -all "\\.in\n" $output {\0} dummy] * 1}]

	# Count the platform specific libraries and executables, since
	# they also have to be patched
	foreach plat $platforms {
	    # Count .in and library files twice
	    # (once for SHLIBPATH, once for TCL_LIBRARY)
	    
	    incr count [expr {[regsub -all "$plat/lib/lib" \
		    $output {\0} dummy] * 2}]

	    # Count executables
	    
	    incr count [regsub -all "$plat/bin/pro" $output {\0} dummy]
	}
    }

    # Bump the count by 10% so progress meter doesn't look peaked towards
    # the end.

    incr count [expr {int($count * 0.1)}]

    return $count
}

# install::installPro --
#
#	Begin the installation of TclPro.
#
# Arguments:
#	src		The directory that contains the installation files.
#	dest		The directory where the installation will be placed.
#	logProc		The name of a procedure to call with logging messages.
#	platform	The platforms being installed.
#	components	The components being installed.
#
# Results:
#	None.

proc install::installPro {src dest logProc platforms components} {
    variable unzip
    variable SHLIBPATH
    variable TCL_LIBRARY
    variable TCL_PACKAGE_PATH
    variable unzipNames
    variable installNames
    variable PREFIX
    variable EXEC_PREFIX

    # Ensure that we have expanded ~ paths
    
    set src [file nativename $src]
    set dest [file nativename $dest]

    catch {setup::openLogFile $dest}
    catch {setup::writeLogFile "Install Begin:\n"}
    catch {setup::writeLogFile "Platforms: $platforms\n"}
    catch {setup::writeLogFile "Components: $components\n\n"}

    # Write the name of the chosen destination directory to the file
    # named by $tclproDestFile so that if the user interrupts the
    # installation using CTRL+C (or a kill command from another shell),
    # the top-level shell script knows how to blow away a partially
    # installed directory tree.
    
    if {[catch {
	set f [open $::tclproDestFile w] 
	puts -nonewline $f [file join [pwd] $dest]
	close $f
    } err]} {
	catch {setup::writeLogFile "Install Error: $err"}
    }
	
    set unzip [FindFile $src $unzipNames($::tclproPlatform)]
	
    # Unzip the distribution files.  Note: The log file should be opened
    # before calling the unzip routine.

    foreach file [install::translateFileNames $platforms $components] {
	catch {setup::writeLogFile "Unzipping: $file\n"}
	Unzip $src $file $dest $logProc
    }

    # Patch the installed executables

    foreach platform $platforms {
	set binDir [file join $dest $platform bin]
	set shlibDir [file join $dest $platform lib]
	set tcllibDir [file join $dest lib tcl$::projectInfo::baseTclVers]
	set pkgPath [list [file join $dest lib] $shlibDir]
    
	# Only perform the next steps if we are installing the common files.

	if {[lsearch $components common] >= 0} {
	    # We need to copy over the installer binaries to the installation
	    # tree as "<plat>/lib/???.in" and truncate it.

	    foreach {pt pw} $installNames($platform) {
		Truncate [FindFile $src $pt] \
			[file join $dest $platform lib wrapbigtclsh83s.in] \
			$install::unwrappedFileSizes($pt) $logProc
		Truncate [FindFile $src $pw] \
			[file join $dest $platform lib wrapbigwish83s.in] \
			$install::unwrappedFileSizes($pw) $logProc
	    }
	    
	    # All of the executables start with "pro", but we need to filter
	    # out *.tcl files
	    
	    foreach file [glob [file join $binDir pro*] \
		    [file join $shlibDir lib*] \
		    [file join $shlibDir *.in]] {
		if {![string match *.tcl $file]} {
		    $logProc ":Patching $file"
		    catch {setup::writeLogFile "Patching $file\n"}

		    # TCL_PACKAGE_PATH and SHLIBPATH is a real build pathname,
		    # so it'll embed a different platform in different
		    # binaries.  So, here we tune it to match.

		    regsub _PLATFORM_ $SHLIBPATH $platform X
		    Patch $file $X $shlibDir

		    regsub _PLATFORM_ $TCL_PACKAGE_PATH $platform X
		    Patch $file $X $pkgPath

		    Patch $file $TCL_LIBRARY $tcllibDir
		}
	    }
	    foreach lib [glob [file join $shlibDir lib*] \
		    [file join $shlibDir *.in]] {
		$logProc ":Patching $lib"
		catch {setup::writeLogFile "Patching $lib\n"}

		Patch $lib $TCL_LIBRARY $tcllibDir
	    }
	    
	    # Patch the tclConfig.sh and the tkConfig.sh files.

	    foreach config [glob [file join $shlibDir *Config.sh]] {
		$logProc ":Patching $config"
		catch {setup::writeLogFile "Patching $config\n"}

		regsub _PLATFORM_ $SHLIBPATH $platform X
		PatchString $config \
		    $X					$shlibDir \
		    TCL_BUILD_LIB_SPEC='.*?'		TCL_BUILD_LIB_SPEC= \
		    TCL_BUILD_STUB_LIB_SPEC='.*?'	TCL_BUILD_STUB_LIB_SPEC= \
		    TCL_BUILD_STUB_LIB_PATH='.*?'	TCL_BUILD_STUB_LIB_PATH= \
		    TK_BUILD_LIB_SPEC='.*?'		TK_BUILD_LIB_SPEC= \
		    TK_BUILD_STUB_LIB_SPEC='.*?'	TK_BUILD_STUB_LIB_SPEC= \
		    TK_BUILD_STUB_LIB_PATH='.*?'	TK_BUILD_STUB_LIB_PATH= \
		    TCLX_BUILD_STUB_LIB_SPEC='.*?'	TCLX_BUILD_STUB_LIB_SPEC= \
		    TKX_BUILD_STUB_LIB_SPEC='.*?'	TKX_BUILD_STUB_LIB_SPEC= \
		    $EXEC_PREFIX			$dest/$platform \
		    $PREFIX				$dest \
		    TCL_SRC_DIR='.*?'			TCL_SRC_DIR='$dest/src/tcl$projectInfo::baseTclVers' \
		    TK_SRC_DIR='.*?'			TK_SRC_DIR='$dest/src/tk$projectInfo::baseTclVers'

	    }
	    
	    # Generate shell wrappers for the installed executables

	    generateShellWrappers $dest $platform $logProc
	}
    }

    if 0 {

    # Upgrade the user preferences from earlier versions of TclPro.

    catch {
	$logProc ":Upgrading prefs"
	set f [open "upgprefs.tcl" r]
	set pipe [open "| [file join $dest $platform bin protclsh83]" w]
	puts $pipe [read $f]
	close $pipe
	close $f
	$logProc ":Upgrading prefs - done"
    }
    }
    # ensure installed files have correct permissions?

    # We'll be nice and assume that if the user CTRL+C's from here on, it's OK
    # and we'll not blow away the installed tree.

    catch {file delete $::tclproDestFile}
    catch {setup::writeLogFile "Install Done\n"}
    catch {setup::closeLogFile}

    return
}

# install::hasSources --
#
#	This function returns true if the installation bundle
#	contains a sources.
#
# Arguments:
#	src		The directory that contains the installation files.
#
# Results:
#	Returns 1 if an Acrobat installer is present, else returns 0.

proc install::hasSources {src} {
    variable zipfiles

    if {[catch {FindFile $src $zipfiles(tclSource)}]} {
	return 0
    } else {
	return 1
    }
}

# install::hasAcrobat --
#
#	This function returns true if the installation bundle
#	contains an Acrobat installer for this platform.
#
# Arguments:
#	src		The directory that contains the installation files.
#	platform	The platform being installed.
#
# Results:
#	Returns 1 if an Acrobat installer is present, else returns 0.

proc install::hasAcrobat {src platform} {
    variable acrobat

    if {[info exists acrobat($platform)]} {
	if {[catch {set dir [FindFile [file join $src ..] acrobat]}]} {
	    return 0
	} else {
	    if {[catch {FindFile $dir $acrobat($platform)}]} {
		return 0
	    } else {
		return 1
	    }
	}
    } else {
	return 0
    }
}

# install::installAcrobat --
#
#	This function invokes the Acrobat reader installation program.
#
# Arguments:
#	src		The directory that contains the installation files.
#	platform	The platform being installed.
#
# Results:
#	None.

proc install::getAcrobatInstaller {src platform} {

    variable acrobat
    variable acrobatInstallPrefix

    # Find the zcat program which we need to use.
    if {[file exists /bin/zcat]} {
	set zcat /bin/zcat
    } elseif {[file exists /usr/bsd/zcat]} {
	set zcat /usr/bsd/zcat
    } else {
	# Let's hope we can find it on the users path...
	set zcat zcat
    }

    set install::acrobatTmpDir /tmp/tclpro[pid]
    file mkdir $install::acrobatTmpDir
    set install::acrobatOldDir [pwd]
    cd $install::acrobatTmpDir
    set instDir $acrobatInstallPrefix($platform).install

    # caller should catch the result to stop the installation
    if {[catch {set dir [FindFile [file join $src ..] acrobat]}]} {
	puts stderr "Unable to locate acrobat directory"
	error "error: out of disk space writing $install::acrobatTmpDir"
    } else {	
	if {[catch { 
	    exec $zcat < [FindFile $dir $acrobat($platform)] | /bin/tar xf -
	} msg]} {
	    puts stderr "Result from uncompressing Acrobat files: $msg"
	    error "error: out of disk space writing $install::acrobatTmpDir"
	}
    }
    return [file join /tmp $install::acrobatTmpDir $instDir INSTALL]
}
	
proc install::installAcrobat {src platform} {

    if {[catch {
        exec [install::getAcrobatInstaller $src $platform] 2>@stderr >@stdout <@stdin
    } msg]} {
        puts stderr $msg
    }
    cd $install::acrobatOldDir
    return [file delete -force $install::acrobatTmpDir]
}

# install::installServer --
#
#	Install the license server.
#
# Arguments:
#	src	The installation source directory.
#	dir	The destination directory.
#	logDir	The logging directory.
#	uid	The user name/id.
#	gid	The group name/id.
#	port	The server port.
#
# Results:
#	Returns any error messages generated by the installation.

proc install::installServer {src dir logDir uid gid port} {
    global tcl_platform
    variable serverPlatform

    catch {setup::openLogFile $dir}
    catch {setup::writeLogFile "log dir:  $logDir\n"}
    catch {setup::writeLogFile "user id:  $uid\n"}
    catch {setup::writeLogFile "group id: $gid\n"}
    catch {setup::writeLogFile "port:     $port\n"}

    # Install and rename the binary

    set srcPath [FindFile $src $serverPlatform($::tclproPlatform)]
    set dstPath [file join $dir $::SERVER_NAME]

    file copy -force $srcPath $dstPath 
    file attributes $dstPath -permissions 0555

    set msg "[linstall::bootScripts $dir]\n[linstall::configFile $dir $logDir $port $uid $gid]"

    catch {setup::writeLogFile "\n$msg\n"}
    catch {setup::closeLogFile}

    return $msg
}

# install::hasServer --
#
#	This function returns true if the installation bundle
#	contains the license server files.
#
# Arguments:
#	src		The directory that contains the installation files.
#	platform	The platform being installed.
#
# Results:
#	Returns 1 if the server is present, else returns 0.

proc install::hasServer {src platform} {
    variable serverPlatform

    if {[info exists serverPlatform($platform)]} {
	if {[catch {FindFile $src $serverPlatform($platform)}]} {
	    return 0
	} else {
	    return 1
	}
    } else {
	return 0
    }
}

# install::calculateSizes
#
#	This function returns true if the installation bundle
#	contains the license server files.
#
# Arguments:
#       src  The directory that contains the installation files
#
# Results:
#	None

proc install::calculateSizeAndCount {src} {
    variable unzip
    variable unzipNames
    set unzip [FindFile $src $unzipNames($::tclproPlatform)]

    foreach fileset [array names install::zipfiles] {
	set install::zipSizes($fileset) 0
	foreach file $install::zipfiles($fileset) {
	    if {![catch {FindFile $src $file}]} {
		scan [exec $unzip -Zt [FindFile $src $file]] \
			{%d files, %d bytes uncompressed} count bytes
		incr install::zipSizes($fileset) $bytes
		set install::zipFileCount($file) $count
	    }
	}
    }
    return
}

# install::componentSize
#
#	This function returns the disk space required by a component.
#
# Arguments:
#       comp       Component (logical name)
#       platforms  list of selected platforms
#
# Results:
#	The size, in kilobytes, of the compoment

proc install::componentSize {comp platforms} {
    set result {}
    set size 0

    switch $comp {
	tclSource -
	tkSource -
	incrSource -
	tclxSource -
	expectSource {
	    # Locate all the files necessary to install this component.  
	    # If all files can be located, we can install it.
	    
	    set size $install::zipSizes($comp)
	}
	common {
	    # common includes both a common set of files
	    # and platform-specific files

	    # count the common files
	    set size $install::zipSizes($comp)

	    # count the platform-specific files
	    if {$platforms != {}} {
		foreach plat $platforms {
		    incr size $install::zipSizes($plat)

		    # count the unwrapped install files size
		    foreach file $install::installNames($plat) {
			incr size $install::unwrappedFileSizes($file)
		    }
		}
	    }
	    
	}
	CDev   {
	    # CDev includes a common component cDevAll
	    # and platform specific components cDev_xxx
		
	    set size $install::zipSizes(cDevAll)

	    if {$platforms != {}} {
		foreach plat $platforms {
		    incr size $install::zipSizes(cDev_$plat)
		}
	    }
	}
	default {
	    error "Error: bad component name \"$comp\""
	}
    }
    
    return [expr { $size / 1024 } ]
}


# install::calculateRequireSpace
#
#     Calculate the required disk space for selected
#     components on selected platforms
#
# Arguments:
#       components   selected components
#       platforms    selected platforms
#
# Results:
#	required disk space, in bytes

proc install::calculateRequiredSpace {components platforms} {
    set size 0

    foreach comp $components {
	switch $comp {
	    tclSource -
	    tkSource -
	    incrSource -
	    tclxSource -
	    expectSource {
		# Locate all the files necessary to install this component.  
		# If all files can be located, we can install it.
		
		incr size $install::zipSizes($comp)
	    }
	    common {
		# common includes both a common set of files
		# and platform-specific files
		
		# count the common files
		incr size $install::zipSizes($comp)
		
		# count the platform-specific files
		if {$platforms != {}} {
		    foreach plat $platforms {
			incr size $install::zipSizes($plat)
			
			# count the unwrapped install files size
			foreach file $install::installNames($plat) {
			    incr size $install::unwrappedFileSizes($file)
			}
		    }
		}
		
	    }
	    CDev   {
		# CDev includes a common component cDevAll
		# and platform specific components cDev_xxx
		
		incr size $install::zipSizes(cDevAll)
		
		if {$platforms != {}} {
		    foreach plat $platforms {
			incr size $install::zipSizes(cDev_$plat)
		    }
		}
	    }
	    default {
		error "Error: bad component name \"$comp\""
	    }
	}
    }
    return $size
}



