#*************************************************************************
#
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# 
# Copyright 2008 by Sun Microsystems, Inc.
#
# OpenOffice.org - a multi-platform office productivity suite
#
# $RCSfile: admin.pl,v $
#
# $Revision: 1.1.2.2 $
#
# This file is part of OpenOffice.org.
#
# OpenOffice.org is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# only, as published by the Free Software Foundation.
#
# OpenOffice.org is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License version 3 for more details
# (a copy is included in the LICENSE file that accompanied this code).
#
# You should have received a copy of the GNU Lesser General Public License
# version 3 along with OpenOffice.org.  If not, see
# <http://www.openoffice.org/license.html>
# for a copy of the LGPLv3 License.
#
#*************************************************************************

use File::Copy;

#################################################################################
# Global settings
#################################################################################

BEGIN
{
	$prog = "msi installer";
	$targetdir = "";
	$databasepath = "";
	$starttime = "";
	$globaltempdirname = "ooopackaging";
	$savetemppath = "";
	$msiinfo_available = 0;

	$plat = $^O;

	if ( $plat =~ /cygwin/i )
	{
		$separator = "/";
		$pathseparator = "\:";
	}
	else
	{
		$separator = "\\";
		$pathseparator = "\;";
	}
}

#################################################################################
# Program information
#################################################################################

sub usage
{
	print <<Ende;
----------------------------------------------------------------------
$prog V1.0 (c) Sun Microsystems 2008
This program installs a Windows Installer installation set
without using msiexec.exe. The installation is comparable
with an administrative installation using the Windows Installer
service.
Required parameter:
-d Path to installation set or msi database
-t Target directory
---------------------------------------------------------------------
Ende
	exit(-1);
}

#################################################################################
# Collecting parameter
#################################################################################

sub getparameter
{
	if (( $#ARGV < 3 ) || ( $#ARGV > 3 )) { usage(); }

	while ( $#ARGV >= 0 )
	{
		my $param = shift(@ARGV);
		
		if ($param eq "-t") { $targetdir = shift(@ARGV); }
		elsif ($param eq "-d") { $databasepath = shift(@ARGV); }
		else
		{
			print "\n**********************************************\n";
			print "Error: Unknows parameter: $param";
			print "\n**********************************************\n";
			usage();
			exit(-1);
		}
	}
}

#################################################################################
# Checking content of parameter
#################################################################################

sub controlparameter
{
	if ( $targetdir eq "" )
	{
		print "\n******************************************************\n";
		print "Error: Target directory not defined (parameter -t)!";
		print "\n******************************************************\n";
		usage();
		exit(-1);
	}

	if ( $databasepath eq "" )
	{
		print "\n******************************************************\n";
		print "Error: Path to msi database not defined (parameter -d)!";
		print "\n******************************************************\n";
		usage();
		exit(-1);
	}
	
	if ( -d $databasepath )
	{
		$databasepath =~ s/\\\s*$//;
		$databasepath =~ s/\/\s*$//;
		
		my $msifiles = find_file_with_file_extension("msi", $databasepath);
	
		if ( $#{$msifiles} < 0 ) { exit_program("ERROR: Did not find msi database in directory $installationdir"); }
		if ( $#{$msifiles} > 0 ) { exit_program("ERROR: Did find more than one msi database in directory $installationdir"); }

		$databasepath = $databasepath . $separator . ${$msifiles}[0];
	}

	if ( ! -f $databasepath ) { exit_program("ERROR: Did not find msi database in directory $databasepath."); }
	
	if ( ! -d $targetdir ) { create_directories($targetdir); }
}

#############################################################################
# Converting a string list with separator $listseparator 
# into an array
#############################################################################

sub convert_stringlist_into_array
{
	my ( $includestringref, $listseparator ) = @_;
	
	my @newarray = ();
	my $first;
	my $last = ${$includestringref};

	while ( $last =~ /^\s*(.+?)\Q$listseparator\E(.+)\s*$/)	# "$" for minimal matching
	{
		$first = $1;
		$last = $2;	
		if ( defined($ENV{'USE_SHELL'}) && $ENV{'USE_SHELL'} eq "4nt" ) { $first =~ s/\//\\/g; }
		# Problem with two directly following listseparators. For example a path with two ";;" directly behind each other
		$first =~ s/^$listseparator//;
		push(@newarray, "$first\n");
	}	

	if ( defined($ENV{'USE_SHELL'}) && $ENV{'USE_SHELL'} eq "4nt" ) { $last =~ s/\//\\/g; }
	push(@newarray, "$last\n");	
	
	return \@newarray;
}

#########################################################
# Checking the local system
# Checking existence of needed files in include path
#########################################################

sub check_system_path
{
	my $onefile;	
	my $error = 0;
	my $pathvariable = $ENV{'PATH'};
	my $local_pathseparator = $pathseparator;
	
	if( $^O =~ /cygwin/i )
	{	# When using cygwin's perl the PATH variable is POSIX style and ...
		$pathvariable = qx{cygpath -mp "$pathvariable"} ;
		# has to be converted to DOS style for further use.
		$local_pathseparator = ';';
	}
	my $patharrayref = convert_stringlist_into_array(\$pathvariable, $local_pathseparator);
	
	my @needed_files_in_path = ("msidb.exe", "expand.exe");
	my @optional_files_in_path = ("msiinfo.exe");

	print("\nChecking required files:\n");

	foreach $onefile ( @needed_files_in_path )
	{
		print("...... searching $onefile ...");

		my $fileref = get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref);

		if ( $$fileref eq "" )
		{
			$error = 1;
			print( "$onefile not found\n" );
		}
		else
		{
			print( "\tFound: $$fileref\n" );
		}		
	}

	if ( $error ) { exit_program("ERROR: Could not find all needed files in path (using setsolar should help)!"); }

	print("\nChecking optional files:\n");

	foreach $onefile ( @optional_files_in_path )
	{
		print("...... searching $onefile ...");

		my $fileref = get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref);

		if ( $$fileref eq "" )
		{
			print( "$onefile not found\n" );
			if ( $onefile eq "msiinfo.exe" ) { $msiinfo_available = 0; }
		}
		else
		{
			print( "\tFound: $$fileref\n" );
			if ( $onefile eq "msiinfo.exe" ) { $msiinfo_available = 1; }
		}		
	}

}

##########################################################################
# Searching a file in a list of pathes
##########################################################################

sub get_sourcepath_from_filename_and_includepath
{
	my ($searchfilenameref, $includepatharrayref) = @_;

	my $onefile = "";
	my $foundsourcefile = 0;
		
	for ( my $j = 0; $j <= $#{$includepatharrayref}; $j++ )
	{
		my $includepath = ${$includepatharrayref}[$j];
		$includepath =~ s/^\s*//;
		$includepath =~ s/\s*$//;

		$onefile = $includepath . $separator . $$searchfilenameref;

		if ( -f $onefile )
		{
			$foundsourcefile = 1;
			last;
		}
	}

	if (!($foundsourcefile)) { $onefile = ""; }
	
	return \$onefile;
}

########################################################
# Finding all files with a specified file extension
# in a specified directory.
########################################################

sub find_file_with_file_extension
{
	my ($extension, $dir) = @_;
	
	my @allfiles = ();
	my @sourcefiles = ();

	$dir =~ s/\Q$separator\E\s*$//;

	opendir(DIR, $dir);
	@sourcefiles = readdir(DIR);
	closedir(DIR);

	my $onefile;
	
	foreach $onefile (@sourcefiles)
	{
		if ((!($onefile eq ".")) && (!($onefile eq "..")))
		{
			if ( $onefile =~ /^\s*(\S.*?)\.$extension\s*$/ )
			{
				push(@allfiles, $onefile)
			}
		}
	}
	
	return \@allfiles;
}

##############################################################
# Creating a directory with all parent directories
##############################################################

sub create_directories
{
	my ($directory) = @_;

	if ( ! try_to_create_directory($directory) )
	{
		my $parentdir = $directory;
		get_path_from_fullqualifiedname(\$parentdir);
		create_directories($parentdir);   # recursive
	}
	
	create_directory($directory);	# now it has to succeed
}

##############################################################
# Creating one directory
##############################################################

sub create_directory
{
	my ($directory) = @_;

	if ( ! -d $directory ) { mkdir($directory, 0775); }
}

##############################################################
# Trying to create a directory, no error if this fails
##############################################################

sub try_to_create_directory
{
	my ($directory) = @_;
	
	my $returnvalue = 1;
	my $created_directory = 0;

	if (!(-d $directory))
	{
		$returnvalue = mkdir($directory, 0775);

		if ($returnvalue)
		{
			$created_directory = 1;
	
            if ( defined $ENV{'USE_SHELL'} && $ENV{'USE_SHELL'} ne "4nt" )
			{
				my $localcall = "chmod 775 $directory \>\/dev\/null 2\>\&1";
				system($localcall);
			}			
		}
		else
		{
			$created_directory = 0;
		}
	}
	else
	{
		$created_directory = 1;
	}

	return $created_directory;
}

###########################################
# Getting path from full file name
###########################################

sub get_path_from_fullqualifiedname
{
	my ($longfilenameref) = @_;

	if ( $$longfilenameref =~ /\Q$separator\E/ )	# Is there a separator in the path? Otherwise the path is empty.
	{	
		if ( $$longfilenameref =~ /^\s*(\S.*\S\Q$separator\E)(\S.+\S?)/ )
		{
			$$longfilenameref = $1;
		}
	}
	else
	{
		$$longfilenameref = "";	# there is no path
	}	
}

##############################################################
# Getting file name from full file name
##############################################################

sub make_absolute_filename_to_relative_filename
{
	my ($longfilenameref) = @_;
	
	# Either '/' or '\'.
	if ( $$longfilenameref =~ /^.*[\/\\](\S.+\S?)/ )
	{
		$$longfilenameref = $1;
	}
}

############################################
# Exiting the program with an error
# This function is used instead of "die"
############################################

sub exit_program
{
	my ($message) = @_;

	print "\n***************************************************************\n";
	print "$message\n";
	print "***************************************************************\n";
	remove_complete_directory($savetemppath, 1);
	print "\n" . get_time_string();
	exit(-1);	
}

#################################################################################
# Unpacking cabinet files with expand
#################################################################################

sub unpack_cabinet_file
{
	my ($cabfilename, $unpackdir) = @_;
	
	my $expandfile = "expand.exe";	# Has to be in the path
	my $expandlogfile = $unpackdir . $separator . "expand.log";

	# exclude cabinet file
	# my $systemcall = $cabarc . " -o X " . $mergemodulehash->{'cabinetfile'};

	my $systemcall = "";
	if ( $^O =~ /cygwin/i ) {
		my $localunpackdir = qx{cygpath -w "$unpackdir"};
		$localunpackdir =~ s/\\/\\\\/g;
		$systemcall = $expandfile . " " . $cabfilename . " -F:\\\* " . $localunpackdir;
	}
	else
	{
		$systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile;
	}

	my $returnvalue = system($systemcall);

	if ($returnvalue) { exit_program("ERROR: Could not execute $systemcall !"); }
}

#################################################################################
# Extracting tables from msi database
#################################################################################

sub extract_tables_from_database
{
	my ($fullmsidatabasepath, $workdir, $tablelist) = @_;

	my $msidb = "msidb.exe";	# Has to be in the path
	my $infoline = "";
	my $systemcall = "";
	my $returnvalue = "";

	# Export of all tables by using "*"
							
	$systemcall = $msidb . " -d " . $fullmsidatabasepath . " -f " . $workdir . " -e $tablelist";
	print "\nAnalyzing msi database\n";
	$returnvalue = system($systemcall);

	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $systemcall !\n";
		exit_program($infoline);
	}
}

################################################################################
# Analyzing the content of Directory.idt
#################################################################################

sub analyze_directory_file
{
	my ($filecontent) = @_;
	
	my %table = ();

	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $dir = $1;
			my $parent = $2;
			my $name = $3;
			
			if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; }
			if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; }
			
			my %helphash = ();
			$helphash{'Directory_Parent'} = $parent;
			$helphash{'DefaultDir'} = $name;
			$table{$dir} = \%helphash;
		}
	}
	
	return \%table;	
}

#################################################################################
# Analyzing the content of Component.idt
#################################################################################

sub analyze_component_file
{
	my ($filecontent) = @_;
	
	my %table = ();
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $component = $1;
			my $dir = $3;
			
			$table{$component} = $dir;
		}
	}

	return \%table;	
}

#################################################################################
# Analyzing the content of File.idt
#################################################################################

sub analyze_file_file
{
	my ($filecontent) = @_;
	
	my %table = ();
	my %fileorder = ();
	my $maxsequence = 0;
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $file = $1;
			my $comp = $2;
			my $filename = $3;
			my $sequence = $8;

			if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; }
			
			my %helphash = ();
			$helphash{'Component'} = $comp;
			$helphash{'FileName'} = $filename;
			$helphash{'Sequence'} = $sequence;

			$table{$file} = \%helphash;
			
			$fileorder{$sequence} = $file;
			
			if ( $sequence > $maxsequence ) { $maxsequence = $sequence; }
		}
	}

	return (\%table, \%fileorder, $maxsequence);
}

####################################################################################
# Recursively creating the directory tree
####################################################################################

sub create_directory_tree
{
	my ($parent, $pathcollector, $fulldir, $dirhash) = @_;	

	foreach my $dir ( keys %{$dirhash} )
	{
		if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
		{
			my $dirname = $dirhash->{$dir}->{'DefaultDir'};
			# Create the directory
			my $newdir = $fulldir . $separator . $dirname;
			if ( ! -f $newdir ) { mkdir $newdir; }
			# Saving in collector
			$pathcollector->{$dir} = $newdir;
			# Iteration
			create_directory_tree($dir, $pathcollector, $newdir, $dirhash);
		}
	}
}

####################################################################################
# Creating the directory tree
####################################################################################

sub create_directory_structure
{
	my ($dirhash, $targetdir) = @_;

	print "Creating directories\n";
	
	my %fullpathhash = ();
	
	my @startparents = ("TARGETDIR", "INSTALLLOCATION");
	
	foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); }
	
	return \%fullpathhash;
}

######################################################
# Creating a new directory with defined privileges
######################################################

sub create_directory_with_privileges
{
	my ($directory, $privileges) = @_;
		
	my $returnvalue = 1;
	my $infoline = "";

	if (!(-d $directory))
	{
		my $localprivileges = oct("0".$privileges); # changes "777" to 0777
		$returnvalue = mkdir($directory, $localprivileges);

		if ($returnvalue)
		{	
            if ( defined $ENV{'USE_SHELL'} && $ENV{'USE_SHELL'} ne "4nt" )
			{
				my $localcall = "chmod $privileges $directory \>\/dev\/null 2\>\&1";
				system($localcall);
			}			
		}
	}
	else
	{
		if ( defined $ENV{'USE_SHELL'} && $ENV{'USE_SHELL'} ne "4nt" )
		{
			my $localcall = "chmod $privileges $directory \>\/dev\/null 2\>\&1";
			system($localcall);
		}			
	}
}

######################################################
# Creating a unique directory with pid extension	
######################################################

sub create_pid_directory
{
	my ($directory) = @_;
	
	$directory =~ s/\Q$separator\E\s*$//;
	my $pid = $$;			# process id
	my $time = time();		# time
	
	$directory = $directory . "_" . $pid . $time;

	if ( ! -d $directory ) { create_directory($directory); }		
	else { exit_program("ERROR: Directory $directory already exists!"); }
	
	return $directory;
}

####################################################################################
# Copying files into installation set
####################################################################################

sub copy_files_into_directory_structure
{
	my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;

	print "Copying files\n";

	my $unopkgfile = "";
	my @extensions = ();
	
	for ( my $i = 1; $i <= $maxsequence; $i++ )
	{
		if ( exists($fileorder->{$i}) )
		{
			my $file = $fileorder->{$i};
			if ( ! exists($filehash->{$file}->{'Component'}) ) { exit_program("ERROR: Did not find component for file: \"$file\"."); }
			my $component = $filehash->{$file}->{'Component'};
			if ( ! exists($componenthash->{$component}) ) { exit_program("ERROR: Did not find directory for component: \"$component\"."); }
			my $dirname = $componenthash->{$component};
			if ( ! exists($fullpathhash->{$dirname}) ) { exit_program("ERROR: Did not find full directory path for dir: \"$dirname\"."); }
			my $destdir = $fullpathhash->{$dirname};
			if ( ! exists($filehash->{$file}->{'FileName'}) ) { exit_program("ERROR: Did not find \"FileName\" for file: \"$file\"."); }
			my $destfile = $filehash->{$file}->{'FileName'};

			$destfile = $destdir . $separator . $destfile;
			my $sourcefile = $unpackdir . $separator . $file;
			
			if ( ! -f $sourcefile )
			{
				# It is possible, that this was an unpacked file
				# Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname)
				# subdir is not recursively analyzed, only one directory.
				
				my $oldsourcefile = $sourcefile;			
				my $subdir = "";
				if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $separator; }
				my $realfilename = $filehash->{$file}->{'FileName'};
				my $localinstalldir = $installdir;
				
				$localinstalldir =~ s/\\\s*$//;
				$localinstalldir =~ s/\/\s*$//;
				
				$sourcefile = $localinstalldir . $separator . $subdir . $realfilename;
				
				if ( ! -f $sourcefile ) { exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\")."); }
			}

			my $copyreturn = copy($sourcefile, $destfile);

			if ( ! $copyreturn) { exit_program("ERROR: Could not copy $source to $dest\n"); }

			# Collecting all extensions
			if ( $destfile =~ /\.oxt\s*$/ ) { push(@extensions, $destfile); }
			# Searching unopkg.exe
			if ( $destfile =~ /unopkg\.exe\s*$/ ) { $unopkgfile = $destfile; }
		}
		# else	# allowing missing sequence numbers ?
		# {
		# 	exit_program("ERROR: No file assigned to sequence $i");
		# }
	}
	
	return ($unopkgfile, \@extensions);
}

######################################################
# Removing a complete directory with subdirectories
######################################################

sub remove_complete_directory
{
	my ($directory, $start) = @_;

	my @content = ();
	my $infoline = "";
	
	$directory =~ s/\Q$separator\E\s*$//;

	if ( -d $directory )
	{
		if ( $start ) { print "Removing directory $directory\n"; }
	
		opendir(DIR, $directory);
		@content = readdir(DIR);
		closedir(DIR);

		my $oneitem;
	
		foreach $oneitem (@content)
		{
			if ((!($oneitem eq ".")) && (!($oneitem eq "..")))
			{
				my $item = $directory . $separator . $oneitem;

				if ( -f $item || -l $item ) 	# deleting files or links
				{
					unlink($item);
				}

				if ( -d $item ) 	# recursive
				{
					remove_complete_directory($item, 0);
				}
			}
		}
		
		# try to remove empty directory
		my $returnvalue = rmdir $directory;
		if ( ! $returnvalue ) { print "Warning: Problem with removing empty dir $directory\n"; }
	}	
}

####################################################################################
# Defining a temporary path
####################################################################################

sub get_temppath
{
	my $temppath = "";
	
	if (( $ENV{'TMP'} ) || ( $ENV{'TEMP'} ))
	{
		if ( $ENV{'TMP'} ) { $temppath = $ENV{'TMP'}; }
		elsif ( $ENV{'TEMP'} )  { $temppath = $ENV{'TEMP'}; }

		$temppath =~ s/\Q$separator\E\s*$//;	# removing ending slashes and backslashes
		$temppath = $temppath . $separator . $globaltempdirname;
		create_directory_with_privileges($temppath, "777");

		my $dirsave = $temppath;

		$temppath = $temppath . $separator . "a";
		$temppath = create_pid_directory($temppath);

		if ( ! -d $temppath ) { exit_program("ERROR: Failed to create directory $temppath ! Possible reason: Wrong privileges in directory $dirsave."); }

		if ( $^O =~ /cygwin/i )
		{
			$temppath =~ s/\\/\\\\/g;
			chomp( $temppath = qx{cygpath -w "$temppath"} ); 
		}
		
		$savetemppath = $temppath;
	}
	else
	{
		exit_program("ERROR: Could not set temporary directory (TMP and TEMP not set!).");
	}
	
	return $temppath;
}

####################################################################################
# Registering one extension
####################################################################################

sub register_one_extension
{
	my ($unopkgfile, $extension, $temppath) = @_;

	print "... $extension\n";

	$temppath =~ s/\\/\//g;
	$temppath = "/".$temppath;

	my $systemcall = "\"" . $unopkgfile . "\"" . " add --shared --verbose " . "\"" . $extension . "\"" . " -env:UserInstallation=file://" . "\"" . $temppath . "\"" . " 2\>\&1 |";

	my @unopkgoutput = ();

	open (UNOPKG, $systemcall);
	while (<UNOPKG>) {push(@unopkgoutput, $_); }
	close (UNOPKG);

	my $returnvalue = $?;	# $? contains the return value of the systemcall

	if ($returnvalue)
	{
		print "ERROR: Could not execute \"$systemcall\"!\nExitcode: '$returnvalue'\n";
		for ( my $j = 0; $j <= $#unopkgoutput; $j++ ) { print "$unopkgoutput[$j]"; }
		exit_program("ERROR: $systemcall failed!");
	}
}

####################################################################################
# Registering all extensions located in /share/extension/install
####################################################################################

sub register_extensions
{
	my ($unopkgfile, $extensions, $temppath) = @_;
	
	if ( $#{$extensions} > -1 )
	{
		print "Registering extensions:\n"; 

		if (( ! -f $unopkgfile ) || ( $unopkgfile eq "" )) { exit_program("ERROR: Could not find unopkg.exe!"); }

		foreach $extension ( @{$extensions} ) { register_one_extension($unopkgfile, $extension, $temppath); }
		
		remove_complete_directory($temppath, 1)
	}
	else
	{
		print "No extensions to register.\n"; 
	}
}

####################################################################################
# Reading one file
####################################################################################

sub read_file
{
	my ($localfile) = @_;

	my @localfile = ();

	open( IN, "<$localfile" ) || exit_program("ERROR: Cannot open file $localfile for reading");

	#	Don't use "my @localfile = <IN>" here, because
	#	perl has a problem with the internal "large_and_huge_malloc" function
	#	when calling perl using MacOS 10.5 with a perl built with MacOS 10.4
	while ( $line = <IN> ) {
		push @localfile, $line;
	}

	close( IN );

	return \@localfile;
}

###############################################################
# Setting the time string for the 
# Summary Information stream in the 
# msi database of the admin installations.
###############################################################

sub get_sis_time_string
{	
	# Syntax: <yyyy/mm/dd hh:mm:ss>
	my $second = (localtime())[0];
	my $minute = (localtime())[1];
	my $hour = (localtime())[2];
	my $day = (localtime())[3];
	my $month = (localtime())[4];
	my $year = 1900 + (localtime())[5];
	
	if ( $second < 10 ) { $second = "0" . $second; }
	if ( $minute < 10 ) { $minute = "0" . $minute; }
	if ( $hour < 10 ) { $hour = "0" . $hour; }
	if ( $day < 10 ) { $day = "0" . $day; }
	if ( $month < 10 ) { $month = "0" . $month; }
	
	my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second;
		
	return $timestring;
}

###############################################################
# Writing content of administrative installations into 
# Summary Information Stream of msi database. 
# This is required for example for following
# patch processes using Windows Installer service.
###############################################################

sub write_sis_info
{
	my ($msidatabase) = @_ ;

	print "Setting SIS in msi database\n";
	
	if ( ! -f $msidatabase ) { exit_program("ERROR: Cannot find file $msidatabase"); }

	my $msiinfo = "msiinfo.exe";	# Has to be in the path
	my $infoline = "";
	my $systemcall = "";
	my $returnvalue = "";

	# Required setting for administrative installations:
	# -w 4   (source files are unpacked),  wordcount
	# -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
	# -l <person_making_admin_installation>, LastSavedBy
	
	my $wordcount = 4;  # Unpacked files
	my $lastprinted = get_sis_time_string();
	my $lastsavedby = "Installer";
							
	$systemcall = $msiinfo . " " . "\"" . $msidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby";
	$returnvalue = system($systemcall);

	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $systemcall !\n";
		exit_program($infoline);
	}	
}

###############################################################
# Convert time string
###############################################################

sub convert_timestring
{
	my ($secondstring) = @_;

	my $timestring = "";
	
	if ( $secondstring < 60 )	 # less than a minute
	{
		if ( $secondstring < 10 ) { $secondstring = "0" . $secondstring; }
		$timestring = "00\:$secondstring min\.";	
	}
	elsif ( $secondstring < 3600 )
	{
		my $minutes = $secondstring / 60;
		my $seconds = $secondstring % 60;
		if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; }
		if ( $minutes < 10 ) { $minutes = "0" . $minutes; }
		if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
		$timestring = "$minutes\:$seconds min\.";
	}
	else	# more than one hour
	{
		my $hours = $secondstring / 3600;
		my $secondstring = $secondstring % 3600;
		my $minutes = $secondstring / 60;
		my $seconds = $secondstring % 60;
		if ( $hours =~ /(\d*)\.\d*/ ) { $hours = $1; }
		if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; }
		if ( $hours < 10 ) { $hours = "0" . $hours; }
		if ( $minutes < 10 ) { $minutes = "0" . $minutes; }
		if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
		$timestring = "$hours\:$minutes\:$seconds hours";		
	}
	
	return $timestring;
}

###############################################################
# Returning time string for logging
###############################################################

sub get_time_string
{
	my $currenttime = time();
	$currenttime = $currenttime - $starttime;
	$currenttime = convert_timestring($currenttime);
	$currenttime = localtime() . " \(" . $currenttime . "\)\n";
	return $currenttime;
}

####################################################################################
# Simulating an administrative installation
####################################################################################

$starttime = time();

getparameter();
controlparameter();
check_system_path();
my $temppath = get_temppath();

print("\nmsi database: $databasepath\n");
print("Destination directory: $targetdir\n" );

my $helperdir = $temppath . $separator . "installhelper";
create_directory($helperdir);
	
# Get File.idt, Component.idt and Directory.idt from database
	
my $tablelist = "File Directory Component";
extract_tables_from_database($databasepath, $helperdir, $tablelist);
	
# Unpack all cab files into $helperdir, cab files must be located next to msi database
my $installdir = $databasepath;
get_path_from_fullqualifiedname(\$installdir);
my $databasefilename = $databasepath;
make_absolute_filename_to_relative_filename(\$databasefilename);

my $cabfiles = find_file_with_file_extension("cab", $installdir);
	
if ( $#{$cabfiles} < 0 ) { exit_program("ERROR: Did not find any cab file in directory $installdir"); }

# Set unpackdir
my $unpackdir = $helperdir . $separator . "unpack";
create_directory($unpackdir);

print "Unpacking files from cabinet file(s)\n";
for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
{
	my $cabfile = $installdir . $separator . ${$cabfiles}[$i];
	unpack_cabinet_file($cabfile, $unpackdir);
}
	
# Reading tables
my $filename = $helperdir . $separator . "Directory.idt";
my $filecontent = read_file($filename);
my $dirhash = analyze_directory_file($filecontent);
	
$filename = $helperdir . $separator . "Component.idt";
$filecontent = read_file($filename);
my $componenthash = analyze_component_file($filecontent);
	
$filename = $helperdir . $separator . "File.idt";
$filecontent = read_file($filename);
my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent);

# Creating the directory structure
my $fullpathhash = create_directory_structure($dirhash, $targetdir);

# Copying files
my ($unopkgfile, $extensions) = copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
	
my $msidatabase = $targetdir . $separator . $databasefilename;
my $copyreturn = copy($databasepath, $msidatabase);
if ( ! $copyreturn) { exit_program("ERROR: Could not copy $source to $dest\n"); }

# Registering extensions
register_extensions($unopkgfile, $extensions, $temppath);

# Saving info in Summary Information Stream of msi database (required for following patches)
if ( $msiinfo_available ) { write_sis_info($msidatabase); }

# Removing the helper directory
remove_complete_directory($temppath, 1);

print "\nSuccessful installation: " . get_time_string();
