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, $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($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, $msi, $component_to_features_map); 651 push @new_files, $file_item; 652 } 653 654 return @new_files; 655} 656 657 658 659 660sub create_script_item_for_deleted_file ($$$) 661{ 662 my ($file_row, $msi, $component_to_features_map) = @_; 663 664 my $uniquename = $file_row->GetValue('File'); 665 666 my $file_map = $msi->GetFileMap(); 667 668 my $directory_item = $file_map->{$uniquename}->{'directory'}; 669 my $source_path = $directory_item->{'full_source_long_name'}; 670 my $target_path = $directory_item->{'full_target_long_name'}; 671 my $full_source_name = File::Spec->catfile( 672 installer::patch::InstallationSet::GetUnpackedCabPath( 673 $msi->{'version'}, 674 $msi->{'is_current_version'}, 675 $msi->{'language'}, 676 $msi->{'package_format'}, 677 $msi->{'product_name'}), 678 $source_path, 679 $uniquename); 680 my ($long_name, undef) = installer::patch::Msi::SplitLongShortName($file_row->GetValue("FileName")); 681 my $target_name = File::Spec->catfile($target_path, $long_name); 682 if ( ! -f $full_source_name) 683 { 684 installer::logger::PrintError("can not find file '%s' in previous version (tried '%s')\n", 685 $uniquename, 686 $full_source_name); 687 return undef; 688 } 689 my $cygwin_full_source_name = qx(cygpath -w '$full_source_name'); 690 my $component_name = $file_row->GetValue('Component_'); 691 my $module_names = join(",", @{$component_to_features_map->{$component_name}}); 692 my $sequence_number = $file_row->GetValue('Sequence'); 693 694 return { 695 'uniquename' => $uniquename, 696 'destination' => $target_name, 697 'componentname' => $component_name, 698 'modules' => $module_names, 699 'UnixRights' => 444, 700 'Name' => $long_name, 701 'sourcepath' => $full_source_name, 702 'cyg_sourcepath' => $cygwin_full_source_name, 703 'sequencenumber' => $sequence_number 704 }; 705} 706 707 708 709 710=head2 create_feature_component_maps($msi) 711 712 Return a hash map that maps from component names to arrays of 713 feature names. In most cases the array of features contains only 714 one element. But there can be cases where the number is greater. 715 716=cut 717sub create_feature_component_map ($) 718{ 719 my ($msi) = @_; 720 721 my $component_to_features_map = {}; 722 my $feature_component_table = $msi->GetTable("FeatureComponents"); 723 my $feature_column_index = $feature_component_table->GetColumnIndex("Feature_"); 724 my $component_column_index = $feature_component_table->GetColumnIndex("Component_"); 725 foreach my $row (@{$feature_component_table->GetAllRows()}) 726 { 727 my $feature = $row->GetValue($feature_column_index); 728 my $component = $row->GetValue($component_column_index); 729 if ( ! defined $component_to_features_map->{$component}) 730 { 731 $component_to_features_map->{$component} = [$feature]; 732 } 733 else 734 { 735 push @{$component_to_features_map->{$component}}, $feature; 736 } 737 } 738 739 return $component_to_features_map; 740} 741 742 743############################################# 744# Returning the Windows language of a file 745############################################# 746 747sub get_language_for_file 748{ 749 my ($fileref) = @_; 750 751 my $language = ""; 752 753 if ( $fileref->{'specificlanguage'} ) { $language = $fileref->{'specificlanguage'}; } 754 755 if ( $language eq "" ) 756 { 757 $language = 0; # language independent 758 # If this is not a font, the return value should be "0" (Check ICE 60) 759 my $styles = ""; 760 if ( $fileref->{'Styles'} ) { $styles = $fileref->{'Styles'}; } 761 if ( $styles =~ /\bFONT\b/ ) { $language = ""; } 762 } 763 else 764 { 765 $language = installer::windows::language::get_windows_language($language); 766 } 767 768 return $language; 769} 770 771#################################################################### 772# Creating a new KeyPath for components in TemplatesFolder. 773#################################################################### 774 775sub generate_registry_keypath 776{ 777 my ($onefile) = @_; 778 779 my $keypath = $onefile->{'Name'}; 780 $keypath =~ s/\.//g; 781 $keypath = lc($keypath); 782 $keypath = "userreg_" . $keypath; 783 784 return $keypath; 785} 786 787 788################################################################### 789# Collecting further conditions for the component table. 790# This is used by multilayer products, to enable installation 791# of separate layers. 792################################################################### 793 794sub get_tree_condition_for_component 795{ 796 my ($onefile, $componentname) = @_; 797 798 if ( $onefile->{'destination'} ) 799 { 800 my $dest = $onefile->{'destination'}; 801 802 # Comparing the destination path with 803 # $installer::globals::hostnametreestyles{$hostname} = $treestyle; 804 # (-> hostname is the key, the style the value!) 805 806 foreach my $hostname ( keys %installer::globals::hostnametreestyles ) 807 { 808 if (( $dest eq $hostname ) || ( $dest =~ /^\s*\Q$hostname\E\\/ )) 809 { 810 # the value is the style 811 my $style = $installer::globals::hostnametreestyles{$hostname}; 812 # the condition is saved in %installer::globals::treestyles 813 my $condition = $installer::globals::treestyles{$style}; 814 # Saving condition to be added in table Property 815 $installer::globals::usedtreeconditions{$condition} = 1; 816 $condition = $condition . "=1"; 817 # saving this condition 818 $installer::globals::treeconditions{$componentname} = $condition; 819 820 # saving also at the file, for usage in fileinfo 821 $onefile->{'layer'} = $installer::globals::treelayername{$style}; 822 } 823 } 824 } 825} 826 827############################################ 828# Collecting all short names, that are 829# already used by the old database 830############################################ 831 832sub collect_shortnames_from_old_database 833{ 834 my ($uniquefilenamehashref, $shortnameshashref) = @_; 835 836 foreach my $key ( keys %{$uniquefilenamehashref} ) 837 { 838 my $value = $uniquefilenamehashref->{$key}; # syntax of $value: ($uniquename;$shortname) 839 840 if ( $value =~ /^\s*(.*?)\;\s*(.*?)\s*$/ ) 841 { 842 my $shortstring = $2; 843 $shortnameshashref->{$shortstring} = 1; # adding the shortname to the array of all shortnames 844 } 845 } 846} 847 848 849sub process_language_conditions ($) 850{ 851 my ($onefile) = @_; 852 853 # Collecting all languages specific conditions 854 if ( $onefile->{'ismultilingual'} ) 855 { 856 if ( $onefile->{'ComponentCondition'} ) 857 { 858 installer::exiter::exit_program( 859 "ERROR: Cannot set language condition. There is already another component condition for file $onefile->{'gid'}: \"$onefile->{'ComponentCondition'}\" !", "create_files_table"); 860 } 861 862 if ( $onefile->{'specificlanguage'} eq "" ) 863 { 864 installer::exiter::exit_program( 865 "ERROR: There is no specific language for file at language module: $onefile->{'gid'} !", "create_files_table"); 866 } 867 my $locallanguage = $onefile->{'specificlanguage'}; 868 my $property = "IS" . $onefile->{'windows_language'}; 869 my $value = 1; 870 my $condition = $property . "=" . $value; 871 872 $onefile->{'ComponentCondition'} = $condition; 873 874 if ( exists($installer::globals::componentcondition{$onefile->{'componentname'}})) 875 { 876 if ( $installer::globals::componentcondition{$onefile->{'componentname'}} ne $condition ) 877 { 878 installer::exiter::exit_program( 879 sprintf( 880 "ERROR: There is already another component condition for file %s: \"%s\" and \"%s\" !", 881 $onefile->{'gid'}, 882 $installer::globals::componentcondition{$onefile->{'componentname'}}, 883 $condition), 884 "create_files_table"); 885 } 886 } 887 else 888 { 889 $installer::globals::componentcondition{$onefile->{'componentname'}} = $condition; 890 } 891 892 # collecting all properties for table Property 893 if ( ! exists($installer::globals::languageproperties{$property}) ) 894 { 895 $installer::globals::languageproperties{$property} = $value; 896 } 897 } 898} 899 900 901 902 903sub has_style ($$) 904{ 905 my ($style_list_string, $style_name) = @_; 906 907 return 0 unless defined $style_list_string; 908 return $style_list_string =~ /\b$style_name\b/ ? 1 : 0; 909} 910 911 912 913 914sub prepare_file_table_creation ($$$) 915{ 916 my ($file_list, $directory_list, $allvariables) = @_; 917 918 if ( $^O =~ /cygwin/i ) 919 { 920 installer::worker::generate_cygwin_pathes($file_list); 921 } 922 923 # Reset the fields 'sequencenumber' and 'uniquename'. They should not yet exist but better be sure. 924 foreach my $file (@$file_list) 925 { 926 delete $file->{'sequencenumber'}; 927 delete $file->{'uniquename'}; 928 } 929 930 # Create FileSequenceList object for the old sequence data. 931 if (defined $installer::globals::source_msi) 932 { 933 my $previous_sequence_data = new installer::patch::FileSequenceList(); 934 $previous_sequence_data->SetFromMsi($installer::globals::source_msi); 935 my @added_files = retrieve_sequence_and_uniquename($file_list, $previous_sequence_data); 936 937 # Extract just the unique names. 938 my %target_unique_names = map {$_->{'uniquename'} => 1} @$file_list; 939 my @removed_items = $previous_sequence_data->get_removed_files(\%target_unique_names); 940 941 $installer::logger::Lang->printf( 942 "there are %d files that have been removed from source and %d files added\n", 943 scalar @removed_items, 944 scalar @added_files); 945 946 my $file_map = $installer::globals::source_msi->GetFileMap(); 947 my $index = 0; 948 foreach my $removed_row (@removed_items) 949 { 950 $installer::logger::Lang->printf(" removed file %d: %s\n", 951 ++$index, 952 $removed_row->GetValue('File')); 953 my $directory = $file_map->{$removed_row->GetValue('File')}->{'directory'}; 954 while (my ($key,$value) = each %$directory) 955 { 956 $installer::logger::Lang->printf(" %16s -> %s\n", $key, $value); 957 } 958 } 959 $index = 0; 960 foreach my $added_file (@added_files) 961 { 962 $installer::logger::Lang->printf(" added file %d: %s\n", 963 ++$index, 964 $added_file->{'uniquename'}); 965 installer::scriptitems::print_script_item($added_file); 966 } 967 my @new_files = create_items_for_missing_files( 968 \@removed_items, 969 $installer::globals::source_msi, 970 $directory_list); 971 push @$file_list, @new_files; 972 } 973 assign_missing_sequence_numbers($file_list); 974 975 foreach my $file (@$file_list) 976 { 977 if ( ! defined $file->{'componentname'}) 978 { 979 $file->{'componentname'} = get_file_component_name($file, $file_list); 980 } 981 if ( ! defined $file->{'uniquename'}) 982 { 983 $file->{'uniquename'} = generate_unique_filename_for_filetable($file->{'Name'}); 984 } 985 986 # Collecting all component conditions 987 if ( $file->{'ComponentCondition'} ) 988 { 989 if ( ! exists($installer::globals::componentcondition{$file->{'componentname'}})) 990 { 991 $installer::globals::componentcondition{$file->{'componentname'}} 992 = $file->{'ComponentCondition'}; 993 } 994 } 995 # Collecting also all tree conditions for multilayer products 996 get_tree_condition_for_component($file, $file->{'componentname'}); 997 998 # Collecting all component names, that have flag VERSION_INDEPENDENT_COMP_ID 999 # This should be all components with constant API, for example URE 1000 if (has_style($file->{'Styles'}, "VERSION_INDEPENDENT_COMP_ID")) 1001 { 1002 $installer::globals::base_independent_components{$file->{'componentname'}} = 1; 1003 } 1004 1005 # Special handling for files in PREDEFINED_OSSHELLNEWDIR. These components 1006 # need as KeyPath a RegistryItem in HKCU 1007 if ($file->{'needs_user_registry_key'} 1008 || (defined $file->{'Dir'} && $file->{'Dir'} =~ /\bPREDEFINED_OSSHELLNEWDIR\b/)) 1009 { 1010 my $keypath = generate_registry_keypath($file); 1011 $file->{'userregkeypath'} = $keypath; 1012 push(@installer::globals::userregistrycollector, $file); 1013 $installer::globals::addeduserregitrykeys = 1; 1014 } 1015 1016 $file->{'windows_language'} = get_language_for_file($file); 1017 1018 process_language_conditions($file); 1019 } 1020 1021 # The filenames must be collected because of uniqueness 1022 # 01-44-~1.DAT, 01-44-~2.DAT, ... 1023 my %shortnames = (); 1024 foreach my $file (@$file_list) 1025 { 1026 $file->{'short_name'} = generate_filename_for_filetable($file, \%shortnames); 1027 } 1028} 1029 1030 1031 1032 1033sub create_file_table_data ($$) 1034{ 1035 my ($file_list, $allvariables) = @_; 1036 1037 my @file_table_data = (); 1038 foreach my $file (@$file_list) 1039 { 1040 my $attributes; 1041 if (has_style($file->{'Styles'}, "DONT_PACK")) 1042 { 1043 # Sourcefile is unpacked (msidbFileAttributesNoncompressed). 1044 $attributes = "8192"; 1045 } 1046 else 1047 { 1048 # Sourcefile is packed (msidbFileAttributesCompressed). 1049 $attributes = "16384"; 1050 } 1051 1052 my $row_data = { 1053 'File' => $file->{'uniquename'}, 1054 'Component_' => $file->{'componentname'}, 1055 'FileName' => $file->{'short_name'}, 1056 'FileSize' => get_filesize($file), 1057 'Version' => get_fileversion($file, $allvariables), 1058 'Language' => $file->{'windows_language'}, 1059 'Attributes' => $attributes, 1060 'Sequence' => $file->{'sequencenumber'} 1061 }; 1062 push @file_table_data, $row_data; 1063 } 1064 1065 return \@file_table_data; 1066} 1067 1068 1069 1070 1071sub collect_components ($) 1072{ 1073 my ($file_list) = @_; 1074 1075 my %components = (); 1076 foreach my $file (@$file_list) 1077 { 1078 $components{$file->{'componentname'}} = 1; 1079 } 1080 return keys %components; 1081} 1082 1083 1084 1085 1086=head filter_files($file_list, $allvariables) 1087 1088 Filter out Java files when not building a Java product. 1089 1090 Is this still triggered? 1091 1092=cut 1093sub filter_files ($$) 1094{ 1095 my ($file_list, $allvariables) = @_; 1096 1097 if ($allvariables->{'JAVAPRODUCT'}) 1098 { 1099 return $file_list; 1100 } 1101 else 1102 { 1103 my @filtered_files = (); 1104 foreach my $file (@$file_list) 1105 { 1106 if ( ! has_style($file->{'Styles'}, "JAVAFILE")) 1107 { 1108 push @filtered_files, $file; 1109 } 1110 } 1111 return \@filtered_files; 1112 } 1113} 1114 1115 1116 1117 1118# Structure of the files table: 1119# File Component_ FileName FileSize Version Language Attributes Sequence 1120sub create_file_table ($$) 1121{ 1122 my ($file_table_data, $basedir) = @_; 1123 1124 # Set up the 'File' table. 1125 my @filetable = (); 1126 installer::windows::idtglobal::write_idt_header(\@filetable, "file"); 1127 my @keys = ('File', 'Component_', 'FileName', 'FileSize', 'Version', 'Language', 'Attributes', 'Sequence'); 1128 my $index = 0; 1129 foreach my $row_data (@$file_table_data) 1130 { 1131 ++$index; 1132 my @values = map {$row_data->{$_}} @keys; 1133 my $line = join("\t", @values) . "\n"; 1134 push(@filetable, $line); 1135 } 1136 1137 my $filetablename = $basedir . $installer::globals::separator . "File.idt"; 1138 installer::files::save_file($filetablename ,\@filetable); 1139 $installer::logger::Lang->print("\n"); 1140 $installer::logger::Lang->printf("Created idt file: %s\n", $filetablename); 1141} 1142 1143 1144 1145 1146sub create_filehash_table ($$) 1147{ 1148 my ($file_list, $basedir) = @_; 1149 1150 my @filehashtable = (); 1151 1152 if ( $installer::globals::prepare_winpatch ) 1153 { 1154 1155 installer::windows::idtglobal::write_idt_header(\@filehashtable, "filehash"); 1156 1157 foreach my $file (@$file_list) 1158 { 1159 my $path = $file->{'sourcepath'}; 1160 if ($^O =~ /cygwin/i) 1161 { 1162 $path = $file->{'cyg_sourcepath'}; 1163 } 1164 1165 open(FILE, $path) or die "ERROR: Can't open $path for creating file hash"; 1166 binmode(FILE); 1167 my $hashinfo = pack("l", 20); 1168 $hashinfo .= Digest::MD5->new->addfile(*FILE)->digest; 1169 1170 my @i = unpack ('x[l]l4', $hashinfo); 1171 my $oneline = join("\t", 1172 ( 1173 $file->{'uniquename'}, 1174 "0", 1175 @i 1176 )); 1177 push (@filehashtable, $oneline . "\n"); 1178 } 1179 1180 my $filehashtablename = $basedir . $installer::globals::separator . "MsiFileHash.idt"; 1181 installer::files::save_file($filehashtablename ,\@filehashtable); 1182 $installer::logger::Lang->print("\n"); 1183 $installer::logger::Lang->printf("Created idt file: %s\n", $filehashtablename); 1184 } 1185} 1186 1187 11881; 1189