create-versioned-image 17.2 KB
Newer Older
1
#!/usr/bin/perl -w
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

#
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
#

use English;
use Getopt::Std;

#
# Client-side to create a disk image. Caller must have sudo permission!
#
# This is an enhanced version of create-image that knows how to work with
# signature files and created delta images (for versioning). It is a
# separate script just to make backward-compat easier.
#
# In addition, it can create multiple images (e.g., to capture multiple
# partitions or disks) based on the parameters given. Right now these
# parameters are either specified on the command line (allows only
# one image to be made) or come from a file (one line per image to make).
# Eventually, these may come down via tmcc.
#
# For each image, the possible parameters are:
#
# METHOD=<method>
#    Method to use for uploading image and up/downloading any metadata
#    (just old/new signature files right now). Choices are "frisbee" or
#    "file" where file means "across NFS". We may add "http" at some point,
#    but currently that would just be a choice for downloading.
#
# SERVER=<name>
51
52
53
54
55
56
57
58
59
60
#    Server to use for uploading image and metadata. May also be used
#    for downloading metadata if DOWNSERVER is not set. If METHOD=file,
#    SERVER won't be set or will be ignored by the client. Otherwise it
#    is the name or IP to use with the frisupload -S option.
#
# DOWNSERVER=<name>
#    Server to use for downloading metadata.  If not set SERVER is used
#    instead. If METHOD=file, DOWNSERVER won't be set or will be ignored
#    by the client. Otherwise it is the name or IP to use with the
#    frisbee -S option.
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#
# IMAGENAME=<string>
#    Context-sensitive name of the image to create. If the SERVER is set
#    and we are using the frisbee uploader, then this string is the argument
#    to present via the -F option (either an imageid or a path). If server
#    is not set, then it is a local (NFS) path at which to save the image.
#
# OLDSIGNAME=<string>
# NEWSIGNAME=<string>
#    Optional context-sensitive names of the old and new signature files.
#    OLDSIGNAME is either a path to read from the filesystem (method=file)
#    or the argument to the frisbee -F option. NEWSIGNAME is where to put
#    the newly created signature; either in the FS or uploaded via frisupload.
#
#    If OLDSIGNAME is given, we are creating a delta image. In this case
#    NEWSIGNAME may also be specified if a new signature is desired.
#
#    If OLDSIGNAME is not given, then we are creating a full disk image.
#    In this case we might or might not create a new signature file for
#    the image depending on whether NEWSIGNAME is present.
#
#    If both are absent, then we are running in the old, pre-delta image
#    world and just creating full disk images always.
#
# DISK=<disk>
#    BSD-style disk name (e.g., "da0") identifying the disk from which to
#    create the image. Used for imagezip disk argument. Note that like
#    the argument to loadinfo, this is not at all a sound technique given
#    the differences in device names used by BSD and Linux and the fact
#    that disk ordering is not deterministic in either! However SNs are
#    hard to extract, so we live with a name instead.
#
# PART=<part>
#    Partition on DISK from which to load image. Used for imagezip -s option.
#    If not set or set to zero, then it is a whole-disk image.
#
97
98
99
# IZOPTS=<string>
#    Additional options for imagezip.
#
100
101
102
# PROXY=<vnodeid>
#    The proxy argument for use on XEN, when acting on behalf of a container.
#
103
104
105
106
107

sub usage()
{
    print STDERR
	"Usage:\n".
108
	"create-versioned-image [-nvx] -f param-file\n".
109
	"  or\n".
110
	"create-versioned-image [-nv] [-x vnode_id] KEY=VALUE ...\n";
111
112
    exit(-1);
}
113
my  $optlist = "f:nvx:";
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

#
# Turn off line buffering on output
#
$| = 1;

#
# Untaint the path
# 
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# No configure vars.
#
129
my $sudo = "";
130
131
132
133
134
135
my $zipper = "/usr/local/bin/imagezip";
my $uploader = "/usr/local/bin/frisupload";
my $frisbee = "/usr/local/bin/frisbee";
my $localdir = "/local";
my $impotent = 0;
my $verbose = 0;
136
my $isxen   = 0;
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

sub process_image($);
sub mysystem($);

my %iinfo = ();

#
# Map DB (BSD-ish) disknames into actual /dev device names on
# FreeBSD or Linux.
#
sub map_diskname($)
{
    my ($dev) = @_;
    my ($dtype, $dunit);

152
153
154
155
156
157
158
    #
    # When called on XEN, the diskname is correct, and in fact we will
    # just mess it up.
    #
    return $dev
	if ($isxen);

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    # strip off /dev/ if it is there
    $dev =~ s/^\/dev\///;

    if ($dev =~ /^([-a-zA-Z_]+)(\d+)$/) {
	($dtype,$dunit) = ($1,$2);
    } else {
	return undef;
    }

    # Hack for the Linux MFS: we still use the BSD device
    # names in the database so we try to convert them to
    # the equivalent Linux devices here.  This happens to
    # work at the moment, but if device names change again
    # it could break.
    if ($^O eq 'linux') {
	$dtype = "sd";
	$dunit -= 4
	    if ($dtype eq 'ad' && $dunit > 3);
	$dunit =~ y/01234567/abcdefgh/;

	#
	# XXX woeful TPM dongle-boot hack.
	# If we are imaging /dev/sda and dmesg reports that
	# that device is write-protected, assume it is the boot dongle
	# and use /dev/sdb instead!
	#
	if ($dunit eq "a") {
	    if (!system("dmesg | fgrep -q '[sda] Write Protect is on'")) {
		print STDERR "WARNING: suspect dongle-booted node, using sdb instead of sda\n";
		$dunit = "b";
	    }
	}
    }

193
    return "/dev/$dtype$dunit";
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
}

sub parse_params(@)
{
    my ($method,$iname,$disk,$part,$sigfile,$nsigfile);
    my $errors = 0;

    my $iid = 1;
    foreach my $line (@_) {
	my @kvs = split(' ', $line);
	foreach my $kv (@kvs) {
	    if ($kv =~ /^([-\w]+)=(\S*)$/) {
		my $key = lc($1);
		my $val = $2;

		if ($key eq "method") {
		    if ($val =~ /^(frisbee|file)$/) {
			$iinfo{$iid}{'method'} = $1;
		    } else {
			print STDERR "Bogus METHOD '$val'\n";
			$errors++;
		    }
		    next;
		}
		if ($key eq "server") {
		    $iinfo{$iid}{'server'} = $val;
		    next;
		}
222
223
224
225
		if ($key eq "downserver") {
		    $iinfo{$iid}{'downserver'} = $val;
		    next;
		}
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
		if ($key eq "imagename") {
		    $iinfo{$iid}{'iname'} = $val;
		    next;
		}
		if ($key eq "disk") {
		    $iinfo{$iid}{'disk'} = map_diskname($val);
		    if (!defined($iinfo{$iid}{'disk'})) {
			print STDERR "Bogus DISK '$val'\n";
			$errors++;
		    }
		    next;
		}
		if ($key eq "part") {
		    if ($val =~ /^(\d+)$/) {
			$iinfo{$iid}{'part'} = $1;
		    } else {
			print STDERR "Bogus PART '$val'\n";
			$errors++;
		    }
		    next;
		}
		if ($key eq "oldsigfile") {
		    $iinfo{$iid}{'sigfile'} = $val;
		    next;
		}
		if ($key eq "newsigfile") {
		    $iinfo{$iid}{'nsigfile'} = $val;
		    next;
		}
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
		if ($key eq "izopts") {
		    #
		    # No spaces in string, so options are encoded; e.g.:
		    #   -N -z 9 -d -a SHA1
		    # would be encoded as:
		    #   N,z=9,d,a=SHA1
		    # We unencode them here.
		    #
		    my $optstr = "";
		    foreach my $opt (split(',', $val)) {
			$optstr .= " -" . join(' ', split('=', $opt));
		    }
		    $iinfo{$iid}{'izopts'} = $optstr;
		    next;
		}
270
271
272
273
		if ($key eq "proxy") {
		    $iinfo{$iid}{'proxy'} = $val;
		    next;
		}
274
275
276
277
278
279
280
281
282
283
284
285
286
	    } else {
		print STDERR "Bogus parameter: '$kv'\n";
		$errors++;
	    }
	}
	$iid++;
    }
    if ($errors) {
	print STDERR "Could not parse all arguments\n";
	exit(2);
    }
}

287
288
289
290
291
#
# If we are running as a user, then we will need sudo
#
if ($EUID != 0) {
    for my $path (qw#/usr/local/bin /usr/bin#) {
292
	if (-e "$path/sudo") {
293
294
	    $sudo = "$path/sudo";
	    last;
295
	}
296
    }
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
}

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"n"})) {
    $impotent = 1;
}
if (defined($options{"v"})) {
    $verbose = 1;
}
313
314
315
316
if (defined($options{"x"})) {
    $isxen = 1;
    $localdir = "/capture/" . $options{"x"} . "/frisbee";
}
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
if (defined($options{"f"})) {
    my $pfile = $options{"f"};
    if ($pfile =~ /^(\/tmp\/[-\w\.]+)$/) {
	$pfile = $1;
	if (! -r "$pfile") {
	    print STDERR "Cannot read paramfile '$pfile'\n";
	    exit(1);
	}
    } else {
	print STDERR "Bogus '-f' file name\n";
	exit(1);
    }
    my @params = `cat $pfile`;
    chomp @params;
    parse_params(@params);
} elsif (@ARGV > 0) {
    my $pline = join(' ', @ARGV);
    parse_params($pline);
} else {
    # someday maybe use tmcc to get params

    print STDERR "No parameters given!\n";
    exit(1);
}

#
# Consistency checks
#
my $dofrisbee = 0;
foreach my $iid (sort keys %iinfo) {
    if (!defined($iinfo{$iid}{'disk'})) {
	print STDERR "Must specify disk\n";
349
	exit(1);
350
351
352
353
354
355
    }
    if (!defined($iinfo{$iid}{'part'})) {
	$iinfo{$iid}{'part'} = 0;
    }
    if (!defined($iinfo{$iid}{'iname'})) {
	print STDERR "Must specify imagename\n";
356
	exit(1);
357
    }
Mike Hibler's avatar
Mike Hibler committed
358
359
    if (!defined($iinfo{$iid}{'method'})) {
	print STDERR "Must specify method\n";
360
	exit(1);
Mike Hibler's avatar
Mike Hibler committed
361
    }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
    if ($iinfo{$iid}{'method'} eq "frisbee") {
	$dofrisbee++;
	if (!defined($iinfo{$iid}{'server'})) {
	    print STDERR "Must specify server for frisbee\n";
	    exit(1);
	}
    }
}

#
# For uniformity, all sigfiles are rooted here.
# For method=frisbee, the actual files will be here.
# For method=file, a symlink to the actual file will be here.
#
376
if (! -e $localdir && mysystem("$sudo mkdir -p $localdir")) {
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
    print STDERR "Could not create $localdir\n";
    exit(1);
}

#
# If any of the images are using frisbee, we need to ensure that we
# have sufficient local storage for old/new signature files.
#
# XXX with our current MBR3 FS size of 16GB, signature files for
# partition images will be around 8MB. For a 500GB disk and full
# disk image, it would be more like 250MB. We need enough space to
# hold up to two of these signature files (old and new). 512MB of
# memory filesystem is too much for our older machines--let's go
# with 64MB for now.
#
392
my $MEMFS_SIZE = "64m";
393
394
if ($dofrisbee) {
    if ($^O eq 'linux') {
395
396
	if (!$isxen &&
	    mysystem("$sudo mount -t tmpfs -o size=$MEMFS_SIZE tmpfs $localdir")) {
397
398
399
400
401
	    print STDERR "Could not create $MEMFS_SIZE byte local MFS\n";

	    # XXX try NFS instead
	    exit(1);
	}
402
    } else {
403
	if (mysystem("$sudo mdconfig -a -t swap -s $MEMFS_SIZE -u 4") ||
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
404
	    mysystem("$sudo newfs -b 8192 -i 25000 -o space /dev/md4") ||
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
	    mysystem("$sudo mount /dev/md4 $localdir")) {
	    print STDERR "Could not create $MEMFS_SIZE byte local MFS\n";

	    # XXX try NFS instead
	    exit(1);
	}
    }
}

if (mysystem("$sudo chmod 1777 $localdir")) {
    print STDERR "Could not make $localdir writeable\n";
    exit(1);
}

#
# Process each image
#
foreach my $iid (sort keys %iinfo) {
    process_image($iid);
}

#
# Get rid of any extra FS.
#
if ($dofrisbee) {
    if ($^O eq 'linux') {
431
	if (!$isxen && mysystem("$sudo umount $localdir")) {
432
433
	    print STDERR "WARNING: could not destroy local MFS\n";
	}
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
    } else {
	if (mysystem("$sudo umount $localdir") ||
	    mysystem("$sudo mdconfig -d -u 4")) {
	    print STDERR "WARNING: could not destroy local MFS\n";
	}
    }
}

exit(0);

sub fetch($$)
{
    my ($iid,$file) = @_;

    my $lfile = "$localdir/$file";
    my $ifile = $iinfo{$iid}{$file};

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
451
452
453
    if ($iinfo{$iid}{'method'} eq "file") {
	if (! -r "$ifile" || mysystem("ln -sf $ifile $lfile")) {
	    return -1;
454
455
456
	}
    }

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
457
    elsif ($iinfo{$iid}{'method'} eq "frisbee") {
458
459
460
461
462
	my $srv = $iinfo{$iid}{'downserver'};
	if (!defined($srv)) {
	    $srv = $iinfo{$iid}{'server'};
	}

463
464
465
466
467
468
	#
	# Since we are fetching small files into an MFS, there is
	# not much need for lots of buffer memory. We also don't
	# randomize requests.
	#
	my $opts = "-O -W 1 -C 32";
469
	if (mysystem("$frisbee $opts -B 5 -N -S $srv -F $ifile $lfile")) {
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
470
471
	    return -1;
	}
472
473
    }

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
474
    return 0;
475
476
}

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
477
sub writetest($$)
478
479
480
481
482
483
{
    my ($iid,$file) = @_;

    my $lfile = "$localdir/$file";
    my $ifile = $iinfo{$iid}{$file};

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
484
485
486
487
488
489
    #
    # For file (NFS) we make sure we can write the actual file.
    # If it doesn't currently exist, we try creating it (if we do create
    # it, we remove it again to make sure we don't leave turds behind).
    # If all is well, we create a local symlink to use.
    #
490
    if ($iinfo{$iid}{'method'} eq "file") {
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
491
492
493
494
495
496
497
498
499
500
501
502
503
	if (! -e "$ifile") {
	    if (!$impotent && !open(FD, ">$ifile")) {
		return -1;
	    }
	    if (!$impotent) {
		close(FD);
		unlink("$ifile");
	    }
	}
	elsif (! -w "$ifile") {
	    return -1;
	}

504
	if (mysystem("ln -sf $ifile $lfile")) {
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
505
	    return -1;
506
507
508
	}
    }

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
    #
    # For frisbee, we just check with the server to ensure we can
    # upload to the file. Note: the local file has to exist for this to
    # work, so we create that.
    #
    elsif ($iinfo{$iid}{'method'} eq "frisbee") {
	my $srv = $iinfo{$iid}{'server'};
	my $cmd = "$uploader -S $srv -Q $ifile $lfile";

	if ($impotent) {
	    print STDERR "Would: '$cmd' ...\n";
	} else {
	    if (open(FD, ">$lfile")) {
		close(FD);
	    }
	    print STDERR "Doing: '$cmd' ...\n"
		if ($verbose);
	    my $out = `$cmd`;
	    if ($? != 0 || $out !~ /error=0/) {
		return -1;
529
530
531
532
	    }
	}
    }

Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
533
    return 0;
534
535
536
537
}

sub process_image($)
{
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
538
    my ($iid) = @_;
539
540
541
542
543
544
545
546
547

    if ($verbose) {
	print "Image #$iid:\n";
	foreach my $k (sort keys %{$iinfo{$iid}}) {
	    print "  $k=", $iinfo{$iid}{$k}, "\n";
	}
    }

    #
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
548
    # Make sure the image file is writeable.
549
    #
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
550
551
552
    if (writetest($iid, "iname")) {
	print STDERR
	    "Cannot write new image '", $iinfo{$iid}{'iname'}, "'\n";
553
554
555
556
	exit(3);
    }

    #
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
557
    # Make sure we can write the new sig file if needed.
558
    #
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
559
560
561
562
563
564
565
566
567
568
569
570
571
572
    if (exists($iinfo{$iid}{'nsigfile'}) && writetest($iid, "nsigfile")) {
	print STDERR
	    "Cannot write new signature file '",
	    $iinfo{$iid}{'nsigfile'}, "'\n";
	exit(3);
    }

    #
    # Download the current signature file if needed.
    #
    if (exists($iinfo{$iid}{'sigfile'}) && fetch($iid, "sigfile")) {
	print STDERR
	    "Cannot fetch/read signature file '",
	    $iinfo{$iid}{'sigfile'}, "'\n";
573
574
575
	exit(3);
    }

576
577
578
579
580
581
    #
    # XXX we don't support GPT right now, so we can only do Linux partition
    # images directly from the partition device.
    #
    my $isgpt = 0;
    if ($^O eq 'linux' && -x "/sbin/sgdisk" &&
582
	!mysystem("$sudo /sbin/sgdisk ".$iinfo{$iid}{'disk'}." >/dev/null 2>&1")) {
583
584
585
586
587
588
589
590
	if ($iinfo{$iid}{'part'} == 0) {
	    print STDERR
		"Cannot take whole disk image of GPT disk\n";
	    exit(3);
	}
	$isgpt = 1;
    }

591
592
593
594
    #
    # Fire off the command:
    #
    # file:
595
    #   imagezip $izopts [-s $part] [-H $sigfile] [-U $nsigfile] $disk $ifile
596
597
    #
    # frisbee:
598
    #   imagezip $izopts [-s $part] [-H $sigfile] [-U $localdir/$nsigfile] $disk - | \
599
600
601
    #	    frisupload -S $server -F $ifile -
    #   [ cat $localdir/$nsigfile | frisupload -S $server -F $nsigfile ]
    #
602
    my $cmd = "$sudo $zipper";
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
603
604
605
    if ($verbose) {
	$cmd .= " -o";
    }
606
607
608
    if (exists($iinfo{$iid}{'izopts'})) {
	$cmd .= $iinfo{$iid}{'izopts'};
    }
609
610
611
612
613
614
615
    if ($isgpt) {
	# force imagezip to treat as an ext filesystem
	$cmd .= " -S 131";
    } else {
	if ($iinfo{$iid}{'part'} != 0) {
	    $cmd .= " -s " . $iinfo{$iid}{'part'};
	}
616
617
    }
    if (exists($iinfo{$iid}{'sigfile'})) {
618
	$cmd .= " -H $localdir/sigfile";
619
620
    }
    if (exists($iinfo{$iid}{'nsigfile'})) {
621
	$cmd .= " -U $localdir/nsigfile";
622
623
    }
    $cmd .= " " . $iinfo{$iid}{'disk'};
624
625
626
627
    if ($isgpt) {
	# use the slice device
	$cmd .= $iinfo{$iid}{'part'};
    }
628
629
630
631
632
633

    my $image = $iinfo{$iid}{'iname'};
    if ($iinfo{$iid}{'method'} eq "file") {
	$cmd .= " $image";
    } elsif ($iinfo{$iid}{'method'} eq "frisbee") {
	my $srv = $iinfo{$iid}{'server'};
634
635
636
637

	# use basic shell sleezy trick to capture exit status from imagezip
	$cmd = "( $cmd - || echo \$? > $localdir/imagezip.stat )";

638
639
640
641
642
643
	$cmd .= " | $uploader -S $srv -F $image";
	
	if (exists($iinfo{$iid}{'proxy'})) {
	    $cmd .= " -P " . $iinfo{$iid}{'proxy'};
	}
	$cmd .= " - ";
644
645
    }

646
    if (mysystem("$cmd") || -e "$localdir/imagezip.stat") {
Mike Hibler's avatar
Tweaks.    
Mike Hibler committed
647
	my $stat = sprintf("0x%04x", $?);
648
649
650
651
652
653
654
	my $izstat = 0;
	if (-e "$localdir/imagezip.stat") {
	    $izstat = `cat $localdir/imagezip.stat`;
	    chomp($izstat);
	}
	$izstat = sprintf("0x%04x", $izstat);

655
	print STDERR "*** Failed to create image!\n";
656
657
658
659
	print STDERR "    command:   '$cmd'\n";
	print STDERR "    status:    $stat\n";
	print STDERR "    izstatus:  $izstat\n"
	    if ($izstat);
660
661
662
663
664
665
666
667
668
669
670
671
672
673
	exit(3);
    }

    if ($iinfo{$iid}{'method'} eq "frisbee" &&
	exists($iinfo{$iid}{'nsigfile'})) {
	$cmd = "$uploader -S " . $iinfo{$iid}{'server'} .
	    " -F " . $iinfo{$iid}{'nsigfile'} . " $localdir/nsigfile";
	if (mysystem("$cmd")) {
	    print STDERR "*** Failed to upload signature for created image!\n";
	    print STDERR "    command: '$cmd'\n";
	    exit(3);
	}
    }

674
    mysystem("$sudo rm $localdir/*");
675
676
677
678
679
680
681
682
683
684
685
686
687
688
}

sub mysystem($)
{
    my ($cmd) = @_;

    if ($impotent) {
	print STDERR "Would: '$cmd' ...\n";
	return 0;
    }
    print STDERR "Doing: '$cmd' ...\n"
	if ($verbose);
    return system($cmd);
}