package INSTALL::common; use strict; use vars qw(@ISA @EXPORT); my ($pParameters,$pOS,$pGlobals); BEGIN { use Exporter; use IO::Handle; use POSIX; use INSTALL::global; use INSTALL::parameter; $pParameters = \%INSTALL::parameter::variables; $pGlobals = \%INSTALL::global::variables; @ISA = qw(Exporter); @EXPORT = qw( headerize error error_log error_display trace trace_log trace_display note note_log note_display report report_log report_both indent basename dirname usleep mkpath realpath locate_binary find_file find_files safe_chomp path_delete libpath_delete path_append libpath_append path_prepend libpath_prepend spinner_wait spinner_wait_signal signal_spinner interactive boolean_value summary_log component_log register_log split_command system_log fork_command copy shallow_copy recursive_rmdir compress decompress uncompress ); unless (exists $pParameters->{no_signal_handlers}) { # Always ignore Ctrl-C. $main::SIG{INT} = "IGNORE"; # Certain Terminals are broke if we set to vt100. # Add to this list, if we know that these terminal will break. my @filter_lists = ("hpterm") ; if (exists $main::ENV{TERM} && ! grep { $main::ENV{TERM} } @filter_lists) { $main::ENV{TERM} = "vt100"; } } umask 022; STDOUT->autoflush(1); } require INSTALL::os; $pOS = \%INSTALL::os::Details; local (*ERROR_LOG,*SUMMARY_LOG,*REGISTER_LOG,*COMPONENT_LOG,*INSTALLER_LOG); my %CONST_OutputModes = ( Log => 1, Display => 2, Both => 3 ); #------------------------------------------------------------------------------ # Functions takes a
and a list of string which it will concatenate # together as follows: #
# # ... # #------------------------------------------------------------------------------ sub headerize { my $header = shift @_; my $separator = "\n"; $separator .= " " x length($header); return($header . join($separator,@_) . "\n"); } #------------------------------------------------------------------------------ # Function takes a pointer to caller info structure (containing a list of # package, filename, and line number), a header, then the subsequent lines # of output. #------------------------------------------------------------------------------ my $report_internal = sub { my $pCaller_info = shift @_; my $output_mode = shift @_; my $return_type = shift @_; my $result = headerize(@_); if ($CONST_OutputModes{Display} & $output_mode) { print STDERR $result; } if (($CONST_OutputModes{Log} & $output_mode) && (-e ERROR_LOG)) { my $time = localtime(time); my ($package,$filename,$line) = @$pCaller_info; print ERROR_LOG ("====================================================== $time\n", $result, "Encountered at:\n", " Package: $package\n", " File: $filename\n", " Line: $line\n", "===============================================================================\n" ); } if ($return_type) { # Wants an array. return(); } else { return(undef); } }; #------------------------------------------------------------------------------ # The report functions take a header, followed by a list of strings to display # on separate lines past the header: # # Example: # report("Warning: ", # "Continuing on with the current course will result in the", # "destruction of your server."); # # ==> "Warning: Continuing on with the current course will result in the # destruction of your server." # # Note: report displays only to the screen # report_log displays only to the log # report_both displays to both the log and the screen #------------------------------------------------------------------------------ sub report { return(&$report_internal([caller],$CONST_OutputModes{Display},wantarray,@_)); } sub report_log { return(&$report_internal([caller],$CONST_OutputModes{Log},wantarray,@_)); } sub report_both { return(&$report_internal([caller],$CONST_OutputModes{Both},wantarray,@_)); } #------------------------------------------------------------------------------ # The error function(s) works similarly to the report function except that the # header is implicitly "Error: ". # # Note: error displays to both the log and the screen # error_log displays to only the log # error_display displays to only the screen #------------------------------------------------------------------------------ sub error { return(&$report_internal([caller],$CONST_OutputModes{Both},wantarray,"Error: ",@_)); } sub error_log { return(&$report_internal([caller],$CONST_OutputModes{Log},wantarray,"Error: ",@_)); } sub error_display { return(&$report_internal([caller],$CONST_OutputModes{Display},wantarray,"Error: ",@_)); } #------------------------------------------------------------------------------ # The trace function(s) works similarly to the report function except that the # header is implicitly "Trace: ". It only displays if the calling module has # its debug flag set (or $main::debug evaluates to true). # # Note: trace displays to both the log and the screen # trace_log displays to only the log # trace_display displays to only the screen #------------------------------------------------------------------------------ sub trace { my @caller_info = caller; my $package = $caller_info[0]; if ("${package}::debug" || "$main::debug" ) { return(&$report_internal(\@caller_info,$CONST_OutputModes{Both},wantarray,"Trace: ",@_)); } } sub trace_log { my @caller_info = caller; my $package = $caller_info[0]; if ("${package}::debug" || "$main::debug" ) { return(&$report_internal(\@caller_info,$CONST_OutputModes{Log},wantarray,"Trace: ",@_)); } } sub trace_display { my @caller_info = caller; my $package = $caller_info[0]; if ("${package}::debug" || "$main::debug" ) { return(&$report_internal(\@caller_info,$CONST_OutputModes{Display},wantarray,"Trace: ",@_)); } } #------------------------------------------------------------------------------ # The note function(s) works similarly to the report function except that the # header is implicitly "Note: ". # # Note: note displays to both the log and the screen # note_log displays to only the log # note_display displays to only the screen #------------------------------------------------------------------------------ sub note { return(&$report_internal([caller],$CONST_OutputModes{Both},wantarray,"Note: ",@_)); } sub note_log { return(&$report_internal([caller],$CONST_OutputModes{Log},wantarray,"Note: ",@_)); } sub note_display { return(&$report_internal([caller],$CONST_OutputModes{Display},wantarray,"Note: ",@_)); } #------------------------------------------------------------------------------ # The indent function(s) works similarly to the report function except that the # header is implicitly the number of spaces specified by the first numeric # parameter. # # Note: indent displays to only the screen # indent_log displays to only the log # indent_both displays to both the log and the screen #------------------------------------------------------------------------------ sub indent { ($#_ >= 0) || die "Invalid number of arguments."; my ($length) = shift @_; return(&$report_internal([caller],$length x " ",wantarray,@_)); } #------------------------------------------------------------------------------ # Strip the extension from the specified path. #------------------------------------------------------------------------------ sub strip_extension { ($#_ == 0) || die "Invalid number of arguments."; my ($path) = @_; $path =~ s/\.[^\.]*$//; return($path); } #------------------------------------------------------------------------------ # Extract the extension from the specified path. #------------------------------------------------------------------------------ sub extract_extension { ($#_ == 0) || die "Invalid number of arguments."; my ($path) = @_; if (s/(\.[^\.]*)$//) { return($1); } else { return(""); } } #------------------------------------------------------------------------------ # Sleep the specified number of microseconds. #------------------------------------------------------------------------------ sub usleep { ($#_ == 0) || die "Invalid number of arguments."; my ($sleeptime)=@_; select undef,undef,undef,($sleeptime/1000000); } #------------------------------------------------------------------------------ # Wait until either any child exits (if no parameters are specified) or # the specified child exits (if one parameter, the pid of the child process, # is specified). # # Note: Meant to be used as a parent waiting for a child process to die. #------------------------------------------------------------------------------ sub spinner_wait { (@_ == 1) || (@_ == 0) || die "Invalid number of arguments."; my $child = -1; my $pid = -1; if (@_) { $pid = shift @_; } my @SPINLIST = ("|\b", "/\b", "-\b", "\\ \b\b", "|\b", "/\b", "-\b", "\\ \b\b"); my $length = $#SPINLIST + 1; my $SPINCOUNT = 0; do { $child = waitpid($pid,&WNOHANG); $SPINCOUNT = ($SPINCOUNT + 1) % $length; print("$SPINLIST[$SPINCOUNT]"); usleep(250000); } until(($child == $pid) || ($child == -1)); } #------------------------------------------------------------------------------ # This is the inverse of spinner_wait. Namely, it has the child process # spinning until the parent process (or any other process, for that matter) # signals it to no longer spin (via signal_spinner). #------------------------------------------------------------------------------ sub spinner_wait_signal { (@_ == 0) || die "Invalid number of arguments."; my @SPINLIST = ("|\b", "/\b", "-\b", "\\ \b\b", "|\b", "/\b", "-\b", "\\ \b\b"); my $length = $#SPINLIST + 1; my $SPINCOUNT = 0; do { $SPINCOUNT = ($SPINCOUNT + 1) % $length; print("$SPINLIST[$SPINCOUNT]"); usleep(250000); } until(0); } #------------------------------------------------------------------------------ # See spinner_wait_signal above. Signal the waiting process (kill it). #------------------------------------------------------------------------------ sub signal_spinner { (@_ == 1) || die "Invalid number of arguments."; my $pid = shift @_; kill(&SIGKILL,$pid); waitpid($pid,0); print "\n"; } sub detect_odbc_version { ($#_ == 0) || die "Invalid number of arguments."; my ($odbcdirect_lib_dir) = @_; (-d $odbcdirect_lib_dir) || die "ODBC library directory '$odbcdirect_lib_dir', does not exist."; # Assume ODBC Direct 3.6, if all else fails. my $version = 15; local *LIBDIR; if (opendir(LIBDIR,$odbcdirect_lib_dir)) { foreach(readdir(LIBDIR)) { if (/YYdbf([0-9]+)/) { $version = $1; last; } } close(LIBDDIR); } return($version); } ######################### We now have to detect and setup perl environment. #------------------------------------------------------------------------------ # This routine tries to locate the perl binary locations and its associated # include and libpath variables. If the perl is located at a location inside # the asphome, it assumes that this will be the perl location shipped by # ChiliSoft and starts working on it. Ths perl shipped by us should have a # directory structure like perl5/bin and perl5/lib # If this directory structure is not followed then we try to look for the # perl installed at the default locations like "usr/bin" etc. # Returns the location of perl and its corresponding include paths if necessary. # otherwise returns the location of perl binary. #------------------------------------------------------------------------------ sub detect_perl { # scan all the normal places my @perl_locations = ( "/usr/bin/perl", "/usr/local/bin/perl", "/opt/perl/bin/perl", "/opt/perl5/bin/perl" ); my $which_perl; if (-x "/usr/bin/which") { $which_perl = `/usr/bin/which perl 2>&1`; } elsif (-x "/bin/which") { $which_perl = `/bin/which perl 2>&1`; } if ($which_perl) { chomp $which_perl; push @perl_locations, $which_perl; } my @perl_list = grep { -x $_ } @perl_locations; if ($#perl_list >= 0) { return($perl_list[0]); } else { return("Unknown"); } } sub detect_chiliperl { # At this moment, we should NOT have the location of perl detected. # We will now try to detect it and update it in our database # so that we can reference it during all future references. # perl binary will contain just the binary executable name. my $perl_binary = undef; my $perl_include = undef; if (ref($pParameters) && -x "$pParameters->{perl_binary}") { $perl_binary = "$pParameters->{perl_binary}"; } # perl will contain the binary executable name and its dependent lib path # locations. my $perl = "$perl_binary" ; #If not user specified perl then let us look for perl in our ASPHOME location. if (! $perl) { my $asphome ; if (-d "$pParameters->{asphome}") { $asphome = "$pParameters->{asphome}"; } elsif ( -d "$pGlobals->{asphome}") { $asphome = "$pGlobals->{asphome}"; } if (! $asphome) { $perl = undef; goto DETECT_USER_DEFINED_PERL ; } my $perl_base_dir = "$pGlobals->{tools_bin_dir}"; my @perl_binary_locations = ( "$perl_base_dir/perl5/bin/perl", "$perl_base_dir/perl/bin/perl", "$perl_base_dir/perl", ); foreach (@perl_binary_locations) { if ( -x "$_") { $perl_binary = "$_" ; last ; } } if ($perl_binary) { my @perl_default_dirs = ( "$perl_base_dir/perl5/lib", "$perl_base_dir/perl/lib", ); foreach (@perl_default_dirs) { if ( -d "$_") { $perl_include .= " -I$_" ; } } # for our perl to work we need to have both # the include and binary file, else error out. unless ($perl_binary && $perl_include) { error ("Installer is not able to detect a perl distribution under your $asphome location."); error ("You will need to have perl (version 5.005_03) or above, installed."); error ("Now this program will try to look for perl in the standard installation location."); $perl = undef ; } else { $perl = "$perl_binary $perl_include"; } # @perl_include = join(" -I ",(split('\s+',(grep { -d $_ } @perl_include_locations)))); } } DETECT_USER_DEFINED_PERL: if (! $perl) { $perl_binary = &detect_perl; $perl= $perl_binary ; } my %chili_perl ; $chili_perl{'perl'} = "$perl" ; $chili_perl{'perl_binary'} = "$perl_binary" ; $chili_perl{'perl_include'} = "$perl_include" ; return (\%chili_perl); } #---------------------------------------------------------------------------- # This method will search the directory for given filenames as input. # If this specified file is present, then this routine will update # the hash 'matchedList' to store the parent directory containing # the input files. #---------------------------------------------------------------------------- sub searchDirectory { my ($pMatchList,$search_path,@inputlist) = @_; my @dirlist; my @subdirs; local *DIR; # this routine need not process further, if either the search list # is invalid or the directory does NOT exist. return (undef) unless (-d "$search_path"); return (undef) unless (@inputlist); unless(opendir(DIR,$search_path)) { return(error("Unable to open $search_path in searchDirectory")); } if ($search_path eq '/') { $search_path = ""; } foreach my $file (grep { ($_ ne ".") && ($_ ne "..") } readdir(DIR)) { my $path = "$search_path/$file"; next if (-l "$path") ; next if ( exists $pMatchList->{'$path'} ); push @subdirs, $path if (-d "$path"); foreach (@inputlist) { $pMatchList->{$search_path} = $search_path if ($_ eq $file) ; last if ($_ eq $file) ; } } closedir(DIR); foreach(@subdirs) { searchDirectory($pMatchList,$_,@inputlist) ; } return (1); } #------------------------------------------------------------------------------ # This method will establish the globals used universally throughout the # installer. It should be noted that this method assumes that the # $pParameters->{asphome} value was set prior to execution. #------------------------------------------------------------------------------ sub set_standard_globals { # Start of Main Code. (exists $pParameters->{asphome}) || die "Required parameter asphome is not set."; (exists $pParameters->{com_layer}) || die "Required parameter com_layer is not set."; (-d $pParameters->{asphome}) || die "Required parameter asphome refers to an invalid directory."; # Strip trailing / off of asphome directory. $pParameters->{asphome} =~ s@/+$@@; if ($pParameters->{asphome} =~ /^\s*$/) { $pParameters->{asphome} = "/"; } # Default to debug, if chilicom is debug or INSTALL_CSTARGET is debug my $CSTARGET = "optimized"; if (-d "$pParameters->{asphome}/chilicom/lib/$pOS->{ostag}_debug") { # ChiliCOM is assumed to be debug, if this is true. $CSTARGET = "debug"; } if (exists $ENV{INSTALL_CSTARGET}) { $CSTARGET=$ENV{INSTALL_CSTARGET}; } my $COM_LAYER = lc($pParameters->{com_layer}); my $version; if (-f "$pParameters->{asphome}/VERSION") { local *VERSION; unless(open(VERSION,"$pParameters->{asphome}/VERSION")) { note_log("Unable to open $pParameters->{asphome}/VERSION to determine version info."); } else { foreach() { if (/Version: ([0-9\.]+[A-Za-z]?)/) { $version = $1; } } } } unless($version) { # Default to 3.6.2. $version = '3.6.2'; } unless($version =~ /[A-Za-z]$/) { $version .= uc(substr($pOS->{os},0,1)); } %INSTALL::global::variables = ( CSTARGET => $CSTARGET, COM_LAYER => $COM_LAYER, com_layer => $COM_LAYER, # Default CodePage and LCID for caspeng substitution. codepage => 1252, lcid => '0409', default_shell => $pOS->{default_shell}, # Java is known to cause some stability problems on some platforms, # disable it by default. java_support => "no", casp_version => $version, odbc_version => detect_odbc_version("$pParameters->{asphome}/odbc/direct/lib"), license_name => "LICENSE.LIC", perl => $pOS->{perl}, perl_binary => $pOS->{perl_binary}, perl_include => $pOS->{perl_include}, execute_params => join(" ",map { "\"$_\"" } grep { not /password/ } @main::ARGV), # Don't pass anything with the keyword password in it. asphome => $pParameters->{asphome}, asp_dir => $pParameters->{asphome}, log_dir => "$pParameters->{asphome}/logs", lib_ext => $pOS->{shlib_extensions}->[0], admin_dir => "$pParameters->{asphome}/admin", admin_bin_dir => "$pParameters->{asphome}/admin/bin", admin_conf_dir => "$pParameters->{asphome}/admin/conf", admin_logs_dir => "$pParameters->{asphome}/admin/logs", admin_module_dir => "$pParameters->{asphome}/admin/conf", # FIXME: The lib directory should have the capacity to be target dependent too. lib_dir => "$pParameters->{asphome}/server/lib/$pOS->{ostag}_$CSTARGET", bin_dir => "$pParameters->{asphome}/server/bin/$pOS->{ostag}_$CSTARGET", tools_bin_dir => "$pParameters->{asphome}/tools/bin/$pOS->{ostag}", tools_lib_dir => "$pParameters->{asphome}/tools/lib/$pOS->{ostag}", com_lib_dir => "$pParameters->{asphome}/chilicom/lib/$pOS->{ostag}_$CSTARGET", com_bin_dir => "$pParameters->{asphome}/chilicom/bin/$pOS->{ostag}_$CSTARGET", bean_dir => "$pParameters->{asphome}/bean", bean_class_dir => "$pParameters->{asphome}/bean/classes", bean_lib_dir => "$pParameters->{asphome}/bean/lib/$pOS->{ostag}_$CSTARGET", bean_bin_dir => "$pParameters->{asphome}/bean/bin/$pOS->{ostag}_$CSTARGET", module_dir => "$pParameters->{asphome}/module/$pOS->{ostag}_$CSTARGET", install_dir => "$pParameters->{asphome}/INSTALL", templates_dir => "$pParameters->{asphome}/INSTALL/templates", template_dir => "$pParameters->{asphome}/INSTALL/templates", components_dir => "$pParameters->{asphome}/components", component_dir => "$pParameters->{asphome}/components", jre_path => "$pParameters->{asphome}/bean/jre1.2.2", jvm_path => "$pParameters->{asphome}/bean/jre1.2.2", jre_version => "1.2.2", chili_ini_dir => "/var/opt/casp", chili_ini => "/var/opt/casp/chili.ini", installed_db => "$pParameters->{asphome}/.installed_db", settings_reg => "$pParameters->{asphome}/INSTALL/settings.reg", perl_header => "", scripts_dir => "$pParameters->{asphome}/INSTALL", script_dir => "$pParameters->{asphome}/INSTALL", # For the time being, leave the scripts in the INSTALL dir. summary_log => "$pParameters->{asphome}/logs/install_summary", register_log => "$pParameters->{asphome}/logs/register_summary", component_log => "$pParameters->{asphome}/logs/component_summary", # Upon each write to the log (via summary_log, register_log, etc), these # text fields will be concatenated with their previous states to contain # the list of all data logged. summary_text => "", register_text => "", component_text => "", installer_output => "$pParameters->{asphome}/logs/.installer_output", %INSTALL::global::variables ); # Detect if there is a chili perl otherwise load the default user specified # perl binary. # It is also Okay if we did not have perl. # for the first time, caspi2.pl will detect this and ask the user for it. # for the rest of the time, we don't care !!! # It is okay to overwrite it on OS data structure. my $pChiliPerl = &detect_chiliperl ; if (ref($pChiliPerl) eq "HASH") { if (ref($pGlobals) && ref($pOS)) { $pOS->{perl} = $pChiliPerl->{'perl'} ; $pOS->{perl_binary} = $pChiliPerl->{'perl_binary'} ; $pOS->{perl_include} = $pChiliPerl->{'perl_include'} ; $pGlobals->{perl} = $pChiliPerl->{'perl'} ; $pGlobals->{perl_binary} = $pChiliPerl->{'perl_binary'} ; $pGlobals->{perl_include} = $pChiliPerl->{'perl_include'} ; if ($pOS->{os} !~ /HP-UX/i) { $pGlobals->{perl_header} = "$pGlobals->{perl} -I$pGlobals->{asphome}"; } else { my @include_dirs = () ; foreach (split('-I',$pGlobals->{perl_include})) { push @include_dirs,$_ if (-d "$_") ; } my @search_files = ( "AutoLoader.pm","IO.pm", "POSIX.pm","B.pm" ); my %searchDirs = () ; my $pSearchDirList = \%searchDirs ; INSTALL::common::searchDirectory($pSearchDirList,@include_dirs,@search_files); $pGlobals->{perl_header} = "$pGlobals->{perl_binary}" ; $pGlobals->{perl_header} .= "\nBEGIN \{"; # Begin Block. foreach my $inc (keys %$pSearchDirList) { $pGlobals->{perl_header} .= "\n push \@INC\,\"$inc\"\;"; } $pGlobals->{perl_header} .= "\n push \@INC\,\"$pGlobals->{asphome}\"\; "; $pGlobals->{perl_header} .= "\n\}"; # Close Block. } } } else { error_log("Unable to detect a valid Perl in your system.\n"); } use Sys::Hostname; use Socket; $pGlobals->{hostname} = hostname(); $pGlobals->{full_hostname} = gethostbyaddr(gethostbyname($pGlobals->{hostname}),AF_INET); unless ($pGlobals->{full_hostname}) { $pGlobals->{full_hostname} = $pGlobals->{hostname}; } #------------------------------------------------------------------------------ # Some folders / files need to be set read / write for certain internal # functions to work in inherit-user or execute-as-user mode(s). #------------------------------------------------------------------------------ my @read_write_files = ( # We're punting on these until the next major release. # "$pGlobals->{lib_dir}/Counter.txt", # "$pGlobals->{lib_dir}/libmyinfo.ini" ); foreach my $file (@read_write_files) { local *FILE; if (open(FILE,">>$file")) { chmod(0666,$file); close(FILE); } } my @read_write_dirs = ( $pGlobals->{log_dir} ); foreach my $dir (@read_write_dirs) { mkpath($dir,0777); # Backup if the path already existed. chmod(0777,$dir); } #------------------------------------------------------------------------------ # Create log files with the appropriate permissions. #------------------------------------------------------------------------------ # Some portions of post install depend on there being an .installed_db. local *INSTALLED_DB; if (open(INSTALLED_DB,">>$pGlobals->{installed_db}")) { chmod(0644,$pGlobals->{installed_db}); close(INSTALLED_DB); } if (open(ERROR_LOG,">>$pGlobals->{log_dir}/installer_errors")) { chmod(0644,"$pGlobals->{log_dir}/installer_errors"); ERROR_LOG->autoflush(1); # print LOG # ( # "===============================================================================\n", # "Logging started: ",scalar(localtime(time)),"\n", # "===============================================================================\n" # ); } if (open(SUMMARY_LOG,">>$pGlobals->{summary_log}")) { chmod(0600,"$pGlobals->{summary_log}"); SUMMARY_LOG->autoflush(1); } if (open(REGISTER_LOG,">>$pGlobals->{register_log}")) { chmod(0600,"$pGlobals->{register_log}"); REGISTER_LOG->autoflush(1); } if (open(COMPONENT_LOG,">>$pGlobals->{component_log}")) { chmod(0600,"$pGlobals->{component_log}"); COMPONENT_LOG->autoflush(1); } if (open(INSTALLER_LOG,">>$pGlobals->{installer_output}")) { chmod(0600,"$pGlobals->{installer_output}"); INSTALLER_LOG->autoflush(1); } return(1); } #------------------------------------------------------------------------------ # Attempt to resolve the location of a binary , using system directories # before any other. The optional 2nd, 3rd, etc. arguments specify alternative # root directories to search. It should be noted that these directories are # prepended to the list to be searched and will thus take precedence over # default system directories. # # Note(0): If no executable path can be found, undef is returned. # Note(1): The result is the full path name of the binary (not merely the # directory). #------------------------------------------------------------------------------ sub locate_binary { ($#_ >= 0) || die "Invalid number of arguments."; my ($name,@optional_dirs) = @_; my $result = undef; if ($#optional_dirs >= 0) { foreach(@optional_dirs) { my $possibility = "$_/$name"; if (-x $possibility) { return($possibility); } } } my @search_dirs = ( $pGlobals->{bin_dir}, $pGlobals->{tools_bin_dir}, $pGlobals->{com_bin_dir}, $pGlobals->{bean_bin_dir}, $pGlobals->{admin_bin_dir}, "/sbin", "/usr/sbin", "/usr/local/sbin", "/bin", "/usr/bin", "/usr/local/bin", "/usr/local/bin"); foreach(@search_dirs) { my $possibility = "$_/$name"; if (-x $possibility) { $result = $possibility ; return($possibility); } } return($result); } #------------------------------------------------------------------------------ # Make a directory structure, indepedent of which of the subdirectories of that # structure already exist. This is like the user-level command # 'mkdir -p ' #------------------------------------------------------------------------------ sub mkpath { ($#_ == 1) || die "Invalid number of arguments."; my ($path,$mode) = @_; my $result = (not -d $path); my @subdirs = split(m@/@,$path); my $const = ""; foreach(@subdirs) { $const .= "$_/"; if (not -d $const) { $result &= mkdir($const,$mode); } } return($result); } #------------------------------------------------------------------------------ # This method returns the realpath of the filename / path specified. See the # realpath manpage. If the file does not exist undef is returned. #------------------------------------------------------------------------------ sub realpath_internal { ($#_ == 0) || die "Invalid number of arguments."; my ($filename) = @_; # Remove relative path dependency. if ($filename =~ m@^[^/]@) { # Relative path. my $pwd = `pwd`; chomp $pwd; $filename = "$pwd/$filename"; } if (-e $filename) { my $done; # Split the path into components, stripping any extra usage of / my @components = grep { $_ ne "" } split(m@/@,$filename); my $fullpath; foreach my $component (@components) { if ($component eq ".") { next; } elsif ($component eq "..") { # Strip off the previous directory. $fullpath =~ s@/[^/]*/$@/@; next; } my $path = "$fullpath$component"; if (-l $path) { my $link = readlink($path); if ($link =~ m@^[^/]@) { # Relative path. my $parent_dir = $path; $parent_dir =~ s@/[^/]*$@@; $path = realpath("$parent_dir/$link"); } else { $path = realpath($link); } } $fullpath = "$path/"; } $fullpath = substr("/$fullpath",0,length($fullpath)); return($fullpath); } else { return(undef); } } sub realpath { if (@_ == 1) { return(realpath_internal($_[0])); } else { return(map(realpath_internal($_), @_)); } } #------------------------------------------------------------------------------ # Returns the directory name of the specified path (like the Unix dirname # command). # # dirname("/usr/home/deanb") = "/usr/home" # dirname("/usr/home/deanb/") = "/usr/home" # dirname("foobar") = "." # dirname("foobar/fubar") = "foobar" #------------------------------------------------------------------------------ sub dirname { my @result = @_; foreach(@result) { s@/$@@; if (m@/@) { s@/[^/]*$@@; } else { $_ = "."; } } wantarray ? return(@result) : return($result[0]); } #------------------------------------------------------------------------------ # Returns the base filename of the specified path (like the Unix basename # command). # # basename("/pub/home/deanb") = "deanb" # basename("/pub/home/deanb/") = "deanb" # basename("foobar") = "foobar" # basename("foobar/fubar") = "fubar" #------------------------------------------------------------------------------ sub basename { my @result = @_; foreach(@result) { s@/$@@; if (m@/([^/]*)$@) { $_ = $1; } } wantarray ? return(@result) : return($result[0]); } #------------------------------------------------------------------------------ # Find file(s) under the specified directory that matches # . If is left unspecified (or is undef), it is # interpreted to mean, find all. # # Upon failure, an empty list is returned. #------------------------------------------------------------------------------ sub find_files { (($#_ == 1) || ($#_ == 2)) || die "Invalid number of arguments."; my ($root,$filename,$find_count) = @_; my @result; if (not defined $find_count) { $find_count = 2**31; } my @subdirs = ($root); my $subdir; while($subdir = pop @subdirs) { local *SUBDIR; opendir(SUBDIR,$subdir) || die "Invalid directory: $subdir"; my @files = readdir(SUBDIR); closedir(SUBDIR); foreach(@files) { (($_ ne ".") && ($_ ne "..")) || next; $find_count || return(@result); my $path = "$subdir/$_"; if ($_ eq $filename) { push @result, $path; $find_count--; } if ((not -l $path) && (-d $path)) { push @subdirs, $path; } } } wantarray ? return(@result) : return($result[0]); } sub find_file { ($#_ == 1) || die "Invalid number of arguments."; my @result = find_files(@_,1); wantarray ? return(@result) : return($result[0]); } #------------------------------------------------------------------------------ # A more safe chomp that will not chomp references into oblivion. It leaves # references alone. #------------------------------------------------------------------------------ sub safe_chomp { map { ((not ref($_)) && chomp $_); $_ } @_; } #------------------------------------------------------------------------------ # Appends the specified <@values> to the end of the pathing variable # <$variable>, such that if any of the <@values> is already a member of the # existing environmental variable, it is left in its current location / # ordering. The existing order after the append will not have changed and # those values appended will appear in the same order as listed in <@values>. # Result: (,) # # Note: Any duplicates in the path are removed. The first occurrence of # the value is used as the output occurrence. #------------------------------------------------------------------------------ sub env_pathing_append { ($#_ >= 1) || die "Invalid number of arguments."; my ($variable,@values) = @_; my @order; my %values; if (exists $ENV{$variable}) { foreach(split(/:/,$ENV{$variable})) { if (not exists $values{$_}) { push @order,$_; $values{$_} = $#order; } } } foreach my $value (@values) { if (not exists $values{$value}) { push @order,$value; $values{$value} = $#order; } } return($ENV{$variable} = join(":",@order)); } #------------------------------------------------------------------------------ # Prepend the specified <@values> to the beginning of the pathing variable # <$variable>, such that if any of the <@values> is already a member of the # existing environmental variable, it is left in its current location / # ordering. The existing order after the append will not have changed and # those values prepended will appear in the same order as listed in <@values>. # Result: (,) # # Note: Any duplicates in the path are removed. The first occurrence of # the value is used as the output occurrence. #------------------------------------------------------------------------------ sub env_pathing_prepend { ($#_ >= 1) || die "Invalid number of arguments."; my ($variable,@values) = @_; my @order; my %values; if (exists $ENV{$variable}) { foreach(split(/:/,$ENV{$variable})) { if (not exists $values{$_}) { push @order,$_; $values{$_} = $#order; } } } my @head; foreach my $value (@values) { if (not exists $values{$value}) { push @head,$value; $values{$value} = $#head; } } return($ENV{$variable} = join(":",@head,@order)); } #------------------------------------------------------------------------------ # Delete the specified <@values> from the pathing variable <$variable>. # # Note: Any duplicates in the path are removed. The first occurrence of # the value is used as the output occurrence. #------------------------------------------------------------------------------ sub env_pathing_delete { ($#_ >= 1) || die "Invalid number of arguments."; my ($variable,@values) = @_; my @order; my %values; if (exists $ENV{$variable}) { foreach(split(/:/,$ENV{$variable})) { if (not exists $values{$_}) { push @order,$_; $values{$_} = $#order; } } } foreach my $value (@values) { if (exists $values{$value}) { $order[$values{$value}] = undef; delete $values{$value}; } } return($ENV{$variable} = join(":",grep { defined($_) } @order)); } #------------------------------------------------------------------------------ # See env_pathing_delete. Operates on the "PATH" environmental variable. #------------------------------------------------------------------------------ sub path_delete { return(env_pathing_delete("PATH",@_)); } #------------------------------------------------------------------------------ # See env_pathing_delete. Operates on the OS specific libpath variable. #------------------------------------------------------------------------------ sub libpath_delete { return(env_pathing_delete($pOS->{libpath_variable},@_)); } #------------------------------------------------------------------------------ # See env_pathing_append. Operates on the "PATH" environmental variable. #------------------------------------------------------------------------------ sub path_append { return(env_pathing_append("PATH",@_)); } #------------------------------------------------------------------------------ # See env_pathing_append. Operates on the OS specific libpath variable. #------------------------------------------------------------------------------ sub libpath_append { return(env_pathing_append($pOS->{libpath_variable},@_)); } #------------------------------------------------------------------------------ # See env_pathing_prepend. Operates on the "PATH" environmental variable. #------------------------------------------------------------------------------ sub path_prepend { return(env_pathing_prepend("PATH",@_)); } #------------------------------------------------------------------------------ # See env_pathing_prepend. Operates on the OS specific libpath variable. #------------------------------------------------------------------------------ sub libpath_prepend { return(env_pathing_prepend($pOS->{libpath_variable},@_)); } #------------------------------------------------------------------------------ # Compare the passed value with what is considered to be true and false. # If the passed value is considered to be true, 1 is returned. Else a value # which evaluates to false will be returned. # # Note: Upon failure to match true / false case, the false value of 0 will be # returned (otherwise the empty list or undef will be returned, # as appropriate). #------------------------------------------------------------------------------ sub boolean_value { my ($value) = @_; if (($value =~ /^yes$/i) || ($value =~ /^true$/i) || ($value =~ /^y$/i) || (($value =~ /[0-9]/) && (int($value) != 0))) { return(1); } elsif ((not $value) || ($value =~ /^no$/i) || ($value =~ /^false$/i) || ($value =~ /^n$/i) || (($value =~ /[0-9]/) && (int($value) == 0))) { return(wantarray ? () : undef); } else { return(0); } } #------------------------------------------------------------------------------ # Are we running in interactive mode? #------------------------------------------------------------------------------ sub interactive { my $non_interactive = $INSTALL::parameter::variables{non_interactive}; return(not boolean_value($non_interactive)); } #------------------------------------------------------------------------------ # Log to the summary log. # # Note: The provided strings are concatenated together such that if # provided the strings: # ("Installing components:", # " Administration console: Success.", # " Patch: Success") # # Would generate in the log: # # Installing components: # Administration console: Success. # Patch: Success. #------------------------------------------------------------------------------ sub summary_log { my $text = join("\n",@_)."\n"; if (not $pGlobals->{summary_text}) { $pGlobals->{summary_text} = $text; } else { $pGlobals->{summary_text} .= "\n$text"; } if (-e SUMMARY_LOG) { my @stats = stat(SUMMARY_LOG); my $file_size = $stats[7]; if ($file_size == 0) { print SUMMARY_LOG ( "\n", " Sun Chili!Soft ASP Installation Summary\n", "===============================================================================\n", " installed version: $pGlobals->{casp_version}\n", " os: $pOS->{os_name}\n", " release: $pOS->{os_version}\n", " arch: $pOS->{hardware}\n", " install dir: $pGlobals->{asphome}\n", " COM layer: $pGlobals->{com_layer}\n" ); if ($pOS->{os} =~ /linux/i) { print SUMMARY_LOG (" distribution: $pOS->{distribution}\n", " distribution flavor: $pOS->{distribution_flavor}\n"); } } print SUMMARY_LOG "\n$text"; return(1); } else { return(undef); } } #------------------------------------------------------------------------------ # Log to the register log. # # Note: See summary_log's note. #------------------------------------------------------------------------------ sub register_log { my $text = join("\n",@_)."\n"; if (not $pGlobals->{register_text}) { $pGlobals->{register_text} = $text; } else { $pGlobals->{register_text} .= "\n$text"; } if (-e REGISTER_LOG) { my @stats = stat(REGISTER_LOG); my $file_size = $stats[7]; if ($file_size == 0) { print REGISTER_LOG ( "\n", " Sun Chili!Soft ASP Registration Summary\n", "===============================================================================\n" ); } print REGISTER_LOG "\n$text"; return(1); } else { return(undef); } } #------------------------------------------------------------------------------ # Log to the component log. # # Note: See summary_log's note. #------------------------------------------------------------------------------ sub component_log { my $text = join("\n",@_)."\n"; if (not $pGlobals->{component_text}) { $pGlobals->{component_text} = $text; } else { $pGlobals->{component_text} .= "\n$text"; } if (-e COMPONENT_LOG) { my @stats = stat(COMPONENT_LOG); my $file_size = $stats[7]; if ($file_size == 0) { print COMPONENT_LOG ( "\n", " Sun Chili!Soft ASP Component Summary\n", "===============================================================================\n" ); } print COMPONENT_LOG "\n$text"; return(1); } else { return(undef); } } #------------------------------------------------------------------------------ # Split a command list in normal shell syntax into an array of the arguments. # #------------------------------------------------------------------------------ sub split_command { ($#_== 0) || die "Invalid number of arguments."; my ($command) = @_; my @result; if ($command =~ /[;|<>=]/) { # Multipart command string / shell command. @result = ('sh','-c',"$command"); } else { while($command) { if ($command =~ s/^\s*((\\.|[^\'\"\s])+)\s*//) { push @result, $1; } elsif ($command =~ s/^\s*\'((\\.|[^\'])*)\'\s*//) { push @result, $1; } elsif ($command =~ s/^\s*\"((\\.|[^\"])*)\"\s*//) { push @result, $1; } else { die "Invalid command specified."; } } } return(@result); } #------------------------------------------------------------------------------ # Log to the installer error log the results of running the specified command. # This command acts exactly like system except that it redirects its stdout # and stderr to the installer error log. #------------------------------------------------------------------------------ sub system_log { ($#_ >= 0) || die "Invalid number of arguments."; my @command; if ($#_ == 0) { @command = split_command(@_[0]); } else { @command = @_; } my $pid = fork; if (not $pid) { open STDOUT, ">&INSTALLER_LOG" || die "Unable to redirect stdout"; open STDERR, ">&INSTALLER_LOG" || die "Unable to redirect stderr"; select(STDOUT); $| = 1; select(STDERR); $| = 1; exec(@command); exit(1); } else { waitpid($pid,0); } return($? >> 8); } #------------------------------------------------------------------------------ # Fork the specified command, redirecting stdout and stderr to a pipe which # is returned as a HANDLE glob. This pipe may be read for the results of # the executed command. #------------------------------------------------------------------------------ sub fork_command { ($#_ >= 0) || die "Invalid number of arguments."; my @command; if ($#_ == 0) { @command = split_command(@_[0]); } else { @command = @_; } local (*CLIENT_WRITE, *SERVER_READ); pipe SERVER_READ, CLIENT_WRITE || die "Unable to create pipes."; my $pid = fork; if (not $pid) { close(SERVER_READ); open STDOUT, ">&CLIENT_WRITE" || die "Unable to redirect stdout"; open STDERR, ">&CLIENT_WRITE" || die "Unable to redirect stderr"; select(STDOUT); $| = 1; select(STDERR); $| = 1; exec(@command); exit(1); } else { close(CLIENT_WRITE); } return(*SERVER_READ{IO}); } #------------------------------------------------------------------------------ # Copy to . Upon success, 1 is returned. Otherwise, # undef is returned. #------------------------------------------------------------------------------ sub copy { (@_ == 2) || die "Invalid number of arguments."; my ($source,$destination) = @_; unless(system_log("/bin/cp $source $destination")) { return(1); } return(undef); } #------------------------------------------------------------------------------ # Create a hard-link from to , if supported, or do a # full-fledged copy if not. If a successful link is created, 1 is returned. # If a copy is performed, 2 is returned. Otherwise, upon failure, undef # is returned. #------------------------------------------------------------------------------ sub shallow_copy { (@_ == 2) || die "Invalid number of arguments."; my ($source,$destination) = @_; if (-d $destination) { $destination .= "/".basename($source); } if (link($source,$destination)) { return(1); } elsif (copy($source,$destination)) { return(2); } # Failure. return(undef); } #------------------------------------------------------------------------------ # Safe recursive rmdir. What 'safe' means here is that this command may not # be used to remove directories not owned by this installation. Owned files # are those that exist at or below the asphome directory. #------------------------------------------------------------------------------ sub recursive_rmdir { (@_ == 1) || die "Invalid number of arguments."; my $dir = shift @_; my $realdir = realpath($dir); if ((-d $realdir) && ($realdir =~ /^$pGlobals->{asphome}/)) { return(not system_log("/bin/rm -rf $realdir")); } else { return(undef); } } #------------------------------------------------------------------------------ # Provided a filename, this method attempts to locate the compressor which # matches the extension of the filename. Upon success, a non-undef resulting # path will be returned. #------------------------------------------------------------------------------ sub locate_compressor { (@_ == 1) || die "Invalid number of arguments."; my $source = shift @_; my $compressor; if (($source =~ /tgz$/) || ($source =~ /tar\.gz$/)) { $compressor = locate_binary('gzip'); } elsif (($source =~ /tZ$/) || ($source =~ /tar\.Z$/)) { $compressor = locate_binary('compress'); } return($compressor); } #------------------------------------------------------------------------------ # Provided a filename, this method attempts to decompress it into the # destination directory. It is assumed that the filename contains enough # information to determine the type of the file being uncompressed. # specifies the source compressed file. must specify an existing # directory. Upon success, non-undef is returned. # # Note: Currently only tar, tar.Z, and tar.gz type files are supported, though # their extensions may be the following: # *.tar.gz # *.tgz # *.tar.Z # *.tZ # *.tar #------------------------------------------------------------------------------ sub uncompress { (@_ == 2) || die "Invalid number of arguments."; my ($source,$destination) = @_; unless(-r $source) { return(error("Unable to open $source for reading.")); } unless (-d $destination) { return(error("$destination is not a directory.")); } my $tar = locate_binary('tar'); my $compressor = locate_compressor($source); my $decompressor; if ($compressor) { $decompressor = "$compressor -dc"; } elsif ($source =~ /\.tar$/) { $decompressor = locate_binary('cat'); } unless ($decompressor) { return(error("Unable to locate decompressor for: $source")); } my $command; if (system_log("cd $destination; $decompressor $source | $tar -xvvpf -")) { return(error("Decompressing $source did not produce a clean return.")); } else { return(1); } } sub decompress { return(uncompress(@_)); } #------------------------------------------------------------------------------ # Provided a source directory, this method attempts to compress it into the # destination filename. It is assumed that the filename contains enough # information to determine the type of the file being compressed. # specifies the source compressed file. must specify an existing # directory. Upon success, non-undef is returned. # # Note(0): Currently only tar, tar.Z, and tar.gz type files are supported, # though their extensions may be the following: # *.tar.gz # *.tgz # *.tar.Z # *.tZ # *.tar # # Note(1): If you specify /usr/local/bin as your , this method will # cd to that directory, then gobble up '.'. #------------------------------------------------------------------------------ sub compress { (@_ == 2) || die "Invalid number of arguments."; my ($source,$destination) = @_; unless(-d $source) { return(error("$source is not a directory.")); } my $tar = locate_binary('tar'); my $compressor = locate_compressor($destination); unless($compressor) { # Assume no compression $compressor = locate_binary('cat'); } unless ($compressor) { return(error("Unable to locate decompressor for: $source")); } my $command; if (system_log("cd $source; $tar -cvvpf - . | $compressor > $destination")) { return(error("Compressing $source did not produce a clean return.")); } else { return(1); } } END { # if (-e ERROR_LOG) { # print ERROR_LOG # ( # "===============================================================================\n", # "Logging stopped: ",scalar(localtime(time)),"\n", # "===============================================================================\n" # ); # } } 1;