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