releasing/cbrtools/perl/Utils.pm
changeset 607 378360dbbdba
parent 602 3145852acc89
equal deleted inserted replaced
591:22486c9c7b15 607:378360dbbdba
       
     1 # Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     2 # All rights reserved.
       
     3 # This component and the accompanying materials are made available
       
     4 # under the terms of the License "Eclipse Public License v1.0"
       
     5 # which accompanies this distribution, and is available
       
     6 # at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     7 #
       
     8 # Initial Contributors:
       
     9 # Nokia Corporation - initial contribution.
       
    10 #
       
    11 # Contributors:
       
    12 #
       
    13 # Description:
       
    14 
       
    15 package Utils;
       
    16 use base qw(Exporter);
       
    17 use strict;
       
    18 use Win32;
       
    19 use Win32::File;
       
    20 use Win32::Console;
       
    21 use File::stat;
       
    22 use File::Path;
       
    23 use File::Basename;
       
    24 use File::Find;
       
    25 use File::Temp;
       
    26 use File::Spec;
       
    27 use FindBin;
       
    28 use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
       
    29 use Cwd 'abs_path';
       
    30 use Data::Dumper;
       
    31 use Time::Local;
       
    32 use IPC::Open2;
       
    33 use Cwd;
       
    34 use Symbian::IPR;
       
    35 
       
    36 $|++;
       
    37 
       
    38 #
       
    39 # Constants.
       
    40 #
       
    41 
       
    42 use constant EPOC_RELATIVE => 1;
       
    43 use constant SOURCE_RELATIVE => 2;
       
    44 use constant MAX_OS_PATH_LENGTH => 255;
       
    45 our @EXPORT = qw(EPOC_RELATIVE SOURCE_RELATIVE);
       
    46 
       
    47 #
       
    48 # Globals;
       
    49 #
       
    50 
       
    51 my $console; # Needs to be global because (for some reason) file descriptors get screwed up if it goes out of scope.
       
    52 my $tempDir;
       
    53 my $haveCheckedEpocRoot;
       
    54 my $haveCheckedSrcRoot;
       
    55 our %zipFileCache; # used to cache the Archive::Zip object of the last zip file used
       
    56 
       
    57 #
       
    58 # Subs.
       
    59 #
       
    60 
       
    61 sub StripWhiteSpace {
       
    62   my $a = shift;
       
    63   $$a =~ s/^\s*//;
       
    64   $$a =~ s/\s*$//;
       
    65 }
       
    66 
       
    67 sub TidyFileName {
       
    68   my $a = shift;
       
    69   $$a =~ s/\//\\/g;      # Change forward slashes to back slashes.
       
    70   $$a =~ s/\\\.\\/\\/g;  # Change "\.\" into "\".
       
    71 
       
    72   if ($$a =~ /^\\\\/) {  # Test for UNC paths.
       
    73     $$a =~ s/\\\\/\\/g;  # Change "\\" into "\".
       
    74     $$a =~ s/^\\/\\\\/;  # Add back a "\\" at the start so that it remains a UNC path.
       
    75   }
       
    76   else {
       
    77     $$a =~ s/\\\\/\\/g;  # Change "\\" into "\".
       
    78   }
       
    79 
       
    80   # Colapse '\..\' sequences.
       
    81   my $hasLeadingSlash = $$a =~ s/^\\//;
       
    82   my $hasTrailingSlash = $$a =~ s/\\$//;
       
    83   my @elements = split (/\\/, $$a);
       
    84   my @result; # An array to store the colapsed result in.
       
    85   foreach my $element (@elements) {
       
    86     if ($element eq '..') {
       
    87       my $last = pop @result;
       
    88       if ($last) {
       
    89 	if ($last eq '..') { # Throw away the previous element, unless it's another '..'.
       
    90 	  push (@result, $last);
       
    91 	  push (@result, $element);
       
    92 	}
       
    93 	next;
       
    94       }
       
    95     }
       
    96     push (@result, $element);
       
    97   }
       
    98   if ($hasLeadingSlash) {
       
    99     $$a = '\\';
       
   100   }
       
   101   else {
       
   102     $$a = '';
       
   103   }
       
   104   $$a .= join ('\\', @result);
       
   105   if ($hasTrailingSlash) {
       
   106     $$a .= '\\';
       
   107   }
       
   108 }
       
   109 
       
   110 sub IsAbsolute {
       
   111   my $path = shift;
       
   112   if ($path =~ /^[\\\/]/) {
       
   113     return 1;
       
   114   }
       
   115   return 0;
       
   116 }
       
   117 
       
   118 sub AbsoluteFileName {
       
   119   my $fileName = shift;
       
   120   (my $base, my $path) = fileparse($$fileName);
       
   121   my $absPath = abs_path($path);
       
   122   $absPath =~ s/^\D://; # Remove drive letter.
       
   123   $$fileName = $absPath;
       
   124   unless ($$fileName =~ /[\\\/]$/) {
       
   125     $$fileName .= "\\";
       
   126   }
       
   127   $$fileName .= $base;
       
   128   TidyFileName($fileName);
       
   129 }
       
   130 
       
   131 sub AbsolutePath {
       
   132   my $path = shift;
       
   133   my $absPath = abs_path($$path);
       
   134   $absPath =~ s/^\D://; # Remove drive letter.
       
   135   $$path = $absPath;
       
   136   TidyFileName($path);
       
   137 }
       
   138 
       
   139 sub EpocRoot {
       
   140   my $epocRoot = $ENV{EPOCROOT};
       
   141   unless ($haveCheckedEpocRoot) {
       
   142     #use Carp qw/cluck/;
       
   143     #cluck "Checking for EpocRoot";
       
   144     die "Error: Must set the EPOCROOT environment variable\n" if (!defined($epocRoot));
       
   145     die "Error: EPOCROOT must not include a drive letter\n" if ($epocRoot =~ /^.:/);
       
   146     die "Error: EPOCROOT must be an absolute path without a drive letter\n" if ($epocRoot !~ /^\\/);
       
   147     die "Error: EPOCROOT must not be a UNC path\n" if ($epocRoot =~ /^\\\\/);
       
   148     die "Error: EPOCROOT must end with a backslash\n" if ($epocRoot !~ /\\$/);
       
   149     die "Error: EPOCROOT must specify an existing directory\n" if (!-d $epocRoot);
       
   150     $haveCheckedEpocRoot = 1;
       
   151   }
       
   152   return $epocRoot;
       
   153 }
       
   154 
       
   155 sub SourceRoot {
       
   156   my $srcRoot = $ENV{SRCROOT};
       
   157   unless ($haveCheckedSrcRoot) {
       
   158     if (defined $srcRoot) { # undefined SRCROOTs are OK
       
   159       die "Error: SRCROOT must not include a drive letter\n" if ($srcRoot =~ /^.:/);
       
   160       die "Error: SRCROOT must be an absolute path without a drive letter\n" if ($srcRoot !~ /^\\/);
       
   161       die "Error: SRCROOT must not be a UNC path\n" if ($srcRoot =~ /^\\\\/);
       
   162       die "Error: SRCROOT must end with a backslash\n" if ($srcRoot !~ /\\$/);
       
   163       die "Error: SRCROOT must specify an existing directory\n" if (!-d $srcRoot);
       
   164     }
       
   165     $haveCheckedSrcRoot = 1;
       
   166   }
       
   167   return $srcRoot || "\\";
       
   168 }
       
   169 
       
   170 sub CheckWithinEpocRoot {
       
   171   my $path = shift;
       
   172   die "Error: \"$path\" is not within EPOCROOT\n" unless (WithinEpocRoot($path));
       
   173 }
       
   174 
       
   175 sub WithinEpocRoot {
       
   176   my $path = shift;
       
   177   my $epocRoot = EpocRoot();
       
   178   return ($path =~ /^\Q$epocRoot\E/i);
       
   179 }
       
   180 
       
   181 sub PrependEpocRoot {
       
   182   my $path = shift;
       
   183   if (EpocRoot() ne "\\") {
       
   184     #use Carp qw/cluck/;
       
   185     #cluck "here";
       
   186     die "Error: EPOCROOT already present in \"$path\"\n" if ($path =~ /^\Q$ENV{EPOCROOT}\E/i);
       
   187   }
       
   188   $path =~ s!^[\\\/]!!; # Remove leading slash.
       
   189   return EpocRoot().$path;
       
   190 }
       
   191 
       
   192 sub RelativeToAbsolutePath {
       
   193 	my $path = shift;
       
   194 	my $iniData = shift;
       
   195 	my $pathType = shift;
       
   196 
       
   197 	if ( $pathType == SOURCE_RELATIVE ) {
       
   198 		if( $iniData->HasMappings() && SourceRoot() eq "\\" ) {
       
   199 			$path = $iniData->PerformMapOnFileName( $path );
       
   200 		}
       
   201 		else{
       
   202 			$path = PrependSourceRoot( $path );
       
   203 		}
       
   204 	}
       
   205 	else {
       
   206 		$path = PrependEpocRoot( $path );
       
   207 	}
       
   208 	return $path;
       
   209 }
       
   210 
       
   211 sub RemoveEpocRoot {
       
   212   my $path = shift;
       
   213   unless ($path =~ s/^\Q$ENV{EPOCROOT}\E//i) {
       
   214     die "Error: Path does not contain EPOCROOT - EPOCROOT:\"$ENV{EPOCROOT}\" - Path:\"$path\"\n";
       
   215   }
       
   216   return $path;
       
   217 }
       
   218 
       
   219 sub CheckWithinSourceRoot {
       
   220   my $path = shift;
       
   221   die "Error: \"$path\" is not within SRCROOT\n" unless (WithinSourceRoot($path));
       
   222 }
       
   223 
       
   224 sub WithinSourceRoot {
       
   225   my $path = shift;
       
   226   my $sourceRoot = SourceRoot();
       
   227   return ($path =~ /^\Q$sourceRoot\E/i);
       
   228 }
       
   229 
       
   230 sub PrependSourceRoot {
       
   231   my $path = shift;
       
   232   my $sourceRoot = SourceRoot();
       
   233   if ($sourceRoot ne "\\") {
       
   234     die "Error: SRCROOT already present in \"$path\"\n" if ($path =~ /^\Q$sourceRoot\E/i);
       
   235   }
       
   236 
       
   237   $path =~ s!^[\\\/]!!; # Remove leading slash.
       
   238   return SourceRoot() . $path;
       
   239 }
       
   240 
       
   241 sub RemoveSourceRoot {
       
   242   my $path = shift;
       
   243   my $sourceRoot = SourceRoot();
       
   244   unless ($path =~ s/^\Q$sourceRoot\E//i) {
       
   245     die "Error: Couldn't remove \"$sourceRoot\" from \"$path\"\n";
       
   246   }
       
   247   return $path;
       
   248 }
       
   249 
       
   250 sub MakeDir ($) {
       
   251   my $dir = shift;
       
   252   $dir =~ s/\//\\/g; # Convert all forward slashes to back slashes in path.
       
   253   unless (-e $dir) {
       
   254     if ($dir =~ /^\\\\/) {
       
   255       # This is a UNC path - make path manually because UNC isn't supported by mkpath.
       
   256       my $dirToMake = '';
       
   257       my @dirs = split /\\/, $dir;
       
   258       shift @dirs;  # Get rid of undefined dir.
       
   259       shift @dirs;  # Get rid of undefined dir.
       
   260       my $server = shift @dirs;
       
   261       my $share = shift @dirs;
       
   262       $dirToMake .= "\\\\$server\\$share";
       
   263       unless (-e $dirToMake) {
       
   264 	die "Error: Network share \"$dirToMake\" does not exist\n";
       
   265       }
       
   266       foreach my $thisDir (@dirs) {
       
   267 	$dirToMake .=  "\\$thisDir";
       
   268 	unless (-e $dirToMake) {
       
   269 	  mkdir($dirToMake,0) or die "Error: Couldn't make directory $dirToMake: $!\n";
       
   270 	}
       
   271       }
       
   272     }
       
   273     else {
       
   274       my @warnings;
       
   275       local $SIG{__WARN__} = sub {push @warnings, $!};
       
   276       
       
   277       eval {mkpath($dir)};
       
   278       if (@warnings) {
       
   279         die "Error: Couldn't make path \"$dir\": " . (join ', ', @warnings) . "\n";
       
   280       }
       
   281     }
       
   282   }
       
   283 }
       
   284 
       
   285 sub FileModifiedTime {
       
   286   my $file = shift;
       
   287   my $st = stat($file) or return 0;
       
   288   return TimeMinusDaylightSaving($st->mtime);
       
   289 }
       
   290 
       
   291 sub FileSize {
       
   292   my $file = shift;
       
   293   my $st = stat($file) or return 0;
       
   294   return $st->size;
       
   295 }
       
   296 
       
   297 sub FileModifiedTimeAndSize {
       
   298   my $file = shift;
       
   299   my $st = stat($file) or return 0;
       
   300   return (TimeMinusDaylightSaving($st->mtime), $st->size);
       
   301 }
       
   302 
       
   303 sub TimeMinusDaylightSaving {
       
   304   my $time = shift;
       
   305   (undef, undef, undef, undef, undef, undef, undef, undef, my $isDaylightSaving) = localtime;
       
   306   if ($isDaylightSaving) {
       
   307     $time -= 3600;
       
   308   }
       
   309   return $time;
       
   310 }
       
   311 
       
   312 sub TextTimeToEpochSeconds {
       
   313   my $textTime = shift;
       
   314   $textTime =~ /(\S+) (\S+) {1,2}(\d+) {1,2}(\d+):(\d+):(\d+) {1,2}(\d+)/;
       
   315   my $weekDay = $1;
       
   316   my $month = $2;
       
   317   my $monthDay = $3;
       
   318   my $hours = $4;
       
   319   my $minutes = $5;
       
   320   my $seconds = $6;
       
   321   my $year = $7 - 1900;
       
   322 
       
   323   if    ($month eq 'Jan') { $month = 0; }
       
   324   elsif ($month eq 'Feb') { $month = 1; }
       
   325   elsif ($month eq 'Mar') { $month = 2; }
       
   326   elsif ($month eq 'Apr') { $month = 3; }
       
   327   elsif ($month eq 'May') { $month = 4; }
       
   328   elsif ($month eq 'Jun') { $month = 5; }
       
   329   elsif ($month eq 'Jul') { $month = 6; }
       
   330   elsif ($month eq 'Aug') { $month = 7; }
       
   331   elsif ($month eq 'Sep') { $month = 8; }
       
   332   elsif ($month eq 'Oct') { $month = 9; }
       
   333   elsif ($month eq 'Nov') { $month = 10; }
       
   334   elsif ($month eq 'Dec') { $month = 11; }
       
   335 
       
   336   return timelocal($seconds, $minutes, $hours, $monthDay, $month, $year);
       
   337 }
       
   338 
       
   339 sub TextDateToEpochSeconds {
       
   340   my $textDate = shift;
       
   341   (my $day, my $month, my $year) = split (/\//, $textDate, 3);
       
   342   unless ($day and $month and $year) {
       
   343     die "Error: Invalid date specification: \"$textDate\"\n";
       
   344   }
       
   345   return timelocal(0, 0, 0, $day, $month - 1, $year - 1900);
       
   346 }
       
   347 
       
   348 sub SetFileReadOnly {
       
   349   my $file = shift;
       
   350   Utils::TidyFileName(\$file);
       
   351   system "attrib +r $file";
       
   352 }
       
   353 
       
   354 sub SetFileWritable {
       
   355   my $file = shift;
       
   356   Utils::TidyFileName(\$file);
       
   357   system "attrib -r $file";
       
   358 }
       
   359 
       
   360 sub SplitFileName {
       
   361   my $fileName = shift;
       
   362   my $path = '';
       
   363   my $base = '';
       
   364   my $ext = '';
       
   365 
       
   366   if ($fileName =~ /\\?([^\\]*?)(\.[^\\\.]*)?$/) {
       
   367     $base = $1;
       
   368   }
       
   369   if ($fileName =~ /^(.*\\)/) {
       
   370     $path = $1;
       
   371   }
       
   372   if ($fileName =~ /(\.[^\\\.]*)$/o) {
       
   373     $ext =  $1;
       
   374   }
       
   375 
       
   376   unless ($fileName eq "$path$base$ext") {
       
   377     my $prob = ($^V eq "v5.6.0")?" There is a known defect in Perl 5.6.0 which triggers this issue with filenames with two extensions (e.g. .exe.map). Please upgrade to Perl 5.6.1.":"";
       
   378     die "Couldn't parse filename \"$fileName\".$prob";
       
   379   }
       
   380   return ($path, $base, $ext);
       
   381 }
       
   382 
       
   383 sub SplitQuotedString {
       
   384   my $string = shift;
       
   385   my $original = $string;
       
   386   my @output = ();
       
   387   $string =~ s/^\s+//; # Remove leading delimiter if present.
       
   388   while ($string) {
       
   389     if ($string =~ s/^\"(.*?)\"//    # Match and remove next quoted string
       
   390 	or $string =~ s/^(.*?)\s+//  # or, match and remove next (but not last) unquoted string
       
   391 	or $string =~ s/^(.*)$//) {  # or, match and remove last unquoted string.
       
   392       push (@output, $1);
       
   393       $string =~ s/^\s+//; # Remove delimiter if present.
       
   394     }
       
   395     else {
       
   396       die "Error: Unable to decode string \"$original\"\n";
       
   397     }
       
   398   }
       
   399   return @output;
       
   400 }
       
   401 
       
   402 sub ConcatenateDirNames {
       
   403   my $dir1 = shift;
       
   404   my $dir2 = shift;
       
   405   TidyFileName(\$dir1);
       
   406   TidyFileName(\$dir2);
       
   407   $dir1 =~ s/([^\\]$)/$1\\/;
       
   408   $dir2 =~ s/^\\//;
       
   409   return $dir1.$dir2;
       
   410 }
       
   411 
       
   412 sub FindInPath {
       
   413   my $file = shift;
       
   414   unless (exists $ENV{PATH}) {
       
   415     die "Error: No path environment variable\n";
       
   416   }
       
   417   foreach my $dir (split (/;/, $ENV{PATH})) {
       
   418     if (-e "$dir\\$file") {
       
   419       return "$dir\\$file";
       
   420     }
       
   421   }
       
   422   die "Error: \"$file\" not found in path\n";
       
   423 }
       
   424 
       
   425 sub ReadDir {
       
   426   my $dir = shift;
       
   427   my @dir;
       
   428   opendir(DIR, $dir) or die "Error: Couldn't open directory \"$dir\": $!\n";
       
   429   while (defined(my $file = readdir(DIR))) {
       
   430     next if ($file eq '.' or $file eq '..');
       
   431     push (@dir, $file);
       
   432   }
       
   433   closedir(DIR);
       
   434   return \@dir;
       
   435 }
       
   436 
       
   437 sub ReadGlob {
       
   438   my $glob = shift;
       
   439   (my $path, my $base, my $ext) = SplitFileName($glob);
       
   440   $glob = "$base$ext";
       
   441   $glob =~ s/\./\\\./g; # Escape '.'
       
   442   $glob =~ s/\*/\.\*/g; # '*' -> '.*'
       
   443   $glob =~ s/\?/\./g;   # '?' -> '.'
       
   444   my @entries;
       
   445   foreach my $entry (@{ReadDir($path)}) {
       
   446     if ($entry =~ /$glob/) {
       
   447       push (@entries, "$path$entry");
       
   448     }
       
   449   }
       
   450   return \@entries;
       
   451 }
       
   452 
       
   453 sub ReadDirDescendingDateOrder {
       
   454   my $dir = shift;
       
   455   my $unsortedList = ReadDir($dir);
       
   456   my %mtimeHash;
       
   457   foreach my $entry (@$unsortedList) {
       
   458     my $mTime = FileModifiedTime("$dir\\$entry");
       
   459     while (exists $mtimeHash{$mTime}) {
       
   460       ++$mTime;
       
   461     }
       
   462     $mtimeHash{$mTime} = $entry;
       
   463   }
       
   464   my @dir;
       
   465   foreach my $key (sort { $b <=> $a } keys %mtimeHash) {
       
   466     push (@dir, $mtimeHash{$key});
       
   467   }
       
   468   return \@dir;
       
   469 }
       
   470 
       
   471 sub SignificantDir {
       
   472   my $dir = shift;
       
   473   my $significantSubDirs = FindSignificantSubDirs($dir);
       
   474   my $commonDir = CommonDir($significantSubDirs);
       
   475   return $commonDir;
       
   476 }
       
   477 
       
   478 
       
   479 # For a given directory, find which sub-directories contain files (rather than just other sub-directories).
       
   480 sub FindSignificantSubDirs {
       
   481   my $dir = shift;
       
   482   my $dirContents = ReadDir($dir);
       
   483   my @files;
       
   484   my @dirs;
       
   485   foreach my $thisEntry (@$dirContents) {
       
   486     if (-f "$dir\\$thisEntry") {
       
   487       push (@files, "$dir\\$thisEntry");
       
   488     }
       
   489     elsif (-d "$dir\\$thisEntry") {
       
   490       push (@dirs, "$dir\\$thisEntry");
       
   491     }
       
   492   }
       
   493   if (scalar @files > 0) {
       
   494     # This directory contains some files, so it is significant.
       
   495     return [$dir];
       
   496   }
       
   497   elsif (scalar @dirs > 0) {
       
   498     # Only sub-directories in this directory, so recurse.
       
   499     my @significantSubDirs;
       
   500     foreach my $thisDir (@dirs) {
       
   501       push (@significantSubDirs, @{FindSignificantSubDirs($thisDir)});
       
   502     }
       
   503     return \@significantSubDirs;
       
   504   }
       
   505   else {
       
   506     # Nothing of interest;
       
   507     return [];
       
   508   }
       
   509 }
       
   510 
       
   511 sub CrossCheckDirs {
       
   512   my $dir1 = shift;
       
   513   my $dir2 = shift;
       
   514   my $matched = CrossCheckDirsOneWay($dir1, $dir2);
       
   515   if ($matched) {
       
   516     $matched = CrossCheckDirsOneWay($dir2, $dir1);
       
   517   }
       
   518   return $matched;
       
   519 }
       
   520 
       
   521 sub CrossCheckDirsOneWay {
       
   522   my $dir1 = shift;
       
   523   my $dir2 = shift;
       
   524 
       
   525   my $matched = 1;
       
   526   opendir(DIR1, $dir1) or die "Error: Couldn't open directory $dir1: $!\n";
       
   527   while (defined(my $dir1File = readdir(DIR1))) {
       
   528     next if ($dir1File eq '.' or $dir1File eq '..');
       
   529     $dir1File = "$dir1\\$dir1File";
       
   530     (my $dir1MTime, my $dir1Size) = Utils::FileModifiedTimeAndSize($dir1File);
       
   531     (undef, my $base, my $extension) = Utils::SplitFileName($dir1File);
       
   532     my $dir2File = "$dir2\\$base$extension";
       
   533     if (-e $dir2File) {
       
   534       (my $dir2MTime, my $dir2Size) = Utils::FileModifiedTimeAndSize($dir2File);
       
   535       unless ($dir2MTime == $dir1MTime and $dir2Size == $dir1Size) {
       
   536 	print "\"$dir1File\" does not match modified time and size of \"$dir2File\"\n";
       
   537 	$matched = 0;
       
   538       }
       
   539     }
       
   540     else {
       
   541       print "\"$dir2File\" not found\n";
       
   542       $matched = 0;
       
   543     }
       
   544   }
       
   545   closedir(DIR1);
       
   546 
       
   547   return $matched;
       
   548 }
       
   549 
       
   550 sub ZipSourceList {
       
   551   my $zipName = shift;
       
   552   my $list = shift;
       
   553   my $verbose = shift;
       
   554   my $relativeTo = shift;
       
   555   my $iniData = shift;
       
   556 
       
   557   if (scalar(@$list) == 0) {
       
   558     if ($verbose) { print "No files to put into $zipName...\n"; }
       
   559     return;
       
   560   }
       
   561 
       
   562   my $dirName = dirname($zipName);
       
   563   unless (-d $dirName) {
       
   564     MakeDir($dirName) || die "ERROR: Unable to create directory.";
       
   565   }
       
   566 
       
   567   if ($verbose) { print "Creating $zipName...\n"; }
       
   568 
       
   569   my $zip = Archive::Zip->new() or die "ERROR: Unable to create new zip.\n";
       
   570   
       
   571   my $processedDirs = {};
       
   572 
       
   573   foreach my $file (@$list) {
       
   574     my $fileToZip = $file;
       
   575     $file = "$relativeTo"."$file";
       
   576 
       
   577     if(-f $file) {
       
   578 	  # We need to add distribution policy files for each directory
       
   579 	  my $dirname = dirname($file);
       
   580 	  
       
   581 	  if (!exists $processedDirs->{$dirname}) {
       
   582 		if (-e File::Spec->catdir($dirname, 'distribution.policy')) {
       
   583 		  push @$list, Utils::RemoveSourceRoot(File::Spec->catdir($dirname, 'distribution.policy'));
       
   584 		  $processedDirs->{$dirname} = 1;
       
   585 		}
       
   586 	  }
       
   587 	  
       
   588       if($iniData->HasMappings()){
       
   589         $fileToZip = $iniData->PerformReverseMapOnFileName($file);
       
   590         $fileToZip = Utils::RemoveSourceRoot($fileToZip);
       
   591       }
       
   592       my $member = $zip->addFile($file, $fileToZip);
       
   593       if (!defined $member) {
       
   594         die "ERROR: Cannot add file '$file' to new zip.\n";
       
   595       }
       
   596       $member->fileAttributeFormat(FA_MSDOS);
       
   597       my $attr = 0;
       
   598       Win32::File::GetAttributes($file, $attr);
       
   599       $member->{'externalFileAttributes'} |= $attr; # preserve win32 attrs
       
   600     }
       
   601     elsif(-e $file){}
       
   602     else {
       
   603       die "ERROR: $file does not exist, so can not add to $zipName.\n";
       
   604     }
       
   605   }
       
   606 
       
   607   # Warning message appears when an error code (which is a non zero) is returned.
       
   608 
       
   609   my $returnVal = $zip->writeToFileNamed($zipName);
       
   610 
       
   611   if ($returnVal) {
       
   612     die "Error: Failed to write ZIP file '$zipName'\n";
       
   613   }
       
   614 }
       
   615 
       
   616 sub ZipList {
       
   617   my $zipName = shift;
       
   618   my $list = shift;
       
   619   my $verbose = shift;
       
   620   my $noCompress = shift;
       
   621   my $relativeTo = shift;
       
   622 
       
   623   if (scalar(@$list) == 0) {
       
   624     if ($verbose) { print "No files to put into $zipName...\n"; }
       
   625     return;
       
   626   }
       
   627 
       
   628   my $dirName = dirname($zipName);
       
   629   unless (-e $dirName) {
       
   630     MakeDir($dirName);
       
   631   }
       
   632 
       
   633   if ($verbose) { print "Creating $zipName...\n"; }
       
   634 
       
   635   my $cwd = Cwd::cwd();
       
   636   if ($relativeTo) {
       
   637     chdir($relativeTo) or die "Error: Couldn't change working directory to \"$relativeTo\": $!\n";
       
   638   }
       
   639 
       
   640   my @opts = ('-@');;
       
   641   if ($verbose == 0) {
       
   642     push @opts, '-qq';
       
   643   }
       
   644   elsif ($verbose == 1) {
       
   645     push @opts, '-q';
       
   646   }
       
   647   elsif ($verbose > 1) {
       
   648     push @opts, '-v';
       
   649   }
       
   650   if ($noCompress) {
       
   651     push @opts, '-0';
       
   652   }
       
   653   
       
   654   my $missing = 0;
       
   655   my $retval;
       
   656   my $count = 0;
       
   657   do{
       
   658      open(ZIP, "| \"$FindBin::Bin\\zip\" @opts $zipName") or die "Error: Couldn't execute _zip.exe - $!\n";
       
   659 
       
   660      foreach my $file (@$list) {
       
   661        unless (-e $file) {
       
   662          $missing = $file;
       
   663          last;
       
   664        }
       
   665        $file =~ s/\[/\[\[\]/g;
       
   666        print ZIP "$file\n";
       
   667      }
       
   668      close(ZIP);
       
   669      
       
   670      $count ++;
       
   671      $retval = $? >> 8;
       
   672      if (!$missing && $retval > 1){
       
   673        print "Warning: Zipping failed with error code $retval for the $count times.\n";
       
   674      }
       
   675      
       
   676   }while(!$missing && $retval > 1 && $count < 10);
       
   677   
       
   678   if ($relativeTo) {
       
   679     chdir($cwd) or die "Error: Couldn't change working directory back to \"$cwd\": $!\n";
       
   680   }
       
   681 
       
   682   if ($missing) {
       
   683     die "Error: \"" . Utils::ConcatenateDirNames($relativeTo, $missing) . "\" does not exist\n";
       
   684   }
       
   685 
       
   686   die "Zipping failed with error code $retval\n" if $retval > 1; # 1 is warnings only
       
   687 }
       
   688 
       
   689 # So EnvDb::UnpackBinaries can be called from the test suite, use %INC to find path instead of FindBin::Bin
       
   690 sub UnzipPath {
       
   691     my $unzippath;
       
   692     my $envdbpath = $INC{'EnvDb.pm'};
       
   693     if(defined $envdbpath) {
       
   694 	# find the unzip binary
       
   695 	$envdbpath =~ s/\\/\//g;
       
   696 	$envdbpath =~ s/\/[^\/]+$//;
       
   697 	$unzippath .= $envdbpath;
       
   698     } else {
       
   699 	$unzippath .= $FindBin::Bin;
       
   700     }
       
   701     $unzippath .= "\\unzip";
       
   702     $unzippath = "\"$unzippath\"";
       
   703 
       
   704     return $unzippath;
       
   705 }
       
   706 
       
   707 sub UnzipSource {
       
   708   my $zipName = shift;
       
   709   my $destinationPath = shift;
       
   710   my $verbose = shift;
       
   711   my $overwrite = shift;
       
   712   my $iniData = shift;
       
   713   my $toValidate = shift;
       
   714   my $comp = shift;
       
   715   
       
   716   unless(defined $overwrite) {
       
   717     $overwrite = 0;
       
   718   }
       
   719 
       
   720   if($verbose) {
       
   721     print "Unpacking ";
       
   722     if($overwrite) {
       
   723       print "[in overwrite mode] ";
       
   724     }
       
   725     print "$zipName...\n";
       
   726   }
       
   727 
       
   728   my $catInArchive;
       
   729   my $changeInCat = 0;
       
   730   my $fileDirBuffer;
       
   731 
       
   732   # Sets $catInArchive to the category found on the source zip.
       
   733   if($toValidate==1 && $zipName =~ /source(.*).zip/){
       
   734     $catInArchive = $1;
       
   735   }
       
   736 
       
   737   my $zip = Archive::Zip->new($zipName);
       
   738   my @members = $zip->members();
       
   739 
       
   740   # Only print warning message if validation is not being performed, destination path is \\ and verbose is set.
       
   741 
       
   742   if($toValidate==0 && $destinationPath ne "\\" && $verbose) {
       
   743     print "Warning: Ignoring all mappings defined since either source path or SRCROOT is set as $destinationPath.\n";
       
   744   }
       
   745 
       
   746   foreach my $member (@members) {
       
   747 
       
   748     my $fileName = $member->fileName();
       
   749 
       
   750     $fileName =~ s/\//\\/g;
       
   751 
       
   752     if($fileName !~ /^\\/) {
       
   753       $fileName = "\\$fileName";
       
   754     }
       
   755 
       
   756     $iniData->CheckFileNameForMappingClash($fileName);
       
   757 
       
   758     my $newFileName;
       
   759 
       
   760     # PerfromMapOnFileName is only used for an validation and if the destintionPath is \\.
       
   761 
       
   762     if($toValidate==1 || $destinationPath eq "\\") {
       
   763       $newFileName = $iniData->PerformMapOnFileName($fileName);
       
   764     }
       
   765     else {
       
   766       $newFileName = $fileName;
       
   767     }
       
   768 
       
   769     # Check if the category has changed. Only occurs for validation.
       
   770     if(defined $catInArchive && -e $newFileName && $toValidate==1) {
       
   771       my $fileDir;
       
   772       my $classifySourceFlag = 1; # Classify source using function ClassifySourceFile only if set as 1 and not when set as 0;
       
   773 
       
   774       if(defined $fileDirBuffer) {
       
   775         ($fileDir) = SplitFileName($newFileName);
       
   776 
       
   777         if($fileDirBuffer =~ /^\Q$fileDir\E$/i){
       
   778           $classifySourceFlag = 0;
       
   779         }
       
   780       }
       
   781 
       
   782       if($classifySourceFlag){
       
   783         my ($catInEnv, $errors) = ClassifyPath($iniData, $newFileName, 0, 0, $comp); # verbose = 0 and logErrors = 0
       
   784         if($catInArchive !~ /$catInEnv/i){
       
   785           $changeInCat = 1;
       
   786         }
       
   787         ($fileDirBuffer) = SplitFileName($newFileName);
       
   788       }
       
   789     }
       
   790     ExtractFile($destinationPath, $newFileName, $member, $toValidate, $overwrite, $verbose);
       
   791   }
       
   792 
       
   793   return $changeInCat;
       
   794 }
       
   795 
       
   796 
       
   797 sub ExtractFile {
       
   798   my $destinationPath = shift;
       
   799   my $newFileName = shift;
       
   800   my $member = shift;
       
   801   my $toValidate = shift;
       
   802   my $overwrite = shift;
       
   803   my $verbose = shift;
       
   804   my $unzipRetVal = shift; # The return value from unzip if it has already been tried
       
   805   my $extractFlag = 0;
       
   806   
       
   807   my $attr;
       
   808 
       
   809   # If the file is a distribution.policy file then set the overwrite flag to true
       
   810   if ($newFileName =~ /distribution\.policy/i) {
       
   811 	$overwrite = 1;
       
   812   }
       
   813 
       
   814   # If extracting file for validation or destination path is not equal to \\ unzip file to $destinationPath.
       
   815 
       
   816   if($toValidate==1 || $destinationPath ne "\\") {
       
   817     $newFileName = File::Spec->catfile($destinationPath, $newFileName);
       
   818   }
       
   819 
       
   820   CheckPathLength($newFileName);
       
   821 
       
   822   # If the file exists need to check if file is to be overwritten.
       
   823 
       
   824   if(-f $newFileName) {
       
   825     if($overwrite) {
       
   826       if((Win32::File::GetAttributes($newFileName, $attr)) && ($attr & HIDDEN)){
       
   827       	Win32::File::SetAttributes($newFileName, ARCHIVE|NORMAL) || die "ERROR: Unable to overwrite the hidden file $newFileName: $!";
       
   828 	  }
       
   829 	  elsif(!-w $newFileName){
       
   830         chmod(0777,$newFileName) || die "ERROR: Unable to overwrite the read-only file $newFileName: $!";
       
   831       }
       
   832       $extractFlag = 1;
       
   833     }
       
   834     else {
       
   835       if($verbose) {
       
   836         print "Ignoring the file $newFileName, as this is already present.\n";
       
   837       }
       
   838     }
       
   839   }
       
   840   else{
       
   841     $extractFlag = 1;
       
   842   }
       
   843 
       
   844   if($extractFlag){
       
   845     {
       
   846       #DEF122018
       
   847       # Invalid paths will cause Archive::Zip to give an error.  We capture the error and re-format it.
       
   848       my @warnings;
       
   849       local $SIG{__WARN__} = sub {
       
   850         push @warnings, $!;
       
   851       };
       
   852       
       
   853       eval { mkpath(dirname($newFileName)) };
       
   854   
       
   855       if (@warnings) {
       
   856         die "Error: Unable to make the directory \"$newFileName\": " . (join "\n", @warnings) . "\n";
       
   857       }
       
   858     }
       
   859 
       
   860     # A non-zero is returned if there is a problem with extractToFileNamed().
       
   861     if($member->extractToFileNamed($newFileName)) {
       
   862       warn "ERROR: Failed to extract $newFileName.\n";
       
   863       CheckUnzipError($unzipRetVal);
       
   864       die;
       
   865     }
       
   866     utime($member->lastModTime(), $member->lastModTime(), $newFileName);
       
   867     my $newattr = $member->externalFileAttributes() & 0xFFFF;
       
   868     Win32::File::SetAttributes($newFileName, $newattr); # reapply win32 attrs
       
   869   }
       
   870 }
       
   871 
       
   872 sub Unzip {
       
   873   my $zipName = shift;
       
   874   my $destinationPath = shift;
       
   875   my $verbose = shift;
       
   876   my $overwrite = shift || '';
       
   877   
       
   878   $overwrite = '-o' if $overwrite eq '1'; # Some callers to this method may send a boolean value rather then an unzip option
       
   879   
       
   880   if ($verbose) {
       
   881     print "Unpacking ";
       
   882     if ($overwrite) {
       
   883       print "[in overwrite mode] ";
       
   884     }
       
   885     print "$zipName...\n";
       
   886   }
       
   887 
       
   888   my $v;
       
   889   if ($verbose == 0) {
       
   890     $v = "-qq";
       
   891   }
       
   892   elsif ($verbose == 1) {
       
   893     $v = "-q";
       
   894   }
       
   895   if ($verbose > 1) {
       
   896     $v = "";
       
   897   }
       
   898 
       
   899   # Here we check that the files in the zip file are not so long they can not be unpacked
       
   900   my $zip = Archive::Zip->new($zipName);
       
   901   my @members = $zip->members();
       
   902 
       
   903   foreach my $member (@members) {
       
   904     my $fileName = File::Spec->catdir('\.', $destinationPath, $member->fileName());
       
   905     CheckPathLength($fileName);
       
   906   }
       
   907 
       
   908   MakeDir($destinationPath);
       
   909   
       
   910   # prepare command
       
   911   my $cmd = "unzip $overwrite $v $zipName -d $destinationPath 2>&1";
       
   912   
       
   913   # run $cmd, fetching io handles for it
       
   914   my $pid = open2(\*IN, \*OUT, $cmd);
       
   915   
       
   916   # one character per read
       
   917   local $/ = \1;
       
   918   
       
   919   # command output line buffer
       
   920   my $line = '';
       
   921   
       
   922   while (<IN>) {
       
   923     # accumulate line data
       
   924     $line .= $_;
       
   925     
       
   926     # look for expected output
       
   927     if ($line =~ /^(?:(replace).*\[r\]ename|new name): $/) {
       
   928       # dump line buffer so user can read prompt
       
   929       print $line and $line = '';
       
   930       
       
   931       # read whole lines for user response
       
   932       local $/ = "\n";
       
   933       
       
   934       # read user's response
       
   935       chomp(my $response = <STDIN>);
       
   936       
       
   937       if (defined $1) { # matched "replace"
       
   938 	# set overwrite mode if the user chooses to replace [A]ll
       
   939 	$overwrite = '-o' if $response =~ /^A/;
       
   940 	
       
   941 	# set no-overwrite mode if the user chooses to replace [N]one
       
   942 	$overwrite = '-n' if $response =~ /^N/;
       
   943       }
       
   944       
       
   945       # convey response to the command
       
   946       print OUT "$response\n";
       
   947     }
       
   948     
       
   949     # dump line buffer at EOL
       
   950     print $line and $line = '' if $line =~ /\n$/;
       
   951   }
       
   952   
       
   953   close (OUT);
       
   954   close (IN);
       
   955   
       
   956   waitpid($pid,0);
       
   957 
       
   958   CheckUnzipError($?);  
       
   959   
       
   960   return $overwrite;
       
   961 }
       
   962 
       
   963 sub CheckUnzipError {
       
   964   my $retval = shift;
       
   965   $retval = $retval >> 8;
       
   966   # Error numbers found in unzip (Info-ZIP) source code: there doesn't
       
   967   # seem to be a manual. Common with return values from PKZIP so
       
   968   # unlikely to change
       
   969   # Error 1 is just a warning, so we only care about those > 1
       
   970   die "Unzip reported an out-of-memory error ($retval)\n" if ($retval>3 && $retval<9);
       
   971   die "Unzip reported a problem with the zip file ($retval)\n" if ($retval>1 && $retval<4);
       
   972   die "Unzip reported disk full (though this might mean it's trying to overwrite files in use) ($retval)\n" if ($retval==50);
       
   973   die "Unzip reported error code ($retval)" if ($retval>1 && $retval<52);
       
   974   warn "Warning: Unzip returned an unexpected error code ($retval)\n" if ($retval >51)
       
   975 }
       
   976 
       
   977 sub UnzipSingleFile {
       
   978   my $zipName = shift;
       
   979   my $file = shift;
       
   980   my $destinationPath = shift;
       
   981   my $verbose = shift;
       
   982   my $overwrite = shift;
       
   983   my $comp = shift;
       
   984   
       
   985   unless (defined $overwrite) {
       
   986     $overwrite = 0;
       
   987   }
       
   988 
       
   989   if ($verbose) {
       
   990     print "Unpacking ";
       
   991     if ($overwrite) {
       
   992       print "[in overwrite mode] ";
       
   993     }
       
   994     print "\"$file\" from \"$zipName\"...\n";
       
   995   }
       
   996 
       
   997 
       
   998   my $v;
       
   999   if ($verbose == 0) {
       
  1000     $v = "-qq";
       
  1001   }
       
  1002   elsif ($verbose == 1) {
       
  1003     $v = "-q";
       
  1004   }
       
  1005   if ($verbose > 1) {
       
  1006     $v = "";
       
  1007   }
       
  1008 
       
  1009   my $o = "";
       
  1010   if ($overwrite) {
       
  1011     $o = "-o";
       
  1012   }
       
  1013 
       
  1014   MakeDir($destinationPath);
       
  1015   my $retval = system(UnzipPath()." $o $v \"$zipName\" \"$file\" -d \"$destinationPath\"");
       
  1016 
       
  1017   unless (-e ConcatenateDirNames($destinationPath, $file)) {
       
  1018     #Fallback to using archive::zip
       
  1019     print "Unable to extract $file using unzip. Trying alternative extraction method...\n";
       
  1020     
       
  1021     my $zip = GetArchiveZipObject($zipName, $comp);
       
  1022 
       
  1023     my $fileWithForwardSlashes = $file;
       
  1024     $fileWithForwardSlashes =~ s/\\/\//g; # Archive::Zip stores file names with forward slashes
       
  1025   
       
  1026     my $member = $zip->memberNamed($fileWithForwardSlashes);
       
  1027     
       
  1028     if (!defined $member) {
       
  1029       # Archive::Zip is also case-sensitive.  If it doesn't find the required file we compile the filename into
       
  1030       # a case insensitive regex and try again.  This takes longer than just calling memberNamed.
       
  1031       my $fileNameRegEx = qr/$fileWithForwardSlashes/i;
       
  1032       ($member) = $zip->membersMatching($fileNameRegEx);
       
  1033       
       
  1034       # If it still can't find the file then it doesn't exist in the zip file
       
  1035       if (!defined $member) {
       
  1036         warn "Unable to find $file in $zipName\n";
       
  1037         CheckUnzipError($retval);
       
  1038         die;
       
  1039       }
       
  1040     }
       
  1041   
       
  1042     ExtractFile($destinationPath, $file, $member, 0, $overwrite, $verbose, $retval);
       
  1043     print "Successfully extracted $file\n";
       
  1044   }
       
  1045 }
       
  1046 
       
  1047 sub ListZip {
       
  1048   my $zipName = shift;
       
  1049   my @list;
       
  1050 
       
  1051   my $zipper = Archive::Zip->new();
       
  1052   unless ($zipper->read($zipName) == AZ_OK) {
       
  1053     die "Error: problem reading \"$zipName\"\n";
       
  1054   }
       
  1055 
       
  1056   my @members = $zipper->members();
       
  1057   foreach my $thisMember (@members) {
       
  1058     my $file = $thisMember->fileName();
       
  1059     TidyFileName(\$file);
       
  1060     unless ($file =~ /^\\/) {
       
  1061       $file = "\\$file";
       
  1062     }
       
  1063     push (@list, $file);
       
  1064   }
       
  1065 
       
  1066   return \@list;
       
  1067 }
       
  1068 
       
  1069 sub CheckZipFileContentsNotPresent {
       
  1070   my $zipName = shift;
       
  1071   my $where = shift;
       
  1072   my $iniData = shift;
       
  1073   my $checkFailed = 0;
       
  1074   foreach my $thisFile (@{ListZip($zipName)}) {
       
  1075     if ($thisFile =~ /\\$/) {
       
  1076       next;
       
  1077     }
       
  1078     my $fullName = ConcatenateDirNames($where, $thisFile);
       
  1079 
       
  1080     if($iniData->HasMappings()){
       
  1081       $fullName = $iniData->PerformMapOnFileName($fullName);
       
  1082     }
       
  1083 
       
  1084 	if ($fullName =~ /distribution\.policy$/i) {
       
  1085 	  return $checkFailed;
       
  1086 	}
       
  1087 
       
  1088     if (-e $fullName) {
       
  1089       print "Error: \"$fullName\" would be overwritten by unpacking \"$zipName\"\n";
       
  1090       $checkFailed = 1;
       
  1091     }
       
  1092   }
       
  1093   return $checkFailed;
       
  1094 }
       
  1095 
       
  1096 sub SignificantZipDir {
       
  1097   my $zipName = shift;
       
  1098 
       
  1099   my $zipper = Archive::Zip->new();
       
  1100   unless ($zipper->read($zipName) == AZ_OK) {
       
  1101     die "Error: problem reading \"$zipName\"\n";
       
  1102   }
       
  1103 
       
  1104   my %dirs;
       
  1105   my @members = $zipper->members();
       
  1106   foreach my $thisMember (@members) {
       
  1107     my $file = $thisMember->fileName();
       
  1108     my $dir = lc(dirname($file));
       
  1109     TidyFileName(\$dir);
       
  1110     unless (exists $dirs{$dir}) {
       
  1111       $dirs{$dir} = 1;
       
  1112     }
       
  1113   }
       
  1114 
       
  1115   my @dirs = sort keys %dirs;
       
  1116   return CommonDir(\@dirs);
       
  1117 }
       
  1118 
       
  1119 # Given an array of directories, find the common directory they share.
       
  1120 sub CommonDir {
       
  1121   my $dirs = shift;
       
  1122   my $disectedDirs = DisectDirs($dirs);
       
  1123   my $numDirs = scalar @$dirs;
       
  1124   if ($numDirs == 1) {
       
  1125 	# if there is only one signifigant directory then this has to be
       
  1126 	# the common one so return it.
       
  1127 	return $dirs->[0];
       
  1128   }
       
  1129   my $commonDir = '';
       
  1130   my $dirLevel = 0;
       
  1131   while (1) {
       
  1132     my $toMatch;
       
  1133     my $allMatch = 0;
       
  1134     for (my $ii = 0; $ii < $numDirs; ++$ii, ++$allMatch) {
       
  1135       if ($dirLevel >= scalar @{$disectedDirs->[$ii]}) {
       
  1136         $allMatch = 0;
       
  1137         last;
       
  1138       }
       
  1139       if (not $toMatch) {
       
  1140         $toMatch = $disectedDirs->[0][$dirLevel];
       
  1141       }
       
  1142       elsif ($disectedDirs->[$ii][$dirLevel] ne $toMatch) {
       
  1143         $allMatch = 0;
       
  1144         last;
       
  1145       }
       
  1146     }
       
  1147     if ($allMatch) {
       
  1148       if ($toMatch =~ /^[a-zA-Z]:/) {
       
  1149         $commonDir .= $toMatch;
       
  1150       }
       
  1151       else {
       
  1152         $commonDir .= "\\$toMatch";
       
  1153       }
       
  1154       ++$dirLevel;
       
  1155     }
       
  1156     else {
       
  1157       last;
       
  1158     }
       
  1159   }
       
  1160   return $commonDir;
       
  1161 }
       
  1162 
       
  1163 sub DisectDirs {
       
  1164   my $dirs = shift;
       
  1165   my $disectedDirs;
       
  1166   my $numDirs = scalar @$dirs;
       
  1167   for (my $ii = 0; $ii < $numDirs; ++$ii) {
       
  1168     my $thisDir = $dirs->[$ii];
       
  1169     $thisDir =~ s/^\\//; # Remove leading backslash to avoid first array entry being empty.
       
  1170     my @thisDisectedDir = split(/\\/, $thisDir);
       
  1171     push (@$disectedDirs, \@thisDisectedDir);
       
  1172   }
       
  1173   return $disectedDirs;
       
  1174 }
       
  1175 
       
  1176 sub CheckExists {
       
  1177   my $file = shift;
       
  1178   unless (-e $file) {
       
  1179     die "Error: $file does not exist\n";
       
  1180   }
       
  1181 }
       
  1182 
       
  1183 sub CheckIsFile {
       
  1184   my $file = shift;
       
  1185   unless (-f $file) {
       
  1186     die "Error: $file is not a file\n";
       
  1187   }
       
  1188 }
       
  1189 
       
  1190 sub CurrentDriveLetter {
       
  1191   my $drive = Win32::GetCwd();
       
  1192   $drive =~ s/^(\D:).*/$1/;
       
  1193   return $drive;
       
  1194 }
       
  1195 
       
  1196 sub InitialiseTempDir {
       
  1197   my $iniData = shift;
       
  1198   
       
  1199   if (defined $iniData->TempDir) {
       
  1200     $tempDir = mkdtemp($iniData->TempDir().'\_XXXX');
       
  1201   }
       
  1202   else {
       
  1203     my $fstempdir = File::Spec->tmpdir;
       
  1204     $fstempdir =~ s/[\\\/]$//;
       
  1205     $tempDir = mkdtemp($fstempdir.'\_XXXX');
       
  1206   }
       
  1207   
       
  1208   die "Error: Problem creating temporary directory \"$tempDir\": $!\n" if (!$tempDir);
       
  1209 }
       
  1210 
       
  1211 sub RemoveTempDir {
       
  1212   die unless $tempDir;
       
  1213   rmtree $tempDir or die "Error: Problem emptying temporary directory \"$tempDir\": $!\n";
       
  1214   undef $tempDir;
       
  1215 }
       
  1216 
       
  1217 sub TempDir {
       
  1218   die unless $tempDir;
       
  1219   return $tempDir;
       
  1220 }
       
  1221 
       
  1222 sub ToolsVersion {
       
  1223   my $relPath = shift;
       
  1224   unless (defined $relPath) {
       
  1225     $relPath = '';
       
  1226   }
       
  1227   my $file = "$FindBin::Bin/$relPath" . 'version.txt';
       
  1228   open (VER, $file) or die "Error: Couldn't open \"$file\": $!\n";
       
  1229   my $ver = <VER>;
       
  1230   chomp $ver;
       
  1231   close (VER);
       
  1232   return $ver;
       
  1233 }
       
  1234 
       
  1235 sub QueryPassword {
       
  1236   unless ($console) {
       
  1237     $console = Win32::Console->new(STD_INPUT_HANDLE);
       
  1238   }
       
  1239   my $origMode = $console->Mode();
       
  1240   $console->Mode(ENABLE_PROCESSED_INPUT);
       
  1241   my $pw = '';
       
  1242   my $notFinished = 1;
       
  1243   while ($notFinished) {
       
  1244     my $char = $console->InputChar();
       
  1245     if ($char and $char eq "\r") {
       
  1246       print "\n";
       
  1247       $notFinished = 0;
       
  1248     }
       
  1249     elsif ($char and $char eq "\b") {
       
  1250       if ($pw) {
       
  1251 	$pw =~ s/.$//;
       
  1252 	print "\b \b";
       
  1253       }
       
  1254     }
       
  1255     else {
       
  1256       $pw .= $char;
       
  1257       print '*';
       
  1258     }
       
  1259   }
       
  1260   $console->Mode($origMode);
       
  1261   return $pw;
       
  1262 }
       
  1263 
       
  1264 sub PrintDeathMessage {
       
  1265   my $exitCode = shift;
       
  1266   my $msg = shift;
       
  1267   my $relPath = shift;
       
  1268   
       
  1269   my $ver = ToolsVersion($relPath);
       
  1270   print "$msg\nLPD Release Tools version $ver\n";
       
  1271   exit $exitCode;
       
  1272 }
       
  1273 
       
  1274 sub PrintTable {
       
  1275   my $data = shift;
       
  1276   my $doHeading = shift;
       
  1277 
       
  1278   require IniData;
       
  1279   my $iniData = New IniData;
       
  1280   my $tf = $iniData->TableFormatter;
       
  1281   $tf->PrintTable($data, $doHeading);
       
  1282 }
       
  1283 
       
  1284 sub QueryUnsupportedTool {
       
  1285   my $warning = shift; # optional
       
  1286   my $reallyrun = shift; # optional - value of a '-f' (force) flag or similar
       
  1287   return if $reallyrun;
       
  1288 
       
  1289   $warning ||= <<GUILTY;
       
  1290 Warning: this tool is unsupported and experimental. You may use it, but there
       
  1291 may be defects. Use at your own risk, and if you find a problem, please report
       
  1292 it to us. Do you want to continue? (y/n)
       
  1293 GUILTY
       
  1294 
       
  1295   print $warning."\n";
       
  1296   my $resp = <STDIN>;
       
  1297   chomp $resp;
       
  1298   die "Cancelled. You typed \"$resp\".\n" unless $resp =~ m/^y/i;
       
  1299 }
       
  1300 
       
  1301 sub CompareVers($$) {
       
  1302   my ($version1, $version2) = @_;
       
  1303 
       
  1304   # New format or old format?
       
  1305   my $style1 = (($version1 =~ /^(\d+\.\d+)/) and ($1 >= 2.8));
       
  1306   my $style2 = (($version2 =~ /^(\d+\.\d+)/) and ($1 >= 2.8));
       
  1307 
       
  1308   # Validate version strings
       
  1309   if ($style1 == 1) {
       
  1310     $version1 = ValidateNewFormatVersion($version1);
       
  1311   } else {
       
  1312     ValidateOldFormatVersion($version1);
       
  1313   }
       
  1314 
       
  1315   if ($style2 == 1) {
       
  1316     $version2 = ValidateNewFormatVersion($version2);
       
  1317   } else {
       
  1318     ValidateOldFormatVersion($version2);
       
  1319   }
       
  1320 
       
  1321   # Compare version strings
       
  1322   if ($style1 != $style2) {
       
  1323     return $style1-$style2; # New format always beats old format
       
  1324   } else  {
       
  1325     return CompareVerFragment($version1, $version2);
       
  1326   }
       
  1327 }
       
  1328 
       
  1329 sub ValidateOldFormatVersion($) {
       
  1330   my ($version) = @_;
       
  1331 
       
  1332   if (($version !~ /^\d[\.\d]*$/) or ($version !~ /\d$/)) {
       
  1333     die "Error: $version is not a valid version number\n";
       
  1334   }
       
  1335   
       
  1336   return $version;
       
  1337 }
       
  1338 
       
  1339 sub ValidateNewFormatVersion($) {
       
  1340   my ($version) = @_;
       
  1341   
       
  1342   my $ver; 
       
  1343   if ($version !~ /^(\d+\.\d+)\.(.+)$/) {
       
  1344     die "Error: $version is not a valid version number; patch number must be given\n";
       
  1345   } else {
       
  1346     $ver = $1;
       
  1347     my $patch = $2;
       
  1348     
       
  1349     if (($patch =~ /^\d*$/) and ($patch > 9999)) {
       
  1350       die "Error: Version number $version has an invalid patch number\n";
       
  1351       
       
  1352     } elsif ($patch =~ /\./) {
       
  1353       die "Error: Version number $version has an invalid patch number\n";
       
  1354       
       
  1355     }
       
  1356   }
       
  1357   
       
  1358   return $ver; # Return significant version number only
       
  1359 }
       
  1360 
       
  1361 sub CompareVerFragment($$) {
       
  1362   # 1.xxx = 01.xxx, while .1.xxx = .10.xxx
       
  1363   my ($frag1, $frag2) = @_;
       
  1364 
       
  1365   my $isfrag1 = defined($frag1) ? 1 : 0;
       
  1366   my $isfrag2 = defined($frag2) ? 1 : 0;
       
  1367 
       
  1368   my $compare;
       
  1369 
       
  1370   if ($isfrag1 and $isfrag2) {
       
  1371     my ($rest1, $rest2);
       
  1372 
       
  1373     $frag1=~s/^(\.?\d+)(\..*)$/$1/ and $rest1=$2; # If pattern fails, $rest1 is undef
       
  1374     $frag2=~s/^(\.?\d+)(\..*)$/$1/ and $rest2=$2;
       
  1375 
       
  1376     $compare = $frag1-$frag2; # Numeric comparison: .1=.10 but .1>.01
       
  1377 
       
  1378     if ($compare == 0) {
       
  1379       $compare = &CompareVerFragment($rest1, $rest2);
       
  1380     }
       
  1381   }
       
  1382   else {
       
  1383     $compare = $isfrag1-$isfrag2;
       
  1384   }
       
  1385   return $compare;
       
  1386 }
       
  1387 
       
  1388 sub ClassifyPath {
       
  1389   my $iniData = shift;
       
  1390   my $path = shift;
       
  1391   if (!WithinSourceRoot($path)){
       
  1392    $path = Utils::PrependSourceRoot($path);
       
  1393   }
       
  1394   my $verbose = shift;
       
  1395   my $logDistributionPolicyErrors = shift; # 0 = no, 1 = yes
       
  1396   my $component = shift;
       
  1397 
       
  1398   if ($verbose) {
       
  1399     print "Finding category of source file $path...\n";
       
  1400   }
       
  1401   
       
  1402   Utils::TidyFileName(\$path);
       
  1403   
       
  1404   my $cat = '';
       
  1405   my $errors = [];
       
  1406   
       
  1407   my $symbianIPR = Symbian::IPR->instance($iniData->UseDistributionPolicyFilesFirst(), $iniData->DisallowUnclassifiedSource(), 'MRPDATA', $verbose, $logDistributionPolicyErrors);
       
  1408   $symbianIPR->PrepareInformationForComponent($component);
       
  1409   eval {($cat, $errors) = $symbianIPR->Category($path)};
       
  1410   
       
  1411   if ($@) {
       
  1412     print $@;
       
  1413   }
       
  1414 
       
  1415   if (uc $cat eq "X" and $iniData->DisallowUnclassifiedSource()) {
       
  1416     die "Error: \"$path\" contains unclassified source code\n";
       
  1417   }
       
  1418 
       
  1419   if ($verbose) {
       
  1420     print "ClassifySource for $path: returning cat $cat";
       
  1421     if (scalar (@$errors) > 0) {
       
  1422       print " and errors @$errors";
       
  1423     }
       
  1424     print "\n";
       
  1425   }
       
  1426   
       
  1427   return uc($cat), $errors; # copy of $errors
       
  1428 }
       
  1429 
       
  1430 sub ClassifyDir {
       
  1431   return ClassifyPath(IniData->New(), @_);  
       
  1432 }
       
  1433 
       
  1434 sub ClassifySourceFile {
       
  1435   return ClassifyPath(@_);
       
  1436 }
       
  1437 
       
  1438 sub CheckForUnicodeCharacters {
       
  1439   my $filename = shift;
       
  1440   
       
  1441   # Unicode characters in filenames are converted to ?'s 
       
  1442   $filename =~ /\?/ ? return 1 : return 0; 
       
  1443 }
       
  1444 
       
  1445 sub CheckIllegalVolume {
       
  1446   my $iniData = shift;
       
  1447   
       
  1448   my ($volume) = File::Spec->splitpath(cwd());
       
  1449   $volume =~ s/://; # remove any : from $volume
       
  1450   
       
  1451   # Check that the environment is not on an illegal volume - INC105548
       
  1452   if (grep /$volume/i, $iniData->IllegalWorkspaceVolumes()) {
       
  1453     die "Error: Development is not permitted on an excluded volume: " . (join ',', $iniData->IllegalWorkspaceVolumes()) . "\n";
       
  1454   }
       
  1455 }
       
  1456 sub ListAllFiles {
       
  1457   my $directory = shift;
       
  1458   my $list = shift;
       
  1459   find(sub { push @{$list}, $File::Find::name if (! -d);}, $directory);
       
  1460 }
       
  1461 
       
  1462 sub CheckPathLength {
       
  1463   my $path = shift;
       
  1464 
       
  1465   if (length($path) > MAX_OS_PATH_LENGTH) {
       
  1466     my $extraMessage = '';
       
  1467     
       
  1468     if ($tempDir && $path =~ /^\Q$tempDir\E/) {
       
  1469       $extraMessage = "\nThe folder you are extracting to is under your temp folder \"$tempDir\". Try reducing the size of your temp folder by using the temp_dir <folder> keyword in your reltools.ini file.";
       
  1470     }
       
  1471     
       
  1472     die "Error: The path \"$path\" contains too many characters and can not be extracted.$extraMessage\n"; 
       
  1473   }  
       
  1474 }
       
  1475 
       
  1476 sub GetArchiveZipObject {
       
  1477   my $zipName = shift;
       
  1478   my $comp = lc(shift);
       
  1479   
       
  1480   my $zip;
       
  1481   
       
  1482   if ($comp) { # If $comp is defined then we need to cache Archive::Zip objects by component
       
  1483     if (exists $zipFileCache{$comp}) {
       
  1484       if (defined $zipFileCache{$comp}->{$zipName}) {
       
  1485         $zip = $zipFileCache{$comp}->{$zipName};
       
  1486       }
       
  1487       else {
       
  1488 	$zip = Archive::Zip->new($zipName);
       
  1489 	$zipFileCache{$comp}->{$zipName} = $zip;
       
  1490       }
       
  1491     }
       
  1492     else { # New component
       
  1493       %zipFileCache = (); # Delete the cache as it is no longer required
       
  1494       $zip = Archive::Zip->new($zipName);
       
  1495       $zipFileCache{$comp}->{$zipName} = $zip;
       
  1496     }
       
  1497   }
       
  1498   else {
       
  1499     $zip = Archive::Zip->new($zipName);
       
  1500   }
       
  1501   
       
  1502   return $zip;
       
  1503 }
       
  1504 
       
  1505 sub CheckDirectoryName {
       
  1506   my $dirName = shift;
       
  1507   
       
  1508   my @dirParts = split /[\\\/]/, $dirName;
       
  1509   
       
  1510   foreach my $dirPart (@dirParts) {
       
  1511     next if ($dirPart =~ /^\w:$/ && $dirName =~ /^$dirPart/);
       
  1512     
       
  1513     if ($dirPart =~ /[:\?\*\"\<\>\|]/) {
       
  1514       die "Error: The directory \"$dirName\" can not contain the characters ? * : \" < > or |\n";
       
  1515     }
       
  1516   }
       
  1517 }
       
  1518 
       
  1519 
       
  1520 1;
       
  1521 
       
  1522 __END__
       
  1523 
       
  1524 =head1 NAME
       
  1525 
       
  1526 Utils.pm - General utility functions.
       
  1527 
       
  1528 =head1 INTERFACE
       
  1529 
       
  1530 =head2 StripWhiteSpace
       
  1531 
       
  1532 Expects a reference to a string. Strips white space off either end of the string.
       
  1533 
       
  1534 =head2 TidyFileName
       
  1535 
       
  1536 Expects a reference to a string. Changes any forward slashes to back slashes. Also changes "\.\" and "\\" to "\" (preserving the "\\" at the start of UNC paths). This is necessary to allow effective comparison of file names.
       
  1537 
       
  1538 =head2 AbsoluteFileName
       
  1539 
       
  1540 Expects a reference to a string containing a file name. Modifies the string to contain the corresponding absolute path version of the file name (without the drive letter). For example, the string ".\test.txt" would generate a return value of "\mydir\test.txt", assuming the current directory is "\mydir".
       
  1541 
       
  1542 =head2 AbsolutePath
       
  1543 
       
  1544 Expects a reference to a string containing a path. Modifies the string to contain the corresponding absolute path (without the drive letter).
       
  1545 
       
  1546 =head2 FileModifiedTime
       
  1547 
       
  1548 Expects a filename, returns C<stat>'s last modified time. If there's a problem getting the stats for the file, an C<mtime> of zero is returned.
       
  1549 
       
  1550 =head2 FileSize
       
  1551 
       
  1552 Expects a filename, returns the file's size.
       
  1553 
       
  1554 =head2 FileModifiedTimeAndSize
       
  1555 
       
  1556 Expects a filename. Returns a list containing the file's last modified time and size.
       
  1557 
       
  1558 =head2 SetFileReadOnly
       
  1559 
       
  1560 Expects to be passed a file name. Sets the file's read only flag.
       
  1561 
       
  1562 =head2 SetFileWritable
       
  1563 
       
  1564 Expects to be passed a file name. Clear the file's read only flag.
       
  1565 
       
  1566 =head2 SplitFileName
       
  1567 
       
  1568 Expects to be passed a file name. Splits this into path, base and extension variables (returned as a list in that order). For example the file name C<\mypath\mybase.myextension> would be split into C<mypath>, C<mybase> and C<.myextension>. An empty string will be returned for segments that don't exist.
       
  1569 
       
  1570 =head2 SplitQuotedString
       
  1571 
       
  1572 Expects to be passed a string. Splits this string on whitespace, ignoring whitespace between quote (C<">) characters. Returns an array containing the split values.
       
  1573 
       
  1574 =head2 ConcatenateDirNames
       
  1575 
       
  1576 Expects to be passed a pair of directory names. Returns a string that contains the two directory names joined together. Ensures that there is one (and only one) back slash character between the two directory names.
       
  1577 
       
  1578 =head2 MakeDir
       
  1579 
       
  1580 Expects to be passed a directory name. Makes all the directories specified. Can copy with UNC and DOS style drive letter paths.
       
  1581 
       
  1582 =head2 ReadDir
       
  1583 
       
  1584 Expects to be passed a directory name. Returns an array of file names found within the specified directory.
       
  1585 
       
  1586 =head2 ReadGlob
       
  1587 
       
  1588 Expects to be passed a scalar containing a file name. The file name path may relative or absolute. The file specification may contains C<*> and/or C<?> characters. Returns a reference to an array of file names that match the file specification.
       
  1589 
       
  1590 =head2 SignificantDir
       
  1591 
       
  1592 Expects to be passed a directory name. Returns the name of the deepest sub-directory that contains all files.
       
  1593 
       
  1594 =head2 CrossCheckDirs
       
  1595 
       
  1596 Expects to be passed a pair of directory names. Checks that the contents of the directories are identical as regards file names, their last modified times and their size. Returns false if any checks fail, otherwise true.
       
  1597 
       
  1598 =head2 ZipList
       
  1599 
       
  1600 Expects to be passed a zip filename and a reference to a list of file to be put into the zip file. The zip filename may contain a full path - missing directories will be created if necessary.
       
  1601 
       
  1602 =head2 Unzip
       
  1603 
       
  1604 Expects to be passed a zip filename, a destination path, a verbosity level, and optionally a flag indicating whether exisitng files should be overwritten or not. Unpacks the named zip file in the specified directory.
       
  1605 
       
  1606 =head2 UnzipSingleFile
       
  1607 
       
  1608 Expects to be passed a zip filename, a filename to unpack, a destination path, a verbosity level, and optionally a flag indicating whether existing files should be overwritten or not. Unpacks only the specified file from the zip file into the specified directory.
       
  1609 
       
  1610 =head2 ListZip
       
  1611 
       
  1612 Expects to be passed a zip filename. Returns a reference to a list containing the names of the files contained in the zip file.
       
  1613 
       
  1614 =head2 CheckZipFileContentsNotPresent
       
  1615 
       
  1616 Expects to be passed a zip filename and a destination path. Prints errors to C<STDOUT> for each file contained within the zip that would overwrite an existing file in the destination path. Returns true if any errors were printed, false otherwise.
       
  1617 
       
  1618 =head2 SignificantZipDir
       
  1619 
       
  1620 Expects to be passed a zip filename. Returns the name of the deepest sub-directory that contains all the files within the zip.
       
  1621 
       
  1622 =head2 CheckExists
       
  1623 
       
  1624 Expects to be passed a filename. Dies if the file is not present.
       
  1625 
       
  1626 =head2 CheckIsFile
       
  1627 
       
  1628 Expects to be passed a filename. Dies if the filename isn't a file.
       
  1629 
       
  1630 =head2 CurrentDriveLetter
       
  1631 
       
  1632 Returns a string containing the current drive letter and a colon.
       
  1633 
       
  1634 =head2 InitialiseTempDir
       
  1635 
       
  1636 Creates an empty temporary directory.
       
  1637 
       
  1638 =head2 RemoveTempDir
       
  1639 
       
  1640 Removes the temporary directory (recursively removing any other directories contained within it).
       
  1641 
       
  1642 =head2 ToolsVersion
       
  1643 
       
  1644 Returns the current version of the release tools. This is read from the file F<version.txt> in the directory the release tools are running from.
       
  1645 
       
  1646 =head2 QueryPassword
       
  1647 
       
  1648 Displays the user's input as '*' characters. Returns the password.
       
  1649 
       
  1650 =head2 PrintDeathMessage
       
  1651 
       
  1652 Expects to be passed a message. Dies with the message plus details of the current tools version.
       
  1653 
       
  1654 =head2 PrintTable
       
  1655 
       
  1656 Expects to be passed a reference to a two dimentional array (a reference to an array (the rows) of referrences to arrays (the columns)). May optionally be passed a flag requesting that a line break be put between the first and second rows (useful to emphasise headings). Prints the data in a left justified table.
       
  1657 
       
  1658 =head2 TextTimeToEpochSeconds
       
  1659 
       
  1660 Convert a human readable time/date string in the format generated by C<scalar localtime> into the equivalent number of epoch seconds.
       
  1661 
       
  1662 =head2 TextDateToEpochSeconds
       
  1663 
       
  1664 Convert a date string in the format C<dd/mm/yyyy> into the equivalent number of epoc seconds.
       
  1665 
       
  1666 =head2 QueryUnsupportedTool
       
  1667 
       
  1668 Warns the user that the tool is unsupported, and asks whether they wish to continue. Takes two parameters, both optional. The first is the text to display (instead of a default). It must finish with an instruction asking the user to type y/n. The second is an optional flag for a 'force' parameter.
       
  1669 
       
  1670 =head2 CompareVers
       
  1671 
       
  1672 Takes two version numbers in the form of a dot separated list of numbers (e.g 2.05.502) and compares them, returning 0 if they are equivalent, more than 0 if the first version given is greater or less than 0 if the first version is lesser. Dies if versions are not of the required format.
       
  1673 
       
  1674 =head2 CompareVerFragment
       
  1675 
       
  1676 The main code behind C<CompareVers()>. This is not meant to be called directly because it assumes version numbers only consist of numbers and dots.
       
  1677 
       
  1678 =head2 ZipSourceList
       
  1679 
       
  1680 Expects to be passed a zip filename and a reference to a list of source files to be put into the zip file.
       
  1681 
       
  1682 =head2 UnzipSource
       
  1683 
       
  1684 Expects to be passed a source zip filename, a destination path, a verbosity level, a flag indicating whether existing files should be overwritten or not, an inidata and a flag indicating whether this operation is for a validation or not. Unpacks the named source zip file to the specified directory. If for validation, a check for change in category occurs. Returns a change in category flag, when flag is 1 a change in category has been found.
       
  1685 
       
  1686 =head2 ExtractFile
       
  1687 
       
  1688 Expects to be passed a destination path, a file name, a member and a flag indicating whether existing files should be overwritten or not. Is used to extract a file from a zip file to a specified location.
       
  1689 
       
  1690 =head2 ClassifySourceFile
       
  1691 
       
  1692 Expects to be passed an iniData, a source filename, a verbosity level, and log error flag. Is used to calculate the category of the source file passed. Returns the category calculated.
       
  1693 
       
  1694 =head2 ListAllFiles
       
  1695 
       
  1696 Expects to be passed a directory path and an array reference. Lists all files from the directory specified and sub directories into an array reference. Entries in the array contain full path of the file, not just file name.
       
  1697 
       
  1698 =head1 KNOWN BUGS
       
  1699 
       
  1700 None.
       
  1701 
       
  1702 =head1 COPYRIGHT
       
  1703 
       
  1704  Copyright (c) 2000-2009 Nokia Corporation and/or its subsidiary(-ies).
       
  1705  All rights reserved.
       
  1706  This component and the accompanying materials are made available
       
  1707  under the terms of the License "Eclipse Public License v1.0"
       
  1708  which accompanies this distribution, and is available
       
  1709  at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
  1710  
       
  1711  Initial Contributors:
       
  1712  Nokia Corporation - initial contribution.
       
  1713  
       
  1714  Contributors:
       
  1715  
       
  1716  Description:
       
  1717  
       
  1718 
       
  1719 =cut