1# 2# This file is part of the LibreOffice project. 3# 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/. 7# 8# This file incorporates work covered by the following license notice: 9# 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 . 17# 18 19package installer::windows::update; 20 21use installer::converter; 22use installer::exiter; 23use installer::files; 24use installer::globals; 25use installer::pathanalyzer; 26use installer::systemactions; 27 28################################################################################# 29# Extracting all tables from an msi database 30################################################################################# 31 32sub extract_all_tables_from_msidatabase 33{ 34 my ($fulldatabasepath, $workdir) = @_; 35 36 my $msidb = "msidb.exe"; # Has to be in the path 37 my $infoline = ""; 38 my $systemcall = ""; 39 my $returnvalue = ""; 40 my $extraslash = ""; # Has to be set for non-ActiveState perl 41 42 # Export of all tables by using "*" 43 44 if ( $^O =~ /cygwin/i ) { 45 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.) 46 $fulldatabasepath =~ s/\//\\\\/g; 47 $workdir =~ s/\//\\\\/g; 48 $extraslash = "\\"; 49 } 50 if ( $^O =~ /linux/i) { 51 $extraslash = "\\"; 52 } 53 54 $systemcall = $msidb . " -d " . $fulldatabasepath . " -f " . $workdir . " -e " . $extraslash . "*"; 55 $returnvalue = system($systemcall); 56 57 $infoline = "Systemcall: $systemcall\n"; 58 push( @installer::globals::logfileinfo, $infoline); 59 60 if ($returnvalue) 61 { 62 $infoline = "ERROR: Could not execute $systemcall !\n"; 63 push( @installer::globals::logfileinfo, $infoline); 64 installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $fulldatabasepath !", "extract_all_tables_from_msidatabase"); 65 } 66 else 67 { 68 $infoline = "Success: Executed $systemcall successfully!\n"; 69 push( @installer::globals::logfileinfo, $infoline); 70 } 71} 72 73################################################################################# 74# Collecting the keys from the first line of the idt file 75################################################################################# 76 77sub collect_all_keys 78{ 79 my ($line) = @_; 80 81 my @allkeys = (); 82 my $rownumber = 0; 83 my $onekey = ""; 84 85 while ( $line =~ /^\s*(\S+?)\t(.*)$/ ) 86 { 87 $onekey = $1; 88 $line = $2; 89 $rownumber++; 90 push(@allkeys, $onekey); 91 } 92 93 # and the last key 94 95 $onekey = $line; 96 $onekey =~ s/^\s*//g; 97 $onekey =~ s/\s*$//g; 98 99 $rownumber++; 100 push(@allkeys, $onekey); 101 102 return (\@allkeys, $rownumber); 103} 104 105################################################################################# 106# Analyzing the content of one line of an idt file 107################################################################################# 108 109sub get_oneline_hash 110{ 111 my ($line, $allkeys, $rownumber) = @_; 112 113 my $counter = 0; 114 my %linehash = (); 115 116 $line =~ s/^\s*//; 117 $line =~ s/\s*$//; 118 119 my $value = ""; 120 my $onekey = ""; 121 122 while ( $line =~ /^(.*?)\t(.*)$/ ) 123 { 124 $value = $1; 125 $line = $2; 126 $onekey = ${$allkeys}[$counter]; 127 $linehash{$onekey} = $value; 128 $counter++; 129 } 130 131 # the last column 132 133 $value = $line; 134 $onekey = ${$allkeys}[$counter]; 135 136 $linehash{$onekey} = $value; 137 138 return \%linehash; 139} 140 141################################################################################# 142# Analyzing the content of an idt file 143################################################################################# 144 145sub analyze_idt_file 146{ 147 my ($filecontent) = @_; 148 149 my %table = (); 150 # keys are written in first line 151 my ($allkeys, $rownumber) = collect_all_keys(${$filecontent}[0]); 152 153 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) 154 { 155 if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; } 156 157 my $onelinehash = get_oneline_hash(${$filecontent}[$i], $allkeys, $rownumber); 158 my $linekey = $i - 2; # ! : The linenumber is the unique key !? Always decrease by two, because of removed first three lines. 159 $table{$linekey} = $onelinehash; 160 } 161 162 return \%table; 163} 164 165################################################################################# 166# Reading all idt files in a specified directory 167################################################################################# 168 169sub read_all_tables_from_msidatabase 170{ 171 my ($workdir) = @_; 172 173 my %database = (); 174 175 my $ext = "idt"; 176 177 my $allidtfiles = installer::systemactions::find_file_with_file_extension($ext, $workdir); 178 179 for ( my $i = 0; $i <= $#{$allidtfiles}; $i++ ) 180 { 181 my $onefilename = ${$allidtfiles}[$i]; 182 my $longonefilename = $workdir . $installer::globals::separator . $onefilename; 183 if ( ! -f $longonefilename ) { installer::exiter::exit_program("ERROR: Could not find idt file: $longonefilename!", "read_all_tables_from_msidatabase"); } 184 my $filecontent = installer::files::read_file($longonefilename); 185 my $idtcontent = analyze_idt_file($filecontent); 186 if ($onefilename eq "Directory.idt") { 187 collect_directories($filecontent); 188 } 189 my $key = $onefilename; 190 $key =~ s/\.idt\s*$//; 191 $database{$key} = $idtcontent; 192 } 193 194 return \%database; 195} 196 197################################################################################# 198# Checking, if this is the correct database. 199################################################################################# 200 201sub correct_database 202{ 203 my ($product, $pro, $langs, $languagestringref) = @_; 204 205 my $correct_database = 0; 206 207 # Comparing $product with $installer::globals::product and 208 # $pro with $installer::globals::pro and 209 # $langs with $languagestringref 210 211 my $product_is_good = 0; 212 213 my $localproduct = $installer::globals::product; 214 if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; } 215 elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; } 216 217 if ( $product eq $localproduct ) { $product_is_good = 1; } 218 219 if ( $product_is_good ) 220 { 221 my $pro_is_good = 0; 222 223 if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; } 224 225 if ( $pro_is_good ) 226 { 227 my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ","); 228 my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_"); 229 230 my $not_included = 0; 231 foreach my $onelang ( keys %{$langlisthash} ) 232 { 233 if ( ! exists($langstringhash->{$onelang}) ) 234 { 235 $not_included = 1; 236 last; 237 } 238 } 239 240 if ( ! $not_included ) 241 { 242 foreach my $onelanguage ( keys %{$langstringhash} ) 243 { 244 if ( ! exists($langlisthash->{$onelanguage}) ) 245 { 246 $not_included = 1; 247 last; 248 } 249 } 250 251 if ( ! $not_included ) { $correct_database = 1; } 252 } 253 } 254 } 255 256 return $correct_database; 257} 258 259################################################################################# 260# Searching for the path to the reference database for this special product. 261################################################################################# 262 263sub get_databasename_from_list 264{ 265 my ($filecontent, $languagestringref, $filename) = @_; 266 267 my $databasepath = ""; 268 269 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) 270 { 271 my $line = ${$filecontent}[$i]; 272 if ( $line =~ /^\s*$/ ) { next; } # empty line 273 if ( $line =~ /^\s*\#/ ) { next; } # comment line 274 275 if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ ) 276 { 277 my $product = $1; 278 my $pro = $2; 279 my $langs = $3; 280 my $path = $4; 281 282 if (( $pro ne "pro" ) && ( $pro ne "nonpro" )) { installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename. Only \"pro\" or \"nonpro\" allowed in column 1! Line: \"$line\"", "get_databasename_from_list"); } 283 284 if ( correct_database($product, $pro, $langs, $languagestringref) ) 285 { 286 $databasepath = $path; 287 last; 288 } 289 } 290 else 291 { 292 installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_databasename_from_list"); 293 } 294 } 295 296 return $databasepath; 297} 298 299################################################################################# 300# Reading an existing database completely 301################################################################################# 302 303sub readdatabase 304{ 305 my ($allvariables, $languagestringref, $includepatharrayref) = @_; 306 307 my $database = ""; 308 my $infoline = ""; 309 310 if ( ! $allvariables->{'UPDATE_DATABASE_LISTNAME'} ) { installer::exiter::exit_program("ERROR: If \"UPDATE_DATABASE\" is set, \"UPDATE_DATABASE_LISTNAME\" is required.", "Main"); } 311 my $listfilename = $allvariables->{'UPDATE_DATABASE_LISTNAME'}; 312 313 # Searching the list in the include paths 314 my $listname = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$listfilename, $includepatharrayref, 1); 315 if ( $$listname eq "" ) { installer::exiter::exit_program("ERROR: List file not found: $listfilename !", "readdatabase"); } 316 my $completelistname = $$listname; 317 318 # Reading list file 319 my $listfile = installer::files::read_file($completelistname); 320 321 # Get name and path of reference database 322 my $databasename = get_databasename_from_list($listfile, $languagestringref, $completelistname); 323 324 # If the correct database was not found, this is not necessarily an error. But in this case, this is not an update packaging process! 325 if (( $databasename ) && ( $databasename ne "" )) # This is an update packaging process! 326 { 327 $installer::globals::updatedatabase = 1; 328 installer::logger::print_message( "... update process, using database $databasename ...\n" ); 329 $infoline = "\nDatabase found in $completelistname: \"$databasename\"\n\n"; 330 # Saving in global variable 331 $installer::globals::updatedatabasepath = $databasename; 332 } 333 else 334 { 335 $infoline = "\nNo database found in $completelistname. This is no update process!\n\n"; 336 } 337 push( @installer::globals::logfileinfo, $infoline); 338 339 if ( $installer::globals::updatedatabase ) 340 { 341 if ( ! -f $databasename ) { installer::exiter::exit_program("ERROR: Could not find reference database: $databasename!", "readdatabase"); } 342 343 my $msifilename = $databasename; 344 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename); 345 346 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase start"); 347 348 # create directory for unpacking 349 my $databasedir = installer::systemactions::create_directories("database", $languagestringref); 350 351 # copy database 352 my $fulldatabasepath = $databasedir . $installer::globals::separator . $msifilename; 353 installer::systemactions::copy_one_file($databasename, $fulldatabasepath); 354 355 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before extracting tables"); 356 357 # extract all tables from database 358 extract_all_tables_from_msidatabase($fulldatabasepath, $databasedir); 359 360 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase: before reading tables"); 361 362 # read all tables 363 $database = read_all_tables_from_msidatabase($databasedir); 364 365 # Test output: 366 367 # foreach my $key1 ( keys %{$database} ) 368 # { 369 # print "Test1: $key1\n"; 370 # foreach my $key2 ( keys %{$database->{$key1}} ) 371 # { 372 # print "\tTest2: $key2\n"; 373 # foreach my $key3 ( keys %{$database->{$key1}->{$key2}} ) 374 # { 375 # print "\t\tTest3: $key3: $database->{$key1}->{$key2}->{$key3}\n"; 376 # } 377 # } 378 # } 379 380 # Example: File table 381 382 # my $filetable = $database->{'File'}; 383 # foreach my $linenumber ( keys %{$filetable} ) 384 # { 385 # print "Test Filenumber: $linenumber\n"; 386 # foreach my $key ( keys %{$filetable->{$linenumber}} ) 387 # { 388 # print "\t\tTest: $key: $filetable->{$linenumber}->{$key}\n"; 389 # } 390 # } 391 392 # Example: Searching for ProductCode in table Property 393 394 # my $column1 = "Property"; 395 # my $column2 = "Value"; 396 # my $searchkey = "ProductCode"; 397 # my $propertytable = $database->{'Property'}; 398 # foreach my $linenumber ( keys %{$propertytable} ) 399 # { 400 # if ( $propertytable->{$linenumber}->{$column1} eq $searchkey ) 401 # { 402 # print("Test: $searchkey : $propertytable->{$linenumber}->{$column2}\n"); 403 # } 404 # } 405 406 installer::logger::include_timestamp_into_logfile("Performance Info: readdatabase end"); 407 } 408 409 return $database; 410} 411 412######################################################################### 413# Reading the file "Directory.idt". 414######################################################################### 415 416sub collect_directories 417{ 418 my ($filecontent) = @_; 419 420 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) 421 { 422 if ( $i <= 2 ) { next; } # ignoring first three lines 423 if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines 424 # Format: Directory Directory_Parent DefaultDir 425 if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ ) 426 { 427 $installer::globals::merge_directory_hash{$1} = 1; 428 } 429 else 430 { 431 my $linecount = $i + 1; 432 installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories"); 433 } 434 } 435} 436 437################################################################################# 438# Files can be included in merge modules. This is also important for update. 439################################################################################# 440 441sub readmergedatabase 442{ 443 my ( $mergemodules, $languagestringref, $includepatharrayref ) = @_; 444 445 installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase start"); 446 447 my $mergemoduledir = installer::systemactions::create_directories("mergedatabase", $languagestringref); 448 449 my %allmergefiles = (); 450 451 foreach my $mergemodule ( @{$mergemodules} ) 452 { 453 my $filename = $mergemodule->{'Name'}; 454 my $mergefile = $ENV{'MSM_PATH'} . $filename; 455 456 if ( ! -f $mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename !", "readmergedatabase"); } 457 my $completesource = $mergefile; 458 459 my $mergegid = $mergemodule->{'gid'}; 460 my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid; 461 if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); } 462 463 my $completedest = $workdir . $installer::globals::separator . $filename; 464 installer::systemactions::copy_one_file($completesource, $completedest); 465 if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "readmergedatabase"); } 466 467 # extract all tables from database 468 extract_all_tables_from_msidatabase($completedest, $workdir); 469 470 # read all tables 471 my $onemergefile = read_all_tables_from_msidatabase($workdir); 472 473 $allmergefiles{$mergegid} = $onemergefile; 474 } 475 476 foreach my $mergefilegid ( keys %allmergefiles ) 477 { 478 my $onemergefile = $allmergefiles{$mergefilegid}; 479 my $filetable = $onemergefile->{'File'}; 480 481 foreach my $linenumber ( keys %{$filetable} ) 482 { 483 # Collecting all files from merge modules in global hash 484 $installer::globals::mergemodulefiles{$filetable->{$linenumber}->{'File'}} = 1; 485 } 486 } 487 488 installer::logger::include_timestamp_into_logfile("Performance Info: readmergedatabase end"); 489} 490 491################################################################################# 492# Creating several useful hashes from old database 493################################################################################# 494 495sub create_database_hashes 496{ 497 my ( $database ) = @_; 498 499 # 1. Hash ( Component -> UniqueFileName ), required in File table. 500 # Read from File table. 501 502 my %uniquefilename = (); 503 my %allupdatesequences = (); 504 my %allupdatecomponents = (); 505 my %allupdatefileorder = (); 506 my %allupdatecomponentorder = (); 507 my %revuniquefilename = (); 508 my %revshortfilename = (); 509 my %shortdirname = (); 510 my %componentid = (); 511 my %componentidkeypath = (); 512 my %alloldproperties = (); 513 my %allupdatelastsequences = (); 514 my %allupdatediskids = (); 515 516 my $filetable = $database->{'File'}; 517 518 foreach my $linenumber ( keys %{$filetable} ) 519 { 520 my $comp = $filetable->{$linenumber}->{'Component_'}; 521 my $uniquename = $filetable->{$linenumber}->{'File'}; 522 my $filename = $filetable->{$linenumber}->{'FileName'}; 523 my $sequence = $filetable->{$linenumber}->{'Sequence'}; 524 525 my $shortname = ""; 526 if ( $filename =~ /^\s*(.*?)\|\s*(.*?)\s*$/ ) 527 { 528 $shortname = $1; 529 $filename = $2; 530 } 531 532 # unique is the combination of $component and $filename 533 my $key = "$comp/$filename"; 534 535 if ( exists($uniquefilename{$key}) ) { installer::exiter::exit_program("ERROR: Component/FileName \"$key\" is not unique in table \"File\" !", "create_database_hashes"); } 536 537 my $value = $uniquename; 538 if ( $shortname ne "" ) { $value = "$uniquename;$shortname"; } 539 $uniquefilename{$key} = $value; # saving the unique keys and short names in hash 540 541 # Saving reverse keys too 542 $revuniquefilename{$uniquename} = $key; 543 if ( $shortname ne "" ) { $revshortfilename{$shortname} = $key; } 544 545 # Saving Sequences for unique names (and also components) 546 $allupdatesequences{$uniquename} = $sequence; 547 $allupdatecomponents{$uniquename} = $comp; 548 549 # Saving unique names and components for sequences 550 $allupdatefileorder{$sequence} = $uniquename; 551 $allupdatecomponentorder{$sequence} = $comp; 552 } 553 554 # 2. Hash, required in Directory table. 555 556 my $dirtable = $database->{'Directory'}; 557 558 foreach my $linenumber ( keys %{$dirtable} ) 559 { 560 my $dir = $dirtable->{$linenumber}->{'Directory'}; # this is a unique name 561 my $defaultdir = $dirtable->{$linenumber}->{'DefaultDir'}; 562 563 my $shortname = ""; 564 if ( $defaultdir =~ /^\s*(.*?)\|\s*(.*?)\s*$/ ) 565 { 566 $shortname = $1; 567 $shortdirname{$dir} = $shortname; # collecting only the short names 568 } 569 } 570 571 # 3. Hash, collecting info from Component table. 572 # ComponentID and KeyPath have to be reused. 573 574 my $comptable = $database->{'Component'}; 575 576 foreach my $linenumber ( keys %{$comptable} ) 577 { 578 my $comp = $comptable->{$linenumber}->{'Component'}; 579 my $compid = $comptable->{$linenumber}->{'ComponentId'}; 580 my $keypath = $comptable->{$linenumber}->{'KeyPath'}; 581 582 $componentid{$comp} = $compid; 583 $componentidkeypath{$comp} = $keypath; 584 } 585 586 # 4. Hash, property table, required for ProductCode and Installlocation. 587 588 my $proptable = $database->{'Property'}; 589 590 foreach my $linenumber ( keys %{$proptable} ) 591 { 592 my $prop = $proptable->{$linenumber}->{'Property'}; 593 my $value = $proptable->{$linenumber}->{'Value'}; 594 595 $alloldproperties{$prop} = $value; 596 } 597 598 # 5. Media table, getting last sequence 599 600 my $mediatable = $database->{'Media'}; 601 $installer::globals::updatelastsequence = 0; 602 603 foreach my $linenumber ( keys %{$mediatable} ) 604 { 605 my $cabname = $mediatable->{$linenumber}->{'Cabinet'}; 606 my $lastsequence = $mediatable->{$linenumber}->{'LastSequence'}; 607 my $diskid = $mediatable->{$linenumber}->{'DiskId'}; 608 $allupdatelastsequences{$cabname} = $lastsequence; 609 $allupdatediskids{$cabname} = $diskid; 610 611 if ( $lastsequence > $installer::globals::updatelastsequence ) { $installer::globals::updatelastsequence = $lastsequence; } 612 } 613 614 $installer::globals::updatesequencecounter = $installer::globals::updatelastsequence; 615 616 return (\%uniquefilename, \%revuniquefilename, \%revshortfilename, \%allupdatesequences, \%allupdatecomponents, \%allupdatefileorder, \%allupdatecomponentorder, \%shortdirname, \%componentid, \%componentidkeypath, \%alloldproperties, \%allupdatelastsequences, \%allupdatediskids); 617} 618 619 6201; 621