create_image.in 50 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2019 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{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/>.
# 
# }}}
Leigh Stoller's avatar
Leigh Stoller committed
23
#
24
use strict;
25 26
use English;
use Getopt::Std;
27
use POSIX qw(setsid :sys_wait_h);
28
use File::Basename;
29
use Cwd qw(realpath);
30
use Errno qw(ENOSPC);
31

32
#
33
# Image Creation Tuneables.
34
#
35 36 37
# $maxwait	Max total wall clock time to allow for image collection.
#		We abort after this length of time even if we are still
#		actively making progress collecting the image.
38 39 40
#		Empirically we have observed about 1.6MB/sec on a pc850
#		for a Windows image (the slowest to create), so figuring
#		1.5MB/sec for a 6GB max image works out to around 72 minutes.
41
#		This value comes from sitevar images/create/maxwait if set.
42
#
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
# $idlewait	Max time to allow between periods of progress.
#		This value ensures that if we get stuck and stop making
#		progress, we don't have to wait the potentially very long
#		time til the $maxwait time expires to notice and abort.
#		This value comes from sitevar images/create/idlewait if set.
#
# $checkwait	Time between progress checks (must be int div of $idlewait)
#		Hardwired here, does not come from DB.
#
# $reportwait	Time between progress reports (must be multiple of $checkwait)
#		Hardwired here, does not come from DB.
#
# $maximagesize	Max size in bytes of an image.  Currently this is site-wide
#		and comes from sitevar images/create/maxsize if set. It should
#		probably be finer-grained (per-project? per-user?) than that.
58
#
59
my $maxwait      = (72 * 60);
60 61
my $idlewait     = ( 8 * 60);
my $reportwait   = ( 2 * 60);
62 63
# Check more frequently for web updates, sez Leigh.
my $checkwait    = 5;
64
my $maximagesize = (6 * 1024**3);
65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
#
# Options for imagezipper on the client-side. These apply only to imagezip,
# i.e., the local node imaging process. They also only apply to the latest
# version of the client script (create-versioned-image). For the older path,
# options are hardwired into the create-image script.
#
# Note that since we cannot have spaces in the string passed to the client,
# options are encoded; e.g.:
#   -N -z 9 -d -a SHA1
# would be encoded as:
#   N,z=9,d,a=SHA1
#
# Specific options:
#
# By default we do not create relocations (-N) in the resulting image for a
# couple of reasons. One is that we never did relocation support for GRUB
# partition boot code, so modern Linux images would not have relocations
# anyway. For FreeBSD this does mean that we cannot relocate them (we use
# a relocation for correct disklabel construction), but we never really
# took advantage of this anyway. The second reason is that ranges with
# relocations are always considered different for hash comparisons, so an
# otherwise empty FreeBSD delta image would have 64K of data in it.
#
89 90 91 92 93 94 95 96 97 98 99
# XXX change of heart: we now will generate relocations for FreeBSD
# partition images. I *have* had occasion to relocate these (e.g., loading
# them into a virtual disk for imagezip testing) and you just cannot use
# them without relocations. Adding the -N flag for other images is done
# later based on the def_boot_osid, so it won't try to do relocations for
# Linux or Windows or anything else.
#
# So, only add "N" here if you absolutely, positively cannot tolerate
# relocations anywhere!
#
my $zipperopts = "";
100

101 102 103 104 105 106 107 108
#
# Create a disk image.
#
# XXX: Device file should come from DB.
#      Start/count slice computation is not generalized at all.
#
sub usage()
{
109
    print(STDERR
110
	  "Usage: create_image [-wsN] [-p <pid>] <imagename> <node>\n" .
111
	  "switches and arguments:\n".
112
	  "-w          - wait for image to be fully created\n".
113 114
	  "-s          - use ssh instead of frisbee uploader\n".
	  "-N          - use NFS (if available) instead of frisbee uploader\n".
115
	  "-F          - create a full image even if deltas are on\n".
116 117
	  "-D          - create a 'delta' image rather than a full image\n".
	  "-S          - create a signature file for the new image\n".
118 119 120
	  "-A <pct>    - when -D is specified, automatically create a full\n".
	  "              image instead when a delta would be more than\n".
	  "              <pct> percent the size of a full image.\n".
121
	  "-M          - do not boot info MFS, run with ssh from current OS\n".
122
	  "-U          - Tell prepare to update master password files\n".
123
	  "-B          - uuid Setup a copyback to origin uuid after snapshot\n".
124 125 126
	  "-p <pid>    - project ID of the image; defaults to system project\n".
	  "<imagename> - imagename to use\n".
	  "<node>      - nodeid to create the image from\n");
127 128
    exit(-1);
}
129
my $optlist  = "p:wsNdfeDSMA:Fb:UB:t:q";
130
my $waitmode = 0;
131 132 133 134
my $usessh   = 0;
my $usenfs   = 0;
my $usefup   = 1;
my $noemail  = 0;
135
my $delta    = 0;
136
my $nodelta  = 0;
137
my $nomfs    = 0;
138
my $quiet    = 0;
139
my $signature= 0;
140
my $deltapct = 0;
141
my $update_prepare = 0;
142
my $bsname;
143
my $origin_uuid;
144
my $webtask;
145 146 147 148 149 150

#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
151
my $TBLOGS      = "@TBLOGSEMAIL@";
152
my $BOSSIP	= "@BOSSNODE_IP@";
153
my $CONTROL     = "@USERNODE@";
154
my $NONFS	= @NOSHAREDFS@;
155
my $PROJROOT    = "@PROJROOT_DIR@";
156
my $GROUPROOT   = "@GROUPSROOT_DIR@";
157 158
my $WITHPROVENANCE= @IMAGEPROVENANCE@;
my $WITHDELTAS  = @IMAGEDELTAS@;
159
my $ISFS        = ("@BOSSNODE_IP@" eq "@FSNODE_IP@") ? 1 : 0;
160 161
my $UPLOADTOFS  = "@IMAGEUPLOADTOFS@";
my $FSIP	= "@FSNODE_IP@";
162 163 164 165 166 167

#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
168
use EmulabConstants;
169
use libtestbed;
170
use libadminmfs;
171
use Experiment;
172
use Node;
173
use User;
174 175
use OSImage;
use Image;  # Cause of datasets.
176
use Logfile;
177
use WebTask;
178 179
use Project;
use EmulabFeatures;
180 181 182 183 184 185 186 187 188 189 190 191

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

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

Leigh Stoller's avatar
Leigh Stoller committed
192 193 194 195 196 197 198 199
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
    die("*** $0:\n".
	"    Must be setuid! Maybe its a development version?\n");
}

200 201
sub cleanup();
sub fatal($);
202
sub check_progress($$);
203
sub run_with_ssh($$);
204

205
my $nodereboot	= "$TB/bin/node_reboot";
206
my $createimage = "/usr/local/bin/create-versioned-image";
207
my $createxenimage = "/usr/local/bin/create-xen-image";
208
my $ocreateimage= "/usr/local/bin/create-image";
209
my $reboot_prep = "@CLIENT_BINDIR@/reboot_prepare";
210
my $EC2SNAP     = "$TB/sbin/ec2import.proxy";
211
my $friskiller  = "$TB/sbin/frisbeehelper";
212
my $osselect    = "$TB/bin/os_select";
213
my $checkquota  = "$TB/sbin/checkquota";
214
my $imagehash	= "$TB/bin/imagehash";
215
my $imagevalidate = "$TB/sbin/imagevalidate";
216
my $TRIGGERUPDATE = "$TB/sbin/protogeni/triggerimageupdate";
217
my $SHA1	= "/sbin/sha1";
218
my $SCP		= "/usr/bin/scp";
219 220 221 222
my $def_devtype	= "ad";
my $def_devnum	= 0;
my $devtype;
my $devnum;
223
my $device;
224
my $mereuser    = 0;
225
my $debug       = 1;
226
my $foreground  = 0;
227
my $imagepid    = TB_OPSPID;
228 229
my $logfile;
my $oldlogfile;
Kirk Webb's avatar
Kirk Webb committed
230
my $needcleanup = 0;
231
my $needunlock  = 0;
232
my $isvirtnode  = 0;
233
my $isxenhost   = 0;
234
my $isec2node   = 0;
235
my $onsharednode= 0;
236 237
my $node_id;
my $node;
238
my $image;
239
my ($experiment,$pid);
240
my $doprovenance = 0;
241
my $hacksigfile;
242 243 244 245 246

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
247
my %options = ();
248 249 250
if (! getopts($optlist, \%options)) {
    usage();
}
251 252 253
if (defined($options{"w"})) {
    $waitmode = 1;
}
254 255 256
if (defined($options{"e"})) {
    $noemail = 1;
}
257
if (defined($options{"s"})) {
258 259 260 261 262 263 264 265 266 267 268
    $usessh = 1;
    $usefup = $usenfs = 0;
}
if (defined($options{"N"})) {
    if (!$NONFS) {
	$usenfs = 1;
	$usefup = $usessh = 0;
    } else {
	print STDERR "NFS not available, cannot use -N\n";
	exit(1);
    }
269 270 271 272
}
if (defined($options{"d"})) {
    $debug = 1;
}
273 274 275
if (defined($options{"q"})) {
    $quiet = 1;
}
276 277
if (defined($options{"f"})) {
    $foreground = 1;
278
    $waitmode = 0;
279
}
280 281 282 283 284 285 286 287 288 289 290
if (defined($options{"B"})) {
    $origin_uuid = $options{"B"};

    if ($origin_uuid =~ /^([-\w]+)$/) {
	$origin_uuid = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $origin_uuid\n");
    }
}
291 292 293 294 295 296 297 298 299 300 301
if (defined($options{"b"})) {
    $bsname = $options{"b"};

    if ($bsname =~ /^([-\w]+)$/) {
	$bsname = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $bsname.\n");
    }
}
302 303 304 305 306 307 308 309 310
if (defined($options{"t"})) {
    my $webtask_id = $options{"t"};
    $webtask = WebTask->Lookup($webtask_id);
    if (!defined($webtask)) {
	die("*** $0:\n".
	    "    No such webtask\n");
    }
    $webtask->AutoStore(1);
}
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
if (defined($options{"D"})) {
    if (!$WITHDELTAS) {
	print STDERR "Delta image support not enabled\n";
	exit(1);
    }
    $delta = 1;
}
if (defined($options{"S"})) {
    if (!$WITHDELTAS) {
	print STDERR "Delta image support not enabled\n";
	exit(1);
    }
    $signature = 1;
}
if (defined($options{"M"})) {
    $nomfs = 1;
    $usessh = 1;
}
329 330 331
if (defined($options{"U"})) {
    $update_prepare = 1;
}
332 333 334 335 336 337 338 339 340 341 342 343
if (defined($options{"A"})) {
    if (!$WITHDELTAS) {
	print STDERR "Delta image support not enabled\n";
	exit(1);
    }
    if ($options{"A"} =~ /^(\d+)$/) {
	$deltapct = int($1);
    } else {
	print STDERR "Invalid percentage for -A\n";
	exit(1);
    }
}
344 345 346
if (defined($options{"F"})) {
    $nodelta = 1;
}
347 348 349 350 351 352 353 354 355 356 357
if (defined($options{"p"})) {
    $imagepid = $options{"p"};

    if ($imagepid =~ /^([-\w\.]+)$/) {
	$imagepid = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $imagepid.\n");
    }
}
358
if (@ARGV != 2) {
359 360 361
    usage();
}

362
my $imagename  = $ARGV[0];
363
my $target     = $ARGV[1];
364

365 366 367 368 369 370 371
#
# There is no reason to run as root unless we are taking a snapshot
# of a VM on a shared node. In that case we will flip back when
# we do the ssh over.
#
$EUID = $UID;

372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
#
# See if we should use libimageops instead.  Note that
# libimageops::CreateImage handles all the permissions checks, etc, so
# no problem cutting to it straight after arg processing.
#
my $usenew = 0;
my $newtarget;
if ($target =~ /^([-\w]+)$/) {
    $target = $1;

    $newtarget = Node->Lookup($target);
    if (defined($newtarget) && $newtarget->isvirtnode()) {
	#
	# Need to know this is a docker-host.
	#
	my $pnode   = Node->Lookup($newtarget->phys_nodeid());
	my $osimage = OSImage->Lookup($pnode->def_boot_osid());
	if (defined($osimage) && $osimage->FeatureSupported("docker-host")) {
	    $usenew = 1;
	}
392
    }
393 394
}
if ($usenew) {
395 396
    use libimageops;

397 398 399 400 401 402 403 404 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 431 432 433 434 435 436
    my $iops = libimageops::Factory("image" => $imagename, "node" => $newtarget,
				    "imagepid" => $imagepid);
    if (!defined($iops)) {
	print STDERR "*** $0:\n$@\n";
	exit(-1);
    }

    # Set up the argv:
    my %args = (
	'debug' => 1,
	'waitmode' => $waitmode,
	'usessh' => $usessh,
	'usenfs' => $usenfs,
	'usefup' => $usefup,
	'noemail' => $noemail,
	'delta' => $delta,
	'nodelta' => $nodelta,
	'nomfs' => $nomfs,
	'quiet' => $quiet,
	'signature' => $signature,
	'deltapct' => $deltapct,
	'update_prepare' => $update_prepare,
	'imagepid' => $imagepid,
	'bsname' => $bsname,
	'origin_uuid' => $origin_uuid,
	'webtask' => $webtask,
	);
    if ($debug) {
	use Data::Dumper;
	print STDERR "create_image libimageops::CreateImage args: " .
	    Dumper(%args) . "\n";
    }

    #
    # Create the image.  Library does all the work.
    #
    my ($rc,$err) = $iops->CreateImage($imagename,$target,\%args);
    if ($rc) {
	print STDERR "*** $0:\n$err\n";
	exit($rc);
437
    }
438
    exit(0);
439 440
}

441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
#
# Reset default values from site variables if they exist.
#
my $tmp;
if (TBGetSiteVar("images/create/maxwait", \$tmp)) {
    $maxwait = $tmp * 60;
}
if (TBGetSiteVar("images/create/idlewait", \$tmp)) {
    $idlewait = $tmp * 60;
}
if (TBGetSiteVar("images/create/maxsize", \$tmp)) {
    $maximagesize = $tmp * 1024**3;
}
$idlewait = $maxwait
    if ($maxwait < $idlewait);
$reportwait = $idlewait
    if ($idlewait < $reportwait);
$checkwait = $reportwait
    if ($reportwait < $checkwait);

461
#
462
# Verify user and get his DB uid and other info for later.
463
#
464 465
my $this_user = User->ThisUser();
if (! defined($this_user)) {
Mike Hibler's avatar
Mike Hibler committed
466
    die("You ($UID) do not exist!\n");
467
}
468 469 470
my $user_uid   = $this_user->uid();
my $user_name  = $this_user->name();
my $user_email = $this_user->email();
471

472 473
if ($UID && ! $this_user->IsAdmin()) {
    $mereuser = 1;
474 475
}

476 477 478 479 480
#
# Grab the imageid description from the DB. We do a permission check, but
# mostly to avoid hard to track errors that would result if the user picked
# the wrong one (which is likely to happen no matter what I do).
#
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
if (defined($bsname)) {
    #
    # Datasets are still special, they have no osinfo. 
    #
    $image = Image->Lookup($imagepid, $imagename);
    if (!defined($image)) {
	die("*** $0:\n".
	    "Dataset must does not exist\n");
    }
    elsif (!$image->isdataset()) {
	die("*** $0:\n".
	    "$image is not a dataset for $bsname\n");
    }
}
else {
    $image = OSImage->Lookup($imagepid, $imagename);
    if (!defined($image)) {
	die("*** $0:\n".
	    "    No such image descriptor $imagename in project $imagepid!\n");
    }
501
}
502
my $imageid = $image->imageid();
503
my $version = $image->version();
504
$imagename  = $image->imagename(); # strip any version
505

506
if ($mereuser &&
507
    ! $image->AccessCheck($this_user, TB_IMAGEID_ACCESS)) {
508 509
    die("*** $0:\n".
	"    You do not have permission to use imageid $imageid!\n");
510 511
}

512 513 514 515 516 517
# Must have a blockstore name if the image is marked as a dataset.
if ($image->isdataset() && !defined($bsname)) {
    die("*** $0:\n".
	"    You must provide a blockstore name (-b) for this image!\n");
}

518 519
#
# Before doing anything else, check for overquota ... lets not waste
520 521 522 523 524 525 526 527 528 529 530
# our time. Make sure user sees the error by exiting with ENOSPC. 
#
my $copt = ($image->pid() eq $image->gid() ?
	    "-p " . $image->pid() :
	    "-g " . $image->pid() . "/" . $image->pid());
if (system("$checkquota $copt -m 3GB $user_uid") != 0) {
    print STDERR
	"*** $0:\n".	
	"    You are over your disk quota on $CONTROL, or there is less\n".
	"    then a minimum amount (3GB) of space. Please login and cleanup!\n";
    exit(ENOSPC);
531 532
}

533 534 535 536 537
#
# See if per-project/per-user provenance feature is set.
#
if ($WITHPROVENANCE) {
    my $project = Project->Lookup($image->pid());
538
    my $group   = Group->Lookup($image->pid(), $image->gid());
539 540 541 542
    if (!defined($project)) {
	die("*** $0:\n".
	    "    Could not lookup project for $image\n");
    }
543 544

    # But allow feature override. 
545
    $doprovenance = EmulabFeatures->FeatureEnabled("ImageProvenance",
546
						   $this_user, $group);
547 548 549
    
    # Temporary override for all geni projects until we can export deltas.
    if ($project->IsNonLocal()) {
550
	$nodelta = 1;
551
    }
552 553 554 555 556 557 558 559
}

#
# When provenance is enabled and we have delta support, we always collect
# signatures and we always try to create deltas. Note that we set them to
# a different non-zero value so we can distinguish this case and not print
# various warnings below.
#
560 561 562 563
# XXX We really shouldn't be doing this implicitly--our caller should just
# be specifying the options when provenance is enabled--but this script is
# called from a surprisingly large number of places, so we do!
#
564 565 566 567 568
if ($doprovenance && $WITHDELTAS) {
    $delta = 2
	if ($delta == 0);
    $signature = 2
	if ($signature == 0);
569 570 571 572

    # XXX let's try this for now
    $deltapct = 50
	if ($deltapct == 0);
573 574 575 576

    # Override delta but still collect signatures. 
    $delta = 0
	if ($image->nodelta() || $nodelta);
577 578 579 580
}

my ($srcsigfile, $srcimage, $dstsigfile);

581 582 583 584 585 586 587 588 589 590 591 592
#
# Is it a local node or a remote EC2 node (need to generalize). 
#
if ($target =~ /^.*@.*$/) {
    if ($target =~ /^([-\w\@\+\.]+)$/) {
	$target = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $target\n");
    }

593 594 595 596 597 598 599 600 601 602
    if ($delta || $signature) {
	# Only warn if they explicitly specified an option
	if ($delta == 1 || $signature == 1) {
	    print STDERR
		"*** WARNING: don't support delta imaging of EC2 images, ".
		"ignoring delta/signature options.\n";
	}
	$delta = $signature = 0;
    }

603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
    $isec2node = 1;
    $usefup = $usessh = 0;
    $pid = $image->pid();
}
else {
    if ($target =~ /^([-\w]+)$/) {
	$node_id = $1;
    }
    else {
	die("*** $0:\n".
	    "    Bad data in $target\n");
    }

    # Check node and permission
    $node = Node->Lookup($node_id);
    if (!defined($node)) {
	die("*** $0:\n".
	    "    Invalid node name $node_id!\n");
    }
    $isvirtnode   = $node->isvirtnode();
    $onsharednode = $node->sharing_mode()
	if ($isvirtnode);

    if (! $node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
	die("*** $0:\n".
	    "    You do not have permission to create an image from $node\n");
    }

631 632 633 634 635
    if ($node->IsTainted()) {
	die("*** $0:\n".
	    "    $node is tainted - image creation denied.\n");
    }

636 637 638 639 640 641 642
    #
    # We need the project id for test below. The target directory for the
    # output file has to be the node project directory, since that is the
    # directory that is going to be NFS mounted by default.
    #
    $experiment = $node->Reservation();
    if (!defined($experiment)) {
643
	fatal("Could not map $node to its experiment object!");
644 645 646
    }
    $pid = $experiment->pid();

647 648 649 650
    if ($isvirtnode) {
	#
	# Need to know this is a xen-host to tailor method below.
	#
651 652 653 654
	my $pnode   = Node->Lookup($node->phys_nodeid());
	my $osimage = OSImage->Lookup($pnode->def_boot_osid());
	if (!defined($osimage)) {
	    fatal("Could not get OSImage for $pnode");
655
	}
656
	if ($osimage->FeatureSupported("xen-host")) {
657 658 659 660 661 662 663 664
	    $isxenhost = 1;

	    if ($image->mbr_version() == 99) {
		$doprovenance = $delta = $signature = 0;
	    }
	}
    }

665 666 667 668
    # Do not create a delta for system images but still collect signatures.
    $delta = 0
	if ($pid eq TBOPSPID());

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
    #
    # If we are creating a delta image, figure out what image we are
    # deriving from so that we can grab the signature.
    #
    if ($delta) {
	#
	# Find the source image we are creating a delta from. When provenance
	# is enabled, we can use the parent image. If not enabled, we attempt
	# to determine what is already on the node via the partitions table.
	#
	# If we cannot determine the source, we just warn and create a full
	# image instead.
	#
	if ($doprovenance) {
	    $srcimage = $image->Parent();
684
	}
685
	if (!defined($srcimage) && !$image->isdataset()) {
686 687 688
	    (undef, $srcimage) = $node->RunningOsImage();
	}
	if (defined($srcimage)) {
689
	    $srcsigfile = $srcimage->FullImageSigFile();
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
	    if (! -e "$srcsigfile") {
		# XXX user may not have direct access to a shared image
		my $SAVEUID = $UID;
		$EUID = $UID = 0;
		if (! -e "$srcsigfile") {
		    $srcsigfile = undef;
		}
		$EUID = $UID = $SAVEUID;
	    }
	    if (!defined($srcsigfile)) {
		if ($delta == 1) {
		    print "*** WARNING: no signature file for source image, ".
			"cannot create delta; creating full image instead.\n";
		}
		$delta = 0;
	    }
	} else {
	    if ($delta == 1) {
		print "*** WARNING: no source for image, ".
		    "cannot create delta; creating full image instead.\n";
	    }
	    $delta = 0;
	}
    }

715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
    #
    # To avoid blowing a cavernous hole ("allow all TCP ports to boss")
    # in the per-experiment firewall, we don't use the frisbee uploader if
    # the node is firewalled.
    # 
    if ($usefup && $experiment->IsFirewalled()) {
	print "*** WARNING: $node_id is firewalled, not using Frisbee uploader\n";
	$usefup = 0;
	if ($NONFS) {
	    $usenfs = 0;
	    $usessh = 1;
	} else {
	    $usenfs = 1;
	    $usessh = 0;
	}
    }
}

733
#
734 735
# Make sure that the directory exists and is writeable for the user.
# We test this by creating the file. Its going to get wiped anyway.
736
#
737
my $filename  = $image->TempImageFile();
738 739 740
my $isglobal  = $image->global();
my $usepath   = 0;
my $isdataset = $image->isdataset();
Leigh Stoller's avatar
Leigh Stoller committed
741
my $prefixdir = $image->SaveDir();
742 743

#
744 745
# If we are creating a signature file for this image, get the
# signature file name.
746
#
747 748 749
if ($signature) {
    # We want to use the temp filename.
    $dstsigfile = $filename . ".sig";
750
}
751 752 753

#
# Redirect pathname for global images. See equiv code in clone_image.
754 755 756
# We also need to do this when uploading images directly to ops since
# ops has no way sure fire way to translate imageids into pathes (because
# of subgroup images that wind up in /groups instead of /proj).
757
#
758
if ($UPLOADTOFS || ($isglobal && $image->IsSystemImage())) {
Leigh Stoller's avatar
Leigh Stoller committed
759
    $filename = $prefixdir . basename($filename);
760 761
    print "*** WARNING: Writing global descriptor to $filename instead!\n"
	if ($isglobal && $image->IsSystemImage());
762 763 764 765

    #
    # Ditto for the signature file
    #
Leigh Stoller's avatar
Leigh Stoller committed
766 767
    if ($signature) {
	$dstsigfile = $prefixdir . basename($dstsigfile);
768 769
    }

770 771 772 773
    #
    # XXX the Emulab config of the master server doesn't know this trick
    # so when it tries to lookup imageid emulab-ops/<whatever> it would
    # still map to /usr/testbed and fail because it cannot update images
774
    # outside of /{users,group,proj}. So we skirt the issue by passing
775 776 777
    # it the full path contructed here rather than the imageid.
    #
    $usepath = 1;
778
}
779

780 781 782 783 784
#
# Make sure real path is someplace that makes sense; remember that the
# image is created on the nodes, and it NFS mounts directories on ops.
# Writing the image to anyplace else is just going to break things.
#
Mike Hibler's avatar
Mike Hibler committed
785 786 787 788 789 790 791
# Use realpath on the directory part of the path to validate. If we ran
# realpath on the filename, it would return null since $filename (a temp
# file) won't exist. Note that we can use dirname/basename here since
# $filename is well formed (both dir and file components).
#
# We still use the original path for passing to the client-side since
# boss and the client may not have the same real path for a file.
792
#
793
my $ofilename = $filename;
Mike Hibler's avatar
Mike Hibler committed
794 795 796 797 798
my $tdir = dirname($filename);
my $translated = realpath($tdir);
if ($translated && $translated =~ /^([-\w\.\/\+:]+)$/) {
    my $tfile = basename($filename);

799
    $filename = $1;
Mike Hibler's avatar
Mike Hibler committed
800 801 802 803 804 805
    # XXX check the last component
    if ($tfile =~ /^([-\w\.\+:]+)$/) {
	$filename = "$filename/$1";
    } else {
	fatal("Bad characters in image filename");
    }
806 807
}
else {
Mike Hibler's avatar
Mike Hibler committed
808 809 810 811 812 813 814 815
    if ($translated) {
	fatal("Bad characters in image pathname");
    }
    fatal("Image directory does not exist");
}
# Make sure the result (really the final component) is not a symlink or dir
if (-l $filename) {
    fatal("$filename is a symlink! Must be a plain file.");
816
}
817
if (-d $filename) {
818
    fatal("$filename is a directory! Must be a plain file.");
819
}
820 821

#
822
# The file must reside in an allowed directory. Since this script
823 824 825
# runs as the caller, regular file permission checks ensure its a file
# the user is allowed to use. 
#
826
if (! TBValidUserDir($filename, $ISFS)) {
827
    fatal("$filename does not resolve to an allowed directory!");
828 829
}

830
#
831
# Before we do anything destructive, we lock the image.
832 833
#
if ($image->Lock()) {
834
    fatal("Image is locked, please try again later!");
835 836 837
}
$needunlock = 1;

838 839 840 841 842
# Now we can set the webtask.
if (defined($webtask)) {
    $image->SetWebTask($webtask);
}

843
if ($doprovenance && !$isdataset && $image->ready()) {
844
    $image->Unlock();
845
    fatal("$image ready flag is set, this is inconsistent!");
846 847
}

848
#
849 850 851 852
# Slight problem here; killing off the running frisbeed will cause
# any experiment trying to load that image, to blow up. So we
# do not want to do this for system images, but for project images
# this is generally okay to do. 
853
#
854
if ($pid ne TBOPSPID()) {
855 856 857 858
    system("$friskiller -k $imageid");
    if ($?) {
	fatal("Could not kill running frisbee for $imageid!");
    }
859
}
860

861
#
862 863 864
# We want to confirm the user can create the temp file in the target
# directory, so create a zero length file. But first, need to make
# sure the target directory exists in the image path is a directory.
865
#
866
# Make sure the path directory exists.
867 868
if ($image->CreateImageDir()) {
    fatal("Could not create image directory");
869
}
870 871
open(FILE, "> $filename") or
    fatal("Could not create $filename: $!");
872
close(FILE) or
873
    fatal("Could not truncate $filename: $!");
874 875 876 877 878 879
#
# XXX this script runs as the user creating the image.
# However, in the uploader case, the uploader runs as the creator of
# the image. In the case those two are not the same, we need to make
# sure that the file we create here is group writable.
#
880 881
chmod(0664, $filename) or
    fatal("Could not make $filename group writable: $!");
882

883 884 885 886 887
#
# For the source signature file of global images, we actually have to copy
# it to somewhere where frisbee can access it (in case NFS is being used).
# Note that we wait to do this until after we are sure the imagedir exists.
#
Leigh Stoller's avatar
Leigh Stoller committed
888
if ($srcsigfile && $srcimage->IsSystemImage()) {
889
    my $osrcsigfile = $srcsigfile;
Leigh Stoller's avatar
Leigh Stoller committed
890
    $srcsigfile = $prefixdir . basename($srcsigfile);
891 892 893 894 895 896 897 898
    if (system("cp -fp $osrcsigfile $srcsigfile")) {
	fatal("Could not copy source signature file ".
	      "$osrcsigfile to $srcsigfile");
    }
    # XXX remember so we can cleanup later
    $hacksigfile = $srcsigfile;
}

899
if (! ($isvirtnode || $isec2node || $isdataset)) {
900 901 902 903 904 905 906 907 908 909 910 911
    #
    # Get the disktype for this node
    #
    $node->disktype(\$devtype);
    $node->bootdisk_unit(\$devnum);

    $devtype = $def_devtype
	if (!defined($devtype));
    $devnum = $def_devnum
	if (!defined($devnum));
    $device = "/dev/${devtype}${devnum}";
}
912

913 914 915 916 917 918
#
# Okay, we want to build up a command line that will run the script on
# on the client node. We use the imageid description to determine what
# slice (or perhaps the entire disk) is going to be zipped up. We do not
# allow arbitrary combos of course. 
#
919 920
my $startslice;
my $loadlength;
921
my $command;
922

923 924
my $SERVER = ($UPLOADTOFS ? $FSIP : $BOSSIP)
;
925 926 927
#
# EC2 images use a special command which is hardwired below.
#
928
if ($isec2node) {
929
    ;
930
}
931 932
#
# Virtnode images use a version of the old create-image script on the vhost
933
# XXX needs to be fixed.
934
#
935
elsif ($isvirtnode && !$isdataset && (!$doprovenance || !$isxenhost)) {
936 937 938 939
    $command = "$ocreateimage";
    if ($usefup) {
	my $id;
	if ($usepath) {
940
	    $id = $ofilename;
941
	} else {
942
	    $id = $image->pid() . "/" . $image->imagename();
943
	}
944
	$command .= " -S $SERVER -F $id";
945 946
    }

947 948 949
    #
    # XXX Need to add XEN package flag to descriptor.
    #
950
    if ($isxenhost) {
951 952 953 954 955 956
	if ($image->mbr_version() == 99) {
	    $command .= " -p";
	}
	if ($image->loadpart()) {
	    $command .= " -s " . $image->loadpart();
	}
957
    }
958
    $command .= " $node_id";
959 960 961 962

    if ($usefup || $usessh) {
	$command .= " -";
    } else {
963
	$command .= " $ofilename";
964
    }
965
}
966
#
967 968 969 970 971 972 973
# Regular nodes with provenance tracking is turned off, use the old script.
#
elsif (!$doprovenance) {
    $command = "$ocreateimage";
    if ($usefup) {
	my $id;
	if ($usepath) {
974
	    $id = $ofilename;
975 976 977
	} else {
	    $id = $image->pid() . "/" . $image->imagename();
	}
978
	$command .= " -S $SERVER -F $id";
979 980
    }

981 982 983 984 985 986 987 988 989 990 991
    if ($isdataset) {
	# This is not backward compatable, but none of the BS code is.
	$command .= " -b $bsname";
    }
    else {
	$startslice = $image->loadpart();
	$loadlength = $image->loadlength();
	if ($startslice || $loadlength == 1) {
	    $command .= " -s $startslice";
	}
	$command .= " $device";
992 993 994 995 996
    }

    if ($usefup || $usessh) {
	$command .= " -";
    } else {
997
	$command .= " $ofilename";
998
    }
999 1000 1001 1002 1003 1004

    #
    # XXX always use ssh for now to get better log info; i.e., all
    # the log info winds up in one logfile.
    #
    $usessh = 1;
1005 1006
}
#
1007
# Otherwise, use the new script with different argument syntax.
1008
#
1009
else {
1010 1011 1012 1013
    my $script = ($isxenhost && !$isdataset ?
		  "$createxenimage" : "$createimage");
    my $sopts = "";
    my $oargs = "";
1014 1015 1016 1017 1018

    #
    # XEN Hosts cannot do provenance/delta without client side update.
    # We need to provide these arguments for backwards compat though.
    #
1019
    if ($isxenhost && !$isdataset) {
1020
	$oargs .= " $node_id";
1021
	if ($usefup || $usessh) {
1022
	    $oargs .= " -";
1023
	} else {
1024
	    $oargs .= " $ofilename";
1025 1026
	}
    }
1027 1028 1029

    my $id;
    if ($usefup) {
1030
	$oargs .= " METHOD=frisbee SERVER=$SERVER";
1031 1032 1033 1034 1035

	# if the node has a subboss, use that for downloads
	my $subboss;
	$node->GetSubboss("frisbee", \$subboss);
	if (defined($subboss)) {
1036
	    $oargs .= " DOWNSERVER=$subboss";
1037 1038
	}

1039
	if ($usepath) {
1040
	    $id = $ofilename;
1041
	} else {
1042
	    $id = $image->pid() . "/" . $image->imagename() . ":$version";
1043 1044
	}
    } else {
1045
	$id = $ofilename;
1046
    }
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056

    #
    # XXX more backward compat:
    #
    # create-xen-image expects the server and image name via options
    # *unless* a *SIGFILE= argument is given. In that case it invokes
    # the newer create-versioned-image script where it will use the
    # SERVER= and IMAGENAME= key-value args. If we don't do this, the
    # images winds up in the logfile!
    #
1057 1058 1059
    # And the slice argument too! Otherwise we take a whole disk image
    # and sell it as a slice image.
    #
1060
    if ($isxenhost && !$isdataset && !$srcsigfile && !$dstsigfile) {
1061
	$sopts = " -S $SERVER -F $id";
1062 1063 1064 1065 1066 1067

	$startslice = $image->loadpart();
	$loadlength = $image->loadlength();
	if ($startslice || $loadlength == 1) {
	    $sopts .= " -s $startslice";
	}
1068 1069 1070
    }

    $oargs .= " IMAGENAME=$id";
1071 1072
    if ($srcsigfile) {
	if (!$usefup) {
1073
	    $oargs .= " OLDSIGFILE=$srcsigfile";
1074
	} else {
1075 1076
	    my $sid = $srcimage->pid() . "/" . $srcimage->imagename() .
		":" . $srcimage->version();
1077
	    $oargs .= " OLDSIGFILE=$sid,sig";
1078 1079 1080 1081
	}
    }
    if ($dstsigfile) {
	if (!$usefup || $usepath) {
1082
	    $oargs .= " NEWSIGFILE=$dstsigfile";
1083
	} else {
1084
	    $oargs .= " NEWSIGFILE=$id,sig";
1085 1086
	}
    }
1087 1088 1089 1090 1091 1092 1093

    #
    # See whether we need the "no relocations" flag or not.
    # We only include generate relocations for FreeBSD parititon images.
    #
    my $needrelocs = 0;
    if ($image->loadpart()) {
1094 1095 1096
	my $pnode   = Node->Lookup($node->phys_nodeid());
	my $osimage = OSImage->Lookup($pnode->def_boot_osid());
	if (defined($osimage) && $osimage->OS() eq "FreeBSD") {
1097 1098 1099 1100 1101 1102 1103 1104 1105
	    $needrelocs = 1;
	}
    }
    if (!$needrelocs) {
	$zipperopts .= ","
	    if ($zipperopts);
	$zipperopts .= "N";
    }

1106 1107 1108 1109 1110
    if ($deltapct > 0) {
	$zipperopts .= ","
	    if ($zipperopts);
	$zipperopts .= "P=$deltapct";
    }
1111

1112 1113
    if ($isdataset) {
	# This is not backward compatable, but none of the BS code is.
1114
	$oargs .= " BSNAME=$bsname";
1115
    }
1116 1117 1118 1119 1120
    else {
	$startslice = $image->loadpart();
	$loadlength = $image->loadlength();

	if ($startslice || $loadlength == 1) {
1121
	    $oargs .= " PART=$startslice";
1122 1123 1124
	}
	if (!$isxenhost) {
	    # The XEN host will figure out what device on its own.
1125
	    $oargs .= " DISK=$device";
1126
	}
1127
    }
1128

1129
    if ($zipperopts) {
1130
	$oargs .= " IZOPTS=$zipperopts";
1131 1132
    }

1133 1134 1135 1136 1137
    #
    # XXX always use ssh for now to get better log info; i.e., all
    # the log info winds up in one logfile.
    #
    $usessh = 1;
1138 1139

    $command = "$script$sopts$oargs";
1140 1141 1142 1143 1144
}

#
# Go to the background since this is going to take a while.
# 
1145
if (! $foreground) {
1146 1147
    $logfile = Logfile->Create((defined($experiment) ?
				$experiment->gid_idx() : $image->gid_idx()));
1148 1149 1150 1151
    fatal("Could not create a logfile")
	if (!defined($logfile));
    # Mark it open since we are going to start using it right away.
    $logfile->Open();
1152

1153 1154
    # Logfile becomes the current spew.
    $image->SetLogFile($logfile);
1155

1156
    if (my $childpid = TBBackGround($logfile->filename())) {
1157 1158
	#
	# Parent exits normally, except if in waitmode. 
1159
	#
1160
	if (!$waitmode) {
1161
	    print("Your image from $target is being created\n".
1162
		  "You will be notified via email when the image has been\n".
1163 1164
		  "completed, and you can load the image on another node.\n")
		if (!$quiet);
1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
	    exit(0);
	}
	print("Waiting for image creation to complete\n");
	print("You may type ^C at anytime; you will be notified via email;\n".
	      "later; you will not actually interrupt image creation.\n");
	
	# Give child a chance to run.
	select(undef, undef, undef, 0.25);
	
	#
	# Reset signal handlers. User can now kill this process, without
	# stopping the child.
	#
	$SIG{TERM} = 'DEFAULT';
	$SIG{INT}  = 'DEFAULT';
	$SIG{QUIT} = 'DEFAULT';

1182
	#
1183 1184 1185 1186 1187 1188
	# Wait until child exits or until user gets bored and types ^C.
	#
	waitpid($childpid, 0);
	
	print("Done. Exited with status: $?\n");
	exit($? >> 8);
1189
    }
1190 1191
}

Kirk Webb's avatar
Kirk Webb committed
1192 1193 1194 1195 1196
#
# From here on out, we should take care to clean up the DB, and
# reboot the source node.
#
$needcleanup = 1;
1197

1198 1199 1200 1201
# This tells the master server what uploader path to use.
$image->SetUploaderPath($filename) == 0
    or fatal("Could not set the uploader path");

1202
# Clear the bootlog; see below.
1203 1204
$node->ClearBootLog()
    if (defined($node));
1205

1206 1207 1208 1209 1210 1211 1212 1213 1214
# check_progress state
my $runticks	 = 0;
my $maxticks	 = int($maxwait / $checkwait);
my $reportticks  = int($reportwait / $checkwait);
my $idleticks    = 0;
my $maxidleticks = int($idlewait / $checkwait);
my $lastsize     = 0;
my $result;

1215
#
1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
# XXX initial idle period. This is the period before any write is performed
# to the file. When creating or checking signatures, it can take a long time
# before anything is written to the new image file. So we give them some
# extra time to get the ball rolling.
#
my $maxiidleticks = $maxidleticks;
if ($delta || $signature) {
    $maxiidleticks *= 2;
}

#
# EC2 nodes.
# Run on ops.
1229
#
1230 1231 1232 1233
if ($isec2node) {
    my $safe_target = User::escapeshellarg($target);
    
    my $cmd = "$TB/bin/sshtb -host $CONTROL $EC2SNAP -u $user_uid ".
1234
	"$safe_target $pid $user_uid $imageid $ofilename";
1235 1236 1237 1238 1239 1240 1241 1242 1243 1244
    print STDERR "About to: '$cmd'\n" if (1 || $debug);

    my $SAVEUID	= $UID;
    $EUID = $UID = 0;

    system($cmd);
    fatal("'$cmd' failed")
	if ($?);

    $EUID = $UID = $SAVEUID;
1245 1246 1247 1248

    if (defined($webtask)) {
	$webtask->status("finishing");
    }
1249 1250
    goto ec2done;
}
1251

1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269
#
# Big hack; we want to tell the node to update the master password
# files. But need to do this in a backwards compatable manner, and
# in way that does not require too much new plumbing. So, just touch
# file in /var/run, the current version of prepare looks for it.
#
if ($update_prepare) {
    my $SAVEUID	= $UID;
    $EUID = $UID = 0;
    my $cmd = "$TB/bin/sshtb -n -o ConnectTimeout=10 ".
	"-host $node_id touch /var/run/updatemasterpasswdfiles";
    print STDERR "About to: '$cmd'\n" if ($debug);
    system($cmd);
    fatal("'$cmd' failed")
	if ($?);
    $EUID = $UID = $SAVEUID;
}

1270 1271 1272 1273
#
# Virtnodes.
# Run on vnode host.
#
1274
if ($isvirtnode || $isdataset) {
1275 1276 1277
    my $SAVEUID	= $UID;
    $EUID = $UID = 0;

1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293
    if (!$isdataset) {
	#
	# XEN creates a problem; the physical host cannot actually
	# execute a command inside the guest, but we need to run
	# reboot_prepare and reboot it. FreeBSD creates an additional
	# problem in that shutdown has to run to invoke prepare; reboot
	# does not run it, and a shutdown from outside the VM has the
	# sae effect; prepare does not run. What a pain. 
	#
	my $cmd = "$TB/bin/sshtb -n -o ConnectTimeout=10 ".
	    "-host $node_id $reboot_prep";
	print STDERR "About to: '$cmd'\n" if ($debug);
	system($cmd);
	fatal("'$cmd' failed")
	    if ($?);
    }
1294

1295 1296 1297 1298
    # Mark webtask
    $webtask->status("imaging")
	if (defined($webtask));

1299 1300
    #
    # Now execute command and wait.
1301 1302
    #
    if ($NONFS) {
1303
	$result = run_with_ssh($command, $ofilename);
1304 1305 1306
    } else {
	$result = run_with_ssh($command, undef);
    }
1307
    $EUID = $UID = $SAVEUID;
1308 1309 1310
    goto done;
}

1311
#
1312
# Normal nodes.
Russ Fish's avatar
Russ Fish committed
1313
# Reboot into admin mode and run the command.
1314 1315
# Note that without a shared FS, we just boot the node into the admin MFS
# and run the command via SSH, capturing the output.
1316 1317 1318 1319
#
my $me           = $0;
my %args         = ();
$args{'name'}    = $me;
1320
$args{'prepare'} = 1;
1321

1322
if ($usessh) {
1323 1324 1325
    #
    # Put the node in admin mode...
    #
1326 1327 1328 1329 1330 1331 1332
    if (!$nomfs) {
	$args{'on'} = 1;
	$args{'clearall'} = 0;
	if (TBAdminMfsSelect(\%args, undef, $node_id)) {
	    $result = "setupfailed";
	    goto done;
	}
1333

1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344
	#
	# ...boot it...
	#
	$args{'reboot'} = 1;
	$args{'retry'} = 0;
	$args{'wait'} = 1;
	my @failed = ();
	if (TBAdminMfsBoot(\%args, \@failed, $node_id)) {
	    $result = "setupfailed";
	    goto done;
	}
1345 1346
    }

1347 1348 1349 1350
    # Mark webtask
    $webtask->status("imaging")
	if (defined($webtask));

1351 1352
    #
    # ...execute command and wait!
1353 1354
    # Note: we do not pass the filename, that is part of the key/value
    # string we built up.
1355
    #
1356 1357
    my $SAVEUID	= $UID;
    $EUID = $UID = 0;
1358
    $result = run_with_ssh($command, undef);
1359
    $EUID = $UID = $SAVEUID;
1360
    if ($result eq "setupfailed") {
1361 1362 1363 1364 1365 1366 1367 1368
	goto done;
    }
} else {
    $args{'command'} = $command;
    $args{'timeout'} = $maxwait + $checkwait;
    $args{'pfunc'}     = \&check_progress;
    $args{'pinterval'} = $checkwait;

1369 1370 1371 1372
    # Mark webtask
    $webtask->status("imaging")
	if (defined($webtask));

1373 1374 1375 1376 1377 1378 1379
    my $retry = 1;
    while ($retry) {
	$retry = 0;
	if (TBAdminMfsRunCmd(\%args, undef, $node_id)) {
	    $result = "setupfailed"
		if (!defined($result));
	}
1380 1381 1382
    }
}

1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405
#
# XXX woeful backward compat hack.
# The old client-side script will not recognize the -S and -F options
# we pass in and will exit(-1).  We detect that here and retry with ssh/nfs.
#
# Note that we only do this in the old, non-provenance world since you
# must have an up-to-date MFS to handle provenance.
#
if (!$doprovenance && $usefup && $result eq "255") {
    print STDERR "MFS does not support frisbee upload, falling back on ",
                 $NONFS ? "ssh" : "nfs", "...\n";

    $command = "$ocreateimage ";

    $startslice = $image->loadpart();
    $loadlength = $image->loadlength();
    if ($startslice || $loadlength == 1) {
	$command .= " -s $startslice";
    }
    $command .= " $device";
    if ($usessh) {
	$command .= " -";
    } else {
1406
	$command .= " $ofilename";
1407 1408 1409 1410 1411 1412 1413 1414 1415 1416
    }

    # reset state for check_progress
    $usefup = 0;
    $runticks = 0;
    $idleticks = 0;
    $lastsize = 0;
    $result = undef;

    if ($NONFS) {
1417
	$result = run_with_ssh($command, $ofilename);
1418 1419 1420 1421 1422
    } else {
	$result = run_with_ssh($command, undef);
    }
}

1423 1424
done:

1425 1426 1427
# Grab boot log now. Node will reboot and possibly erase it. We should
# probably come up with a better way to handle this.
my $bootlog;
1428 1429 1430 1431
if (!$isdataset) {
    if ($node->GetBootLog(\$bootlog)) {
	$bootlog = undef;
    }
1432
}
1433
if (defined($webtask)) {
1434 1435
    # Cause of the fork in run_with_ssh.
    $webtask->Refresh();
1436 1437
    $webtask->status("finishing");
}
Kirk Webb's avatar
Kirk Webb committed
1438 1439
if (! cleanup()) {
    fatal("Problem encountered while cleaning up!\n");
1440 1441 1442
}

#
Kirk Webb's avatar
Kirk Webb committed
1443 1444
# If we timed out, if the result code was bad, or if the image size
# grew too large.
1445
#
1446 1447
if ($result eq "setupfailed") {
    fatal("FAILED: Node setup failed ... \n");
1448
}
1449 1450
if ($result eq "timeout") {
    fatal("FAILED: Timed out generating image ... \n");
1451
}
1452
if ($result eq "toobig") {
Kirk Webb's avatar
Kirk Webb committed
1453 1454
    fatal("FAILED: Maximum image size ($maximagesize bytes) exceeded ... \n");
}
1455
if ($result ne "0") {
Mike Hibler's avatar
Mike Hibler committed
1456
    fatal("FAILED: Returned error code $result generating image ... \n");
1457
}
1458

1459
ec2done:
1460

1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489
#
# XXX ugh! If we were doing the autodelta thing, we have to check our logfile
# to see if imagezip reported creating a full image instead of a delta.
# Here we are relying on the fact that we are using SSH, that we are in the
# background and thus keeping a log (so the message will wind up in our log),
# and we depend on the format of the message itself.
#
# Ugly? Yes, but worst case one of our assumptions fails and we record an
# image as a delta when it isn't, which is just inefficient when it comes to
# loading the image.
#
if ($delta && $deltapct > 0 && defined($logfile)) {
    if (open(FD, "<" . $logfile->filename())) {
	# XXX should occur early in the log
	my $maxlines = 100;
	while ($maxlines--) {
	    my $line = <FD>;
	    if ($line =~ /^Auto image selection creating (\S+) image/) {
		if ($1 eq "full") {
		    print "Chose to create full image rather than delta.\n";
		    $delta = 0;
		}
		last;
	    }
	}
	close(FD);
    }
}

1490 1491
#
# The upload completed okay, so move the files into place so that
1492 1493 1494 1495
# imagevalidate finds them in the correct place. We have to watch for
# the case that usepath=1 (target is in /usr/testbed); we do not want
# to rename them to the target (will not work anyway), they have to
# stay in /proj. More succintly, we always move the new files to the
Leigh Stoller's avatar
Leigh Stoller committed
1496
# prefix location.
1497
#
Leigh Stoller's avatar
Leigh Stoller committed
1498
my $hfilename = $prefixdir .
1499