~x2go/x2go/x2goclient_build-main

375 by Mike Gabriel
Add scripts and additional files for building X2Go Client disk images for Mac OS X. (Fixes: #131).
1
#!/usr/bin/perl
2
# ***** BEGIN LICENSE BLOCK *****
3
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
#
5
# The contents of this file are subject to the Mozilla Public License Version
6
# 1.1 (the "License"); you may not use this file except in compliance with
7
# the License. You may obtain a copy of the License at
1736 by Mihai Moldovan
misc: change http:// to https:// where appropriate, but in actual code and translation files for now.
8
# https://www.mozilla.org/MPL/
375 by Mike Gabriel
Add scripts and additional files for building X2Go Client disk images for Mac OS X. (Fixes: #131).
9
#
10
# Software distributed under the License is distributed on an "AS IS" basis,
11
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12
# for the specific language governing rights and limitations under the
13
# License.
14
#
15
# The Original Code is pkg-dmg, a Mac OS X disk image (.dmg) packager
16
#
17
# The Initial Developer of the Original Code is
18
# Mark Mentovai <mark@moxienet.com>.
19
# Portions created by the Initial Developer are Copyright (C) 2005
20
# the Initial Developer. All Rights Reserved.
21
#
22
# Contributor(s):
23
#
24
# Alternatively, the contents of this file may be used under the terms of
25
# either the GNU General Public License Version 2 or later (the "GPL"), or
26
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27
# in which case the provisions of the GPL or the LGPL are applicable instead
28
# of those above. If you wish to allow use of your version of this file only
29
# under the terms of either the GPL or the LGPL, and not to allow others to
30
# use your version of this file under the terms of the MPL, indicate your
31
# decision by deleting the provisions above and replace them with the notice
32
# and other provisions required by the GPL or the LGPL. If you do not delete
33
# the provisions above, a recipient may use your version of this file under
34
# the terms of any one of the MPL, the GPL or the LGPL.
35
#
36
# ***** END LICENSE BLOCK *****
37
38
use strict;
39
use warnings;
40
41
=pod
42
43
=head1 NAME
44
45
B<pkg-dmg> - Mac OS X disk image (.dmg) packager
46
47
=head1 SYNOPSIS
48
49
B<pkg-dmg>
50
B<--source> I<source-folder>
51
B<--target> I<target-image>
52
[B<--format> I<format>]
53
[B<--volname> I<volume-name>]
54
[B<--tempdir> I<temp-dir>]
55
[B<--mkdir> I<directory>]
56
[B<--copy> I<source>[:I<dest>]]
57
[B<--symlink> I<source>[:I<dest>]]
58
[B<--license> I<file>]
59
[B<--resource> I<file>]
60
[B<--icon> I<icns-file>]
61
[B<--attribute> I<a>:I<file>[:I<file>...]
62
[B<--idme>]
63
[B<--sourcefile>]
64
[B<--verbosity> I<level>]
65
[B<--dry-run>]
66
67
=head1 DESCRIPTION
68
69
I<pkg-dmg> takes a directory identified by I<source-folder> and transforms
70
it into a disk image stored as I<target-image>.  The disk image will
71
occupy the least space possible for its format, or the least space that the
72
authors have been able to figure out how to achieve.
73
74
=head1 OPTIONS
75
76
=over 5
77
78
==item B<--source> I<source-folder>
79
80
Identifies the directory that will be packaged up.  This directory is not
81
touched, a copy will be made in a temporary directory for staging purposes.
82
See B<--tempdir>.
83
84
==item B<--target> I<target-image>
85
86
The disk image to create.  If it exists and is not in use, it will be
87
overwritten.  If I<target-image> already contains a suitable extension,
88
it will be used unmodified.  If no extension is present, or the extension
89
is incorrect for the selected format, the proper extension will be added.
90
See B<--format>.
91
92
==item B<--format> I<format>
93
94
The format to create the disk image in.  Valid values for I<format> are:
95
     - UDZO - zlib-compressed, read-only; extension I<.dmg>
96
     - UDBZ - bzip2-compressed, read-only; extension I<.dmg>;
97
              create and use on 10.4 ("Tiger") and later only
98
     - UDRO - uncompressed, read-only; extension I<.dmg>
99
     - UDRW - uncompressed, read-write; extension I<.dmg>
100
     - UDSP - uncompressed, read-write, sparse; extension I<.sparseimage>
101
102
UDZO is the default format.
103
104
See L<hdiutil(1)> for a description of these formats.
105
106
=item B<--volname> I<volume-name>
107
108
The name of the volume in the disk image.  If not specified, I<volume-name>
109
defaults to the name of the source directory from B<--source>.
110
111
=item B<--tempdir> I<temp-dir>
112
113
A temporary directory to stage intermediate files in.  I<temp-dir> must
114
have enough space available to accommodate twice the size of the files
115
being packaged.  If not specified, defaults to the same directory that
116
the I<target-image> is to be placed in.  B<pkg-dmg> will remove any
117
temporary files it places in I<temp-dir>.
118
119
=item B<--mkdir> I<directory>
120
121
Specifies a directory that should be created in the disk image.
122
I<directory> and any ancestor directories will be created.  This is
123
useful in conjunction with B<--copy>, when copying files to directories
124
that may not exist in I<source-folder>.  B<--mkdir> may appear multiple
125
times.
126
127
=item B<--copy> I<source>[:I<dest>]
128
129
Additional files to copy into the disk image.  If I<dest> is
130
specified, I<source> is copied to the location I<dest> identifies,
131
otherwise, I<source> is copied to the root of the new volume.  B<--copy>
132
provides a way to package up a I<source-folder> by adding files to it
133
without modifying the original I<source-folder>.  B<--copy> may appear
134
multiple times.
135
136
This option is useful for adding .DS_Store files and window backgrounds
137
to disk images.
138
139
=item B<--symlink> I<source>[:I<dest>]
140
141
Like B<--copy>, but allows symlinks to point out of the volume. Empty symlink
142
destinations are interpreted as "like the source path, but inside the dmg"
143
144
This option is useful for adding symlinks to external resources,
145
e.g. to /Applications.
146
147
=item B<--license> I<file>
148
149
A plain text file containing a license agreement to be displayed before
150
the disk image is mounted.  English is the only supported language.  To
151
include license agreements in other languages, in multiple languages,
152
or to use formatted text, prepare a resource and use L<--resource>.
153
154
=item B<--resource> I<file>
155
156
A resource file to merge into I<target-image>.  If I<format> is UDZO, UDBZ,
157
or UDRO, the disk image will be flattened to a single-fork file that contains
158
the resource but may be freely transferred without any special encodings.
159
I<file> must be in a format suitable for L<Rez(1)>.  See L<Rez(1)> for a
160
description of the format, and L<hdiutil(1)> for a discussion on flattened
161
disk images.  B<--resource> may appear multiple times.
162
163
This option is useful for adding license agreements and other messages
164
to disk images.
165
166
=item B<--icon> I<icns-file>
167
168
Specifies an I<icns> file that will be used as the icon for the root of
169
the volume.  This file will be copied to the new volume and the custom
170
icon attribute will be set on the root folder.
171
172
=item B<--attribute> I<a>:I<file>[:I<file>...]
173
174
Sets the attributes of I<file> to the attribute list in I<a>.  See
175
L<SetFile(1)>
176
177
=item B<--idme>
178
179
Enable IDME to make the disk image "Internet-enabled."  The first time
180
the image is mounted, if IDME processing is enabled on the system, the
181
contents of the image will be copied out of the image and the image will
182
be placed in the trash with IDME disabled.
183
184
=item B<--sourcefile>
185
186
If this option is present, I<source-folder> is treated as a file, and is
187
placed as a file within the volume's root folder.  Without this option,
188
I<source-folder> is treated as the volume root itself.
189
190
=item B<--verbosity> I<level>
191
192
Adjusts the level of loudness of B<pkg-dmg>.  The possible values for
193
I<level> are:
194
     0 - Only error messages are displayed.
195
     1 - Print error messages and command invocations.
196
     2 - Print everything, including command output.
197
198
The default I<level> is 2.
199
200
=item B<--dry-run>
201
202
When specified, the commands that would be executed are printed, without
203
actually executing them.  When commands depend on the output of previous
204
commands, dummy values are displayed.
205
206
=back
207
208
=head1 NON-OPTIONS
209
210
=over 5
211
212
=item
213
214
Resource forks aren't copied.
215
216
=item
217
218
The root folder of the created volume is designated as the folder
219
to open when the volume is mounted.  See L<bless(8)>.
220
221
=item
222
223
All files in the volume are set to be world-readable, only writable
224
by the owner, and world-executable when appropriate.  All other
225
permissions bits are cleared.
226
227
=item
228
229
When possible, disk images are created without any partition tables.  This
230
is what L<hdiutil(1)> refers to as I<-layout NONE>, and saves a handful of
231
kilobytes.  The alternative, I<SPUD>, contains a partition table that
232
is not terribly handy on disk images that are not intended to represent any
233
physical disk.
234
235
=item
236
237
Read-write images are created with journaling off.  Any read-write image
238
created by this tool is expected to be transient, and the goal of this tool
239
is to create images which consume a minimum of space.
240
241
=back
242
243
=head1 EXAMPLE
244
245
pkg-dmg --source /Applications/DeerPark.app --target ~/DeerPark.dmg
246
  --sourcefile --volname DeerPark --icon ~/DeerPark.icns
247
  --mkdir /.background
248
  --copy DeerParkBackground.png:/.background/background.png
249
  --copy DeerParkDSStore:/.DS_Store
250
  --symlink /Applications:"/Drag to here"
251
252
=head1 REQUIREMENTS
253
254
I<pkg-dmg> has been tested with Mac OS X releases 10.2 ("Jaguar")
255
through 10.4 ("Tiger").  Certain adjustments to behavior are made
256
depending on the host system's release.  Mac OS X 10.3 ("Panther") or
257
later are recommended.
258
259
=head1 LICENSE
260
261
MPL 1.1/GPL 2.0/LGPL 2.1.  Your choice.
262
263
=head1 AUTHOR
264
265
Mark Mentovai
266
267
=head1 SEE ALSO
268
269
L<bless(8)>, L<diskutil(8)>, L<hdid(8)>, L<hdiutil(1)>, L<Rez(1)>,
270
L<rsync(1)>, L<SetFile(1)>
271
272
=cut
273
274
use Fcntl;
275
use POSIX;
276
use Getopt::Long;
277
278
sub argumentEscape(@);
279
sub cleanupDie($);
280
sub command(@);
281
sub commandInternal($@);
282
sub commandInternalVerbosity($$@);
283
sub commandOutput(@);
284
sub commandOutputVerbosity($@);
285
sub commandVerbosity($@);
286
sub copyFiles($@);
287
sub diskImageMaker($$$$$$$$);
288
sub giveExtension($$);
289
sub hdidMountImage($@);
290
sub isFormatReadOnly($);
291
sub licenseMaker($$);
292
sub pathSplit($);
293
sub setAttributes($@);
294
sub trapSignal($);
295
sub usage();
296
297
# Variables used as globals
298
my(@gCleanup, %gConfig, $gDarwinMajor, $gDryRun, $gVerbosity);
299
300
# Use the commands by name if they're expected to be in the user's
301
# $PATH (/bin:/sbin:/usr/bin:/usr/sbin).  Otherwise, go by absolute
302
# path.  These may be overridden with --config.
303
%gConfig = ('cmd_bless'          => 'bless',
304
            'cmd_chmod'          => 'chmod',
305
            'cmd_diskutil'       => 'diskutil',
306
            'cmd_du'             => 'du',
307
            'cmd_hdid'           => 'hdid',
308
            'cmd_hdiutil'        => 'hdiutil',
309
            'cmd_mkdir'          => 'mkdir',
310
            'cmd_mktemp'         => 'mktemp',
311
            'cmd_Rez'            => '/usr/bin/Rez',
312
            'cmd_rm'             => 'rm',
313
            'cmd_rsync'          => 'rsync',
314
            'cmd_SetFile'        => '/usr/bin/SetFile',
315
316
            # create_directly indicates whether hdiutil create supports
317
            # -srcfolder and -srcdevice.  It does on >= 10.3 (Panther).
318
            # This is fixed up for earlier systems below.  If false,
319
            # hdiutil create is used to create empty disk images that
320
            # are manually filled.
321
            'create_directly'    => 1,
322
323
            # If hdiutil attach -mountpoint exists, use it to avoid
324
            # mounting disk images in the default /Volumes.  This reduces
325
            # the likelihood that someone will notice a mounted image and
326
            # interfere with it.  Only available on >= 10.3 (Panther),
327
            # fixed up for earlier systems below.
328
            #
329
            # This is presently turned off for all systems, because there
330
            # is an infrequent synchronization problem during ejection.
331
            # diskutil eject might return before the image is actually
332
            # unmounted.  If pkg-dmg then attempts to clean up its
333
            # temporary directory, it could remove items from a read-write
334
            # disk image or attempt to remove items from a read-only disk
335
            # image (or a read-only item from a read-write image) and fail,
336
            # causing pkg-dmg to abort.  This problem is experienced
337
            # under Tiger, which appears to eject asynchronously where
338
            # previous systems treated it as a synchronous operation.
339
            # Using hdiutil attach -mountpoint didn't always keep images
340
            # from showing up on the desktop anyway.
341
            'hdiutil_mountpoint' => 0,
342
343
            # hdiutil makehybrid results in optimized disk images that
344
            # consume less space and mount more quickly.  Use it when
345
            # it's available, but that's only on >= 10.3 (Panther).
346
            # If false, hdiutil create is used instead.  Fixed up for
347
            # earlier systems below.
348
            'makehybrid'         => 1,
349
350
            # hdiutil create doesn't allow specifying a folder to open
351
            # at volume mount time, so those images are mounted and
352
            # their root folders made holy with bless -openfolder.  But
353
            # only on >= 10.3 (Panther).  Earlier systems are out of luck.
354
            # Even on Panther, bless refuses to run unless root.
355
            # Fixed up below.
356
            'openfolder_bless'   => 1,
357
358
            # It's possible to save a few more kilobytes by including the
359
            # partition only without any partition table in the image.
360
            # This is a good idea on any system, so turn this option off.
361
            #
362
            # Except it's buggy.  "-layout NONE" seems to be creating
363
            # disk images with more data than just the partition table
364
            # stripped out.  You might wind up losing the end of the
365
            # filesystem - the last file (or several) might be incomplete.
366
            'partition_table'    => 1,
367
368
            # To create a partition table-less image from something
369
            # created by makehybrid, the hybrid image needs to be
370
            # mounted and a new image made from the device associated
371
            # with the relevant partition.  This requires >= 10.4
372
            # (Tiger), presumably because earlier systems have
373
            # problems creating images from devices themselves attached
374
            # to images.  If this is false, makehybrid images will
375
            # have partition tables, regardless of the partition_table
376
            # setting.  Fixed up for earlier systems below.
377
            'recursive_access'   => 1);
378
379
# --verbosity
380
$gVerbosity = 2;
381
382
# --dry-run
383
$gDryRun = 0;
384
385
# %gConfig fix-ups based on features and bugs present in certain releases.
386
my($ignore, $uname_r, $uname_s);
387
($uname_s, $ignore, $uname_r, $ignore, $ignore) = POSIX::uname();
388
if($uname_s eq 'Darwin') {
389
  ($gDarwinMajor, $ignore) = split(/\./, $uname_r, 2);
390
391
  # $major is the Darwin major release, which for our purposes, is 4 higher
392
  # than the interesting digit in a Mac OS X release.
393
  if($gDarwinMajor <= 6) {
394
    # <= 10.2 (Jaguar)
395
    # hdiutil create does not support -srcfolder or -srcdevice
396
    $gConfig{'create_directly'} = 0;
397
    # hdiutil attach does not support -mountpoint
398
    $gConfig{'hdiutil_mountpoint'} = 0;
399
    # hdiutil mkhybrid does not exist
400
    $gConfig{'makehybrid'} = 0;
401
  }
402
  if($gDarwinMajor <= 7) {
403
    # <= 10.3 (Panther)
404
    # Can't mount a disk image and then make a disk image from the device
405
    $gConfig{'recursive_access'} = 0;
406
    # bless does not support -openfolder on 10.2 (Jaguar) and must run
407
    # as root under 10.3 (Panther)
408
    $gConfig{'openfolder_bless'} = 0;
409
  }
410
}
411
else {
412
  # If it's not Mac OS X, just assume all of those good features are
413
  # available.  They're not, but things will fail long before they
414
  # have a chance to make a difference.
415
  #
416
  # Now, if someone wanted to document some of these private formats...
417
  print STDERR ($0.": warning, not running on Mac OS X, ".
418
   "this could be interesting.\n");
419
}
420
421
# Non-global variables used in Getopt
422
my(@attributes, @copyFiles, @createSymlinks, $iconFile, $idme, $licenseFile,
423
 @makeDirs, $outputFormat, @resourceFiles, $sourceFile, $sourceFolder,
424
 $targetImage, $tempDir, $volumeName);
425
426
# --format
427
$outputFormat = 'UDZO';
428
429
# --idme
430
$idme = 0;
431
432
# --sourcefile
433
$sourceFile = 0;
434
435
# Leaving this might screw up the Apple tools.
436
delete $ENV{'NEXT_ROOT'};
437
438
# This script can get pretty messy, so trap a few signals.
439
$SIG{'INT'} = \&trapSignal;
440
$SIG{'HUP'} = \&trapSignal;
441
$SIG{'TERM'} = \&trapSignal;
442
443
Getopt::Long::Configure('pass_through');
444
GetOptions('source=s'    => \$sourceFolder,
445
           'target=s'    => \$targetImage,
446
           'volname=s'   => \$volumeName,
447
           'format=s'    => \$outputFormat,
448
           'tempdir=s'   => \$tempDir,
449
           'mkdir=s'     => \@makeDirs,
450
           'copy=s'      => \@copyFiles,
451
           'symlink=s'   => \@createSymlinks,
452
           'license=s'   => \$licenseFile,
453
           'resource=s'  => \@resourceFiles,
454
           'icon=s'      => \$iconFile,
455
           'attribute=s' => \@attributes,
456
           'idme'        => \$idme,
457
           'sourcefile'  => \$sourceFile,
458
           'verbosity=i' => \$gVerbosity,
459
           'dry-run'     => \$gDryRun,
460
           'config=s'    => \%gConfig); # "hidden" option not in usage()
461
462
if(@ARGV) {
463
  # All arguments are parsed by Getopt
464
  usage();
465
  exit(1);
466
}
467
468
if($gVerbosity<0 || $gVerbosity>2) {
469
  usage();
470
  exit(1);
471
}
472
473
if(!defined($sourceFolder) || $sourceFolder eq '' ||
474
 !defined($targetImage) || $targetImage eq '') {
475
  # --source and --target are required arguments
476
  usage();
477
  exit(1);
478
}
479
480
# Make sure $sourceFolder doesn't contain trailing slashes.  It messes with
481
# rsync.
482
while(substr($sourceFolder, -1) eq '/') {
483
  chop($sourceFolder);
484
}
485
486
if(!defined($volumeName)) {
487
  # Default volumeName is the name of the source directory.
488
  my(@components);
489
  @components = pathSplit($sourceFolder);
490
  $volumeName = pop(@components);
491
}
492
493
my(@tempDirComponents, $targetImageFilename);
494
@tempDirComponents = pathSplit($targetImage);
495
$targetImageFilename = pop(@tempDirComponents);
496
497
if(defined($tempDir)) {
498
  @tempDirComponents = pathSplit($tempDir);
499
}
500
else {
501
  # Default tempDir is the same directory as what is specified for
502
  # targetImage
503
  $tempDir = join('/', @tempDirComponents);
504
}
505
506
# Ensure that the path of the target image has a suitable extension.  If
507
# it didn't, hdiutil would add one, and we wouldn't be able to find the
508
# file.
509
#
510
# Note that $targetImageFilename is not being reset.  This is because it's
511
# used to build other names below, and we don't need to be adding all sorts
512
# of extra unnecessary extensions to the name.
513
my($originalTargetImage, $requiredExtension);
514
$originalTargetImage = $targetImage;
515
if($outputFormat eq 'UDSP') {
516
  $requiredExtension = '.sparseimage';
517
}
518
else {
519
  $requiredExtension = '.dmg';
520
}
521
$targetImage = giveExtension($originalTargetImage, $requiredExtension);
522
523
if($targetImage ne $originalTargetImage) {
524
  print STDERR ($0.": warning: target image extension is being added\n");
525
  print STDERR ('  The new filename is '.
526
   giveExtension($targetImageFilename,$requiredExtension)."\n");
527
}
528
529
# Make a temporary directory in $tempDir for our own nefarious purposes.
530
my(@output, $tempSubdir, $tempSubdirTemplate);
531
$tempSubdirTemplate=join('/', @tempDirComponents,
532
 'pkg-dmg.'.$$.'.XXXXXXXX');
533
if(!(@output = commandOutput($gConfig{'cmd_mktemp'}, '-d',
534
 $tempSubdirTemplate)) || $#output != 0) {
535
  cleanupDie('mktemp failed');
536
}
537
538
if($gDryRun) {
539
  (@output)=($tempSubdirTemplate);
540
}
541
542
($tempSubdir) = @output;
543
544
push(@gCleanup,
545
 sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempSubdir);});
546
547
my($tempMount, $tempRoot, @tempsToMake);
548
$tempRoot = $tempSubdir.'/stage';
549
$tempMount = $tempSubdir.'/mount';
550
push(@tempsToMake, $tempRoot);
551
if($gConfig{'hdiutil_mountpoint'}) {
552
  push(@tempsToMake, $tempMount);
553
}
554
555
if(command($gConfig{'cmd_mkdir'}, @tempsToMake) != 0) {
556
  cleanupDie('mkdir tempRoot/tempMount failed');
557
}
558
559
# This cleanup object is not strictly necessary, because $tempRoot is inside
560
# of $tempSubdir, but the rest of the script relies on this object being
561
# on the cleanup stack and expects to remove it.
562
push(@gCleanup,
563
 sub {commandVerbosity(0, $gConfig{'cmd_rm'}, '-rf', $tempRoot);});
564
565
# If $sourceFile is true, it means that $sourceFolder is to be treated as
566
# a file and placed as a file within the volume root, as opposed to being
567
# treated as the volume root itself.  rsync will do this by default, if no
568
# trailing '/' is present.  With a trailing '/', $sourceFolder becomes
569
# $tempRoot, instead of becoming an entry in $tempRoot.
570
if(command($gConfig{'cmd_rsync'}, '-aC', '--include', '*.so',
571
 '--copy-unsafe-links', $sourceFolder.($sourceFile?'':'/'),$tempRoot) != 0) {
572
  cleanupDie('rsync failed');
573
}
574
575
if(@makeDirs) {
576
  my($makeDir, @tempDirsToMake);
577
  foreach $makeDir (@makeDirs) {
578
    if($makeDir =~ /^\//) {
579
      push(@tempDirsToMake, $tempRoot.$makeDir);
580
    }
581
    else {
582
      push(@tempDirsToMake, $tempRoot.'/'.$makeDir);
583
    }
584
  }
585
  if(command($gConfig{'cmd_mkdir'}, '-p', @tempDirsToMake) != 0) {
586
    cleanupDie('mkdir failed');
587
  }
588
}
589
590
# copy files and/or create symlinks
591
copyFiles($tempRoot, 'copy', @copyFiles);
592
copyFiles($tempRoot, 'symlink', @createSymlinks);
593
594
if($gConfig{'create_directly'}) {
595
  # If create_directly is false, the contents will be rsynced into a
596
  # disk image and they would lose their attributes.
597
  setAttributes($tempRoot, @attributes);
598
}
599
600
if(defined($iconFile)) {
601
  if(command($gConfig{'cmd_rsync'}, '-aC', '--include', '*.so',
602
   '--copy-unsafe-links', $iconFile, $tempRoot.'/.VolumeIcon.icns') != 0) {
603
    cleanupDie('rsync failed for volume icon');
604
  }
605
606
  # It's pointless to set the attributes of the root when diskutil create
607
  # -srcfolder is being used.  In that case, the attributes will be set
608
  # later, after the image is already created.
609
  if(isFormatReadOnly($outputFormat) &&
610
   (command($gConfig{'cmd_SetFile'}, '-a', 'C', $tempRoot) != 0)) {
611
    cleanupDie('SetFile failed');
612
  }
613
}
614
615
if(command($gConfig{'cmd_chmod'}, '-R', 'a+rX,a-st,u+w,go-w',
616
 $tempRoot) != 0) {
617
  cleanupDie('chmod failed');
618
}
619
620
my($unflattenable);
621
if(isFormatReadOnly($outputFormat)) {
622
  $unflattenable = 1;
623
}
624
else {
625
  $unflattenable = 0;
626
}
627
628
diskImageMaker($tempRoot, $targetImage, $outputFormat, $volumeName,
629
 $tempSubdir, $tempMount, $targetImageFilename, defined($iconFile));
630
631
if(defined($licenseFile) && $licenseFile ne '') {
632
  my($licenseResource);
633
  $licenseResource = $tempSubdir.'/license.r';
634
  if(!licenseMaker($licenseFile, $licenseResource)) {
635
    cleanupDie('licenseMaker failed');
636
  }
637
  push(@resourceFiles, $licenseResource);
638
  # Don't add a cleanup object because licenseResource is in tempSubdir.
639
}
640
641
if(@resourceFiles) {
642
  # Add resources, such as a license agreement.
643
644
  # Only unflatten read-only and compressed images.  It's not supported
645
  # on other image times.
646
  if($unflattenable &&
647
   (command($gConfig{'cmd_hdiutil'}, 'unflatten', $targetImage)) != 0) {
648
    cleanupDie('hdiutil unflatten failed');
649
  }
650
  # Don't push flatten onto the cleanup stack.  If we fail now, we'll be
651
  # removing $targetImage anyway.
652
653
  # Type definitions come from Carbon.r.
654
  if(command($gConfig{'cmd_Rez'}, 'Carbon.r', @resourceFiles, '-a', '-o',
655
   $targetImage) != 0) {
656
    cleanupDie('Rez failed');
657
  }
658
659
  # Flatten.  This merges the resource fork into the data fork, so no
660
  # special encoding is needed to transfer the file.
661
  if($unflattenable &&
662
   (command($gConfig{'cmd_hdiutil'}, 'flatten', $targetImage)) != 0) {
663
    cleanupDie('hdiutil flatten failed');
664
  }
665
}
666
667
# $tempSubdir is no longer needed.  It's buried on the stack below the
668
# rm of the fresh image file.  Splice in this fashion is equivalent to
669
# pop-save, pop, push-save.
670
splice(@gCleanup, -2, 1);
671
# No need to remove licenseResource separately, it's in tempSubdir.
672
if(command($gConfig{'cmd_rm'}, '-rf', $tempSubdir) != 0) {
673
  cleanupDie('rm -rf tempSubdir failed');
674
}
675
676
if($idme) {
677
  if(command($gConfig{'cmd_hdiutil'}, 'internet-enable', '-yes',
678
   $targetImage) != 0) {
679
    cleanupDie('hdiutil internet-enable failed');
680
  }
681
}
682
683
# Done.
684
685
exit(0);
686
687
# argumentEscape(@arguments)
688
#
689
# Takes a list of @arguments and makes them shell-safe.
690
sub argumentEscape(@) {
691
  my(@arguments);
692
  @arguments = @_;
693
  my($argument, @argumentsOut);
694
  foreach $argument (@arguments) {
695
    $argument =~ s%([^A-Za-z0-9_\-/.=+,])%\\$1%g;
696
    push(@argumentsOut, $argument);
697
  }
698
  return @argumentsOut;
699
}
700
701
# cleanupDie($message)
702
#
703
# Displays $message as an error message, and then runs through the
704
# @gCleanup stack, performing any cleanup operations needed before
705
# exiting.  Does not return, exits with exit status 1.
706
sub cleanupDie($) {
707
  my($message);
708
  ($message) = @_;
709
  print STDERR ($0.': '.$message.(@gCleanup?' (cleaning up)':'')."\n");
710
  while(@gCleanup) {
711
    my($subroutine);
712
    $subroutine = pop(@gCleanup);
713
    &$subroutine;
714
  }
715
  exit(1);
716
}
717
718
# command(@arguments)
719
#
720
# Runs the specified command at the verbosity level defined by $gVerbosity.
721
# Returns nonzero on failure, returning the exit status if appropriate.
722
# Discards command output.
723
sub command(@) {
724
  my(@arguments);
725
  @arguments = @_;
726
  return commandVerbosity($gVerbosity,@arguments);
727
}
728
729
# commandInternal($command, @arguments)
730
#
731
# Runs the specified internal command at the verbosity level defined by
732
# $gVerbosity.
733
# Returns zero(!) on failure, because commandInternal is supposed to be a
734
# direct replacement for the Perl system call wrappers, which, unlike shell
735
# commands and C equivalent system calls, return true (instead of 0) to
736
# indicate success.
737
sub commandInternal($@) {
738
  my(@arguments, $command);
739
  ($command, @arguments) = @_;
740
  return commandInternalVerbosity($gVerbosity, $command, @arguments);
741
}
742
743
# commandInternalVerbosity($verbosity, $command, @arguments)
744
#
745
# Run an internal command, printing a bogus command invocation message if
746
# $verbosity is true.
747
#
748
# If $command is unlink:
749
# Removes the files specified by @arguments.  Wraps unlink.
750
#
751
# If $command is symlink:
752
# Creates the symlink specified by @arguments. Wraps symlink.
753
sub commandInternalVerbosity($$@) {
754
  my(@arguments, $command, $verbosity);
755
  ($verbosity, $command, @arguments) = @_;
756
  if($command eq 'unlink') {
757
    if($verbosity || $gDryRun) {
758
      print(join(' ', 'rm', '-f', argumentEscape(@arguments))."\n");
759
    }
760
    if($gDryRun) {
761
      return $#arguments+1;
762
    }
763
    return unlink(@arguments);
764
  }
765
  elsif($command eq 'symlink') {
766
    if($verbosity || $gDryRun) {
767
      print(join(' ', 'ln', '-s', argumentEscape(@arguments))."\n");
768
    }
769
    if($gDryRun) {
770
      return 1;
771
    }
772
    my($source, $target);
773
    ($source, $target) = @arguments;
774
    return symlink($source, $target);
775
  }
776
}
777
778
# commandOutput(@arguments)
779
#
780
# Runs the specified command at the verbosity level defined by $gVerbosity.
781
# Output is returned in an array of lines.  undef is returned on failure.
782
# The exit status is available in $?.
783
sub commandOutput(@) {
784
  my(@arguments);
785
  @arguments = @_;
786
  return commandOutputVerbosity($gVerbosity, @arguments);
787
}
788
789
# commandOutputVerbosity($verbosity, @arguments)
790
#
791
# Runs the specified command at the verbosity level defined by the
792
# $verbosity argument.  Output is returned in an array of lines.  undef is
793
# returned on failure.  The exit status is available in $?.
794
#
795
# If an error occurs in fork or exec, an error message is printed to
796
# stderr and undef is returned.
797
#
798
# If $verbosity is 0, the command invocation is not printed, and its
799
# stdout is not echoed back to stdout.
800
#
801
# If $verbosity is 1, the command invocation is printed.
802
#
803
# If $verbosity is 2, the command invocation is printed and the output
804
# from stdout is echoed back to stdout.
805
#
806
# Regardless of $verbosity, stderr is left connected.
807
sub commandOutputVerbosity($@) {
808
  my(@arguments, $verbosity);
809
  ($verbosity, @arguments) = @_;
810
  my($pid);
811
  if($verbosity || $gDryRun) {
812
    print(join(' ', argumentEscape(@arguments))."\n");
813
  }
814
  if($gDryRun) {
815
    return(1);
816
  }
817
  if (!defined($pid = open(*COMMAND, '-|'))) {
818
    printf STDERR ($0.': fork: '.$!."\n");
819
    return undef;
820
  }
821
  elsif ($pid) {
822
    # parent
823
    my(@lines);
824
    while(!eof(*COMMAND)) {
825
      my($line);
826
      chop($line = <COMMAND>);
827
      if($verbosity > 1) {
828
        print($line."\n");
829
      }
830
      push(@lines, $line);
831
    }
832
    close(*COMMAND);
833
    if ($? == -1) {
834
      printf STDERR ($0.': fork: '.$!."\n");
835
      return undef;
836
    }
837
    elsif ($? & 127) {
838
      printf STDERR ($0.': exited on signal '.($? & 127).
839
       ($? & 128 ? ', core dumped' : '')."\n");
840
      return undef;
841
    }
842
    return @lines;
843
  }
844
  else {
845
    # child; this form of exec is immune to shell games
846
    if(!exec {$arguments[0]} (@arguments)) {
847
      printf STDERR ($0.': exec: '.$!."\n");
848
      exit(-1);
849
    }
850
  }
851
}
852
853
# commandVerbosity($verbosity, @arguments)
854
#
855
# Runs the specified command at the verbosity level defined by the
856
# $verbosity argument.  Returns nonzero on failure, returning the exit
857
# status if appropriate.  Discards command output.
858
sub commandVerbosity($@) {
859
  my(@arguments, $verbosity);
860
  ($verbosity, @arguments) = @_;
861
  if(!defined(commandOutputVerbosity($verbosity, @arguments))) {
862
    return -1;
863
  }
864
  return $?;
865
}
866
867
# copyFiles($tempRoot, $method, @arguments)
868
#
869
# Copies files or create symlinks in the disk image.
870
# See --copy and --symlink descriptions for details.
871
# If $method is 'copy', @arguments are interpreted as source:target, if $method
872
# is 'symlink', @arguments are interpreted as symlink:target.
873
sub copyFiles($@) {
874
  my(@fileList, $method, $tempRoot);
875
  ($tempRoot, $method, @fileList) = @_;
876
  my($file, $isSymlink);
877
  $isSymlink = ($method eq 'symlink');
878
  foreach $file (@fileList) {
879
    my($source, $target);
880
    ($source, $target) = split(/:/, $file);
881
    if(!defined($target) and $isSymlink) {
882
      # empty symlink targets would result in an invalid target and fail,
883
      # but they shall be interpreted as "like source path, but inside dmg"
884
      $target = $source;
885
    }
886
    if(!defined($target)) {
887
      $target = $tempRoot;
888
    }
889
    elsif($target =~ /^\//) {
890
      $target = $tempRoot.$target;
891
    }
892
    else {
893
      $target = $tempRoot.'/'.$target;
894
    }
895
896
    my($success);
897
    if($isSymlink) {
898
      $success = commandInternal('symlink', $source, $target);
899
    }
900
    else {
901
      $success = !command($gConfig{'cmd_rsync'}, '-aC', '--include', '*.so',
902
                          '--copy-unsafe-links', $source, $target);
903
    }
904
    if(!$success) {
905
      cleanupDie('copyFiles failed for method '.$method);
906
    }
907
  }
908
}
909
910
# diskImageMaker($source, $destination, $format, $name, $tempDir, $tempMount,
911
#  $baseName, $setRootIcon)
912
#
913
# Creates a disk image in $destination of format $format corresponding to the
914
# source directory $source.  $name is the volume name.  $tempDir is a good
915
# place to write temporary files, which should be empty (aside from the other
916
# things that this script might create there, like stage and mount).
917
# $tempMount is a mount point for temporary disk images.  $baseName is the
918
# name of the disk image, and is presently unused.  $setRootIcon is true if
919
# a volume icon was added to the staged $source and indicates that the
920
# custom volume icon bit on the volume root needs to be set.
921
sub diskImageMaker($$$$$$$$) {
922
  my($baseName, $destination, $format, $name, $setRootIcon, $source,
923
   $tempDir, $tempMount);
924
  ($source, $destination, $format, $name, $tempDir, $tempMount,
925
   $baseName, $setRootIcon) = @_;
926
  if(isFormatReadOnly($format)) {
927
    my($uncompressedImage);
928
929
    if($gConfig{'makehybrid'}) {
930
      my($hybridImage);
931
      $hybridImage = giveExtension($tempDir.'/hybrid', '.dmg');
932
933
      if(command($gConfig{'cmd_hdiutil'}, 'makehybrid', '-hfs',
934
       '-hfs-volume-name', $name,
935
       ($gConfig{'openfolder_bless'} ? ('-hfs-openfolder', $source) : ()),
936
       '-ov', $source, '-o', $hybridImage) != 0) {
937
        cleanupDie('hdiutil makehybrid failed');
938
      }
939
940
      $uncompressedImage = $hybridImage;
941
942
      # $source is no longer needed and will be removed before anything
943
      # else can fail.  splice in this form is the same as pop/push.
944
      splice(@gCleanup, -1, 1,
945
       sub {commandInternalVerbosity(0, 'unlink', $hybridImage);});
946
947
      if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) {
948
        cleanupDie('rm -rf failed');
949
      }
950
951
      if(!$gConfig{'partition_table'} && $gConfig{'recursive_access'}) {
952
        # Even if we do want to create disk images without partition tables,
953
        # it's impossible unless recursive_access is set.
954
        my($rootDevice, $partitionDevice, $partitionMountPoint);
955
956
        if(!(($rootDevice, $partitionDevice, $partitionMountPoint) =
957
         hdidMountImage($tempMount, '-readonly', $hybridImage))) {
958
          cleanupDie('hdid mount failed');
959
        }
960
961
        push(@gCleanup, sub {commandVerbosity(0,
962
         $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);});
963
964
        my($udrwImage);
965
        $udrwImage = giveExtension($tempDir.'/udrw', '.dmg');
966
967
        if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', 'UDRW',
968
         '-ov', '-srcdevice', $partitionDevice, $udrwImage) != 0) {
969
          cleanupDie('hdiutil create failed');
970
        }
971
972
        $uncompressedImage = $udrwImage;
973
974
        # Going to eject before anything else can fail.  Get the eject off
975
        # the stack.
976
        pop(@gCleanup);
977
978
        # $hybridImage will be removed soon, but until then, it needs to
979
        # stay on the cleanup stack.  It needs to wait until after
980
        # ejection.  $udrwImage is staying around.  Make it appear as
981
        # though it's been done before $hybridImage.
982
        #
983
        # splice in this form is the same as popping one element to
984
        # @tempCleanup and pushing the subroutine.
985
        my(@tempCleanup);
986
        @tempCleanup = splice(@gCleanup, -1, 1,
987
         sub {commandInternalVerbosity(0, 'unlink', $udrwImage);});
988
        push(@gCleanup, @tempCleanup);
989
990
        if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) {
991
          cleanupDie('diskutil eject failed');
992
        }
993
994
        # Pop unlink of $uncompressedImage
995
        pop(@gCleanup);
996
997
        if(commandInternal('unlink', $hybridImage) != 1) {
998
          cleanupDie('unlink hybridImage failed: '.$!);
999
        }
1000
      }
1001
    }
1002
    else {
1003
      # makehybrid is not available, fall back to making a UDRW and
1004
      # converting to a compressed image.  It ought to be possible to
1005
      # create a compressed image directly, but those come out far too
1006
      # large (journaling?) and need to be read-write to fix up the
1007
      # volume icon anyway.  Luckily, we can take advantage of a single
1008
      # call back into this function.
1009
      my($udrwImage);
1010
      $udrwImage = giveExtension($tempDir.'/udrw', '.dmg');
1011
1012
      diskImageMaker($source, $udrwImage, 'UDRW', $name, $tempDir,
1013
       $tempMount, $baseName, $setRootIcon);
1014
1015
      # The call back into diskImageMaker already removed $source.
1016
1017
      $uncompressedImage = $udrwImage;
1018
    }
1019
1020
    # The uncompressed disk image is now in its final form.  Compress it.
1021
    # Jaguar doesn't support hdiutil convert -ov, but it always allows
1022
    # overwriting.
1023
    # bzip2-compressed UDBZ images can only be created and mounted on 10.4
1024
    # and later.  The bzip2-level imagekey is only effective when creating
1025
    # images in 10.5.  In 10.4, bzip2-level is harmlessly ignored, and the
1026
    # default value of 1 is always used.
1027
    if(command($gConfig{'cmd_hdiutil'}, 'convert', '-format', $format,
1028
     ($format eq 'UDZO' ? ('-imagekey', 'zlib-level=9') : ()),
1029
     ($format eq 'UDBZ' ? ('-imagekey', 'bzip2-level=9') : ()),
1030
     (defined($gDarwinMajor) && $gDarwinMajor <= 6 ? () : ('-ov')),
1031
     $uncompressedImage, '-o', $destination) != 0) {
1032
      cleanupDie('hdiutil convert failed');
1033
    }
1034
1035
    # $uncompressedImage is going to be unlinked before anything else can
1036
    # fail.  splice in this form is the same as pop/push.
1037
    splice(@gCleanup, -1, 1,
1038
     sub {commandInternalVerbosity(0, 'unlink', $destination);});
1039
1040
    if(commandInternal('unlink', $uncompressedImage) != 1) {
1041
      cleanupDie('unlink uncompressedImage failed: '.$!);
1042
    }
1043
1044
    # At this point, the only thing that the compressed block has added to
1045
    # the cleanup stack is the removal of $destination.  $source has already
1046
    # been removed, and its cleanup entry has been removed as well.
1047
  }
1048
  elsif($format eq 'UDRW' || $format eq 'UDSP') {
1049
    my(@extraArguments);
1050
    if(!$gConfig{'partition_table'}) {
1051
      @extraArguments = ('-layout', 'NONE');
1052
    }
1053
1054
    if($gConfig{'create_directly'}) {
1055
      # Use -fs HFS+ to suppress the journal.
1056
      if(command($gConfig{'cmd_hdiutil'}, 'create', '-format', $format,
1057
       @extraArguments, '-fs', 'HFS+', '-volname', $name,
1058
       '-ov', '-srcfolder', $source, $destination) != 0) {
1059
        cleanupDie('hdiutil create failed');
1060
      }
1061
1062
      # $source is no longer needed and will be removed before anything
1063
      # else can fail.  splice in this form is the same as pop/push.
1064
      splice(@gCleanup, -1, 1,
1065
       sub {commandInternalVerbosity(0, 'unlink', $destination);});
1066
1067
      if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) {
1068
        cleanupDie('rm -rf failed');
1069
      }
1070
    }
1071
    else {
1072
      # hdiutil create does not support -srcfolder or -srcdevice, it only
1073
      # knows how to create blank images.  Figure out how large an image
1074
      # is needed, create it, and fill it.  This is needed for Jaguar.
1075
1076
      # Use native block size for hdiutil create -sectors.
1077
      delete $ENV{'BLOCKSIZE'};
1078
1079
      my(@duOutput, $ignore, $sizeBlocks, $sizeOverhead, $sizeTotal, $type);
1080
      if(!(@output = commandOutput($gConfig{'cmd_du'}, '-s', $tempRoot)) ||
1081
       $? != 0) {
1082
        cleanupDie('du failed');
1083
      }
1084
      ($sizeBlocks, $ignore) = split(' ', $output[0], 2);
1085
1086
      # The filesystem itself takes up 152 blocks of its own blocks for the
1087
      # filesystem up to 8192 blocks, plus 64 blocks for every additional
1088
      # 4096 blocks or portion thereof.
1089
      $sizeOverhead = 152 + 64 * POSIX::ceil(
1090
       (($sizeBlocks - 8192) > 0) ? (($sizeBlocks - 8192) / (4096 - 64)) : 0);
1091
1092
      # The number of blocks must be divisible by 8.
1093
      my($mod);
1094
      if($mod = ($sizeOverhead % 8)) {
1095
        $sizeOverhead += 8 - $mod;
1096
      }
1097
1098
      # sectors is taken as the size of a disk, not a filesystem, so the
1099
      # partition table eats into it.
1100
      if($gConfig{'partition_table'}) {
1101
        $sizeOverhead += 80;
1102
      }
1103
1104
      # That was hard.  Leave some breathing room anyway.  Use 1024 sectors
1105
      # (512kB).  These read-write images wouldn't be useful if they didn't
1106
      # have at least a little free space.
1107
      $sizeTotal = $sizeBlocks + $sizeOverhead + 1024;
1108
1109
      # Minimum sizes - these numbers are larger on Jaguar than on later
1110
      # systems.  Just use the Jaguar numbers, since it's unlikely to wind
1111
      # up here on any other release.
1112
      if($gConfig{'partition_table'} && $sizeTotal < 8272) {
1113
        $sizeTotal = 8272;
1114
      }
1115
      if(!$gConfig{'partition_table'} && $sizeTotal < 8192) {
1116
        $sizeTotal = 8192;
1117
      }
1118
1119
      # hdiutil create without -srcfolder or -srcdevice will not accept
1120
      # -format.  It uses -type.  Fortunately, the two supported formats
1121
      # here map directly to the only two supported types.
1122
      if ($format eq 'UDSP') {
1123
        $type = 'SPARSE';
1124
      }
1125
      else {
1126
        $type = 'UDIF';
1127
      }
1128
1129
      if(command($gConfig{'cmd_hdiutil'}, 'create', '-type', $type,
1130
       @extraArguments, '-fs', 'HFS+', '-volname', $name,
1131
       '-ov', '-sectors', $sizeTotal, $destination) != 0) {
1132
        cleanupDie('hdiutil create failed');
1133
      }
1134
1135
      push(@gCleanup,
1136
       sub {commandInternalVerbosity(0, 'unlink', $destination);});
1137
1138
      # The rsync will occur shortly.
1139
    }
1140
1141
    my($mounted, $rootDevice, $partitionDevice, $partitionMountPoint);
1142
1143
    $mounted=0;
1144
    if(!$gConfig{'create_directly'} || $gConfig{'openfolder_bless'} ||
1145
     $setRootIcon) {
1146
      # The disk image only needs to be mounted if:
1147
      #  create_directly is false, because the content needs to be copied
1148
      #  openfolder_bless is true, because bless -openfolder needs to run
1149
      #  setRootIcon is true, because the root needs its attributes set.
1150
      if(!(($rootDevice, $partitionDevice, $partitionMountPoint) =
1151
       hdidMountImage($tempMount, $destination))) {
1152
        cleanupDie('hdid mount failed');
1153
      }
1154
1155
      $mounted=1;
1156
1157
      push(@gCleanup, sub {commandVerbosity(0,
1158
       $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);});
1159
    }
1160
1161
    if(!$gConfig{'create_directly'}) {
1162
      # Couldn't create and copy directly in one fell swoop.  Now that
1163
      # the volume is mounted, copy the files.  --copy-unsafe-links is
1164
      # unnecessary since it was used to copy everything to the staging
1165
      # area.  There can be no more unsafe links.
1166
      if(command($gConfig{'cmd_rsync'}, '-aC', '--include', '*.so',
1167
       $source.'/',$partitionMountPoint) != 0) {
1168
        cleanupDie('rsync to new volume failed');
1169
      }
1170
1171
      # We need to get the rm -rf of $source off the stack, because it's
1172
      # being cleaned up here.  There are two items now on top of it:
1173
      # removing the target image and, above that, ejecting it.  Splice it
1174
      # out.
1175
      my(@tempCleanup);
1176
      @tempCleanup = splice(@gCleanup, -2);
1177
      # The next splice is the same as popping once and pushing @tempCleanup.
1178
      splice(@gCleanup, -1, 1, @tempCleanup);
1179
1180
      if(command($gConfig{'cmd_rm'}, '-rf', $source) != 0) {
1181
        cleanupDie('rm -rf failed');
1182
      }
1183
    }
1184
1185
    if($gConfig{'openfolder_bless'}) {
1186
      # On Tiger, the bless docs say to use --openfolder, but only
1187
      # --openfolder is accepted on Panther.  Tiger takes it with a single
1188
      # dash too.  Jaguar is out of luck.
1189
      if(command($gConfig{'cmd_bless'}, '-openfolder',
1190
       $partitionMountPoint) != 0) {
1191
        cleanupDie('bless failed');
1192
      }
1193
    }
1194
1195
    setAttributes($partitionMountPoint, @attributes);
1196
1197
    if($setRootIcon) {
1198
      # When "hdiutil create -srcfolder" is used, the root folder's
1199
      # attributes are not copied to the new volume.  Fix up.
1200
1201
      if(command($gConfig{'cmd_SetFile'}, '-a', 'C',
1202
       $partitionMountPoint) != 0) {
1203
        cleanupDie('SetFile failed');
1204
      }
1205
    }
1206
1207
    if($mounted) {
1208
      # Pop diskutil eject
1209
      pop(@gCleanup);
1210
1211
      if(command($gConfig{'cmd_diskutil'}, 'eject', $rootDevice) != 0) {
1212
        cleanupDie('diskutil eject failed');
1213
      }
1214
    }
1215
1216
    # End of UDRW/UDSP section.  At this point, $source has been removed
1217
    # and its cleanup entry has been removed from the stack.
1218
  }
1219
  else {
1220
    cleanupDie('unrecognized format');
1221
    print STDERR ($0.": unrecognized format\n");
1222
    exit(1);
1223
  }
1224
}
1225
1226
# giveExtension($file, $extension)
1227
#
1228
# If $file does not end in $extension, $extension is added.  The new
1229
# filename is returned.
1230
sub giveExtension($$) {
1231
  my($extension, $file);
1232
  ($file, $extension) = @_;
1233
  if(substr($file, -length($extension)) ne $extension) {
1234
    return $file.$extension;
1235
  }
1236
  return $file;
1237
}
1238
1239
# hdidMountImage($mountPoint, @arguments)
1240
#
1241
# Runs the hdid command with arguments specified by @arguments.
1242
# @arguments may be a single-element array containing the name of the
1243
# disk image to mount.  Returns a three-element array, with elements
1244
# corresponding to:
1245
#  - The root device of the mounted image, suitable for ejection
1246
#  - The device corresponding to the mounted partition
1247
#  - The mounted partition's mount point
1248
#
1249
# If running on a system that supports easy mounting at points outside
1250
# of the default /Volumes with hdiutil attach, it is used instead of hdid,
1251
# and $mountPoint is used as the mount point.
1252
#
1253
# The root device will differ from the partition device when the disk
1254
# image contains a partition table, otherwise, they will be identical.
1255
#
1256
# If hdid fails, undef is returned.
1257
sub hdidMountImage($@) {
1258
  my(@arguments, @command, $mountPoint);
1259
  ($mountPoint, @arguments) = @_;
1260
  my(@output);
1261
1262
  if($gConfig{'hdiutil_mountpoint'}) {
1263
    @command=($gConfig{'cmd_hdiutil'}, 'attach', @arguments,
1264
     '-mountpoint', $mountPoint);
1265
  }
1266
  else {
1267
    @command=($gConfig{'cmd_hdid'}, @arguments);
1268
  }
1269
1270
  if(!(@output = commandOutput(@command)) ||
1271
   $? != 0) {
1272
    return undef;
1273
  }
1274
1275
  if($gDryRun) {
1276
    return('/dev/diskX','/dev/diskXsY','/Volumes/'.$volumeName);
1277
  }
1278
1279
  my($line, $restOfLine, $rootDevice);
1280
1281
  foreach $line (@output) {
1282
    my($device, $mountpoint);
1283
    if($line !~ /^\/dev\//) {
1284
      # Consider only lines that correspond to /dev entries
1285
      next;
1286
    }
1287
    ($device, $restOfLine) = split(' ', $line, 2);
1288
1289
    if(!defined($rootDevice) || $rootDevice eq '') {
1290
      # If this is the first device seen, it's the root device to be
1291
      # used for ejection.  Keep it.
1292
      $rootDevice = $device;
1293
    }
1294
1295
    if($restOfLine =~ /(\/.*)/) {
1296
      # The first partition with a mount point is the interesting one.  It's
1297
      # usually Apple_HFS and usually the last one in the list, but beware of
1298
      # the possibility of other filesystem types and the Apple_Free partition.
1299
      # If the disk image contains no partition table, the partition will not
1300
      # have a type, so look for the mount point by looking for a slash.
1301
      $mountpoint = $1;
1302
      return($rootDevice, $device, $mountpoint);
1303
    }
1304
  }
1305
1306
  # No mount point?  This is bad.  If there's a root device, eject it.
1307
  if(defined($rootDevice) && $rootDevice ne '') {
1308
    # Failing anyway, so don't care about failure
1309
    commandVerbosity(0, $gConfig{'cmd_diskutil'}, 'eject', $rootDevice);
1310
  }
1311
1312
  return undef;
1313
}
1314
1315
# isFormatReadOnly($format)
1316
#
1317
# Returns true if $format corresponds to a read-only disk image format.
1318
# Returns false otherwise.
1319
sub isFormatReadOnly($) {
1320
  my($format);
1321
  ($format) = @_;
1322
  return $format eq 'UDZO' || $format eq 'UDBZ' || $format eq 'UDRO';
1323
}
1324
1325
# licenseMaker($text, $resource)
1326
#
1327
# Takes a plain text file at path $text and creates a license agreement
1328
# resource containing the text at path $license.  English-only, and
1329
# no special formatting.  This is the bare-bones stuff.  For more
1330
# intricate license agreements, create your own resource.
1331
#
1332
# ftp://ftp.apple.com/developer/Development_Kits/SLAs_for_UDIFs_1.0.dmg
1333
sub licenseMaker($$) {
1334
  my($resource, $text);
1335
  ($text, $resource) = @_;
1336
  if(!sysopen(*TEXT, $text, O_RDONLY)) {
1337
    print STDERR ($0.': licenseMaker: sysopen text: '.$!."\n");
1338
    return 0;
1339
  }
1340
  if(!sysopen(*RESOURCE, $resource, O_WRONLY|O_CREAT|O_EXCL)) {
1341
    print STDERR ($0.': licenseMaker: sysopen resource: '.$!."\n");
1342
    return 0;
1343
  }
1344
  print RESOURCE << '__EOT__';
1345
// See /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Headers/Script.h for language IDs.
1346
data 'LPic' (5000) {
1347
  // Default language ID, 0 = English
1348
  $"0000"
1349
  // Number of entries in list
1350
  $"0001"
1351
1352
  // Entry 1
1353
  // Language ID, 0 = English
1354
  $"0000"
1355
  // Resource ID, 0 = STR#/TEXT/styl 5000
1356
  $"0000"
1357
  // Multibyte language, 0 = no
1358
  $"0000"
1359
};
1360
1361
resource 'STR#' (5000, "English") {
1362
  {
1363
    // Language (unused?) = English
1364
    "English",
1365
    // Agree
1366
    "Agree",
1367
    // Disagree
1368
    "Disagree",
1369
__EOT__
1370
    # This stuff needs double-quotes for interpolations to work.
1371
    print RESOURCE ("    // Print, ellipsis is 0xC9\n");
1372
    print RESOURCE ("    \"Print\xc9\",\n");
1373
    print RESOURCE ("    // Save As, ellipsis is 0xC9\n");
1374
    print RESOURCE ("    \"Save As\xc9\",\n");
1375
    print RESOURCE ('    // Descriptive text, curly quotes are 0xD2 and 0xD3'.
1376
     "\n");
1377
    print RESOURCE ('    "If you agree to the terms of this license '.
1378
     "agreement, click \xd2Agree\xd3 to access the software.  If you ".
1379
     "do not agree, press \xd2Disagree.\xd3\"\n");
1380
print RESOURCE << '__EOT__';
1381
  };
1382
};
1383
1384
// Beware of 1024(?) byte (character?) line length limitation.  Split up long
1385
// lines.
1386
// If straight quotes are used ("), remember to escape them (\").
1387
// Newline is \n, to leave a blank line, use two of them.
1388
// 0xD2 and 0xD3 are curly double-quotes ("), 0xD4 and 0xD5 are curly
1389
//   single quotes ('), 0xD5 is also the apostrophe.
1390
data 'TEXT' (5000, "English") {
1391
__EOT__
1392
1393
  while(!eof(*TEXT)) {
1394
    my($line);
1395
    chop($line = <TEXT>);
1396
1397
    while(defined($line)) {
1398
      my($chunk);
1399
1400
      # Rez doesn't care for lines longer than (1024?) characters.  Split
1401
      # at less than half of that limit, in case everything needs to be
1402
      # backwhacked.
1403
      if(length($line)>500) {
1404
        $chunk = substr($line, 0, 500);
1405
        $line = substr($line, 500);
1406
      }
1407
      else {
1408
        $chunk = $line;
1409
        $line = undef;
1410
      }
1411
1412
      if(length($chunk) > 0) {
1413
        # Unsafe characters are the double-quote (") and backslash (\), escape
1414
        # them with backslashes.
1415
        $chunk =~ s/(["\\])/\\$1/g;
1416
1417
        print RESOURCE '  "'.$chunk.'"'."\n";
1418
      }
1419
    }
1420
    print RESOURCE '  "\n"'."\n";
1421
  }
1422
  close(*TEXT);
1423
1424
  print RESOURCE << '__EOT__';
1425
};
1426
1427
data 'styl' (5000, "English") {
1428
  // Number of styles following = 1
1429
  $"0001"
1430
1431
  // Style 1.  This is used to display the first two lines in bold text.
1432
  // Start character = 0
1433
  $"0000 0000"
1434
  // Height = 16
1435
  $"0010"
1436
  // Ascent = 12
1437
  $"000C"
1438
  // Font family = 1024 (Lucida Grande)
1439
  $"0400"
1440
  // Style bitfield, 0x1=bold 0x2=italic 0x4=underline 0x8=outline
1441
  // 0x10=shadow 0x20=condensed 0x40=extended
1442
  $"00"
1443
  // Style, unused?
1444
  $"02"
1445
  // Size = 12 point
1446
  $"000C"
1447
  // Color, RGB
1448
  $"0000 0000 0000"
1449
};
1450
__EOT__
1451
  close(*RESOURCE);
1452
1453
  return 1;
1454
}
1455
1456
# pathSplit($pathname)
1457
#
1458
# Splits $pathname into an array of path components.
1459
sub pathSplit($) {
1460
  my($pathname);
1461
  ($pathname) = @_;
1462
  return split(/\//, $pathname);
1463
}
1464
1465
# setAttributes($root, @attributeList)
1466
#
1467
# @attributeList is an array, each element of which must be in the form
1468
# <a>:<file>.  <a> is a list of attributes, per SetFile.  <file> is a file
1469
# which is taken as relative to $root (even if it appears as an absolute
1470
# path.)  SetFile is called to set the attributes on each file in
1471
# @attributeList.
1472
sub setAttributes($@) {
1473
  my(@attributes, $root);
1474
  ($root, @attributes) = @_;
1475
  my($attribute);
1476
  foreach $attribute (@attributes) {
1477
    my($attrList, $file, @fileList, @fixedFileList);
1478
    ($attrList, @fileList) = split(/:/, $attribute);
1479
    if(!defined($attrList) || !@fileList) {
1480
      cleanupDie('--attribute requires <attributes>:<file>');
1481
    }
1482
    @fixedFileList=();
1483
    foreach $file (@fileList) {
1484
      if($file =~ /^\//) {
1485
        push(@fixedFileList, $root.$file);
1486
      }
1487
      else {
1488
        push(@fixedFileList, $root.'/'.$file);
1489
      }
1490
    }
1491
    if(command($gConfig{'cmd_SetFile'}, '-a', $attrList, @fixedFileList)) {
1492
      cleanupDie('SetFile failed to set attributes');
1493
    }
1494
  }
1495
  return;
1496
}
1497
1498
sub trapSignal($) {
1499
  my($signalName);
1500
  ($signalName) = @_;
1501
  cleanupDie('exiting on SIG'.$signalName);
1502
}
1503
1504
sub usage() {
1505
  print STDERR (
1506
"usage: pkg-dmg --source <source-folder>\n".
1507
"               --target <target-image>\n".
1508
"              [--format <format>]           (default: UDZO)\n".
1509
"              [--volname <volume-name>]     (default: same name as source)\n".
1510
"              [--tempdir <temp-dir>]        (default: same dir as target)\n".
1511
"              [--mkdir <directory>]         (make directory in image)\n".
1512
"              [--copy <source>[:<dest>]]    (extra files to add)\n".
1513
"              [--symlink <source>[:<dest>]] (extra symlinks to add)\n".
1514
"              [--license <file>]            (plain text license agreement)\n".
1515
"              [--resource <file>]           (flat .r files to merge)\n".
1516
"              [--icon <icns-file>]          (volume icon)\n".
1517
"              [--attribute <a>:<file>]      (set file attributes)\n".
1518
"              [--idme]                      (make Internet-enabled image)\n".
1519
"              [--sourcefile]                (treat --source as a file)\n".
1520
"              [--verbosity <level>]         (0, 1, 2; default=2)\n".
1521
"              [--dry-run]                   (print what would be done)\n");
1522
  return;
1523
}