cid#1607171 Data race condition
[LibreOffice.git] / solenv / bin / modules / installer / windows / admin.pm
blob3bc2330d43bc5014c8e5a6fddba5392cf88798a8
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;
21 use strict;
22 use warnings;
24 use File::Copy;
25 use installer::exiter;
26 use installer::files;
27 use installer::globals;
28 use installer::pathanalyzer;
29 use installer::systemactions;
30 use installer::worker;
31 use installer::windows::idtglobal;
33 #################################################################################
34 # Unpacking cabinet files with expand
35 #################################################################################
37 sub unpack_cabinet_file
39 my ($cabfilename, $unpackdir) = @_;
41 my $infoline = "Unpacking cabinet file: $cabfilename\n";
42 push( @installer::globals::logfileinfo, $infoline);
44 my $expandfile = "expand.exe"; # Has to be in the path
46 # expand.exe has to be located in the system directory.
47 # Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course.
48 # But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack
49 # cabinet files.
51 if ( $^O =~ /cygwin/i )
53 $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
54 chomp $expandfile;
57 my $expandlogfile = $unpackdir . $installer::globals::separator . "expand.log";
59 # exclude cabinet file
61 my $systemcall = "";
62 if ( $^O =~ /cygwin/i ) {
63 my $localunpackdir = qx{cygpath -w "$unpackdir"};
64 chomp ($localunpackdir);
65 $localunpackdir =~ s/\\/\\\\/g;
66 $cabfilename =~ s/\\/\\\\/g;
67 $cabfilename =~ s/\s*$//g;
68 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $localunpackdir . " \> " . $expandlogfile;
70 else
72 $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile;
75 my $returnvalue = system($systemcall);
76 $infoline = "Systemcall: $systemcall\n";
77 push( @installer::globals::logfileinfo, $infoline);
79 if ($returnvalue)
81 $infoline = "ERROR: Could not execute $systemcall !\n";
82 push( @installer::globals::logfileinfo, $infoline);
83 installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
85 else
87 $infoline = "Success: Executed $systemcall successfully!\n";
88 push( @installer::globals::logfileinfo, $infoline);
92 #################################################################################
93 # Extracting tables from msi database
94 #################################################################################
96 sub extract_tables_from_pcpfile
98 my ($fullmsidatabasepath, $workdir, $tablelist) = @_;
100 my $msidb = "msidb.exe"; # Has to be in the path
101 my $infoline = "";
102 my $systemcall = "";
103 my $returnvalue = "";
105 my $localfullmsidatabasepath = $fullmsidatabasepath;
107 # Export of all tables by using "*"
109 if ( $^O =~ /cygwin/i ) {
110 # Copying the msi database locally guarantees the format of the directory.
111 # Otherwise it is defined in the file of UPDATE_DATABASE_LISTNAME
113 my $msifilename = $localfullmsidatabasepath;
114 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename);
115 my $destdatabasename = $workdir . $installer::globals::separator . $msifilename;
116 installer::systemactions::copy_one_file($localfullmsidatabasepath, $destdatabasename);
117 $localfullmsidatabasepath = $destdatabasename;
119 chomp( $localfullmsidatabasepath = qx{cygpath -w "$localfullmsidatabasepath"} );
120 chomp( $workdir = qx{cygpath -w "$workdir"} );
122 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
123 $localfullmsidatabasepath =~ s/\\/\\\\/g;
124 $workdir =~ s/\\/\\\\/g;
126 # and if there are still slashes, they also need to be double backslash
127 $localfullmsidatabasepath =~ s/\//\\\\/g;
128 $workdir =~ s/\//\\\\/g;
131 $systemcall = $msidb . " -d " . $localfullmsidatabasepath . " -f " . $workdir . " -e $tablelist";
132 $returnvalue = system($systemcall);
134 $infoline = "Systemcall: $systemcall\n";
135 push( @installer::globals::logfileinfo, $infoline);
137 if ($returnvalue)
139 $infoline = "ERROR: Could not execute $systemcall !\n";
140 push( @installer::globals::logfileinfo, $infoline);
141 installer::exiter::exit_program("ERROR: Could not exclude tables from pcp file: $localfullmsidatabasepath !", "extract_tables_from_pcpfile");
143 else
145 $infoline = "Success: Executed $systemcall successfully!\n";
146 push( @installer::globals::logfileinfo, $infoline);
150 ################################################################################
151 # Analyzing the content of Directory.idt
152 #################################################################################
154 sub analyze_directory_file
156 my ($filecontent) = @_;
158 my %table = ();
160 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
162 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
164 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
166 my $dir = $1;
167 my $parent = $2;
168 my $name = $3;
170 if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; }
171 if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; }
173 my %helphash = ();
174 $helphash{'Directory_Parent'} = $parent;
175 $helphash{'DefaultDir'} = $name;
176 $table{$dir} = \%helphash;
180 return \%table;
183 #################################################################################
184 # Analyzing the content of Component.idt
185 #################################################################################
187 sub analyze_component_file
189 my ($filecontent) = @_;
191 my %table = ();
193 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
195 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
197 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
199 my $component = $1;
200 my $dir = $3;
202 $table{$component} = $dir;
206 return \%table;
209 #################################################################################
210 # Analyzing the content of File.idt
211 #################################################################################
213 sub analyze_file_file
215 my ($filecontent) = @_;
217 my %table = ();
218 my %fileorder = ();
219 my $maxsequence = 0;
221 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
223 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
225 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
227 my $file = $1;
228 my $comp = $2;
229 my $filename = $3;
230 my $sequence = $8;
232 if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; }
234 my %helphash = ();
235 $helphash{'Component'} = $comp;
236 $helphash{'FileName'} = $filename;
237 $helphash{'Sequence'} = $sequence;
239 $table{$file} = \%helphash;
241 $fileorder{$sequence} = $file;
243 if ( $sequence > $maxsequence ) { $maxsequence = $sequence; }
247 return (\%table, \%fileorder, $maxsequence);
250 ####################################################################################
251 # Recursively creating the directory tree
252 ####################################################################################
254 sub create_directory_tree
256 my ($parent, $pathcollector, $fulldir, $dirhash) = @_;
258 foreach my $dir ( keys %{$dirhash} )
260 if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
262 my $dirname = $dirhash->{$dir}->{'DefaultDir'};
263 # Create the directory
264 my $newdir = $fulldir . $installer::globals::separator . $dirname;
265 if ( ! -f $newdir ) { mkdir $newdir; }
266 # Saving in collector
267 $pathcollector->{$dir} = $newdir;
268 # Iteration
269 create_directory_tree($dir, $pathcollector, $newdir, $dirhash);
274 ####################################################################################
275 # Creating the directory tree
276 ####################################################################################
278 sub create_directory_structure
280 my ($dirhash, $targetdir) = @_;
282 my %fullpathhash = ();
284 my @startparents = ("TARGETDIR", "INSTALLLOCATION");
286 foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); }
288 # Also adding the paths of the startparents
289 foreach $dir (@startparents)
291 if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; }
294 return \%fullpathhash;
297 ####################################################################################
298 # Copying files into installation set
299 ####################################################################################
301 sub copy_files_into_directory_structure
303 my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;
305 for ( my $i = 1; $i <= $maxsequence; $i++ )
307 if ( exists($fileorder->{$i}) )
309 my $file = $fileorder->{$i};
310 if ( ! exists($filehash->{$file}->{'Component'}) ) { installer::exiter::exit_program("ERROR: Did not find component for file: \"$file\".", "copy_files_into_directory_structure"); }
311 my $component = $filehash->{$file}->{'Component'};
312 if ( ! exists($componenthash->{$component}) ) { installer::exiter::exit_program("ERROR: Did not find directory for component: \"$component\".", "copy_files_into_directory_structure"); }
313 my $dirname = $componenthash->{$component};
314 if ( ! exists($fullpathhash->{$dirname}) ) { installer::exiter::exit_program("ERROR: Did not find full directory path for dir: \"$dirname\".", "copy_files_into_directory_structure"); }
315 my $destdir = $fullpathhash->{$dirname};
316 if ( ! exists($filehash->{$file}->{'FileName'}) ) { installer::exiter::exit_program("ERROR: Did not find \"FileName\" for file: \"$file\".", "copy_files_into_directory_structure"); }
317 my $destfile = $filehash->{$file}->{'FileName'};
319 $destfile = $destdir . $installer::globals::separator . $destfile;
320 my $sourcefile = $unpackdir . $installer::globals::separator . $file;
322 if ( ! -f $sourcefile )
324 # It is possible, that this was an unpacked file
325 # Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname)
326 # subdir is not recursively analyzed, only one directory.
328 my $oldsourcefile = $sourcefile;
329 my $subdir = "";
330 if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $installer::globals::separator; }
331 my $realfilename = $filehash->{$file}->{'FileName'};
332 my $localinstalldir = $installdir;
334 $localinstalldir =~ s/\\\s*$//;
335 $localinstalldir =~ s/\/\s*$//;
337 $sourcefile = $localinstalldir . $installer::globals::separator . $subdir . $realfilename;
339 if ( ! -f $sourcefile )
341 installer::exiter::exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\").", "copy_files_into_directory_structure");
345 my $copyreturn = copy($sourcefile, $destfile);
347 if ( ! $copyreturn) # only logging problems
349 my $infoline = "ERROR: Could not copy $sourcefile to $destfile (insufficient disc space for $destfile ?)\n";
350 $returnvalue = 0;
351 push(@installer::globals::logfileinfo, $infoline);
352 installer::exiter::exit_program($infoline, "copy_files_into_directory_structure");
359 ###############################################################
360 # Setting the time string for the
361 # Summary Information stream in the
362 # msi database of the admin installations.
363 ###############################################################
365 sub get_sis_time_string
367 # Syntax: <yyyy/mm/dd hh:mm:ss>
368 my $second = (localtime())[0];
369 my $minute = (localtime())[1];
370 my $hour = (localtime())[2];
371 my $day = (localtime())[3];
372 my $month = (localtime())[4];
373 my $year = 1900 + (localtime())[5];
375 $month++; # zero based month
377 if ( $second < 10 ) { $second = "0" . $second; }
378 if ( $minute < 10 ) { $minute = "0" . $minute; }
379 if ( $hour < 10 ) { $hour = "0" . $hour; }
380 if ( $day < 10 ) { $day = "0" . $day; }
381 if ( $month < 10 ) { $month = "0" . $month; }
383 my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second;
385 return $timestring;
388 ###############################################################
389 # Writing content of administrative installations into
390 # Summary Information Stream of msi database.
391 # This is required for example for following
392 # patch processes using Windows Installer service.
393 ###############################################################
395 sub write_sis_info
397 my ($msidatabase) = @_ ;
399 if ( ! -f $msidatabase ) { installer::exiter::exit_program("ERROR: Cannot find file $msidatabase", "write_sis_info"); }
401 my $msiinfo = "msiinfo.exe"; # Has to be in the path
402 my $infoline = "";
403 my $systemcall = "";
404 my $returnvalue = "";
406 # Required setting for administrative installations:
407 # -w 4 (source files are unpacked), wordcount
408 # -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
409 # -l <person_making_admin_installation>, LastSavedBy
411 my $wordcount = 4; # Unpacked files
412 my $lastprinted = get_sis_time_string();
413 my $lastsavedby = "Installer";
415 my $localmsidatabase = $msidatabase;
417 if( $^O =~ /cygwin/i )
419 $localmsidatabase = qx{cygpath -w "$localmsidatabase"};
420 $localmsidatabase =~ s/\\/\\\\/g;
421 $localmsidatabase =~ s/\s*$//g;
424 $systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby";
425 push(@installer::globals::logfileinfo, $systemcall);
426 $returnvalue = system($systemcall);
428 if ($returnvalue)
430 $infoline = "ERROR: Could not execute $systemcall !\n";
431 push(@installer::globals::logfileinfo, $infoline);
432 installer::exiter::exit_program($infoline, "write_sis_info");
436 ####################################################################################
437 # Simulating an administrative installation
438 ####################################################################################
440 sub make_admin_install
442 my ($databasepath, $targetdir) = @_;
444 # Create helper directory
446 installer::logger::print_message( "... installing $databasepath in directory $targetdir ...\n" );
448 my $helperdir = $targetdir . $installer::globals::separator . "installhelper";
449 installer::systemactions::create_directory($helperdir);
451 # Get File.idt, Component.idt and Directory.idt from database
453 my $tablelist = "File Directory Component Registry";
454 extract_tables_from_pcpfile($databasepath, $helperdir, $tablelist);
456 # Unpack all cab files into $helperdir, cab files must be located next to msi database
457 my $installdir = $databasepath;
459 if ( $^O =~ /cygwin/i ) { $installdir =~ s/\\/\//g; } # backslash to slash
461 installer::pathanalyzer::get_path_from_fullqualifiedname(\$installdir);
463 if ( $^O =~ /cygwin/i ) { $installdir =~ s/\//\\/g; } # slash to backslash
465 my $databasefilename = $databasepath;
466 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$databasefilename);
468 my $cabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir);
470 if ( $#{$cabfiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find any cab file in directory $installdir", "make_admin_install"); }
472 # Set unpackdir
473 my $unpackdir = $helperdir . $installer::globals::separator . "unpack";
474 installer::systemactions::create_directory($unpackdir);
476 for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
478 my $cabfile = "";
479 if ( $^O =~ /cygwin/i )
481 $cabfile = $installdir . ${$cabfiles}[$i];
483 else
485 $cabfile = $installdir . $installer::globals::separator . ${$cabfiles}[$i];
487 unpack_cabinet_file($cabfile, $unpackdir);
490 # Reading tables
491 my $filename = $helperdir . $installer::globals::separator . "Directory.idt";
492 my $filecontent = installer::files::read_file($filename);
493 my $dirhash = analyze_directory_file($filecontent);
495 $filename = $helperdir . $installer::globals::separator . "Component.idt";
496 my $componentfilecontent = installer::files::read_file($filename);
497 my $componenthash = analyze_component_file($componentfilecontent);
499 $filename = $helperdir . $installer::globals::separator . "File.idt";
500 $filecontent = installer::files::read_file($filename);
501 my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent);
503 # Creating the directory structure
504 my $fullpathhash = create_directory_structure($dirhash, $targetdir);
506 # Copying files
507 copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
509 my $msidatabase = $targetdir . $installer::globals::separator . $databasefilename;
510 installer::systemactions::copy_one_file($databasepath, $msidatabase);
512 # Saving info in Summary Information Stream of msi database (required for following patches)
513 write_sis_info($msidatabase);
515 return $msidatabase;