2 # This file is part of the LibreOffice project.
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # This file incorporates work covered by the following license notice:
10 # Licensed to the Apache Software Foundation (ASF) under one or more
11 # contributor license agreements. See the NOTICE file distributed
12 # with this work for additional information regarding copyright
13 # ownership. The ASF licenses this file to you under the Apache
14 # License, Version 2.0 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 package installer
::windows
::admin
;
22 use installer
::exiter
;
24 use installer
::globals
;
25 use installer
::pathanalyzer
;
26 use installer
::systemactions
;
27 use installer
::worker
;
28 use installer
::windows
::idtglobal
;
30 #################################################################################
31 # Unpacking cabinet files with expand
32 #################################################################################
34 sub unpack_cabinet_file
36 my ($cabfilename, $unpackdir) = @_;
38 my $infoline = "Unpacking cabinet file: $cabfilename\n";
39 push( @installer::globals
::logfileinfo
, $infoline);
41 my $expandfile = "expand.exe"; # Has to be in the path
42 if ( $installer::globals
::isunix
)
44 $infoline = "ERROR: We need to change this to use cabextract instead of expand.exe\n";
45 push( @installer::globals
::logfileinfo
, $infoline);
48 # expand.exe has to be located in the system directory.
49 # Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course.
50 # But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack
53 if ( $^O
=~ /cygwin/i )
55 $expandfile = qx(cygpath
-u
"$ENV{WINDIR}"/System32/expand
.exe
);
59 my $expandlogfile = $unpackdir . $installer::globals
::separator
. "expand.log";
61 # exclude cabinet file
64 if ( $^O
=~ /cygwin/i ) {
65 my $localunpackdir = qx{cygpath
-w
"$unpackdir"};
66 chomp ($localunpackdir);
67 $localunpackdir =~ s/\\/\\\\/g;
68 $cabfilename =~ s/\\/\\\\/g;
69 $cabfilename =~ s/\s*$//g;
70 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $localunpackdir . " \> " . $expandlogfile;
74 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile;
77 my $returnvalue = system($systemcall);
78 $infoline = "Systemcall: $systemcall\n";
79 push( @installer::globals
::logfileinfo
, $infoline);
83 $infoline = "ERROR: Could not execute $systemcall !\n";
84 push( @installer::globals
::logfileinfo
, $infoline);
85 installer
::exiter
::exit_program
("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
89 $infoline = "Success: Executed $systemcall successfully!\n";
90 push( @installer::globals
::logfileinfo
, $infoline);
94 #################################################################################
95 # Extracting tables from msi database
96 #################################################################################
98 sub extract_tables_from_pcpfile
100 my ($fullmsidatabasepath, $workdir, $tablelist) = @_;
102 my $msidb = "msidb.exe"; # Has to be in the path
103 if ( $installer::globals
::isunix
)
105 $msidb = "$ENV{'WORKDIR_FOR_BUILD'}/LinkTarget/Executable/msidb.exe";
109 my $returnvalue = "";
111 my $localfullmsidatabasepath = $fullmsidatabasepath;
113 # Export of all tables by using "*"
115 if ( $^O
=~ /cygwin/i ) {
116 # Copying the msi database locally guarantees the format of the directory.
117 # Otherwise it is defined in the file of UPDATE_DATABASE_LISTNAME
119 my $msifilename = $localfullmsidatabasepath;
120 installer
::pathanalyzer
::make_absolute_filename_to_relative_filename
(\
$msifilename);
121 my $destdatabasename = $workdir . $installer::globals
::separator
. $msifilename;
122 installer
::systemactions
::copy_one_file
($localfullmsidatabasepath, $destdatabasename);
123 $localfullmsidatabasepath = $destdatabasename;
125 chomp( $localfullmsidatabasepath = qx{cygpath
-w
"$localfullmsidatabasepath"} );
126 chomp( $workdir = qx{cygpath
-w
"$workdir"} );
128 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
129 $localfullmsidatabasepath =~ s/\\/\\\\/g;
130 $workdir =~ s/\\/\\\\/g;
132 # and if there are still slashes, they also need to be double backslash
133 $localfullmsidatabasepath =~ s/\//\\\\/g
;
134 $workdir =~ s/\//\\\\/g
;
137 $systemcall = $msidb . " -d " . $localfullmsidatabasepath . " -f " . $workdir . " -e $tablelist";
138 $returnvalue = system($systemcall);
140 $infoline = "Systemcall: $systemcall\n";
141 push( @installer::globals
::logfileinfo
, $infoline);
145 $infoline = "ERROR: Could not execute $systemcall !\n";
146 push( @installer::globals
::logfileinfo
, $infoline);
147 installer
::exiter
::exit_program
("ERROR: Could not exclude tables from pcp file: $localfullmsidatabasepath !", "extract_tables_from_pcpfile");
151 $infoline = "Success: Executed $systemcall successfully!\n";
152 push( @installer::globals
::logfileinfo
, $infoline);
156 ################################################################################
157 # Analyzing the content of Directory.idt
158 #################################################################################
160 sub analyze_directory_file
162 my ($filecontent) = @_;
166 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
168 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
170 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
176 if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; }
177 if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; }
180 $helphash{'Directory_Parent'} = $parent;
181 $helphash{'DefaultDir'} = $name;
182 $table{$dir} = \
%helphash;
189 #################################################################################
190 # Analyzing the content of Component.idt
191 #################################################################################
193 sub analyze_component_file
195 my ($filecontent) = @_;
199 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
201 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
203 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
208 $table{$component} = $dir;
215 #################################################################################
216 # Analyzing the content of File.idt
217 #################################################################################
219 sub analyze_file_file
221 my ($filecontent) = @_;
227 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
229 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
231 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
238 if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; }
241 $helphash{'Component'} = $comp;
242 $helphash{'FileName'} = $filename;
243 $helphash{'Sequence'} = $sequence;
245 $table{$file} = \
%helphash;
247 $fileorder{$sequence} = $file;
249 if ( $sequence > $maxsequence ) { $maxsequence = $sequence; }
253 return (\
%table, \
%fileorder, $maxsequence);
256 ####################################################################################
257 # Recursively creating the directory tree
258 ####################################################################################
260 sub create_directory_tree
262 my ($parent, $pathcollector, $fulldir, $dirhash) = @_;
264 foreach my $dir ( keys %{$dirhash} )
266 if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
268 my $dirname = $dirhash->{$dir}->{'DefaultDir'};
269 # Create the directory
270 my $newdir = $fulldir . $installer::globals
::separator
. $dirname;
271 if ( ! -f
$newdir ) { mkdir $newdir; }
272 # Saving in collector
273 $pathcollector->{$dir} = $newdir;
275 create_directory_tree
($dir, $pathcollector, $newdir, $dirhash);
280 ####################################################################################
281 # Creating the directory tree
282 ####################################################################################
284 sub create_directory_structure
286 my ($dirhash, $targetdir) = @_;
288 my %fullpathhash = ();
290 my @startparents = ("TARGETDIR", "INSTALLLOCATION");
292 foreach $dir (@startparents) { create_directory_tree
($dir, \
%fullpathhash, $targetdir, $dirhash); }
294 # Also adding the paths of the startparents
295 foreach $dir (@startparents)
297 if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; }
300 return \
%fullpathhash;
303 ####################################################################################
304 # Copying files into installation set
305 ####################################################################################
307 sub copy_files_into_directory_structure
309 my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;
311 for ( my $i = 1; $i <= $maxsequence; $i++ )
313 if ( exists($fileorder->{$i}) )
315 my $file = $fileorder->{$i};
316 if ( ! exists($filehash->{$file}->{'Component'}) ) { installer
::exiter
::exit_program
("ERROR: Did not find component for file: \"$file\".", "copy_files_into_directory_structure"); }
317 my $component = $filehash->{$file}->{'Component'};
318 if ( ! exists($componenthash->{$component}) ) { installer
::exiter
::exit_program
("ERROR: Did not find directory for component: \"$component\".", "copy_files_into_directory_structure"); }
319 my $dirname = $componenthash->{$component};
320 if ( ! exists($fullpathhash->{$dirname}) ) { installer
::exiter
::exit_program
("ERROR: Did not find full directory path for dir: \"$dirname\".", "copy_files_into_directory_structure"); }
321 my $destdir = $fullpathhash->{$dirname};
322 if ( ! exists($filehash->{$file}->{'FileName'}) ) { installer
::exiter
::exit_program
("ERROR: Did not find \"FileName\" for file: \"$file\".", "copy_files_into_directory_structure"); }
323 my $destfile = $filehash->{$file}->{'FileName'};
325 $destfile = $destdir . $installer::globals
::separator
. $destfile;
326 my $sourcefile = $unpackdir . $installer::globals
::separator
. $file;
328 if ( ! -f
$sourcefile )
330 # It is possible, that this was an unpacked file
331 # Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname)
332 # subdir is not recursively analyzed, only one directory.
334 my $oldsourcefile = $sourcefile;
336 if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $installer::globals
::separator
; }
337 my $realfilename = $filehash->{$file}->{'FileName'};
338 my $localinstalldir = $installdir;
340 $localinstalldir =~ s/\\\s*$//;
341 $localinstalldir =~ s/\/\s*$//;
343 $sourcefile = $localinstalldir . $installer::globals
::separator
. $subdir . $realfilename;
345 if ( ! -f
$sourcefile )
347 installer
::exiter
::exit_program
("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\").", "copy_files_into_directory_structure");
351 my $copyreturn = copy
($sourcefile, $destfile);
353 if ( ! $copyreturn) # only logging problems
355 my $infoline = "ERROR: Could not copy $sourcefile to $destfile (insufficient disc space for $destfile ?)\n";
357 push(@installer::globals
::logfileinfo
, $infoline);
358 installer
::exiter
::exit_program
($infoline, "copy_files_into_directory_structure");
365 ###############################################################
366 # Setting the time string for the
367 # Summary Information stream in the
368 # msi database of the admin installations.
369 ###############################################################
371 sub get_sis_time_string
373 # Syntax: <yyyy/mm/dd hh:mm:ss>
374 my $second = (localtime())[0];
375 my $minute = (localtime())[1];
376 my $hour = (localtime())[2];
377 my $day = (localtime())[3];
378 my $month = (localtime())[4];
379 my $year = 1900 + (localtime())[5];
381 $month++; # zero based month
383 if ( $second < 10 ) { $second = "0" . $second; }
384 if ( $minute < 10 ) { $minute = "0" . $minute; }
385 if ( $hour < 10 ) { $hour = "0" . $hour; }
386 if ( $day < 10 ) { $day = "0" . $day; }
387 if ( $month < 10 ) { $month = "0" . $month; }
389 my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second;
394 ###############################################################
395 # Writing content of administrative installations into
396 # Summary Information Stream of msi database.
397 # This is required for example for following
398 # patch processes using Windows Installer service.
399 ###############################################################
403 my ($msidatabase) = @_ ;
405 if ( ! -f
$msidatabase ) { installer
::exiter
::exit_program
("ERROR: Cannot find file $msidatabase", "write_sis_info"); }
407 my $msiinfo = "msiinfo.exe"; # Has to be in the path
408 if ( $installer::globals
::isunix
)
410 $msiinfo = "$ENV{'WORKDIR_FOR_BUILD'}/LinkTarget/Executable/msiinfo.exe";
414 my $returnvalue = "";
416 # Required setting for administrative installations:
417 # -w 4 (source files are unpacked), wordcount
418 # -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
419 # -l <person_making_admin_installation>, LastSavedBy
421 my $wordcount = 4; # Unpacked files
422 my $lastprinted = get_sis_time_string
();
423 my $lastsavedby = "Installer";
425 my $localmsidatabase = $msidatabase;
427 if( $^O
=~ /cygwin/i )
429 $localmsidatabase = qx{cygpath
-w
"$localmsidatabase"};
430 $localmsidatabase =~ s/\\/\\\\/g;
431 $localmsidatabase =~ s/\s*$//g;
434 $systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby";
435 push(@installer::globals
::logfileinfo
, $systemcall);
436 $returnvalue = system($systemcall);
440 $infoline = "ERROR: Could not execute $systemcall !\n";
441 push(@installer::globals
::logfileinfo
, $infoline);
442 installer
::exiter
::exit_program
($infoline, "write_sis_info");
446 ####################################################################################
447 # Simulating an administrative installation
448 ####################################################################################
450 sub make_admin_install
452 my ($databasepath, $targetdir) = @_;
454 # Create helper directory
456 installer
::logger
::print_message
( "... installing $databasepath in directory $targetdir ...\n" );
458 my $helperdir = $targetdir . $installer::globals
::separator
. "installhelper";
459 installer
::systemactions
::create_directory
($helperdir);
461 # Get File.idt, Component.idt and Directory.idt from database
463 my $tablelist = "File Directory Component Registry";
464 extract_tables_from_pcpfile
($databasepath, $helperdir, $tablelist);
466 # Unpack all cab files into $helperdir, cab files must be located next to msi database
467 my $installdir = $databasepath;
469 if ( $^O
=~ /cygwin/i ) { $installdir =~ s/\\/\//g
; } # backslash to slash
471 installer
::pathanalyzer
::get_path_from_fullqualifiedname
(\
$installdir);
473 if ( $^O
=~ /cygwin/i ) { $installdir =~ s/\//\\/g
; } # slash to backslash
475 my $databasefilename = $databasepath;
476 installer
::pathanalyzer
::make_absolute_filename_to_relative_filename
(\
$databasefilename);
478 my $cabfiles = installer
::systemactions
::find_file_with_file_extension
("cab", $installdir);
480 if ( $#{$cabfiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find any cab file in directory $installdir", "make_admin_install"); }
483 my $unpackdir = $helperdir . $installer::globals
::separator
. "unpack";
484 installer
::systemactions
::create_directory
($unpackdir);
486 for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
489 if ( $^O
=~ /cygwin/i )
491 $cabfile = $installdir . ${$cabfiles}[$i];
495 $cabfile = $installdir . $installer::globals
::separator
. ${$cabfiles}[$i];
497 unpack_cabinet_file
($cabfile, $unpackdir);
501 my $filename = $helperdir . $installer::globals
::separator
. "Directory.idt";
502 my $filecontent = installer
::files
::read_file
($filename);
503 my $dirhash = analyze_directory_file
($filecontent);
505 $filename = $helperdir . $installer::globals
::separator
. "Component.idt";
506 my $componentfilecontent = installer
::files
::read_file
($filename);
507 my $componenthash = analyze_component_file
($componentfilecontent);
509 $filename = $helperdir . $installer::globals
::separator
. "File.idt";
510 $filecontent = installer
::files
::read_file
($filename);
511 my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file
($filecontent);
513 # Creating the directory structure
514 my $fullpathhash = create_directory_structure
($dirhash, $targetdir);
517 copy_files_into_directory_structure
($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
519 my $msidatabase = $targetdir . $installer::globals
::separator
. $databasefilename;
520 installer
::systemactions
::copy_one_file
($databasepath, $msidatabase);
522 # Saving info in Summary Information Stream of msi database (required for following patches)
523 write_sis_info
($msidatabase);