xref: /AOO41X/main/solenv/bin/modules/installer/patch/Msi.pm (revision c9b362f6b4b94fc79a706186f718dbfbc8ea72b0)
1*c9b362f6SAndre Fischer#**************************************************************
2*c9b362f6SAndre Fischer#
3*c9b362f6SAndre Fischer#  Licensed to the Apache Software Foundation (ASF) under one
4*c9b362f6SAndre Fischer#  or more contributor license agreements.  See the NOTICE file
5*c9b362f6SAndre Fischer#  distributed with this work for additional information
6*c9b362f6SAndre Fischer#  regarding copyright ownership.  The ASF licenses this file
7*c9b362f6SAndre Fischer#  to you under the Apache License, Version 2.0 (the
8*c9b362f6SAndre Fischer#  "License"); you may not use this file except in compliance
9*c9b362f6SAndre Fischer#  with the License.  You may obtain a copy of the License at
10*c9b362f6SAndre Fischer#
11*c9b362f6SAndre Fischer#    http://www.apache.org/licenses/LICENSE-2.0
12*c9b362f6SAndre Fischer#
13*c9b362f6SAndre Fischer#  Unless required by applicable law or agreed to in writing,
14*c9b362f6SAndre Fischer#  software distributed under the License is distributed on an
15*c9b362f6SAndre Fischer#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16*c9b362f6SAndre Fischer#  KIND, either express or implied.  See the License for the
17*c9b362f6SAndre Fischer#  specific language governing permissions and limitations
18*c9b362f6SAndre Fischer#  under the License.
19*c9b362f6SAndre Fischer#
20*c9b362f6SAndre Fischer#**************************************************************
21*c9b362f6SAndre Fischer
22*c9b362f6SAndre Fischerpackage installer::patch::Msi;
23*c9b362f6SAndre Fischer
24*c9b362f6SAndre Fischeruse installer::patch::MsiTable;
25*c9b362f6SAndre Fischeruse installer::patch::Tools;
26*c9b362f6SAndre Fischeruse strict;
27*c9b362f6SAndre Fischer
28*c9b362f6SAndre Fischer
29*c9b362f6SAndre Fischer=head1 NAME
30*c9b362f6SAndre Fischer
31*c9b362f6SAndre Fischer    package installer::patch::Msi - Class represents a single MSI file and gives access to its tables.
32*c9b362f6SAndre Fischer
33*c9b362f6SAndre Fischer=cut
34*c9b362f6SAndre Fischer
35*c9b362f6SAndre Fischer
36*c9b362f6SAndre Fischer
37*c9b362f6SAndre Fischer=head2 new($class, $version, $language, $product_name)
38*c9b362f6SAndre Fischer
39*c9b362f6SAndre Fischer    Create a new object of the Msi class.  The values of $version, $language, and $product_name define
40*c9b362f6SAndre Fischer    where to look for the msi file.
41*c9b362f6SAndre Fischer
42*c9b362f6SAndre Fischer    If construction fails then IsValid() will return false.
43*c9b362f6SAndre Fischer
44*c9b362f6SAndre Fischer=cut
45*c9b362f6SAndre Fischersub new ($$$$)
46*c9b362f6SAndre Fischer{
47*c9b362f6SAndre Fischer    my ($class, $version, $language, $product_name) = @_;
48*c9b362f6SAndre Fischer
49*c9b362f6SAndre Fischer    my $path = installer::patch::InstallationSet::GetUnpackedMsiPath(
50*c9b362f6SAndre Fischer        $version,
51*c9b362f6SAndre Fischer        $language,
52*c9b362f6SAndre Fischer        "msi",
53*c9b362f6SAndre Fischer        $product_name);
54*c9b362f6SAndre Fischer
55*c9b362f6SAndre Fischer    # Find the msi in the path.
56*c9b362f6SAndre Fischer    my $filename = undef;
57*c9b362f6SAndre Fischer    if ( -d $path)
58*c9b362f6SAndre Fischer    {
59*c9b362f6SAndre Fischer        my @msi_files = glob(File::Spec->catfile($path, "*.msi"));
60*c9b362f6SAndre Fischer        if (scalar @msi_files != 1)
61*c9b362f6SAndre Fischer        {
62*c9b362f6SAndre Fischer            printf STDERR ("there are %d msi files in %s, should be 1", scalar @msi_files, $filename);
63*c9b362f6SAndre Fischer            $filename = "";
64*c9b362f6SAndre Fischer        }
65*c9b362f6SAndre Fischer        else
66*c9b362f6SAndre Fischer        {
67*c9b362f6SAndre Fischer            $filename = $msi_files[0];
68*c9b362f6SAndre Fischer        }
69*c9b362f6SAndre Fischer    }
70*c9b362f6SAndre Fischer    else
71*c9b362f6SAndre Fischer    {
72*c9b362f6SAndre Fischer        installer::logger::PrintError("can not access path '%s' to find msi\n", $path);
73*c9b362f6SAndre Fischer        return undef;
74*c9b362f6SAndre Fischer    }
75*c9b362f6SAndre Fischer
76*c9b362f6SAndre Fischer    if ( ! -f $filename)
77*c9b362f6SAndre Fischer    {
78*c9b362f6SAndre Fischer        installer::logger::PrintError("can not access MSI file at '%s'\n", $filename);
79*c9b362f6SAndre Fischer        return undef;
80*c9b362f6SAndre Fischer    }
81*c9b362f6SAndre Fischer
82*c9b362f6SAndre Fischer    my $self = {
83*c9b362f6SAndre Fischer        'filename' => $filename,
84*c9b362f6SAndre Fischer        'path' => $path,
85*c9b362f6SAndre Fischer        'version' => $version,
86*c9b362f6SAndre Fischer        'language' => $language,
87*c9b362f6SAndre Fischer        'package_format' => "msi",
88*c9b362f6SAndre Fischer        'product_name' => $product_name,
89*c9b362f6SAndre Fischer        'tmpdir' => File::Temp->newdir(CLEANUP => 1),
90*c9b362f6SAndre Fischer        'is_valid' => -f $filename
91*c9b362f6SAndre Fischer    };
92*c9b362f6SAndre Fischer    bless($self, $class);
93*c9b362f6SAndre Fischer
94*c9b362f6SAndre Fischer    return $self;
95*c9b362f6SAndre Fischer}
96*c9b362f6SAndre Fischer
97*c9b362f6SAndre Fischer
98*c9b362f6SAndre Fischer
99*c9b362f6SAndre Fischer
100*c9b362f6SAndre Fischersub IsValid ($)
101*c9b362f6SAndre Fischer{
102*c9b362f6SAndre Fischer    my ($self) = @_;
103*c9b362f6SAndre Fischer
104*c9b362f6SAndre Fischer    return $self->{'is_valid'};
105*c9b362f6SAndre Fischer}
106*c9b362f6SAndre Fischer
107*c9b362f6SAndre Fischer
108*c9b362f6SAndre Fischer
109*c9b362f6SAndre Fischer
110*c9b362f6SAndre Fischer=head2 GetTable($seld, $table_name)
111*c9b362f6SAndre Fischer
112*c9b362f6SAndre Fischer    Return an MsiTable object for $table_name.  Table objects are kept
113*c9b362f6SAndre Fischer    alive for the life time of the Msi object.  Therefore the second
114*c9b362f6SAndre Fischer    call for the same table is very cheap.
115*c9b362f6SAndre Fischer
116*c9b362f6SAndre Fischer=cut
117*c9b362f6SAndre Fischersub GetTable ($$)
118*c9b362f6SAndre Fischer{
119*c9b362f6SAndre Fischer    my ($self, $table_name) = @_;
120*c9b362f6SAndre Fischer
121*c9b362f6SAndre Fischer    my $table = $self->{'tables'}->{$table_name};
122*c9b362f6SAndre Fischer    if ( ! defined $table)
123*c9b362f6SAndre Fischer    {
124*c9b362f6SAndre Fischer        my $table_filename = File::Spec->catfile($self->{'tmpdir'}, $table_name .".idt");
125*c9b362f6SAndre Fischer        if ( ! -f $table_filename
126*c9b362f6SAndre Fischer            || ! EnsureAYoungerThanB($table_filename, $self->{'fullname'}))
127*c9b362f6SAndre Fischer        {
128*c9b362f6SAndre Fischer            # Extract table from database to text file on disk.
129*c9b362f6SAndre Fischer            my $truncated_table_name = length($table_name)>8 ? substr($table_name,0,8) : $table_name;
130*c9b362f6SAndre Fischer            my $command = join(" ",
131*c9b362f6SAndre Fischer                "msidb.exe",
132*c9b362f6SAndre Fischer                "-d", installer::patch::Tools::CygpathToWindows($self->{'filename'}),
133*c9b362f6SAndre Fischer                "-f", installer::patch::Tools::CygpathToWindows($self->{'tmpdir'}),
134*c9b362f6SAndre Fischer                "-e", $table_name);
135*c9b362f6SAndre Fischer            my $result = qx($command);
136*c9b362f6SAndre Fischer            print $result;
137*c9b362f6SAndre Fischer        }
138*c9b362f6SAndre Fischer
139*c9b362f6SAndre Fischer        # Read table into memory.
140*c9b362f6SAndre Fischer        $table = new installer::patch::MsiTable($table_filename, $table_name);
141*c9b362f6SAndre Fischer        $self->{'tables'}->{$table_name} = $table;
142*c9b362f6SAndre Fischer    }
143*c9b362f6SAndre Fischer
144*c9b362f6SAndre Fischer    return $table;
145*c9b362f6SAndre Fischer}
146*c9b362f6SAndre Fischer
147*c9b362f6SAndre Fischer
148*c9b362f6SAndre Fischer
149*c9b362f6SAndre Fischer
150*c9b362f6SAndre Fischer=head2 EnsureAYoungerThanB ($filename_a, $filename_b)
151*c9b362f6SAndre Fischer
152*c9b362f6SAndre Fischer    Internal function (not a method) that compares to files according
153*c9b362f6SAndre Fischer    to their last modification times (mtime).
154*c9b362f6SAndre Fischer
155*c9b362f6SAndre Fischer=cut
156*c9b362f6SAndre Fischersub EnsureAYoungerThanB ($$)
157*c9b362f6SAndre Fischer{
158*c9b362f6SAndre Fischer    my ($filename_a, $filename_b) = @_;
159*c9b362f6SAndre Fischer
160*c9b362f6SAndre Fischer    die("file $filename_a does not exist") unless -f $filename_a;
161*c9b362f6SAndre Fischer    die("file $filename_b does not exist") unless -f $filename_b;
162*c9b362f6SAndre Fischer
163*c9b362f6SAndre Fischer    my @stat_a = stat($filename_a);
164*c9b362f6SAndre Fischer    my @stat_b = stat($filename_b);
165*c9b362f6SAndre Fischer
166*c9b362f6SAndre Fischer    if ($stat_a[9] <= $stat_b[9])
167*c9b362f6SAndre Fischer    {
168*c9b362f6SAndre Fischer        return 0;
169*c9b362f6SAndre Fischer    }
170*c9b362f6SAndre Fischer    else
171*c9b362f6SAndre Fischer    {
172*c9b362f6SAndre Fischer        return 1;
173*c9b362f6SAndre Fischer    }
174*c9b362f6SAndre Fischer}
175*c9b362f6SAndre Fischer
176*c9b362f6SAndre Fischer
177*c9b362f6SAndre Fischer
178*c9b362f6SAndre Fischer
179*c9b362f6SAndre Fischer=head2 SplitLongShortName($name)
180*c9b362f6SAndre Fischer
181*c9b362f6SAndre Fischer    Split $name (typically from the 'FileName' column in the 'File'
182*c9b362f6SAndre Fischer    table or 'DefaultDir' column in the 'Directory' table) at the '|'
183*c9b362f6SAndre Fischer    into short (8.3) and long names.  If there is no '|' in $name then
184*c9b362f6SAndre Fischer    $name is returned as both short and long name.
185*c9b362f6SAndre Fischer
186*c9b362f6SAndre Fischer    Returns long and short name (in this order) as array.
187*c9b362f6SAndre Fischer
188*c9b362f6SAndre Fischer=cut
189*c9b362f6SAndre Fischersub SplitLongShortName ($)
190*c9b362f6SAndre Fischer{
191*c9b362f6SAndre Fischer    my ($name) = @_;
192*c9b362f6SAndre Fischer
193*c9b362f6SAndre Fischer    if ($name =~ /^([^\|]*)\|(.*)$/)
194*c9b362f6SAndre Fischer    {
195*c9b362f6SAndre Fischer        return ($2,$1);
196*c9b362f6SAndre Fischer    }
197*c9b362f6SAndre Fischer    else
198*c9b362f6SAndre Fischer    {
199*c9b362f6SAndre Fischer        return ($name,$name);
200*c9b362f6SAndre Fischer    }
201*c9b362f6SAndre Fischer}
202*c9b362f6SAndre Fischer
203*c9b362f6SAndre Fischer
204*c9b362f6SAndre Fischer
205*c9b362f6SAndre Fischer=head2 SplitTargetSourceLongShortName ($name)
206*c9b362f6SAndre Fischer
207*c9b362f6SAndre Fischer    Split $name first at the ':' into target and source parts and each
208*c9b362f6SAndre Fischer    of those at the '|'s into long and short parts.  Names that follow
209*c9b362f6SAndre Fischer    this pattern come from the 'DefaultDir' column in the 'Directory'
210*c9b362f6SAndre Fischer    table.
211*c9b362f6SAndre Fischer
212*c9b362f6SAndre Fischer=cut
213*c9b362f6SAndre Fischersub SplitTargetSourceLongShortName ($)
214*c9b362f6SAndre Fischer{
215*c9b362f6SAndre Fischer    my ($name) = @_;
216*c9b362f6SAndre Fischer
217*c9b362f6SAndre Fischer    if ($name =~ /^([^:]*):(.*)$/)
218*c9b362f6SAndre Fischer    {
219*c9b362f6SAndre Fischer        return (installer::patch::Msi::SplitLongShortName($1), installer::patch::Msi::SplitLongShortName($2));
220*c9b362f6SAndre Fischer    }
221*c9b362f6SAndre Fischer    else
222*c9b362f6SAndre Fischer    {
223*c9b362f6SAndre Fischer        my ($long,$short) = installer::patch::Msi::SplitLongShortName($name);
224*c9b362f6SAndre Fischer        return ($long,$short,$long,$short);
225*c9b362f6SAndre Fischer    }
226*c9b362f6SAndre Fischer}
227*c9b362f6SAndre Fischer
228*c9b362f6SAndre Fischer
229*c9b362f6SAndre Fischer
230*c9b362f6SAndre Fischer
231*c9b362f6SAndre Fischer=head2 GetFileToDirectoryMap ($)
232*c9b362f6SAndre Fischer
233*c9b362f6SAndre Fischer    Return a map (hash) that maps the unique name (column 'File' in
234*c9b362f6SAndre Fischer    the 'File' table) to its directory names.  Each value is a
235*c9b362f6SAndre Fischer    reference to an array of two elements: the source path and the
236*c9b362f6SAndre Fischer    target path.
237*c9b362f6SAndre Fischer
238*c9b362f6SAndre Fischer    The map is kept alive for the lifetime of the Msi object.  All
239*c9b362f6SAndre Fischer    calls but the first are cheap.
240*c9b362f6SAndre Fischer
241*c9b362f6SAndre Fischer=cut
242*c9b362f6SAndre Fischersub GetFileToDirectoryMap ($)
243*c9b362f6SAndre Fischer{
244*c9b362f6SAndre Fischer    my ($self) = @_;
245*c9b362f6SAndre Fischer
246*c9b362f6SAndre Fischer    if (defined $self->{'FileToDirectoryMap'})
247*c9b362f6SAndre Fischer    {
248*c9b362f6SAndre Fischer        return $self->{'FileToDirectoryMap'};
249*c9b362f6SAndre Fischer    }
250*c9b362f6SAndre Fischer
251*c9b362f6SAndre Fischer    my $file_table = $self->GetTable("File");
252*c9b362f6SAndre Fischer    my $directory_table = $self->GetTable("Directory");
253*c9b362f6SAndre Fischer    my $component_table = $self->GetTable("Component");
254*c9b362f6SAndre Fischer    $installer::logger::Info->printf("got access to tables File, Directory, Component\n");
255*c9b362f6SAndre Fischer
256*c9b362f6SAndre Fischer    my %dir_map = ();
257*c9b362f6SAndre Fischer    foreach my $row (@{$directory_table->GetAllRows()})
258*c9b362f6SAndre Fischer    {
259*c9b362f6SAndre Fischer        my ($target_name, undef, $source_name, undef)
260*c9b362f6SAndre Fischer            = installer::patch::Msi::SplitTargetSourceLongShortName($row->GetValue("DefaultDir"));
261*c9b362f6SAndre Fischer        $dir_map{$row->GetValue("Directory")} = {
262*c9b362f6SAndre Fischer            'parent' => $row->GetValue("Directory_Parent"),
263*c9b362f6SAndre Fischer            'source_name' => $source_name,
264*c9b362f6SAndre Fischer            'target_name' => $target_name};
265*c9b362f6SAndre Fischer    }
266*c9b362f6SAndre Fischer
267*c9b362f6SAndre Fischer    # Set up full names for all directories.
268*c9b362f6SAndre Fischer    my @todo = map {$_} (keys %dir_map);
269*c9b362f6SAndre Fischer    my $process_count = 0;
270*c9b362f6SAndre Fischer    my $push_count = 0;
271*c9b362f6SAndre Fischer    while (scalar @todo > 0)
272*c9b362f6SAndre Fischer    {
273*c9b362f6SAndre Fischer        ++$process_count;
274*c9b362f6SAndre Fischer
275*c9b362f6SAndre Fischer        my $key = shift @todo;
276*c9b362f6SAndre Fischer        my $item = $dir_map{$key};
277*c9b362f6SAndre Fischer        next if defined $item->{'full_source_name'};
278*c9b362f6SAndre Fischer
279*c9b362f6SAndre Fischer        if ($item->{'parent'} eq "")
280*c9b362f6SAndre Fischer        {
281*c9b362f6SAndre Fischer            # Directory has no parent => full names are the same as the name.
282*c9b362f6SAndre Fischer            $item->{'full_source_name'} = $item->{'source_name'};
283*c9b362f6SAndre Fischer            $item->{'full_target_name'} = $item->{'target_name'};
284*c9b362f6SAndre Fischer        }
285*c9b362f6SAndre Fischer        else
286*c9b362f6SAndre Fischer        {
287*c9b362f6SAndre Fischer            my $parent = $dir_map{$item->{'parent'}};
288*c9b362f6SAndre Fischer            if ( defined $parent->{'full_source_name'})
289*c9b362f6SAndre Fischer            {
290*c9b362f6SAndre Fischer                # Parent aleady has full names => we can create the full name of the current item.
291*c9b362f6SAndre Fischer                $item->{'full_source_name'} = $parent->{'full_source_name'} . "/" . $item->{'source_name'};
292*c9b362f6SAndre Fischer                $item->{'full_target_name'} = $parent->{'full_target_name'} . "/" . $item->{'target_name'};
293*c9b362f6SAndre Fischer            }
294*c9b362f6SAndre Fischer            else
295*c9b362f6SAndre Fischer            {
296*c9b362f6SAndre Fischer                # Parent has to be processed before the current item can be processed.
297*c9b362f6SAndre Fischer                # Push both to the head of the list.
298*c9b362f6SAndre Fischer                unshift @todo, $key;
299*c9b362f6SAndre Fischer                unshift @todo, $item->{'parent'};
300*c9b362f6SAndre Fischer
301*c9b362f6SAndre Fischer                ++$push_count;
302*c9b362f6SAndre Fischer            }
303*c9b362f6SAndre Fischer        }
304*c9b362f6SAndre Fischer    }
305*c9b362f6SAndre Fischer
306*c9b362f6SAndre Fischer    foreach my $key (keys %dir_map)
307*c9b362f6SAndre Fischer    {
308*c9b362f6SAndre Fischer        $dir_map{$key}->{'full_source_name'} =~ s/\/(\.\/)+/\//g;
309*c9b362f6SAndre Fischer        $dir_map{$key}->{'full_source_name'} =~ s/^SourceDir\///;
310*c9b362f6SAndre Fischer        $dir_map{$key}->{'full_target_name'} =~ s/\/(\.\/)+/\//g;
311*c9b362f6SAndre Fischer        $dir_map{$key}->{'full_target_name'} =~ s/^SourceDir\///;
312*c9b362f6SAndre Fischer    }
313*c9b362f6SAndre Fischer    $installer::logger::Info->printf("for %d directories there where %d processing steps and %d pushes\n",
314*c9b362f6SAndre Fischer        $directory_table->GetRowCount(),
315*c9b362f6SAndre Fischer        $process_count,
316*c9b362f6SAndre Fischer        $push_count);
317*c9b362f6SAndre Fischer
318*c9b362f6SAndre Fischer    # Setup a map from component names to directory items.
319*c9b362f6SAndre Fischer    my %component_to_directory_map = map {$_->GetValue('Component') => $_->GetValue('Directory_')} @{$component_table->GetAllRows()};
320*c9b362f6SAndre Fischer
321*c9b362f6SAndre Fischer    # Finally, create the map from files to directories.
322*c9b362f6SAndre Fischer    my $map = {};
323*c9b362f6SAndre Fischer    my $file_component_index = $file_table->GetColumnIndex("Component_");
324*c9b362f6SAndre Fischer    my $file_file_index = $file_table->GetColumnIndex("File");
325*c9b362f6SAndre Fischer    foreach my $file_row (@{$file_table->GetAllRows()})
326*c9b362f6SAndre Fischer    {
327*c9b362f6SAndre Fischer        my $component_name = $file_row->GetValue($file_component_index);
328*c9b362f6SAndre Fischer        my $directory_name = $component_to_directory_map{$component_name};
329*c9b362f6SAndre Fischer        my $dir_item = $dir_map{$directory_name};
330*c9b362f6SAndre Fischer        my $unique_name = $file_row->GetValue($file_file_index);
331*c9b362f6SAndre Fischer        $map->{$unique_name} = [$dir_item->{'full_source_name'},$dir_item->{'full_target_name'}];
332*c9b362f6SAndre Fischer    }
333*c9b362f6SAndre Fischer
334*c9b362f6SAndre Fischer    $installer::logger::Info->printf("got full paths for %d files\n",
335*c9b362f6SAndre Fischer        $file_table->GetRowCount());
336*c9b362f6SAndre Fischer
337*c9b362f6SAndre Fischer    $self->{'FileToDirectoryMap'} = $map;
338*c9b362f6SAndre Fischer    return $map;
339*c9b362f6SAndre Fischer}
340*c9b362f6SAndre Fischer
341*c9b362f6SAndre Fischer
342*c9b362f6SAndre Fischer1;
343