~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to build/macosx/universal/unify

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/perl
2
 
# This Source Code Form is subject to the terms of the Mozilla Public
3
 
# License, v. 2.0. If a copy of the MPL was not distributed with this
4
 
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
 
 
6
 
use strict;
7
 
use warnings;
8
 
 
9
 
=pod
10
 
 
11
 
=head1 NAME
12
 
 
13
 
B<unify> - Mac OS X universal binary packager
14
 
 
15
 
=head1 SYNOPSIS
16
 
 
17
 
B<unify>
18
 
I<ppc-path>
19
 
I<x86-path>
20
 
I<universal-path>
21
 
[B<--dry-run>]
22
 
[B<--only-one> I<action>]
23
 
[B<--verbosity> I<level>]
24
 
[B<--unify-with-sort> I<regex>]
25
 
 
26
 
=head1 DESCRIPTION
27
 
 
28
 
I<unify> merges any two architecture-specific files or directory trees
29
 
into a single file or tree suitable for use on either architecture as a
30
 
"fat" or "universal binary."
31
 
 
32
 
Architecture-specific Mach-O files will be merged into fat Mach-O files
33
 
using L<lipo(1)>.  Non-Mach-O files in the architecture-specific trees
34
 
are compared to ensure that they are equivalent before copying.  Symbolic
35
 
links are permitted in the architecture-specific trees and will cause
36
 
identical links to be created in the merged tree, provided that the source
37
 
links have identical targets.  Directories are processed recursively.
38
 
 
39
 
If the architecture-specific source trees contain zip archives (including
40
 
jar files) that are not identical according to a byte-for-byte check, they
41
 
are still assumed to be equivalent if both archives contain exactly the
42
 
same members with identical checksums and sizes.
43
 
 
44
 
Behavior when one architecture-specific tree contains files that the other
45
 
does not is controlled by the B<--only-one> option.
46
 
 
47
 
If Mach-O files cannot be merged using L<lipo(1)>, zip archives are not
48
 
equivalent, regular files are not identical, or any other error occurs,
49
 
B<unify> will fail with an exit status of 1.  Diagnostic messages are
50
 
typically printed to stderr; this behavior can be controlled with the
51
 
B<--verbosity> option.
52
 
 
53
 
=head1 OPTIONS
54
 
 
55
 
=over 5
56
 
 
57
 
=item I<ppc-path>
58
 
 
59
 
=item I<x86-path>
60
 
 
61
 
The paths to directory trees containing PowerPC and x86 builds,
62
 
respectively.  I<ppc-path> and I<x86-path> are permitted to contain files
63
 
that are already "fat," and only the appropriate architecture's images will
64
 
be used.
65
 
 
66
 
I<ppc-path> and I<x86-path> are also permitted to both be files, in which
67
 
case B<unify> operates solely on those files, and produces an appropriate
68
 
merged file at I<target-path>.
69
 
 
70
 
=item I<target-path>
71
 
 
72
 
The path to the merged file or directory tree.  This path will be created,
73
 
and it must not exist prior to running B<unify>.
74
 
 
75
 
=item B<--dry-run>
76
 
 
77
 
When specified, the commands that would be executed are printed, without
78
 
actually executing them.  Note that B<--dry-run> and the equivalent
79
 
B<--verbosity> level during "wet" runs may print equivalent commands when
80
 
no commands are in fact executed: certain operations are handled internally
81
 
within B<unify>, and an approximation of a command that performs a similar
82
 
task is printed.
83
 
 
84
 
=item B<--only-one> I<action>
85
 
 
86
 
Controls handling of files that are only present in one of the two source
87
 
trees.  I<action> may be:
88
 
  skip - These files are skipped.
89
 
  copy - These files are copied from the tree in which they exist.
90
 
  fail - When this condition occurs, it is treated as an error.
91
 
 
92
 
The default I<action> is copy.
93
 
 
94
 
=item B<--verbosity> I<level>
95
 
 
96
 
Adjusts the level of loudness of B<unify>.  The possible values for
97
 
I<level> are:
98
 
  0 - B<unify> never prints anything.
99
 
      (Other programs that B<unify> calls may still print messages.)
100
 
  1 - Fatal error messages are printed to stderr.
101
 
  2 - Nonfatal warnings are printed to stderr.
102
 
  3 - Commands are printed to stdout as they are executed.
103
 
 
104
 
The default I<level> is 2.
105
 
 
106
 
=item B<--unify-with-sort> I<regex>
107
 
 
108
 
Allows merging files matching I<regex> that differ only by the ordering
109
 
of the lines contained within them. The unified file will have its contents
110
 
sorted. This option may be given multiple times to specify multiple
111
 
regexes for matching files.
112
 
 
113
 
=back
114
 
 
115
 
=head1 EXAMPLES
116
 
 
117
 
=over 5
118
 
 
119
 
=item Create a universal .app bundle from two architecture-specific .app
120
 
bundles:
121
 
 
122
 
unify --only-one copy ppc/dist/firefox/Firefox.app
123
 
  x86/dist/firefox/Firefox.app universal/Firefox.app
124
 
  --verbosity 3
125
 
 
126
 
=item Merge two identical architecture-specific trees:
127
 
 
128
 
unify --only-one fail /usr/local /nfs/x86/usr/local
129
 
  /tmp/usrlocal.fat
130
 
 
131
 
=back
132
 
 
133
 
=head1 REQUIREMENTS
134
 
 
135
 
The only esoteric requirement of B<unify> is that the L<lipo(1)> command
136
 
be available.  It is present on Mac OS X systems at least as early as
137
 
10.3.9, and probably earlier.  Mac OS X 10.4 ("Tiger") or later are
138
 
recommended.
139
 
 
140
 
=head1 LICENSE
141
 
 
142
 
MPL 1.1/GPL 2.0/LGPL 2.1.  Your choice
143
 
 
144
 
=head1 AUTHOR
145
 
 
146
 
The software was initially written by Mark Mentovai; copyright 2006
147
 
Google Inc.
148
 
 
149
 
=head1 SEE ALSO
150
 
 
151
 
L<cmp(1)>, L<ditto(1)>, L<lipo(1)>
152
 
 
153
 
=cut
154
 
 
155
 
use Archive::Zip(':ERROR_CODES');
156
 
use Errno;
157
 
use Fcntl;
158
 
use File::Compare;
159
 
use File::Copy;
160
 
use Getopt::Long;
161
 
 
162
 
my (%gConfig, $gDryRun, $gOnlyOne, $gVerbosity, @gSortMatches);
163
 
 
164
 
sub argumentEscape(@);
165
 
sub command(@);
166
 
sub compareZipArchives($$);
167
 
sub complain($$@);
168
 
sub copyIfIdentical($$$);
169
 
sub slurp($);
170
 
sub get_sorted($);
171
 
sub compare_sorted($$);
172
 
sub copyIfIdenticalWhenSorted($$$);
173
 
sub createUniqueFile($$);
174
 
sub makeUniversal($$$);
175
 
sub makeUniversalDirectory($$$);
176
 
sub makeUniversalInternal($$$$);
177
 
sub makeUniversalFile($$$);
178
 
sub usage();
179
 
sub readZipCRCs($);
180
 
 
181
 
{
182
 
  package FileAttrCache;
183
 
 
184
 
  sub new($$);
185
 
 
186
 
  sub isFat($);
187
 
  sub isMachO($);
188
 
  sub isZip($);
189
 
  sub lIsDir($);
190
 
  sub lIsExecutable($);
191
 
  sub lIsRegularFile($);
192
 
  sub lIsSymLink($);
193
 
  sub lstat($);
194
 
  sub lstatMode($);
195
 
  sub lstatType($);
196
 
  sub magic($);
197
 
  sub magic2($);
198
 
  sub path($);
199
 
  sub stat($);
200
 
  sub statSize($);
201
 
}
202
 
 
203
 
%gConfig = (
204
 
  'cmd_lipo' => 'lipo',
205
 
  'cmd_rm'   => 'rm',
206
 
);
207
 
 
208
 
$gDryRun = 0;
209
 
$gOnlyOne = 'copy';
210
 
$gVerbosity = 2;
211
 
@gSortMatches = ();
212
 
 
213
 
Getopt::Long::Configure('pass_through');
214
 
GetOptions('dry-run'           => \$gDryRun,
215
 
           'only-one=s'        => \$gOnlyOne,
216
 
           'verbosity=i'       => \$gVerbosity,
217
 
           'unify-with-sort=s' => \@gSortMatches,
218
 
           'config=s'          => \%gConfig); # "hidden" option not in usage()
219
 
 
220
 
if (scalar(@ARGV) != 3 || $gVerbosity < 0 || $gVerbosity > 3 ||
221
 
    ($gOnlyOne ne 'skip' && $gOnlyOne ne 'copy' && $gOnlyOne ne 'fail')) {
222
 
  usage();
223
 
  exit(1);
224
 
}
225
 
 
226
 
if (!makeUniversal($ARGV[0],$ARGV[1],$ARGV[2])) {
227
 
  # makeUniversal or something it called will have printed an error.
228
 
  exit(1);
229
 
}
230
 
 
231
 
exit(0);
232
 
 
233
 
# argumentEscape(@arguments)
234
 
#
235
 
# Takes a list of @arguments and makes them shell-safe.
236
 
sub argumentEscape(@) {
237
 
  my (@arguments);
238
 
  @arguments = @_;
239
 
 
240
 
  my ($argument, @argumentsOut);
241
 
  foreach $argument (@arguments) {
242
 
    $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g;
243
 
    push(@argumentsOut, $argument);
244
 
  }
245
 
 
246
 
  return @argumentsOut;
247
 
}
248
 
 
249
 
# command(@arguments)
250
 
#
251
 
# Runs the specified command by calling system(@arguments).  If $gDryRun
252
 
# is true, the command is printed but not executed, and 0 is returned.
253
 
# if $gVerbosity is greater than 1, the command is printed before being
254
 
# executed.  When the command is executed, the system() return value will
255
 
# be returned.  stdout and stderr are left connected for command output.
256
 
sub command(@) {
257
 
  my (@arguments);
258
 
  @arguments = @_;
259
 
  if ($gVerbosity >= 3 || $gDryRun) {
260
 
    print(join(' ', argumentEscape(@arguments))."\n");
261
 
  }
262
 
  if ($gDryRun) {
263
 
    return 0;
264
 
  }
265
 
  return system(@arguments);
266
 
}
267
 
 
268
 
# compareZipArchives($zip1, $zip2)
269
 
#
270
 
# Given two pathnames to zip archives, determines whether or not they are
271
 
# functionally identical.  Returns true if they are, false if they differ in
272
 
# some substantial way, and undef if an error occurs.  If the zip files
273
 
# differ, diagnostic messages are printed indicating how they differ.
274
 
#
275
 
# Zip files will differ if any of the members are different as defined by
276
 
# readZipCRCs, which consider CRCs, sizes, and file types as stored in the
277
 
# file header.  Timestamps are not considered.  Zip files also differ if one
278
 
# file contains members that the other one does not.  $gOnlyOne has no
279
 
# effect on this behavior.
280
 
sub compareZipArchives($$) {
281
 
  my ($zip1, $zip2);
282
 
  ($zip1, $zip2) = @_;
283
 
 
284
 
  my ($CRCHash1, $CRCHash2);
285
 
  if (!defined($CRCHash1 = readZipCRCs($zip1))) {
286
 
    # readZipCRCs printed an error.
287
 
    return undef;
288
 
  }
289
 
  if (!defined($CRCHash2 = readZipCRCs($zip2))) {
290
 
    # readZipCRCs printed an error.
291
 
    return undef;
292
 
  }
293
 
 
294
 
  my (@diffCRCs, @onlyInZip1);
295
 
  @diffCRCs = ();
296
 
  @onlyInZip1 = ();
297
 
 
298
 
  my ($memberName);
299
 
  foreach $memberName (keys(%$CRCHash1)) {
300
 
    if (!exists($$CRCHash2{$memberName})) {
301
 
      # The member is present in $zip1 but not $zip2.
302
 
      push(@onlyInZip1, $memberName);
303
 
    }
304
 
    elsif ($$CRCHash1{$memberName} ne $$CRCHash2{$memberName}) {
305
 
      # The member is present in both archives but its CRC or some other
306
 
      # other critical attribute isn't identical.
307
 
      push(@diffCRCs, $memberName);
308
 
    }
309
 
    delete($$CRCHash2{$memberName});
310
 
  }
311
 
 
312
 
  # If any members remain in %CRCHash2, it's because they're not present
313
 
  # in $zip1.
314
 
  my (@onlyInZip2);
315
 
  @onlyInZip2 = keys(%$CRCHash2);
316
 
 
317
 
  if (scalar(@onlyInZip1) + scalar(@onlyInZip2) + scalar(@diffCRCs)) {
318
 
    complain(1, 'compareZipArchives: zip archives differ:',
319
 
             $zip1,
320
 
             $zip2);
321
 
    if (scalar(@onlyInZip1)) {
322
 
      complain(1, 'compareZipArchives: members only in former:',
323
 
               @onlyInZip1);
324
 
    }
325
 
    if (scalar(@onlyInZip2)) {
326
 
      complain(1, 'compareZipArchives: members only in latter:',
327
 
               @onlyInZip2);
328
 
    }
329
 
    if (scalar(@diffCRCs)) {
330
 
      complain(1, 'compareZipArchives: members differ:',
331
 
               @diffCRCs);
332
 
    }
333
 
    return 0;
334
 
  }
335
 
 
336
 
  return 1;
337
 
}
338
 
 
339
 
# complain($severity, $message, @list)
340
 
#
341
 
# Prints $message to stderr if $gVerbosity allows it for severity level
342
 
# $severity.  @list is a list of words that will be shell-escaped and printed
343
 
# after $message, one per line, intended to be used, for example, to list
344
 
# arguments to a call that failed.
345
 
#
346
 
# Expected severity levels are 1 for hard errors and 2 for non-fatal warnings.
347
 
#
348
 
# Always returns false as a convenience, so callers can return complain's
349
 
# return value when it is used to signal errors.
350
 
sub complain($$@) {
351
 
  my ($severity, $message, @list);
352
 
  ($severity, $message, @list) = @_;
353
 
 
354
 
  if ($gVerbosity >= $severity) {
355
 
    print STDERR ($0.': '.$message."\n");
356
 
 
357
 
    my ($item);
358
 
    while ($item = shift(@list)) {
359
 
      print STDERR ('  '.(argumentEscape($item))[0].
360
 
                    (scalar(@list)?',':'')."\n");
361
 
    }
362
 
  }
363
 
 
364
 
  return 0;
365
 
}
366
 
 
367
 
# copyIfIdentical($source1, $source2, $target)
368
 
#
369
 
# $source1 and $source2 are FileAttrCache objects that are compared, and if
370
 
# identical, copied to path string $target.  The comparison is initially
371
 
# done as a byte-for-byte comparison, but if the files differ and appear to
372
 
# be zip archives, compareZipArchives is called to determine whether
373
 
# files that are not byte-for-byte identical are equivalent archives.
374
 
#
375
 
# Returns true on success, false for files that are not identical or
376
 
# equivalent archives, and undef if an error occurs.
377
 
#
378
 
# One of $source1 and $source2 is permitted to be undef.  In this event,
379
 
# whichever source is defined is copied directly to $target without performing
380
 
# any comparisons.  This enables the $gOnlyOne = 'copy' mode, which is
381
 
# driven by makeUniversalDirectory and makeUniversalInternal.
382
 
sub copyIfIdentical($$$) {
383
 
  my ($source1, $source2, $target);
384
 
  ($source1, $source2, $target) = @_;
385
 
 
386
 
  if (!defined($source1)) {
387
 
    # If there's only one source file, make it the first file.  Order
388
 
    # isn't important here, and this makes it possible to use
389
 
    # defined($source2) as the switch, and to always copy from $source1.
390
 
    $source1 = $source2;
391
 
    $source2 = undef;
392
 
  }
393
 
 
394
 
  if (defined($source2)) {
395
 
    # Only do the comparisons if there are two source files.  If there's
396
 
    # only one source file, skip the comparisons and go straight to the
397
 
    # copy operation.
398
 
    if ($gVerbosity >= 3 || $gDryRun) {
399
 
      print('cmp -s '.
400
 
            join(' ',argumentEscape($source1->path(), $source2->path()))."\n");
401
 
    }
402
 
    my ($comparison);
403
 
    if (!defined($comparison = compare($source1->path(), $source2->path())) ||
404
 
        $comparison == -1) {
405
 
      return complain(1, 'copyIfIdentical: compare: '.$!.' while comparing:',
406
 
                      $source1->path(),
407
 
                      $source2->path());
408
 
    }
409
 
    elsif ($comparison != 0) {
410
 
      my ($zip1, $zip2);
411
 
      if (defined($zip1 = $source1->isZip()) &&
412
 
          defined($zip2 = $source2->isZip()) &&
413
 
          $zip1 && $zip2) {
414
 
        my ($zipComparison);
415
 
        if (!defined($zipComparison = compareZipArchives($source1->path(),
416
 
                                                         $source2->path)) ||
417
 
            !$zipComparison) {
418
 
          # An error occurred or the zip files aren't sufficiently identical.
419
 
          # compareZipArchives will have printed an error message.
420
 
          return 0;
421
 
        }
422
 
        # The zip files were compared successfully, and they both contain
423
 
        # all of the same members, and all of their members' CRCs are
424
 
        # identical.  For the purposes of this script, the zip files can be
425
 
        # treated as identical, so reset $comparison.
426
 
        $comparison = 0;
427
 
      }
428
 
    }
429
 
    if ($comparison != 0) {
430
 
      return complain(1, 'copyIfIdentical: files differ:',
431
 
                      $source1->path(),
432
 
                      $source2->path());
433
 
    }
434
 
  }
435
 
 
436
 
  if ($gVerbosity >= 3 || $gDryRun) {
437
 
    print('cp '.
438
 
          join(' ',argumentEscape($source1->path(), $target))."\n");
439
 
  }
440
 
 
441
 
  if (!$gDryRun) {
442
 
    my ($isExecutable);
443
 
 
444
 
    # Set the execute bits (as allowed by the umask) on the new file if any
445
 
    # execute bit is set on either old file.
446
 
    $isExecutable = $source1->lIsExecutable() ||
447
 
                    (defined($source2) && $source2->lIsExecutable());
448
 
 
449
 
    if (!createUniqueFile($target, $isExecutable ? 0777 : 0666)) {
450
 
      # createUniqueFile printed an error.
451
 
      return 0;
452
 
    }
453
 
 
454
 
    if (!copy($source1->path(), $target)) {
455
 
      complain(1, 'copyIfIdentical: copy: '.$!.' while copying',
456
 
               $source1->path(),
457
 
               $target);
458
 
      unlink($target);
459
 
      return 0;
460
 
    }
461
 
  }
462
 
 
463
 
  return 1;
464
 
}
465
 
 
466
 
# slurp($file)
467
 
#
468
 
# Read the contents of $file into an array and return it.
469
 
# Returns undef on error.
470
 
sub slurp($) {
471
 
  my $file = $_[0];
472
 
  open FILE, $file or return undef;
473
 
  my @lines = <FILE>;
474
 
  close FILE;
475
 
  return @lines;
476
 
}
477
 
 
478
 
# get_sorted($file)
479
 
# Get the sorted lines of a file as a list, normalizing a newline on the last line if necessary.
480
 
sub get_sorted($) {
481
 
  my ($file) = @_;
482
 
  my @lines = slurp($file);
483
 
  my $lastline = $lines[-1];
484
 
  if (!($lastline =~ /\n/)) {
485
 
    $lines[-1] = $lastline . "\n";
486
 
  }
487
 
  return sort(@lines);
488
 
}
489
 
 
490
 
# compare_sorted($file1, $file2)
491
 
#
492
 
# Read the contents of both files into arrays, sort the arrays,
493
 
# and then compare the two arrays for equality.
494
 
#
495
 
# Returns 0 if the sorted array contents are equal, or 1 if not.
496
 
# Returns undef on error.
497
 
sub compare_sorted($$) {
498
 
  my ($file1, $file2) = @_;
499
 
  my @lines1 = get_sorted($file1);
500
 
  my @lines2 = get_sorted($file2);
501
 
 
502
 
  return undef if !@lines1 || !@lines2;
503
 
  return 1 unless scalar @lines1 == scalar @lines2;
504
 
 
505
 
  for (my $i = 0; $i < scalar @lines1; $i++) {
506
 
    return 1 if $lines1[$i] ne $lines2[$i];
507
 
  }
508
 
  return 0;
509
 
}
510
 
 
511
 
# copyIfIdenticalWhenSorted($source1, $source2, $target)
512
 
#
513
 
# $source1 and $source2 are FileAttrCache objects that are compared, and if
514
 
# identical, copied to path string $target.  The comparison is done by
515
 
# sorting the individual lines within the two files and comparing the results.
516
 
#
517
 
# Returns true on success, false for files that are not equivalent,
518
 
# and undef if an error occurs.
519
 
sub copyIfIdenticalWhenSorted($$$) {
520
 
  my ($source1, $source2, $target);
521
 
  ($source1, $source2, $target) = @_;
522
 
 
523
 
  if ($gVerbosity >= 3 || $gDryRun) {
524
 
    print('cmp -s '.
525
 
          join(' ',argumentEscape($source1->path(), $source2->path()))."\n");
526
 
  }
527
 
  my ($comparison);
528
 
  if (!defined($comparison = compare_sorted($source1->path(),
529
 
                                            $source2->path())) ||
530
 
      $comparison == -1) {
531
 
    return complain(1, 'copyIfIdenticalWhenSorted: compare: '.$!
532
 
                    .' while comparing:',
533
 
                      $source1->path(),
534
 
                      $source2->path());
535
 
  }
536
 
  if ($comparison != 0) {
537
 
    return complain(1, 'copyIfIdenticalWhenSorted: files differ:',
538
 
                    $source1->path(),
539
 
                    $source2->path());
540
 
  }
541
 
 
542
 
  if ($gVerbosity >= 3 || $gDryRun) {
543
 
    print('cp '.
544
 
          join(' ',argumentEscape($source1->path(), $target))."\n");
545
 
  }
546
 
 
547
 
  if (!$gDryRun) {
548
 
    my ($isExecutable);
549
 
 
550
 
    # Set the execute bits (as allowed by the umask) on the new file if any
551
 
    # execute bit is set on either old file.
552
 
    $isExecutable = $source1->lIsExecutable() ||
553
 
                    (defined($source2) && $source2->lIsExecutable());
554
 
 
555
 
    if (!createUniqueFile($target, $isExecutable ? 0777 : 0666)) {
556
 
      # createUniqueFile printed an error.
557
 
      return 0;
558
 
    }
559
 
 
560
 
    if (!copy($source1->path(), $target)) {
561
 
      complain(1, 'copyIfIdenticalWhenSorted: copy: '.$!
562
 
               .' while copying',
563
 
               $source1->path(),
564
 
               $target);
565
 
      unlink($target);
566
 
      return 0;
567
 
    }
568
 
  }
569
 
 
570
 
  return 1;
571
 
}
572
 
 
573
 
# createUniqueFile($path, $mode)
574
 
#
575
 
# Creates a new plain empty file at pathname $path, provided it does not
576
 
# yet exist.  $mode is used as the file mode.  The actual file's mode will
577
 
# be modified by the effective umask.  Returns false if the file could
578
 
# not be created, setting $! to the error.  An error message is printed
579
 
# in the event of failure.
580
 
sub createUniqueFile($$) {
581
 
  my ($path, $mode);
582
 
  ($path, $mode) = @_;
583
 
 
584
 
  my ($fh);
585
 
  if (!sysopen($fh, $path, O_WRONLY | O_CREAT | O_EXCL, $mode)) {
586
 
    return complain(1, 'createUniqueFile: open: '.$!.' for:',
587
 
                    $path);
588
 
  }
589
 
  close($fh);
590
 
 
591
 
  return 1;
592
 
}
593
 
 
594
 
# makeUniversal($pathPPC, $pathX86, $pathTarget)
595
 
#
596
 
# The top-level call.  $pathPPC, $pathX86, and $pathTarget are strings
597
 
# identifying the ppc and x86 files or directories to merge and the location
598
 
# to merge them to.  Returns false on failure and true on success.
599
 
sub makeUniversal($$$) {
600
 
  my ($pathTarget, $pathPPC, $pathX86);
601
 
  ($pathPPC, $pathX86, $pathTarget) = @_;
602
 
 
603
 
  my ($filePPC, $fileX86);
604
 
  $filePPC = FileAttrCache->new($pathPPC);
605
 
  $fileX86 = FileAttrCache->new($pathX86);
606
 
 
607
 
  return makeUniversalInternal(1, $filePPC, $fileX86, $pathTarget);
608
 
}
609
 
 
610
 
# makeUniversalDirectory($dirPPC, $dirX86, $dirTarget)
611
 
#
612
 
# This is part of the heart of recursion.  $dirPPC and $dirX86 are
613
 
# FileAttrCache objects designating the source ppc and x86 directories to
614
 
# merge into a universal directory at $dirTarget, a string.  For each file
615
 
# in $dirPPC and $dirX86, makeUniversalInternal is called.
616
 
# makeUniversalInternal will call back into makeUniversalDirectory for
617
 
# directories, thus completing the recursion.  If a failure is encountered
618
 
# in ths function or in makeUniversalInternal or anything that it calls,
619
 
# false is returned, otherwise, true is returned.
620
 
#
621
 
# If there are files present in one source directory but not both, the
622
 
# value of $gOnlyOne controls the behavior.  If $gOnlyOne is 'copy', the
623
 
# single source file is copied into $pathTarget.  If it is 'skip', it is
624
 
# skipped.  If it is 'fail', such files will trigger makeUniversalDirectory
625
 
# to fail.
626
 
#
627
 
# If either source directory is undef, it is treated as having no files.
628
 
# This facilitates deep recursion when entire directories are only present
629
 
# in one source when $gOnlyOne = 'copy'.
630
 
sub makeUniversalDirectory($$$) {
631
 
  my ($dirPPC, $dirX86, $dirTarget);
632
 
  ($dirPPC, $dirX86, $dirTarget) = @_;
633
 
 
634
 
  my ($dh, @filesPPC, @filesX86);
635
 
 
636
 
  @filesPPC = ();
637
 
  if (defined($dirPPC)) {
638
 
    if (!opendir($dh, $dirPPC->path())) {
639
 
      return complain(1, 'makeUniversalDirectory: opendir ppc: '.$!.' for:',
640
 
                      $dirPPC->path());
641
 
    }
642
 
    @filesPPC = readdir($dh);
643
 
    closedir($dh);
644
 
  }
645
 
 
646
 
  @filesX86 = ();
647
 
  if (defined($dirX86)) {
648
 
    if (!opendir($dh, $dirX86->path())) {
649
 
      return complain(1, 'makeUniversalDirectory: opendir x86: '.$!.' for:',
650
 
                      $dirX86->path());
651
 
     }
652
 
    @filesX86 = readdir($dh);
653
 
    closedir($dh);
654
 
  }
655
 
 
656
 
  my (%common, $file, %onlyPPC, %onlyX86);
657
 
 
658
 
  %onlyPPC = ();
659
 
  foreach $file (@filesPPC) {
660
 
    if ($file eq '.' || $file eq '..') {
661
 
      next;
662
 
    }
663
 
    $onlyPPC{$file}=1;
664
 
  }
665
 
 
666
 
  %common = ();
667
 
  %onlyX86 = ();
668
 
  foreach $file (@filesX86) {
669
 
    if ($file eq '.' || $file eq '..') {
670
 
      next;
671
 
    }
672
 
    if ($onlyPPC{$file}) {
673
 
      delete $onlyPPC{$file};
674
 
      $common{$file}=1;
675
 
    }
676
 
    else {
677
 
      $onlyX86{$file}=1;
678
 
    }
679
 
  }
680
 
 
681
 
  # First, handle files common to both.
682
 
  foreach $file (sort(keys(%common))) {
683
 
    if (!makeUniversalInternal(0,
684
 
                               FileAttrCache->new($dirPPC->path().'/'.$file),
685
 
                               FileAttrCache->new($dirX86->path().'/'.$file),
686
 
                               $dirTarget.'/'.$file)) {
687
 
      # makeUniversalInternal will have printed an error.
688
 
      return 0;
689
 
    }
690
 
  }
691
 
 
692
 
  # Handle files found only in a single directory here.  There are three
693
 
  # options, dictated by $gOnlyOne: fail if files are only present in
694
 
  # one directory, skip any files only present in one directory, or copy
695
 
  # these files straight over to the target directory.  In any event,
696
 
  # a message will be printed indicating that the file trees don't match
697
 
  # exactly.
698
 
  if (keys(%onlyPPC)) {
699
 
    complain(($gOnlyOne eq 'fail' ? 1 : 2),
700
 
             ($gOnlyOne ne 'fail' ? 'warning: ' : '').
701
 
             'makeUniversalDirectory: only in ppc '.
702
 
             (argumentEscape($dirPPC->path()))[0].':',
703
 
             argumentEscape(keys(%onlyPPC)));
704
 
  }
705
 
 
706
 
  if (keys(%onlyX86)) {
707
 
    complain(($gOnlyOne eq 'fail' ? 1 : 2),
708
 
             ($gOnlyOne ne 'fail' ? 'warning: ' : '').
709
 
             'makeUniversalDirectory: only in x86 '.
710
 
             (argumentEscape($dirX86->path()))[0].':',
711
 
             argumentEscape(keys(%onlyX86)));
712
 
  }
713
 
 
714
 
  if ($gOnlyOne eq 'fail' && (keys(%onlyPPC) || keys(%onlyX86))) {
715
 
    # Error message(s) printed above.
716
 
    return 0;
717
 
  }
718
 
 
719
 
  if ($gOnlyOne eq 'copy') {
720
 
    foreach $file (sort(keys(%onlyPPC))) {
721
 
      if (!makeUniversalInternal(0,
722
 
                                 FileAttrCache->new($dirPPC->path().'/'.$file),
723
 
                                 undef,
724
 
                                 $dirTarget.'/'.$file)) {
725
 
        # makeUniversalInternal will have printed an error.
726
 
        return 0;
727
 
      }
728
 
    }
729
 
 
730
 
    foreach $file (sort(keys(%onlyX86))) {
731
 
      if (!makeUniversalInternal(0,
732
 
                                 undef,
733
 
                                 FileAttrCache->new($dirX86->path().'/'.$file),
734
 
                                 $dirTarget.'/'.$file)) {
735
 
        # makeUniversalInternal will have printed an error.
736
 
        return 0;
737
 
      }
738
 
    }
739
 
  }
740
 
 
741
 
  return 1;
742
 
}
743
 
 
744
 
# makeUniversalFile($sourcePPC, $sourceX86, $targetPath)
745
 
#
746
 
# Creates a universal file at pathname $targetPath based on a ppc image at
747
 
# $sourcePPC and an x86 image at $sourceX86.  $sourcePPC and $sourceX86 are
748
 
# both FileAttrCache objects.  Returns true on success and false on failure.
749
 
# On failure, diagnostics will be printed to stderr.
750
 
#
751
 
# The source files may be either thin Mach-O images of the appropriate
752
 
# architecture, or fat Mach-O files that contain images of the appropriate
753
 
# architecture.
754
 
#
755
 
# This function wraps the lipo utility, see lipo(1).
756
 
sub makeUniversalFile($$$) {
757
 
  my ($sourcePPC, $sourceX86, $targetPath, @tempThinFiles, $thinPPC, $thinX86);
758
 
  ($sourcePPC, $sourceX86, $targetPath) = @_;
759
 
  $thinPPC = $sourcePPC;
760
 
  $thinX86 = $sourceX86;
761
 
 
762
 
  @tempThinFiles = ();
763
 
 
764
 
  # The source files might already be fat.  They should be thinned out to only
765
 
  # contain a single architecture.
766
 
 
767
 
  my ($isFatPPC, $isFatX86);
768
 
 
769
 
  if(!defined($isFatPPC = $sourcePPC->isFat())) {
770
 
    # isFat printed its own error
771
 
    return 0;
772
 
  }
773
 
  elsif($isFatPPC) {
774
 
    $thinPPC = FileAttrCache->new($targetPath.'.ppc');
775
 
    push(@tempThinFiles, $thinPPC->path());
776
 
    if (command($gConfig{'cmd_lipo'}, '-thin', 'ppc',
777
 
                $sourcePPC->path(), '-output', $thinPPC->path()) != 0) {
778
 
      unlink(@tempThinFiles);
779
 
      return complain(1, 'lipo thin ppc failed for:',
780
 
                      $sourcePPC->path(),
781
 
                      $thinPPC->path());
782
 
    }
783
 
  }
784
 
 
785
 
  if(!defined($isFatX86 = $sourceX86->isFat())) {
786
 
    # isFat printed its own error
787
 
    unlink(@tempThinFiles);
788
 
    return 0;
789
 
  }
790
 
  elsif($isFatX86) {
791
 
    $thinX86 = FileAttrCache->new($targetPath.'.x86');
792
 
    push(@tempThinFiles, $thinX86->path());
793
 
    if (command($gConfig{'cmd_lipo'}, '-thin', 'i386',
794
 
                $sourceX86->path(), '-output', $thinX86->path()) != 0) {
795
 
      unlink(@tempThinFiles);
796
 
      return complain(1, 'lipo thin x86 failed for:',
797
 
                      $sourceX86->path(),
798
 
                      $thinX86->path());
799
 
    }
800
 
  }
801
 
 
802
 
  # The image for each architecture in the fat file will be aligned on
803
 
  # a specific boundary, default 4096 bytes, see lipo(1) -segalign.
804
 
  # Since there's no tail-padding, the fat file will consume the least
805
 
  # space on disk if the image that comes last exceeds the segment size
806
 
  # by the smallest amount.
807
 
  #
808
 
  # This saves an average of 1kB per fat file over the naive approach of
809
 
  # always putting one architecture first: average savings is 2kB per
810
 
  # file, but the naive approach would have gotten it right half of the
811
 
  # time.
812
 
 
813
 
  my ($sizePPC, $sizeX86, $thinPPCForStat, $thinX86ForStat);
814
 
 
815
 
  if (!$gDryRun) {
816
 
    $thinPPCForStat = $thinPPC;
817
 
    $thinX86ForStat = $thinX86;
818
 
  }
819
 
  else {
820
 
    # Normally, fat source files will have been converted into temporary
821
 
    # thin files.  During a dry run, that doesn't happen, so fake it up
822
 
    # a little bit by always using the source file, fat or thin, for the
823
 
    # stat.
824
 
    $thinPPCForStat = $sourcePPC;
825
 
    $thinX86ForStat = $sourceX86;
826
 
  }
827
 
 
828
 
  if (!defined($sizePPC = $thinPPCForStat->statSize())) {
829
 
    unlink(@tempThinFiles);
830
 
    return complain(1, 'stat ppc: '.$!.' for:',
831
 
                    $thinPPCForStat->path());
832
 
  }
833
 
  if (!defined($sizeX86 = $thinX86ForStat->statSize())) {
834
 
    unlink(@tempThinFiles);
835
 
    return complain(1, 'stat x86: '.$!.' for:',
836
 
                    $thinX86ForStat->path());
837
 
  }
838
 
 
839
 
  $sizePPC = $sizePPC % 4096;
840
 
  $sizeX86 = $sizeX86 % 4096;
841
 
 
842
 
  my (@thinFiles);
843
 
 
844
 
  if ($sizePPC == 0) {
845
 
    # PPC image ends on an alignment boundary, there will be no padding before
846
 
    # starting the x86 image.
847
 
    @thinFiles = ($thinPPC->path(), $thinX86->path());
848
 
  }
849
 
  elsif ($sizeX86 == 0 || $sizeX86 > $sizePPC) {
850
 
    # x86 image ends on an alignment boundary, there will be no padding before
851
 
    # starting the PPC image, or the x86 image exceeds its alignment boundary
852
 
    # by more than the PPC image, so there will be less padding if the x86
853
 
    # comes first.
854
 
    @thinFiles = ($thinX86->path(), $thinPPC->path());
855
 
  }
856
 
  else {
857
 
    # PPC image exceeds its alignment boundary by more than the x86 image, so
858
 
    # there will be less padding if the PPC comes first.
859
 
    @thinFiles = ($thinPPC->path(), $thinX86->path());
860
 
  }
861
 
 
862
 
  my ($isExecutable);
863
 
  $isExecutable = $sourcePPC->lIsExecutable() ||
864
 
                  $sourceX86->lIsExecutable();
865
 
 
866
 
  if (!$gDryRun) {
867
 
    # Ensure that the file does not yet exist.
868
 
 
869
 
    # Set the execute bits (as allowed by the umask) on the new file if any
870
 
    # execute bit is set on either old file.  Yes, it is possible to have
871
 
    # proper Mach-O files without x-bits: think object files (.o) and static
872
 
    # archives (.a).
873
 
    if (!createUniqueFile($targetPath, $isExecutable ? 0777 : 0666)) {
874
 
      # createUniqueFile printed an error.
875
 
      unlink(@tempThinFiles);
876
 
      return 0;
877
 
    }
878
 
  }
879
 
 
880
 
  # Create the fat file.
881
 
  if (command($gConfig{'cmd_lipo'}, '-create', @thinFiles,
882
 
              '-output', $targetPath) != 0) {
883
 
    unlink(@tempThinFiles, $targetPath);
884
 
    return complain(1, 'lipo create fat failed for:',
885
 
                    @thinFiles,
886
 
                    $targetPath);
887
 
  }
888
 
 
889
 
  unlink(@tempThinFiles);
890
 
 
891
 
  if (!$gDryRun) {
892
 
    # lipo seems to think that it's free to set its own file modes that
893
 
    # ignore the umask, which is bogus when the rest of this script
894
 
    # respects the umask.
895
 
    if (!chmod(($isExecutable ? 0777 : 0666) & ~umask(), $targetPath)) {
896
 
      complain(1, 'makeUniversalFile: chmod: '.$!.' for',
897
 
               $targetPath);
898
 
      unlink($targetPath);
899
 
      return 0;
900
 
    }
901
 
  }
902
 
 
903
 
  return 1;
904
 
}
905
 
 
906
 
# makeUniversalInternal($isToplevel, $filePPC, $fileX86, $fileTargetPath)
907
 
#
908
 
# Given FileAttrCache objects $filePPC and $fileX86, compares filetypes
909
 
# and performs the appropriate action to produce a universal file at
910
 
# path string $fileTargetPath.  $isToplevel should be true if this is
911
 
# the recursive base and false otherwise; this controls cleanup behavior
912
 
# (cleanup is only performed at the base, because cleanup itself is
913
 
# recursive).
914
 
#
915
 
# This handles regular files by determining whether they are Mach-O files
916
 
# and calling makeUniversalFile if so and copyIfIdentical otherwise.  Symbolic
917
 
# links are handled directly in this function by ensuring that the source link
918
 
# targets are identical and creating a new link with the same target
919
 
# at $fileTargetPath.  Directories are handled by calling
920
 
# makeUniversalDirectory.
921
 
#
922
 
# One of $filePPC and $fileX86 is permitted to be undef.  In that case,
923
 
# the defined source file is copied directly to the target if a regular
924
 
# file, and symlinked appropriately if a symbolic link.  This facilitates
925
 
# use of $gOnlyOne = 'copy', although no $gOnlyOne checks are made in this
926
 
# function, they are all handled in makeUniversalDirectory.
927
 
#
928
 
# Returns true on success.  Returns false on failure, including failures
929
 
# in other functions called.
930
 
sub makeUniversalInternal($$$$) {
931
 
  my ($filePPC, $fileTargetPath, $fileX86, $isToplevel);
932
 
  ($isToplevel, $filePPC, $fileX86, $fileTargetPath) = @_;
933
 
 
934
 
  my ($typePPC, $typeX86);
935
 
  if (defined($filePPC) && !defined($typePPC = $filePPC->lstatType())) {
936
 
    return complain(1, 'makeUniversal: lstat ppc: '.$!.' for:',
937
 
                    $filePPC->path());
938
 
  }
939
 
  if (defined($fileX86) && !defined($typeX86 = $fileX86->lstatType())) {
940
 
    return complain(1, 'makeUniversal: lstat x86: '.$!.' for:',
941
 
                    $fileX86->path());
942
 
  }
943
 
 
944
 
  if (defined($filePPC) && defined($fileX86) && $typePPC != $typeX86) {
945
 
    return complain(1, 'makeUniversal: incompatible types:',
946
 
                    $filePPC->path(),
947
 
                    $fileX86->path());
948
 
  }
949
 
 
950
 
  # $aSourceFile will contain a FileAttrCache object that will return
951
 
  # the correct type data.  It's used because it's possible for one of
952
 
  # the two source files to be undefined (indicating a straight copy).
953
 
  my ($aSourceFile);
954
 
  if (defined($filePPC)) { 
955
 
    $aSourceFile = $filePPC;
956
 
  }
957
 
  else {
958
 
    $aSourceFile = $fileX86;
959
 
  }
960
 
 
961
 
  if ($aSourceFile->lIsDir()) {
962
 
    if ($gVerbosity >= 3 || $gDryRun) {
963
 
      print('mkdir '.(argumentEscape($fileTargetPath))[0]."\n");
964
 
    }
965
 
    if (!$gDryRun && !mkdir($fileTargetPath)) {
966
 
      return complain(1, 'makeUniversal: mkdir: '.$!.' for:',
967
 
                      $fileTargetPath);
968
 
    }
969
 
 
970
 
    my ($rv);
971
 
 
972
 
    if (!($rv = makeUniversalDirectory($filePPC, $fileX86, $fileTargetPath))) {
973
 
      # makeUniversalDirectory printed an error.
974
 
      if ($isToplevel) {
975
 
        command($gConfig{'cmd_rm'},'-rf','--',$fileTargetPath);
976
 
      }
977
 
    }
978
 
    else {
979
 
      # Touch the directory when leaving it.  If unify is being run on an
980
 
      # .app bundle, the .app might show up without an icon because the
981
 
      # system might have found the .app before it was completely built.
982
 
      # Touching it dirties it in LaunchServices' mind.
983
 
      if ($gVerbosity >= 3) {
984
 
        print('touch '.(argumentEscape($fileTargetPath))[0]."\n");
985
 
      }
986
 
      utime(undef, undef, $fileTargetPath);
987
 
    }
988
 
 
989
 
    return $rv;
990
 
  }
991
 
  elsif ($aSourceFile->lIsSymLink()) {
992
 
    my ($linkPPC, $linkX86);
993
 
    if (defined($filePPC) && !defined($linkPPC=readlink($filePPC->path()))) {
994
 
      return complain(1, 'makeUniversal: readlink ppc: '.$!.' for:',
995
 
                      $filePPC->path());
996
 
    }
997
 
    if (defined($fileX86) && !defined($linkX86=readlink($fileX86->path()))) {
998
 
      return complain(1, 'makeUniversal: readlink x86: '.$!.' for:',
999
 
                      $fileX86->path());
1000
 
    }
1001
 
    if (defined($filePPC) && defined($fileX86) && $linkPPC ne $linkX86) {
1002
 
      return complain(1, 'makeUniversal: symbolic links differ:',
1003
 
                      $filePPC->path(),
1004
 
                      $fileX86->path());
1005
 
    }
1006
 
 
1007
 
    # $aLink here serves the same purpose as $aSourceFile in the enclosing
1008
 
    # block: it refers to the target of the symbolic link, whether there
1009
 
    # is one valid source or two.
1010
 
    my ($aLink);
1011
 
    if (defined($linkPPC)) {
1012
 
      $aLink = $linkPPC;
1013
 
    }
1014
 
    else {
1015
 
      $aLink = $linkX86;
1016
 
    }
1017
 
 
1018
 
    if ($gVerbosity >= 3 || $gDryRun) {
1019
 
      print('ln -s '.
1020
 
            join(' ',argumentEscape($aLink, $fileTargetPath))."\n");
1021
 
    }
1022
 
    if (!$gDryRun && !symlink($aLink, $fileTargetPath)) {
1023
 
      return complain(1, 'makeUniversal: symlink: '.$!.' for:',
1024
 
                      $aLink,
1025
 
                      $fileTargetPath);
1026
 
    }
1027
 
 
1028
 
    return 1;
1029
 
  }
1030
 
  elsif($aSourceFile->lIsRegularFile()) {
1031
 
    my ($machPPC, $machX86, $fileName);
1032
 
    if (!defined($filePPC) || !defined($fileX86)) {
1033
 
      # One of the source files isn't present.  The right thing to do is
1034
 
      # to just copy what does exist straight over, so skip Mach-O checks.
1035
 
      $machPPC = 0;
1036
 
      $machX86 = 0;
1037
 
      if (defined($filePPC)) {
1038
 
        $fileName = $filePPC;
1039
 
      } elsif (defined($fileX86)) {
1040
 
        $fileName = $fileX86;
1041
 
      } else {
1042
 
        complain(1, "The file must exist in at least one directory");
1043
 
        exit(1);
1044
 
      }
1045
 
    }
1046
 
    else {
1047
 
      # both files exist, pick the name of one.
1048
 
      $fileName = $fileX86;
1049
 
      if (!defined($machPPC=$filePPC->isMachO())) {
1050
 
        return complain(1, 'makeUniversal: isFileMachO ppc failed for:',
1051
 
                        $filePPC->path());
1052
 
      }
1053
 
      if (!defined($machX86=$fileX86->isMachO())) {
1054
 
        return complain(1, 'makeUniversal: isFileMachO x86 failed for:',
1055
 
                        $fileX86->path());
1056
 
      }
1057
 
    }
1058
 
 
1059
 
    if ($machPPC != $machX86) {
1060
 
      return complain(1, 'makeUniversal: variant Mach-O attributes:',
1061
 
                      $filePPC->path(),
1062
 
                  $fileX86->path());
1063
 
    }
1064
 
 
1065
 
    if ($machPPC) {
1066
 
      # makeUniversalFile will print an error if it fails.
1067
 
      return makeUniversalFile($filePPC, $fileX86, $fileTargetPath);
1068
 
    }
1069
 
 
1070
 
    if (grep { $fileName->path() =~ m/$_/; } @gSortMatches) {
1071
 
      # Regular files, but should be compared with sorting first.
1072
 
      # copyIfIdenticalWhenSorted will print an error if it fails.
1073
 
      return copyIfIdenticalWhenSorted($filePPC, $fileX86, $fileTargetPath);
1074
 
    }
1075
 
 
1076
 
    # Regular file.  copyIfIdentical will print an error if it fails.
1077
 
    return copyIfIdentical($filePPC, $fileX86, $fileTargetPath);
1078
 
  }
1079
 
 
1080
 
  # Special file, don't know how to handle.
1081
 
  return complain(1, 'makeUniversal: cannot handle special file:',
1082
 
                  $filePPC->path(),
1083
 
                  $fileX86->path());
1084
 
}
1085
 
 
1086
 
# usage()
1087
 
#
1088
 
# Give the user a hand.
1089
 
sub usage() {
1090
 
  print STDERR (
1091
 
"usage: unify <ppc-path> <x86-path> <universal-path>\n".
1092
 
"            [--dry-run]           (print what would be done)\n".
1093
 
"            [--only-one <action>] (skip, copy, fail; default=copy)\n".
1094
 
"            [--verbosity <level>] (0, 1, 2, 3; default=2)\n");
1095
 
  return;
1096
 
}
1097
 
 
1098
 
# readZipCRCs($zipFile)
1099
 
#
1100
 
# $zipFile is the pathname to a zip file whose directory will be read.
1101
 
# A reference to a hash is returned, with the member pathnames from the
1102
 
# zip file as keys, and reasonably unique identifiers as values.  The
1103
 
# format of the values is not specified exactly, but does include the
1104
 
# member CRCs and sizes and differentiates between files and directories.
1105
 
# It specifically does not distinguish between modification times.  On
1106
 
# failure, prints a message and returns undef.
1107
 
sub readZipCRCs($) {
1108
 
  my ($zipFile);
1109
 
  ($zipFile) = @_;
1110
 
 
1111
 
  my ($ze, $zip);
1112
 
  $zip = Archive::Zip->new();
1113
 
 
1114
 
  if (($ze = $zip->read($zipFile)) != AZ_OK) {
1115
 
    complain(1, 'readZipCRCs: read error '.$ze.' for:',
1116
 
             $zipFile);
1117
 
    return undef;
1118
 
  }
1119
 
 
1120
 
  my ($member, %memberCRCs, @memberList);
1121
 
  %memberCRCs = ();
1122
 
  @memberList = $zip->members();
1123
 
 
1124
 
  foreach $member (@memberList) {
1125
 
    # Take a few of the attributes that identify the file and stuff them into
1126
 
    # the members hash.  Directories will show up with size 0 and crc32 0,
1127
 
    # so isDirectory() is used to distinguish them from empty files.
1128
 
    $memberCRCs{$member->fileName()} = join(',', $member->isDirectory() ? 1 : 0,
1129
 
                                                 $member->uncompressedSize(),
1130
 
                                                 $member->crc32String());
1131
 
  }
1132
 
 
1133
 
  return {%memberCRCs};
1134
 
}
1135
 
 
1136
 
{
1137
 
  # FileAttrCache allows various attributes about a file to be cached
1138
 
  # so that if they are needed again after first use, no system calls
1139
 
  # will be made and the program won't need to hit the disk.
1140
 
 
1141
 
  package FileAttrCache;
1142
 
 
1143
 
  # from /usr/include/mach-o/loader.h
1144
 
  use constant MH_MAGIC    => 0xfeedface;
1145
 
  use constant MH_CIGAM    => 0xcefaedfe;
1146
 
  use constant MH_MAGIC_64 => 0xfeedfacf;
1147
 
  use constant MH_CIGAM_64 => 0xcffaedfe;
1148
 
 
1149
 
  use Fcntl(':DEFAULT', ':mode');
1150
 
 
1151
 
  # FileAttrCache->new($path)
1152
 
  #
1153
 
  # Creates a new FileAttrCache object for the file at path $path and
1154
 
  # returns it.  The cache is not primed at creation time, values are
1155
 
  # fetched lazily as they are needed.
1156
 
  sub new($$) {
1157
 
    my ($class, $path, $proto, $this);
1158
 
    ($proto, $path) = @_;
1159
 
    if (!($class = ref($proto))) {
1160
 
      $class = $proto;
1161
 
    }
1162
 
    $this = {
1163
 
      'path'        => $path,
1164
 
      'lstat'       => undef,
1165
 
      'lstatErrno'  => 0,
1166
 
      'lstatInit'   => 0,
1167
 
      'magic'       => undef,
1168
 
      'magic2'       => undef,
1169
 
      'magicErrno'  => 0,
1170
 
      'magicErrMsg' => undef,
1171
 
      'magicInit'   => 0,
1172
 
      'stat'        => undef,
1173
 
      'statErrno'   => 0,
1174
 
      'statInit'    => 0,
1175
 
    };
1176
 
    bless($this, $class);
1177
 
    return($this);
1178
 
  }
1179
 
 
1180
 
  # $FileAttrCache->isFat()
1181
 
  #
1182
 
  # Returns true if the file is a fat Mach-O file, false if it's not, and
1183
 
  # undef if an error occurs.  See /usr/include/mach-o/fat.h.
1184
 
  sub isFat($) {
1185
 
    my ($magic, $magic2, $this);
1186
 
    ($this) = @_;
1187
 
 
1188
 
    # magic() caches, there's no separate cache because isFat() doesn't hit
1189
 
    # the disk other than by calling magic().
1190
 
 
1191
 
    if (!defined($magic = $this->magic())) {
1192
 
      return undef;
1193
 
    }
1194
 
    $magic2 = $this->magic2();
1195
 
 
1196
 
    # We have to sanity check the second four bytes, because Java class
1197
 
    # files use the same magic number as Mach-O fat binaries.
1198
 
    # This logic is adapted from file(1), which says that Mach-O uses
1199
 
    # these bytes to count the number of architectures within, while
1200
 
    # Java uses it for a version number. Conveniently, there are only
1201
 
    # 18 labelled Mach-O architectures, and Java's first released
1202
 
    # class format used the version 43.0.
1203
 
    if ($magic == 0xcafebabe && $magic2 < 20) {
1204
 
      return 1;
1205
 
    }
1206
 
 
1207
 
    return 0;
1208
 
  }
1209
 
 
1210
 
  # $FileAttrCache->isMachO()
1211
 
  #
1212
 
  # Returns true if the file is a Mach-O image (including a fat file), false
1213
 
  # if it's not, and undef if an error occurs.  See
1214
 
  # /usr/include/mach-o/loader.h and /usr/include/mach-o/fat.h.
1215
 
  sub isMachO($) {
1216
 
    my ($magic, $this);
1217
 
    ($this) = @_;
1218
 
 
1219
 
    # magic() caches, there's no separate cache because isMachO() doesn't hit
1220
 
    # the disk other than by calling magic().
1221
 
 
1222
 
    if (!defined($magic = $this->magic())) {
1223
 
      return undef;
1224
 
    }
1225
 
 
1226
 
    # Accept Mach-O fat files or Mach-O thin files of either endianness.
1227
 
    if ($magic == MH_MAGIC ||
1228
 
        $magic == MH_CIGAM ||
1229
 
        $magic == MH_MAGIC_64 ||
1230
 
        $magic == MH_CIGAM_64 ||
1231
 
        $this->isFat()) {
1232
 
      return 1;
1233
 
    }
1234
 
 
1235
 
    return 0;
1236
 
  }
1237
 
 
1238
 
  # $FileAttrCache->isZip()
1239
 
  #
1240
 
  # Returns true if the file is a zip file, false if it's not, and undef if
1241
 
  # an error occurs.  See http://www.pkware.com/business_and_developers/developer/popups/appnote.txt .
1242
 
  sub isZip($) {
1243
 
    my ($magic, $this);
1244
 
    ($this) = @_;
1245
 
 
1246
 
    # magic() caches, there's no separate cache because isFat() doesn't hit
1247
 
    # the disk other than by calling magic().
1248
 
 
1249
 
    if (!defined($magic = $this->magic())) {
1250
 
      return undef;
1251
 
    }
1252
 
 
1253
 
    if ($magic == 0x504b0304) {
1254
 
      return 1;
1255
 
    }
1256
 
 
1257
 
    return 0;
1258
 
  }
1259
 
 
1260
 
  # $FileAttrCache->lIsExecutable()
1261
 
  #
1262
 
  # Wraps $FileAttrCache->lstat(), returning true if the file is has any,
1263
 
  # execute bit set, false if none are set, or undef if an error occurs.
1264
 
  # On error, $! is set to lstat's errno.
1265
 
  sub lIsExecutable($) {
1266
 
    my ($mode, $this);
1267
 
    ($this) = @_;
1268
 
 
1269
 
    if (!defined($mode = $this->lstatMode())) {
1270
 
      return undef;
1271
 
    }
1272
 
 
1273
 
    return $mode & (S_IXUSR | S_IXGRP | S_IXOTH);
1274
 
  }
1275
 
 
1276
 
  # $FileAttrCache->lIsDir()
1277
 
  #
1278
 
  # Wraps $FileAttrCache->lstat(), returning true if the file is a directory,
1279
 
  # false if it isn't, or undef if an error occurs.  Because lstat is used,
1280
 
  # this will return false even if the file is a symlink pointing to a
1281
 
  # directory.  On error, $! is set to lstat's errno.
1282
 
  sub lIsDir($) {
1283
 
    my ($type, $this);
1284
 
    ($this) = @_;
1285
 
 
1286
 
    if (!defined($type = $this->lstatType())) {
1287
 
      return undef;
1288
 
    }
1289
 
 
1290
 
    return S_ISDIR($type);
1291
 
  }
1292
 
 
1293
 
  # $FileAttrCache->lIsRegularFile()
1294
 
  #
1295
 
  # Wraps $FileAttrCache->lstat(), returning true if the file is a regular,
1296
 
  # file, false if it isn't, or undef if an error occurs.  Because lstat is
1297
 
  # used, this will return false even if the file is a symlink pointing to a
1298
 
  # regular file.  On error, $! is set to lstat's errno.
1299
 
  sub lIsRegularFile($) {
1300
 
    my ($type, $this);
1301
 
    ($this) = @_;
1302
 
 
1303
 
    if (!defined($type = $this->lstatType())) {
1304
 
      return undef;
1305
 
    }
1306
 
 
1307
 
    return S_ISREG($type);
1308
 
  }
1309
 
 
1310
 
  # $FileAttrCache->lIsSymLink()
1311
 
  #
1312
 
  # Wraps $FileAttrCache->lstat(), returning true if the file is a symbolic,
1313
 
  # link, false if it isn't, or undef if an error occurs.  On error, $! is
1314
 
  # set to lstat's errno.
1315
 
  sub lIsSymLink($) {
1316
 
    my ($type, $this);
1317
 
    ($this) = @_;
1318
 
   
1319
 
    if (!defined($type = $this->lstatType())) {
1320
 
      return undef;
1321
 
    }
1322
 
 
1323
 
    return S_ISLNK($type);
1324
 
  }
1325
 
 
1326
 
  # $FileAttrCache->lstat()
1327
 
  #
1328
 
  # Wraps the lstat system call, providing a cache to speed up multiple
1329
 
  # lstat calls for the same file.  See lstat(2) and lstat in perlfunc(1).
1330
 
  sub lstat($) {
1331
 
    my (@stat, $this);
1332
 
    ($this) = @_;
1333
 
 
1334
 
    # Use the cached lstat result.
1335
 
    if ($$this{'lstatInit'}) {
1336
 
      if (defined($$this{'lstatErrno'})) {
1337
 
        $! = $$this{'lstatErrno'};
1338
 
      }
1339
 
      return @{$$this{'lstat'}};
1340
 
    }
1341
 
    $$this{'lstatInit'} = 1;
1342
 
 
1343
 
    if (!(@stat = CORE::lstat($$this{'path'}))) {
1344
 
      $$this{'lstatErrno'} = $!;
1345
 
    }
1346
 
 
1347
 
    $$this{'lstat'} = [@stat];
1348
 
    return @stat;
1349
 
  }
1350
 
 
1351
 
  # $FileAttrCache->lstatMode()
1352
 
  #
1353
 
  # Wraps $FileAttrCache->lstat(), returning the mode bits from the st_mode
1354
 
  # field, or undef if an error occurs.  On error, $! is set to lstat's
1355
 
  # errno.
1356
 
  sub lstatMode($) {
1357
 
    my (@stat, $this);
1358
 
    ($this) = @_;
1359
 
 
1360
 
    if (!(@stat = $this->lstat())) {
1361
 
      return undef;
1362
 
    }
1363
 
 
1364
 
    return S_IMODE($stat[2]);
1365
 
  }
1366
 
 
1367
 
  # $FileAttrCache->lstatType()
1368
 
  #
1369
 
  # Wraps $FileAttrCache->lstat(), returning the type bits from the st_mode
1370
 
  # field, or undef if an error occurs.  On error, $! is set to lstat's
1371
 
  # errno.
1372
 
  sub lstatType($) {
1373
 
    my (@stat, $this);
1374
 
    ($this) = @_;
1375
 
 
1376
 
    if (!(@stat = $this->lstat())) {
1377
 
      return undef;
1378
 
    }
1379
 
 
1380
 
    return S_IFMT($stat[2]);
1381
 
  }
1382
 
 
1383
 
  # $FileAttrCache->magic()
1384
 
  #
1385
 
  # Returns the "magic number" for the file by reading its first four bytes
1386
 
  # as a big-endian unsigned 32-bit integer and returning the result.  If an
1387
 
  # error occurs, returns undef and prints diagnostic messages to stderr.  If
1388
 
  # the file is shorter than 32 bits, returns -1.  A cache is provided to
1389
 
  # speed multiple magic calls for the same file.
1390
 
  sub magic($) {
1391
 
    my ($this);
1392
 
    ($this) = @_;
1393
 
 
1394
 
    # Use the cached magic result.
1395
 
    if ($$this{'magicInit'}) {
1396
 
      if (defined($$this{'magicErrno'})) {
1397
 
        if (defined($$this{'magicErrMsg'})) {
1398
 
          complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
1399
 
                   $$this{'path'});
1400
 
        }
1401
 
        $! = $$this{'magicErrno'};
1402
 
      }
1403
 
      return $$this{'magic'};
1404
 
    }
1405
 
 
1406
 
    $$this{'magicInit'} = 1;
1407
 
 
1408
 
    my ($fh);
1409
 
    if (!sysopen($fh, $$this{'path'}, O_RDONLY)) {
1410
 
      $$this{'magicErrno'} = $!;
1411
 
      $$this{'magicErrMsg'} = 'open "'.$$this{'path'}.'": '.$!;
1412
 
      complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
1413
 
               $$this{'path'});
1414
 
      return undef;
1415
 
    }
1416
 
 
1417
 
    $! = 0;
1418
 
    my ($bytes, $magic, $bytes2, $magic2);
1419
 
    if (!defined($bytes = sysread($fh, $magic, 4))) {
1420
 
      $$this{'magicErrno'} = $!;
1421
 
      $$this{'magicErrMsg'} = 'read "'.$$this{'path'}.'": '.$!;
1422
 
      complain(1, 'FileAttrCache::magic: '.$$this{'magicErrMsg'}.' for:',
1423
 
               $$this{'path'});
1424
 
      close($fh);
1425
 
      return undef;
1426
 
    }
1427
 
    else {
1428
 
      $bytes2 = sysread($fh, $magic2, 4);
1429
 
    }
1430
 
 
1431
 
    close($fh);
1432
 
 
1433
 
    if ($bytes != 4) {
1434
 
      # The file is too short, didn't read a magic number.  This isn't really
1435
 
      # an error.  Return an unlikely value.
1436
 
      $$this{'magic'} = -1;
1437
 
      $$this{'magic2'} = -1;
1438
 
      return -1;
1439
 
    }
1440
 
    if ($bytes2 != 4) {
1441
 
      # File is too short to read a second 4 bytes.
1442
 
      $magic2 = -1;
1443
 
    }
1444
 
 
1445
 
    $$this{'magic'} = unpack('N', $magic);
1446
 
    $$this{'magic2'} = unpack('N', $magic2);
1447
 
    return $$this{'magic'};
1448
 
  }
1449
 
 
1450
 
  # $FileAttrCache->magic2()
1451
 
  #
1452
 
  # Returns the second four bytes of the file as a 32-bit little endian number.
1453
 
  # See magic(), above for more info.
1454
 
  sub magic2($) {
1455
 
    my ($this);
1456
 
    ($this) = @_;
1457
 
 
1458
 
    # we do the actual work (and cache it) in magic().
1459
 
    if (!$$this{'magicInit'}) {
1460
 
      my $magic = $$this->magic();
1461
 
    }
1462
 
 
1463
 
    return $$this{'magic2'};
1464
 
  }
1465
 
 
1466
 
  # $FileAttrCache->path()
1467
 
  #
1468
 
  # Returns the file's pathname.
1469
 
  sub path($) {
1470
 
    my ($this);
1471
 
    ($this) = @_;
1472
 
    return $$this{'path'};
1473
 
  }
1474
 
 
1475
 
  # $FileAttrCache->stat()
1476
 
  #
1477
 
  # Wraps the stat system call, providing a cache to speed up multiple
1478
 
  # stat calls for the same file.  If lstat() has already been called and
1479
 
  # the file is not a symbolic link, the cached lstat() result will be used.
1480
 
  # See stat(2) and lstat in perlfunc(1).
1481
 
  sub stat($) {
1482
 
    my (@stat, $this);
1483
 
    ($this) = @_;
1484
 
 
1485
 
    # Use the cached stat result.
1486
 
    if ($$this{'statInit'}) {
1487
 
      if (defined($$this{'statErrno'})) {
1488
 
        $! = $$this{'statErrno'};
1489
 
      }
1490
 
      return @{$$this{'stat'}};
1491
 
    }
1492
 
 
1493
 
    $$this{'statInit'} = 1;
1494
 
 
1495
 
    # If lstat has already been called, and the file isn't a symbolic link,
1496
 
    # use the cached lstat result.
1497
 
    if ($$this{'lstatInit'} && !$$this{'lstatErrno'} &&
1498
 
        !S_ISLNK(${$$this{'lstat'}}[2])) {
1499
 
      $$this{'stat'} = $$this{'lstat'};
1500
 
      return @{$$this{'stat'}};
1501
 
    }
1502
 
 
1503
 
    if (!(@stat = CORE::stat($$this{'path'}))) {
1504
 
      $$this{'statErrno'} = $!;
1505
 
    }
1506
 
 
1507
 
    $$this{'stat'} = [@stat];
1508
 
    return @stat;
1509
 
  }
1510
 
 
1511
 
  # $FileAttrCache->statSize()
1512
 
  #
1513
 
  # Wraps $FileAttrCache->stat(), returning the st_size field, or undef
1514
 
  # undef if an error occurs.  On error, $! is set to stat's errno.
1515
 
  sub statSize($) {
1516
 
    my (@stat, $this);
1517
 
    ($this) = @_;
1518
 
 
1519
 
    if (!(@stat = $this->lstat())) {
1520
 
      return undef;
1521
 
    }
1522
 
 
1523
 
    return $stat[7];
1524
 
  }
1525
 
}