create_image.in 49.7 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
#
3
# Copyright (c) 2000-2018 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 B. Stoller's avatar
Leigh B. 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@;
Leigh B Stoller's avatar
Leigh B Stoller committed
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 B Stoller's avatar
Leigh B 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;
Leigh B Stoller's avatar
Leigh B Stoller committed
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;
Leigh B Stoller's avatar
Leigh B Stoller committed
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) {
David Johnson's avatar
David Johnson committed
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)) {
Leigh B Stoller's avatar
Leigh B Stoller committed
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)) {
Leigh B Stoller's avatar
Leigh B Stoller committed
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
#
Leigh B Stoller's avatar
Leigh B Stoller committed
737
my $filename  = $image->TempImageFile();
738
739
740
my $isglobal  = $image->global();
my $usepath   = 0;
my $isdataset = $image->isdataset();
Leigh B Stoller's avatar
Leigh B Stoller committed
741
my $prefixdir = $image->SaveDir();
742
743

#
Leigh B Stoller's avatar
Leigh B Stoller committed
744
745
# If we are creating a signature file for this image, get the
# signature file name.
746
#
Leigh B Stoller's avatar
Leigh B Stoller committed
747
748
749
if ($signature) {
    # We want to use the temp filename.
    $dstsigfile = $filename . ".sig";
750
}
Leigh B Stoller's avatar
Leigh B Stoller committed
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).
Leigh B Stoller's avatar
Leigh B Stoller committed
757
#
758
if ($UPLOADTOFS || ($isglobal && $image->IsSystemImage())) {
Leigh B Stoller's avatar
Leigh B 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 B Stoller's avatar
Leigh B 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) {
Leigh B Stoller's avatar
Leigh B Stoller committed
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)) {
Leigh B Stoller's avatar
Leigh B Stoller committed
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()) {
Leigh B Stoller's avatar
Leigh B Stoller committed
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();
Leigh B Stoller's avatar
Leigh B Stoller committed
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
#
Leigh B Stoller's avatar
Leigh B Stoller committed
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
#
Leigh B Stoller's avatar
Leigh B Stoller committed
866
# Make sure the path directory exists.
867
868
if ($image->CreateImageDir()) {
    fatal("Could not create image directory");
869
}
Leigh B Stoller's avatar
Leigh B Stoller committed
870
871
open(FILE, "> $filename") or
    fatal("Could not create $filename: $!");
872
close(FILE) or
Leigh B Stoller's avatar
Leigh B Stoller committed
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.
#
Leigh B Stoller's avatar
Leigh B Stoller committed
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 B Stoller's avatar
Leigh B Stoller committed
888
if ($srcsigfile && $srcimage->IsSystemImage()) {
889
    my $osrcsigfile = $srcsigfile;
Leigh B Stoller's avatar
Leigh B 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.
# 
Leigh B Stoller's avatar
Leigh B Stoller committed
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

Leigh B Stoller's avatar
Leigh B Stoller committed
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.