1#************************************************************** 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19# 20#************************************************************** 21 22 23 24package installer::windows::file; 25 26use Digest::MD5; 27use installer::existence; 28use installer::exiter; 29use installer::files; 30use installer::globals; 31use installer::logger; 32use installer::pathanalyzer; 33use installer::worker; 34use installer::windows::font; 35use installer::windows::idtglobal; 36use installer::windows::msiglobal; 37use installer::windows::language; 38use installer::patch::InstallationSet; 39use installer::patch::FileSequenceList; 40use File::Basename; 41use File::Spec; 42use strict; 43 44########################################################################## 45# Assigning one cabinet file to each file. This is requrired, 46# if cabinet files shall be equivalent to packages. 47########################################################################## 48 49sub assign_cab_to_files 50{ 51 my ( $filesref ) = @_; 52 53 my $infoline = ""; 54 55 foreach my $file (@$filesref) 56 { 57 if ( ! exists($file->{'modules'}) ) 58 { 59 installer::exiter::exit_program( 60 sprintf("ERROR: No module assignment found for %s", $file->{'gid'}), 61 "assign_cab_to_files"); 62 } 63 my $module = $file->{'modules'}; 64 # If modules contains a list of modules, only taking the first one. 65 if ( $module =~ /^\s*(.*?)\,/ ) { $module = $1; } 66 67 if ( ! exists($installer::globals::allcabinetassigns{$module}) ) 68 { 69 installer::exiter::exit_program( 70 sprintf("ERROR: No cabinet file assigned to module \"%s\" %s", 71 $module, 72 $file->{'gid'}), 73 "assign_cab_to_files"); 74 } 75 $file->{'assignedcabinetfile'} = $installer::globals::allcabinetassigns{$module}; 76 77 # Counting the files in each cabinet file 78 if ( ! exists($installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}}) ) 79 { 80 $installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}} = 1; 81 } 82 else 83 { 84 $installer::globals::cabfilecounter{$file->{'assignedcabinetfile'}}++; 85 } 86 } 87 88 # assigning startsequencenumbers for each cab file 89 90 my %count = (); 91 my $offset = 1; 92 foreach my $cabfile ( sort keys %installer::globals::cabfilecounter ) 93 { 94 my $filecount = $installer::globals::cabfilecounter{$cabfile}; 95 $count{$cabfile} = $filecount; 96 $installer::globals::cabfilecounter{$cabfile} = $offset; 97 $offset = $offset + $filecount; 98 99 $installer::globals::lastsequence{$cabfile} = $offset - 1; 100 } 101 102 # logging the number of files in each cabinet file 103 104 $installer::logger::Lang->print("\n"); 105 $installer::logger::Lang->print("Cabinet files:\n"); 106 foreach my $cabfile (sort keys %installer::globals::cabfilecounter) 107 { 108 $installer::logger::Lang->printf( 109 "%-30s : %4s files, from %4d to %4d\n", 110 $cabfile, 111 $count{$cabfile}, 112 $installer::globals::cabfilecounter{$cabfile}, 113 $installer::globals::lastsequence{$cabfile}); 114 } 115} 116 117########################################################################## 118# Assigning sequencenumbers to files. This is requrired, 119# if cabinet files shall be equivalent to packages. 120########################################################################## 121 122sub assign_sequencenumbers_to_files 123{ 124 my ( $filesref ) = @_; 125 126 my %directaccess = (); 127 my %allassigns = (); 128 129 for ( my $i = 0; $i <= $#{$filesref}; $i++ ) 130 { 131 my $onefile = ${$filesref}[$i]; 132 133 # Keeping order in cabinet files 134 # -> collecting all files in one cabinet file 135 # -> sorting files and assigning numbers 136 137 # Saving counter $i for direct access into files array 138 # "destination" of the file is a unique identifier ('Name' is not unique!) 139 if ( exists($directaccess{$onefile->{'destination'}}) ) { installer::exiter::exit_program("ERROR: 'destination' at file not unique: $onefile->{'destination'}", "assign_sequencenumbers_to_files"); } 140 $directaccess{$onefile->{'destination'}} = $i; 141 142 my $cabfilename = $onefile->{'assignedcabinetfile'}; 143 # collecting files in cabinet files 144 if ( ! exists($allassigns{$cabfilename}) ) 145 { 146 my %onecabfile = (); 147 $onecabfile{$onefile->{'destination'}} = 1; 148 $allassigns{$cabfilename} = \%onecabfile; 149 } 150 else 151 { 152 $allassigns{$cabfilename}->{$onefile->{'destination'}} = 1; 153 } 154 } 155 156 # Sorting each hash and assigning numbers 157 # The destination of the file determines the sort order, not the filename! 158 my $cabfile; 159 foreach $cabfile ( sort keys %allassigns ) 160 { 161 my $counter = $installer::globals::cabfilecounter{$cabfile}; 162 my $dest; 163 foreach $dest ( sort keys %{$allassigns{$cabfile}} ) # <- sorting the destination! 164 { 165 my $directaccessnumber = $directaccess{$dest}; 166 ${$filesref}[$directaccessnumber]->{'assignedsequencenumber'} = $counter; 167 $counter++; 168 } 169 } 170} 171 172######################################################### 173# Create a shorter version of a long component name, 174# because maximum length in msi database is 72. 175# Attention: In multi msi installation sets, the short 176# names have to be unique over all packages, because 177# this string is used to create the globally unique id 178# -> no resetting of 179# %installer::globals::allshortcomponents 180# after a package was created. 181# Using no counter because of reproducibility. 182######################################################### 183 184sub generate_new_short_componentname 185{ 186 my ($componentname) = @_; 187 188 my $startversion = substr($componentname, 0, 60); # taking only the first 60 characters 189 my $subid = installer::windows::msiglobal::calculate_id($componentname, 9); # taking only the first 9 digits 190 my $shortcomponentname = $startversion . "_" . $subid; 191 192 if ( exists($installer::globals::allshortcomponents{$shortcomponentname}) ) { installer::exiter::exit_program("Failed to create unique component name: \"$shortcomponentname\"", "generate_new_short_componentname"); } 193 194 $installer::globals::allshortcomponents{$shortcomponentname} = 1; 195 196 return $shortcomponentname; 197} 198 199############################################### 200# Generating the component name from a file 201############################################### 202 203sub get_file_component_name 204{ 205 my ($fileref, $filesref) = @_; 206 207 my $componentname = ""; 208 209 # Special handling for files with ASSIGNCOMPOMENT 210 211 my $styles = ""; 212 if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } 213 if ( $styles =~ /\bASSIGNCOMPOMENT\b/ ) 214 { 215 $componentname = get_component_from_assigned_file($fileref->{'AssignComponent'}, $filesref); 216 } 217 else 218 { 219 # In this function exists the rule to create components from files 220 # Rule: 221 # Two files get the same componentid, if: 222 # both have the same destination directory. 223 # both have the same "gid" -> both were packed in the same zip file 224 # All other files are included into different components! 225 226 # my $componentname = $fileref->{'gid'} . "_" . $fileref->{'Dir'}; 227 228 # $fileref->{'Dir'} is not sufficient! All files in a zip file have the same $fileref->{'Dir'}, 229 # but can be in different subdirectories. 230 # Solution: destination=share\Scripts\beanshell\Capitalise\capitalise.bsh 231 # in which the filename (capitalise.bsh) has to be removed and all backslashes (slashes) are 232 # converted into underline. 233 234 my $destination = $fileref->{'destination'}; 235 installer::pathanalyzer::get_path_from_fullqualifiedname(\$destination); 236 $destination =~ s/\s//g; 237 $destination =~ s/\\/\_/g; 238 $destination =~ s/\//\_/g; 239 $destination =~ s/\_\s*$//g; # removing ending underline 240 241 $componentname = $fileref->{'gid'} . "__" . $destination; 242 243 # Files with different languages, need to be packed into different components. 244 # Then the installation of the language specific component is determined by a language condition. 245 246 if ( $fileref->{'ismultilingual'} ) 247 { 248 my $officelanguage = $fileref->{'specificlanguage'}; 249 $componentname = $componentname . "_" . $officelanguage; 250 } 251 252 $componentname = lc($componentname); # componentnames always lowercase 253 254 $componentname =~ s/\-/\_/g; # converting "-" to "_" 255 $componentname =~ s/\./\_/g; # converting "-" to "_" 256 257 # Attention: Maximum length for the componentname is 72 258 # %installer::globals::allcomponents_in_this_database : resetted for each database 259 # %installer::globals::allcomponents : not resetted for each database 260 # Component strings must be unique for the complete product, because they are used for 261 # the creation of the globally unique identifier. 262 263 my $fullname = $componentname; # This can be longer than 72 264 265 if (( exists($installer::globals::allcomponents{$fullname}) ) && ( ! exists($installer::globals::allcomponents_in_this_database{$fullname}) )) 266 { 267 # This is not allowed: One component cannot be installed with different packages. 268 installer::exiter::exit_program("ERROR: Component \"$fullname\" is already included into another package. This is not allowed.", "get_file_component_name"); 269 } 270 271 if ( exists($installer::globals::allcomponents{$fullname}) ) 272 { 273 $componentname = $installer::globals::allcomponents{$fullname}; 274 } 275 else 276 { 277 if ( length($componentname) > 70 ) 278 { 279 $componentname = generate_new_short_componentname($componentname); # This has to be unique for the complete product, not only one package 280 } 281 282 $installer::globals::allcomponents{$fullname} = $componentname; 283 $installer::globals::allcomponents_in_this_database{$fullname} = 1; 284 } 285 286 # $componentname =~ s/gid_file_/g_f_/g; 287 # $componentname =~ s/_extra_/_e_/g; 288 # $componentname =~ s/_config_/_c_/g; 289 # $componentname =~ s/_org_openoffice_/_o_o_/g; 290 # $componentname =~ s/_program_/_p_/g; 291 # $componentname =~ s/_typedetection_/_td_/g; 292 # $componentname =~ s/_linguistic_/_l_/g; 293 # $componentname =~ s/_module_/_m_/g; 294 # $componentname =~ s/_optional_/_opt_/g; 295 # $componentname =~ s/_packages/_pack/g; 296 # $componentname =~ s/_menubar/_mb/g; 297 # $componentname =~ s/_common_/_cm_/g; 298 # $componentname =~ s/_export_/_exp_/g; 299 # $componentname =~ s/_table_/_tb_/g; 300 # $componentname =~ s/_sofficecfg_/_sc_/g; 301 # $componentname =~ s/_soffice_cfg_/_sc_/g; 302 # $componentname =~ s/_startmodulecommands_/_smc_/g; 303 # $componentname =~ s/_drawimpresscommands_/_dic_/g; 304 # $componentname =~ s/_basiccommands_/_bac_/g; 305 # $componentname =~ s/_basicidecommands_/_baic_/g; 306 # $componentname =~ s/_genericcommands_/_genc_/g; 307 # $componentname =~ s/_bibliographycommands_/_bibc_/g; 308 # $componentname =~ s/_gentiumbookbasicbolditalic_/_gbbbi_/g; 309 # $componentname =~ s/_share_/_s_/g; 310 # $componentname =~ s/_extension_/_ext_/g; 311 # $componentname =~ s/_extensions_/_exs_/g; 312 # $componentname =~ s/_modules_/_ms_/g; 313 # $componentname =~ s/_uiconfig_zip_/_ucz_/g; 314 # $componentname =~ s/_productivity_/_pr_/g; 315 # $componentname =~ s/_wizard_/_wz_/g; 316 # $componentname =~ s/_import_/_im_/g; 317 # $componentname =~ s/_javascript_/_js_/g; 318 # $componentname =~ s/_template_/_tpl_/g; 319 # $componentname =~ s/_tplwizletter_/_twl_/g; 320 # $componentname =~ s/_beanshell_/_bs_/g; 321 # $componentname =~ s/_presentation_/_bs_/g; 322 # $componentname =~ s/_columns_/_cls_/g; 323 # $componentname =~ s/_python_/_py_/g; 324 325 # $componentname =~ s/_tools/_ts/g; 326 # $componentname =~ s/_transitions/_trs/g; 327 # $componentname =~ s/_scriptbinding/_scrb/g; 328 # $componentname =~ s/_spreadsheet/_ssh/g; 329 # $componentname =~ s/_publisher/_pub/g; 330 # $componentname =~ s/_presenter/_pre/g; 331 # $componentname =~ s/_registry/_reg/g; 332 333 # $componentname =~ s/screen/sc/g; 334 # $componentname =~ s/wordml/wm/g; 335 # $componentname =~ s/openoffice/oo/g; 336 } 337 338 return $componentname; 339} 340 341#################################################################### 342# Returning the component name for a defined file gid. 343# This is necessary for files with flag ASSIGNCOMPOMENT 344#################################################################### 345 346sub get_component_from_assigned_file 347{ 348 my ($gid, $filesref) = @_; 349 350 my $onefile = installer::existence::get_specified_file($filesref, $gid); 351 my $componentname = ""; 352 if ( $onefile->{'componentname'} ) { $componentname = $onefile->{'componentname'}; } 353 else { installer::exiter::exit_program("ERROR: No component defined for file: $gid", "get_component_from_assigned_file"); } 354 355 return $componentname; 356} 357 358#################################################################### 359# Generating the special filename for the database file File.idt 360# Sample: CONTEXTS, CONTEXTS1 361# This name has to be unique. 362# In most cases this is simply the filename. 363#################################################################### 364 365sub generate_unique_filename_for_filetable ($) 366{ 367 my ($oldname) = @_; 368 369 # This new filename has to be saved into $fileref, because this is needed to find the source. 370 # The filename sbasic.idx/OFFSETS is changed to OFFSETS, but OFFSETS is not unique. 371 # In this procedure names like OFFSETS5 are produced. And exactly this string has to be added to 372 # the array of all files. 373 374 my $uniquefilename = $oldname; 375 if ( ! defined $uniquefilename || $uniquefilename eq "") 376 { 377 installer::logger::PrintError("file name does not exist or is empty, can not create unique name for it."); 378 die; 379 return; 380 } 381 382 # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs 383 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$uniquefilename); 384 385 $uniquefilename =~ s/\-/\_/g; # no "-" allowed 386 $uniquefilename =~ s/\@/\_/g; # no "@" allowed 387 $uniquefilename =~ s/\$/\_/g; # no "$" allowed 388 $uniquefilename =~ s/^\s*\./\_/g; # no "." at the beginning allowed allowed 389 $uniquefilename =~ s/^\s*\d/\_d/g; # no number at the beginning allowed allowed (even file "0.gif", replacing to "_d.gif") 390 $uniquefilename =~ s/org_openoffice_/ooo_/g; # shorten the unique file name 391 392 my $lcuniquefilename = lc($uniquefilename); # only lowercase names 393 394 my $newname = 0; 395 396 if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename})) 397 { 398 $installer::globals::alluniquefilenames{$uniquefilename} = 1; 399 $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; 400 $newname = 1; 401 } 402 403 if ( ! $newname ) 404 { 405 # adding a number until the name is really unique: OFFSETS, OFFSETS1, OFFSETS2, ... 406 # But attention: Making "abc.xcu" to "abc1.xcu" 407 408 my $uniquefilenamebase = $uniquefilename; 409 410 my $counter = 0; 411 do 412 { 413 $counter++; 414 415 if ( $uniquefilenamebase =~ /\./ ) 416 { 417 $uniquefilename = $uniquefilenamebase; 418 $uniquefilename =~ s/\./$counter\./; 419 } 420 else 421 { 422 $uniquefilename = $uniquefilenamebase . $counter; 423 } 424 425 $newname = 0; 426 $lcuniquefilename = lc($uniquefilename); # only lowercase names 427 428 if ( ! exists($installer::globals::alllcuniquefilenames{$lcuniquefilename})) 429 { 430 $installer::globals::alluniquefilenames{$uniquefilename} = 1; 431 $installer::globals::alllcuniquefilenames{$lcuniquefilename} = 1; 432 $newname = 1; 433 } 434 } 435 until ( $newname ) 436 } 437 438 return $uniquefilename; 439} 440 441#################################################################### 442# Generating the special file column for the database file File.idt 443# Sample: NAMETR~1.TAB|.nametranslation.table 444# The first part has to be 8.3 conform. 445#################################################################### 446 447sub generate_filename_for_filetable ($$) 448{ 449 my ($fileref, $shortnamesref) = @_; 450 451 my $returnstring = ""; 452 453 my $filename = $fileref->{'Name'}; 454 455 # making /registry/schema/org/openoffice/VCL.xcs to VCL.xcs 456 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$filename); 457 458 my $shortstring = installer::windows::idtglobal::make_eight_three_conform_with_hash($filename, "file", $shortnamesref); 459 460 if ( $shortstring eq $filename ) 461 { 462 # nothing changed 463 $returnstring = $filename; 464 } 465 else 466 { 467 $returnstring = $shortstring . "\|" . $filename; 468 } 469 470 return $returnstring; 471} 472 473######################################### 474# Returning the filesize of a file 475######################################### 476 477sub get_filesize 478{ 479 my ($fileref) = @_; 480 481 my $file = $fileref->{'sourcepath'}; 482 483 my $filesize; 484 485 if ( -f $file ) # test of existence. For instance services.rdb does not always exist 486 { 487 $filesize = ( -s $file ); # file size can be "0" 488 } 489 else 490 { 491 $filesize = -1; 492 } 493 494 return $filesize; 495} 496 497############################################# 498# Returning the file version, if required 499# Sample: "8.0.1.8976"; 500############################################# 501 502sub get_fileversion 503{ 504 my ($onefile, $allvariables) = @_; 505 506 my $fileversion = ""; 507 508 if ( $allvariables->{'USE_FILEVERSION'} ) 509 { 510 if ( ! $allvariables->{'LIBRARYVERSION'} ) 511 { 512 installer::exiter::exit_program("ERROR: USE_FILEVERSION is set, but not LIBRARYVERSION", "get_fileversion"); 513 } 514 my $libraryversion = $allvariables->{'LIBRARYVERSION'}; 515 if ( $libraryversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\s*$/ ) 516 { 517 my $major = $1; 518 my $minor = $2; 519 my $micro = $3; 520 my $concat = 100 * $minor + $micro; 521 $libraryversion = $major . "\." . $concat; 522 } 523 my $vendornumber = 0; 524 if ( $allvariables->{'VENDORPATCHVERSION'} ) 525 { 526 $vendornumber = $allvariables->{'VENDORPATCHVERSION'}; 527 } 528 $fileversion = $libraryversion . "\." . $installer::globals::buildid . "\." . $vendornumber; 529 if ( $onefile->{'FileVersion'} ) 530 { 531 # overriding FileVersion in scp 532 $fileversion = $onefile->{'FileVersion'}; 533 } 534 } 535 536 if ( $installer::globals::prepare_winpatch ) 537 { 538 # Windows patches do not allow this version # -> who says so? 539 $fileversion = ""; 540 } 541 542 return $fileversion; 543} 544 545 546 547 548sub retrieve_sequence_and_uniquename ($$) 549{ 550 my ($file_list, $source_data) = @_; 551 552 my @added_files = (); 553 554 # Read the sequence numbers of the previous version. 555 if ($installer::globals::is_release) 556 { 557 foreach my $file (@$file_list) 558 { 559 # Use the source path of the file as key to retrieve sequence number and unique name. 560 # The source path is the part of the 'destination' without the first part. 561 # There is a special case when 'Dir' is PREDEFINED_OSSHELLNEWDIR. 562 my $source_path; 563 if (defined $file->{'Dir'} && $file->{'Dir'} eq "PREDEFINED_OSSHELLNEWDIR") 564 { 565 $source_path = $installer::globals::templatefoldername 566 . $installer::globals::separator 567 . $file->{'Name'}; 568 } 569 else 570 { 571 $source_path = $file->{'destination'}; 572 $source_path =~ s/^[^\/]+\///; 573 } 574 my ($sequence, $uniquename) = $source_data->get_sequence_and_unique_name($source_path); 575 if (defined $sequence && defined $uniquename) 576 { 577 $file->{'sequencenumber'} = $sequence; 578 $file->{'uniquename'} = $uniquename; 579 } 580 else 581 { 582 # No data found in the source release. File has been added. 583 push @added_files, $file; 584 } 585 } 586 } 587 588 return @added_files; 589} 590 591 592 593 594=head2 assign_mssing_sequence_numbers ($file_list) 595 596 Assign sequence numbers where still missing. 597 598 When we are preparing a patch then all files that have no sequence numbers 599 at this point are new. Otherwise no file has a sequence number yet. 600 601=cut 602sub assign_missing_sequence_numbers ($) 603{ 604 my ($file_list) = @_; 605 606 # First, set up a hash on the sequence numbers that are already in use. 607 my %used_sequence_numbers = (); 608 foreach my $file (@$file_list) 609 { 610 next unless defined $file->{'sequencenumber'}; 611 $used_sequence_numbers{$file->{'sequencenumber'}} = 1; 612 } 613 614 # Assign sequence numbers. Try consecutive numbers, starting at 1. 615 my $current_sequence_number = 1; 616 foreach my $file (@$file_list) 617 { 618 # Skip over all files that already have sequence numbers. 619 next if defined $file->{'sequencenumber'}; 620 621 # Find the next available number. 622 while (defined $used_sequence_numbers{$current_sequence_number}) 623 { 624 ++$current_sequence_number; 625 } 626 627 # Use the number and mark it as used. 628 $file->{'sequencenumber'} = $current_sequence_number; 629 $used_sequence_numbers{$current_sequence_number} = 1; 630 } 631} 632 633 634 635 636sub create_items_for_missing_files ($$$) 637{ 638 my ($missing_items, $source_msi, $directory_list) = @_; 639 640 # For creation of the FeatureComponent table (in a later step) we 641 # have to provide references from the file to component and 642 # modules (ie features). Note that Each file belongs to exactly 643 # one component but one component can belong to multiple features. 644 my $component_to_features_map = create_feature_component_map($source_msi); 645 646 my @new_files = (); 647 foreach my $row (@$missing_items) 648 { 649 $installer::logger::Info->printf("creating new file item for '%s'\n", $row->GetValue('File')); 650 my $file_item = create_script_item_for_deleted_file($row, $source_msi, $component_to_features_map); 651 push @new_files, $file_item; 652 } 653 654 return @new_files; 655} 656 657 658 659 660=head2 create_script_item_for_deleted_file (($file_row, $source_msi, $component_to_features_map) 661 662 Create a new script item for a file that was present in the 663 previous release but isn't anymore. Most of the necessary 664 information is taken from the 'File' table of the source release. 665 666 The values of 'sourcepath' and 'cyg_sourcepath' will point to the 667 respective file in the unpacked source release. An alternative 668 would be to let them point to an empty file. That, however, might 669 make the patch bigger (diff between identical file contents is 670 (almost) empty, diff between file and empty file is the 'inverse' 671 of the file). 672 673=cut 674 675my $use_source_files_for_missing_files = 1; 676 677sub create_script_item_for_deleted_file ($$$) 678{ 679 my ($file_row, $source_msi, $component_to_features_map) = @_; 680 681 my $uniquename = $file_row->GetValue('File'); 682 683 my $file_map = $source_msi->GetFileMap(); 684 685 my $file_item = $file_map->{$uniquename}; 686 my $directory_item = $file_item->{'directory'}; 687 my $source_path = $directory_item->{'full_source_long_name'}; 688 my $target_path = $directory_item->{'full_target_long_name'}; 689 my $full_source_name = undef; 690 if ($use_source_files_for_missing_files) 691 { 692 $full_source_name = File::Spec->catfile( 693 installer::patch::InstallationSet::GetUnpackedCabPath( 694 $source_msi->{'version'}, 695 $source_msi->{'is_current_version'}, 696 $source_msi->{'language'}, 697 $source_msi->{'package_format'}, 698 $source_msi->{'product_name'}), 699 $source_path, 700 $file_item->{'long_name'}); 701 } 702 else 703 { 704 $full_source_name = "/c/tmp/missing/".$uniquename; 705 installer::patch::Tools::touch($full_source_name); 706 } 707 my ($long_name, undef) = installer::patch::Msi::SplitLongShortName($file_row->GetValue("FileName")); 708 my $target_name = File::Spec->catfile($target_path, $long_name); 709 if ( ! -f $full_source_name) 710 { 711 installer::logger::PrintError("can not find file '%s' in previous version (tried '%s')\n", 712 $uniquename, 713 $full_source_name); 714 return undef; 715 } 716 my $cygwin_full_source_name = qx(cygpath -w '$full_source_name'); 717 my $component_name = $file_row->GetValue('Component_'); 718 my $module_names = join(",", @{$component_to_features_map->{$component_name}}); 719 my $sequence_number = $file_row->GetValue('Sequence'); 720 721 return { 722 'uniquename' => $uniquename, 723 'destination' => $target_name, 724 'componentname' => $component_name, 725 'modules' => $module_names, 726 'UnixRights' => 444, 727 'Name' => $long_name, 728 'sourcepath' => $full_source_name, 729 'cyg_sourcepath' => $cygwin_full_source_name, 730 'sequencenumber' => $sequence_number 731 }; 732} 733 734 735 736 737=head2 create_feature_component_maps($msi) 738 739 Return a hash map that maps from component names to arrays of 740 feature names. In most cases the array of features contains only 741 one element. But there can be cases where the number is greater. 742 743=cut 744sub create_feature_component_map ($) 745{ 746 my ($msi) = @_; 747 748 my $component_to_features_map = {}; 749 my $feature_component_table = $msi->GetTable("FeatureComponents"); 750 my $feature_column_index = $feature_component_table->GetColumnIndex("Feature_"); 751 my $component_column_index = $feature_component_table->GetColumnIndex("Component_"); 752 foreach my $row (@{$feature_component_table->GetAllRows()}) 753 { 754 my $feature = $row->GetValue($feature_column_index); 755 my $component = $row->GetValue($component_column_index); 756 if ( ! defined $component_to_features_map->{$component}) 757 { 758 $component_to_features_map->{$component} = [$feature]; 759 } 760 else 761 { 762 push @{$component_to_features_map->{$component}}, $feature; 763 } 764 } 765 766 return $component_to_features_map; 767} 768 769 770############################################# 771# Returning the Windows language of a file 772############################################# 773 774sub get_language_for_file 775{ 776 my ($fileref) = @_; 777 778 my $language = ""; 779 780 if ( $fileref->{'specificlanguage'} ) { $language = $fileref->{'specificlanguage'}; } 781 782 if ( $language eq "" ) 783 { 784 $language = 0; # language independent 785 # If this is not a font, the return value should be "0" (Check ICE 60) 786 my $styles = ""; 787 if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } 788 if ( $styles =~ /\bFONT\b/ ) { $language = ""; } 789 } 790 else 791 { 792 $language = installer::windows::language::get_windows_language($language); 793 } 794 795 return $language; 796} 797 798#################################################################### 799# Creating a new KeyPath for components in TemplatesFolder. 800#################################################################### 801 802sub generate_registry_keypath 803{ 804 my ($onefile) = @_; 805 806 my $keypath = $onefile->{'Name'}; 807 $keypath =~ s/\.//g; 808 $keypath = lc($keypath); 809 $keypath = "userreg_" . $keypath; 810 811 return $keypath; 812} 813 814 815################################################################### 816# Collecting further conditions for the component table. 817# This is used by multilayer products, to enable installation 818# of separate layers. 819################################################################### 820 821sub get_tree_condition_for_component 822{ 823 my ($onefile, $componentname) = @_; 824 825 if ( $onefile->{'destination'} ) 826 { 827 my $dest = $onefile->{'destination'}; 828 829 # Comparing the destination path with 830 # $installer::globals::hostnametreestyles{$hostname} = $treestyle; 831 # (-> hostname is the key, the style the value!) 832 833 foreach my $hostname ( keys %installer::globals::hostnametreestyles ) 834 { 835 if (( $dest eq $hostname ) || ( $dest =~ /^\s*\Q$hostname\E\\/ )) 836 { 837 # the value is the style 838 my $style = $installer::globals::hostnametreestyles{$hostname}; 839 # the condition is saved in %installer::globals::treestyles 840 my $condition = $installer::globals::treestyles{$style}; 841 # Saving condition to be added in table Property 842 $installer::globals::usedtreeconditions{$condition} = 1; 843 $condition = $condition . "=1"; 844 # saving this condition 845 $installer::globals::treeconditions{$componentname} = $condition; 846 847 # saving also at the file, for usage in fileinfo 848 $onefile->{'layer'} = $installer::globals::treelayername{$style}; 849 } 850 } 851 } 852} 853 854############################################ 855# Collecting all short names, that are 856# already used by the old database 857############################################ 858 859sub collect_shortnames_from_old_database 860{ 861 my ($uniquefilenamehashref, $shortnameshashref) = @_; 862 863 foreach my $key ( keys %{$uniquefilenamehashref} ) 864 { 865 my $value = $uniquefilenamehashref->{$key}; # syntax of $value: ($uniquename;$shortname) 866 867 if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ ) 868 { 869 my $shortstring = $2; 870 $shortnameshashref->{$shortstring} = 1; # adding the shortname to the array of all shortnames 871 } 872 } 873} 874 875 876sub process_language_conditions ($) 877{ 878 my ($onefile) = @_; 879 880 # Collecting all languages specific conditions 881 if ( $onefile->{'ismultilingual'} ) 882 { 883 if ( $onefile->{'ComponentCondition'} ) 884 { 885 installer::exiter::exit_program( 886 "ERROR: Cannot set language condition. There is already another component condition for file $onefile->{'gid'}: \"$onefile->{'ComponentCondition'}\" !", "create_files_table"); 887 } 888 889 if ( $onefile->{'specificlanguage'} eq "" ) 890 { 891 installer::exiter::exit_program( 892 "ERROR: There is no specific language for file at language module: $onefile->{'gid'} !", "create_files_table"); 893 } 894 my $locallanguage = $onefile->{'specificlanguage'}; 895 my $property = "IS" . $onefile->{'windows_language'}; 896 my $value = 1; 897 my $condition = $property . "=" . $value; 898 899 $onefile->{'ComponentCondition'} = $condition; 900 901 if ( exists($installer::globals::componentcondition{$onefile->{'componentname'}})) 902 { 903 if ( $installer::globals::componentcondition{$onefile->{'componentname'}} ne $condition ) 904 { 905 installer::exiter::exit_program( 906 sprintf( 907 "ERROR: There is already another component condition for file %s: \"%s\" and \"%s\" !", 908 $onefile->{'gid'}, 909 $installer::globals::componentcondition{$onefile->{'componentname'}}, 910 $condition), 911 "create_files_table"); 912 } 913 } 914 else 915 { 916 $installer::globals::componentcondition{$onefile->{'componentname'}} = $condition; 917 } 918 919 # collecting all properties for table Property 920 if ( ! exists($installer::globals::languageproperties{$property}) ) 921 { 922 $installer::globals::languageproperties{$property} = $value; 923 } 924 } 925} 926 927 928 929 930sub has_style ($$) 931{ 932 my ($style_list_string, $style_name) = @_; 933 934 return 0 unless defined $style_list_string; 935 return $style_list_string =~ /\b$style_name\b/ ? 1 : 0; 936} 937 938 939 940 941sub prepare_file_table_creation ($$$) 942{ 943 my ($file_list, $directory_list, $allvariables) = @_; 944 945 if ( $^O =~ /cygwin/i ) 946 { 947 installer::worker::generate_cygwin_pathes($file_list); 948 } 949 950 # Reset the fields 'sequencenumber' and 'uniquename'. They should not yet exist but better be sure. 951 foreach my $file (@$file_list) 952 { 953 delete $file->{'sequencenumber'}; 954 delete $file->{'uniquename'}; 955 } 956 957 # Create FileSequenceList object for the old sequence data. 958 if (defined $installer::globals::source_msi) 959 { 960 my $previous_sequence_data = new installer::patch::FileSequenceList(); 961 $previous_sequence_data->SetFromMsi($installer::globals::source_msi); 962 my @added_files = retrieve_sequence_and_uniquename($file_list, $previous_sequence_data); 963 964 # Extract just the unique names. 965 my %target_unique_names = map {$_->{'uniquename'} => 1} @$file_list; 966 my @removed_items = $previous_sequence_data->get_removed_files(\%target_unique_names); 967 968 $installer::logger::Lang->printf( 969 "there are %d files that have been removed from source and %d files added\n", 970 scalar @removed_items, 971 scalar @added_files); 972 973 my $file_map = $installer::globals::source_msi->GetFileMap(); 974 my $index = 0; 975 foreach my $removed_row (@removed_items) 976 { 977 $installer::logger::Lang->printf(" removed file %d: %s\n", 978 ++$index, 979 $removed_row->GetValue('File')); 980 my $directory = $file_map->{$removed_row->GetValue('File')}->{'directory'}; 981 while (my ($key,$value) = each %$directory) 982 { 983 $installer::logger::Lang->printf(" %16s -> %s\n", $key, $value); 984 } 985 } 986 $index = 0; 987 foreach my $added_file (@added_files) 988 { 989 $installer::logger::Lang->printf(" added file %d: %s\n", 990 ++$index, 991 $added_file->{'uniquename'}); 992 installer::scriptitems::print_script_item($added_file); 993 } 994 my @new_files = create_items_for_missing_files( 995 \@removed_items, 996 $installer::globals::source_msi, 997 $directory_list); 998 push @$file_list, @new_files; 999 } 1000 assign_missing_sequence_numbers($file_list); 1001 1002 foreach my $file (@$file_list) 1003 { 1004 if ( ! defined $file->{'componentname'}) 1005 { 1006 $file->{'componentname'} = get_file_component_name($file, $file_list); 1007 } 1008 if ( ! defined $file->{'uniquename'}) 1009 { 1010 $file->{'uniquename'} = generate_unique_filename_for_filetable($file->{'Name'}); 1011 } 1012 1013 # Collecting all component conditions 1014 if ( $file->{'ComponentCondition'} ) 1015 { 1016 if ( ! exists($installer::globals::componentcondition{$file->{'componentname'}})) 1017 { 1018 $installer::globals::componentcondition{$file->{'componentname'}} 1019 = $file->{'ComponentCondition'}; 1020 } 1021 } 1022 # Collecting also all tree conditions for multilayer products 1023 get_tree_condition_for_component($file, $file->{'componentname'}); 1024 1025 # Collecting all component names, that have flag VERSION_INDEPENDENT_COMP_ID 1026 # This should be all components with constant API, for example URE 1027 if (has_style($file->{'Styles'}, "VERSION_INDEPENDENT_COMP_ID")) 1028 { 1029 $installer::globals::base_independent_components{$file->{'componentname'}} = 1; 1030 } 1031 1032 # Special handling for files in PREDEFINED_OSSHELLNEWDIR. These components 1033 # need as KeyPath a RegistryItem in HKCU 1034 if ($file->{'needs_user_registry_key'} 1035 || (defined $file->{'Dir'} && $file->{'Dir'} =~ /\bPREDEFINED_OSSHELLNEWDIR\b/)) 1036 { 1037 my $keypath = generate_registry_keypath($file); 1038 $file->{'userregkeypath'} = $keypath; 1039 push(@installer::globals::userregistrycollector, $file); 1040 $installer::globals::addeduserregitrykeys = 1; 1041 } 1042 1043 $file->{'windows_language'} = get_language_for_file($file); 1044 1045 process_language_conditions($file); 1046 } 1047 1048 # The filenames must be collected because of uniqueness 1049 # 01-44-~1.DAT, 01-44-~2.DAT, ... 1050 my %shortnames = (); 1051 foreach my $file (@$file_list) 1052 { 1053 $file->{'short_name'} = generate_filename_for_filetable($file, \%shortnames); 1054 } 1055} 1056 1057 1058 1059 1060sub create_file_table_data ($$) 1061{ 1062 my ($file_list, $allvariables) = @_; 1063 1064 my @file_table_data = (); 1065 foreach my $file (@$file_list) 1066 { 1067 my $attributes; 1068 if (has_style($file->{'Styles'}, "DONT_PACK")) 1069 { 1070 # Sourcefile is unpacked (msidbFileAttributesNoncompressed). 1071 $attributes = "8192"; 1072 } 1073 else 1074 { 1075 # Sourcefile is packed (msidbFileAttributesCompressed). 1076 $attributes = "16384"; 1077 } 1078 1079 my $row_data = { 1080 'File' => $file->{'uniquename'}, 1081 'Component_' => $file->{'componentname'}, 1082 'FileName' => $file->{'short_name'}, 1083 'FileSize' => get_filesize($file), 1084 'Version' => get_fileversion($file, $allvariables), 1085 'Language' => $file->{'windows_language'}, 1086 'Attributes' => $attributes, 1087 'Sequence' => $file->{'sequencenumber'} 1088 }; 1089 push @file_table_data, $row_data; 1090 } 1091 1092 return \@file_table_data; 1093} 1094 1095 1096 1097 1098sub collect_components ($) 1099{ 1100 my ($file_list) = @_; 1101 1102 my %components = (); 1103 foreach my $file (@$file_list) 1104 { 1105 $components{$file->{'componentname'}} = 1; 1106 } 1107 return keys %components; 1108} 1109 1110 1111 1112 1113=head filter_files($file_list, $allvariables) 1114 1115 Filter out Java files when not building a Java product. 1116 1117 Is this still triggered? 1118 1119=cut 1120sub filter_files ($$) 1121{ 1122 my ($file_list, $allvariables) = @_; 1123 1124 if ($allvariables->{'JAVAPRODUCT'}) 1125 { 1126 return $file_list; 1127 } 1128 else 1129 { 1130 my @filtered_files = (); 1131 foreach my $file (@$file_list) 1132 { 1133 if ( ! has_style($file->{'Styles'}, "JAVAFILE")) 1134 { 1135 push @filtered_files, $file; 1136 } 1137 } 1138 return \@filtered_files; 1139 } 1140} 1141 1142 1143 1144 1145# Structure of the files table: 1146# File Component_ FileName FileSize Version Language Attributes Sequence 1147sub create_file_table ($$) 1148{ 1149 my ($file_table_data, $basedir) = @_; 1150 1151 # Set up the 'File' table. 1152 my @filetable = (); 1153 installer::windows::idtglobal::write_idt_header(\@filetable, "file"); 1154 my @keys = ('File', 'Component_', 'FileName', 'FileSize', 'Version', 'Language', 'Attributes', 'Sequence'); 1155 my $index = 0; 1156 foreach my $row_data (@$file_table_data) 1157 { 1158 ++$index; 1159 my @values = map {$row_data->{$_}} @keys; 1160 my $line = join("\t", @values) . "\n"; 1161 push(@filetable, $line); 1162 } 1163 1164 my $filetablename = $basedir . $installer::globals::separator . "File.idt"; 1165 installer::files::save_file($filetablename ,\@filetable); 1166 $installer::logger::Lang->print("\n"); 1167 $installer::logger::Lang->printf("Created idt file: %s\n", $filetablename); 1168} 1169 1170 1171 1172 1173sub create_filehash_table ($$) 1174{ 1175 my ($file_list, $basedir) = @_; 1176 1177 my @filehashtable = (); 1178 1179 if ( $installer::globals::prepare_winpatch ) 1180 { 1181 1182 installer::windows::idtglobal::write_idt_header(\@filehashtable, "filehash"); 1183 1184 foreach my $file (@$file_list) 1185 { 1186 my $path = $file->{'sourcepath'}; 1187 if ($^O =~ /cygwin/i) 1188 { 1189 $path = $file->{'cyg_sourcepath'}; 1190 } 1191 1192 open(FILE, $path) or die "ERROR: Can't open $path for creating file hash"; 1193 binmode(FILE); 1194 my $hashinfo = pack("l", 20); 1195 $hashinfo .= Digest::MD5->new->addfile(*FILE)->digest; 1196 1197 my @i = unpack ('x[l]l4', $hashinfo); 1198 my $oneline = join("\t", 1199 ( 1200 $file->{'uniquename'}, 1201 "0", 1202 @i 1203 )); 1204 push (@filehashtable, $oneline . "\n"); 1205 } 1206 1207 my $filehashtablename = $basedir . $installer::globals::separator . "MsiFileHash.idt"; 1208 installer::files::save_file($filehashtablename ,\@filehashtable); 1209 $installer::logger::Lang->print("\n"); 1210 $installer::logger::Lang->printf("Created idt file: %s\n", $filehashtablename); 1211 } 1212} 1213 1214 12151; 1216