libsetup.pm 92.4 KB
Newer Older
1
#!/usr/bin/perl -w
2

Leigh B. Stoller's avatar
Leigh B. Stoller committed
3
#
4
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# 
# {{{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
24
#
25
# TODO: Signal handlers for protecting db files.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
26

27 28 29 30 31 32 33
#
# Common routines and constants for the client bootime setup stuff.
#
package libsetup;
use Exporter;
@ISA = "Exporter";
@EXPORT =
Ryan Jackson's avatar
Ryan Jackson committed
34
    qw ( libsetup_init libsetup_setvnodeid libsetup_settimeout cleanup_node
35
	 getifconfig getrouterconfig gettrafgenconfig gettunnelconfig
Ryan Jackson's avatar
Ryan Jackson committed
36
	 check_nickname	bootsetup startcmdstatus whatsmynickname
37
	 TBForkCmd vnodejailsetup plabsetup vnodeplabsetup
Ryan Jackson's avatar
Ryan Jackson committed
38
	 jailsetup dojailconfig findiface libsetup_getvnodeid
Timothy Stack's avatar
 
Timothy Stack committed
39
	 ixpsetup libsetup_refresh gettopomap getfwconfig gettiptunnelconfig
40
	 gettraceconfig genhostsfile getmotelogconfig calcroutes fakejailsetup
41
	 getlocalevserver genvnodesetup getgenvnodeconfig stashgenvnodeconfig
42
         getlinkdelayconfig getloadinfo getbootwhat getnodeattributes
43
	 copyfilefromnfs getnodeuuid getarpinfo
44
	 getstorageconfig getstoragediskinfo getimagesize
45
         getmanifest fetchmanifestblobs runbootscript runhooks 
46
         build_fake_macs getenvvars getpnetnodeattrs
47

48 49
	 TBDebugTimeStamp TBDebugTimeStampWithDate
	 TBDebugTimeStampsOn TBDebugTimeStampsOff
50

51 52
	 MFS REMOTE REMOTEDED CONTROL FSNODE WINDOWS JAILED PLAB LOCALROOTFS
	 IXP USESFS SHADOW FSRVTYPE PROJDIR EXPDIR
53

54
	 SIMTRAFGEN SIMHOST ISDELAYNODEPATH JAILHOST DELAYHOST STARGATE
55
	 ISFW FAKEJAILED LINUXJAILED GENVNODE GENVNODETYPE GENVNODEHOST
56
	 SHAREDHOST SUBBOSS STORAGEHOST
57

58
	 CONFDIR LOGDIR TMDELAY TMBRIDGES TMJAILNAME TMSIMRC TMCC TMCCBIN
59
	 TMNICKNAME TMSTARTUPCMD FINDIF
60
	 TMROUTECONFIG TMLINKDELAY TMDELMAP TMTOPOMAP TMLTMAP TMLTPMAP
61
	 TMGATEDCONFIG TMSYNCSERVER TMKEYHASH TMNODEID TMNODEUUID TMEVENTKEY
Ryan Jackson's avatar
Ryan Jackson committed
62
	 TMCREATOR TMSWAPPER TMFWCONFIG TMGENVNODECONFIG
63
	 TMSTORAGEMAP TMDISKINFO TMEXTRAFS
Leigh B Stoller's avatar
Leigh B Stoller committed
64
	 INXENVM INVZVM
65 66 67 68
       );

# Must come after package declaration!
use English;
69
use Errno;
70

71
my $debug = 0;
72

73 74
# The tmcc library.
use libtmcc;
75
use librc;
76 77 78 79 80 81 82

#
# This is the VERSION. We send it through to tmcd so it knows what version
# responses this file is expecting.
#
# BE SURE TO BUMP THIS AS INCOMPATIBILE CHANGES TO TMCD ARE MADE!
#
83 84 85
# IMPORTANT NOTE: if you change the version here, you must also change it
# in clientside/lib/tmcd/tmcd.h!
#
86
sub TMCD_VERSION()	{ 40; };
87 88 89 90 91
libtmcc::configtmcc("version", TMCD_VERSION());

# Control tmcc timeout.
sub libsetup_settimeout($) { libtmcc::configtmcc("timeout", $_[0]); };

92
# Refresh tmcc cache.
93 94
sub libsetup_refresh()	   { libtmcc::tmccgetconfig(); };

95
#
96 97
# For virtual (multiplexed nodes). If defined, tack onto tmcc command.
# and use in pathnames. Used in conjunction with jailed virtual nodes.
98
# I am also using this for subnodes; eventually everything will be subnodes.
99
#
100
my $vnodeid;
101 102
sub libsetup_setvnodeid($)
{
103 104 105 106 107 108 109 110 111 112 113
    my ($vid) = @_;

    if ($vid =~ /^([-\w]+)$/) {
	$vid = $1;
    }
    else {
	die("Bad data in vnodeid: $vid");
    }

    $vnodeid = $vid;
    libtmcc::configtmcc("subnode", $vnodeid);
114 115 116 117 118
}
sub libsetup_getvnodeid()
{
    return $vnodeid;
}
119

120
#
Ryan Jackson's avatar
Ryan Jackson committed
121 122
# True if running inside a jail. Set just below.
#
123 124
my $injail;

125 126 127 128 129 130
#
# True if $injail == TRUE and running on Linux.
# Right now this means vserves on RHL.
#
my $inlinuxjail;

131 132 133 134 135
#
# True if running inside a vm.
#
my $ingenvnode;

136 137
#
# True if running as a fake jail (no jail, just processes).
Ryan Jackson's avatar
Ryan Jackson committed
138
#
139 140
my $nojail;

141 142 143 144 145
#
# True if running in a Plab vserver.
#
my $inplab;

146 147 148 149 150 151
#
# Ditto for IXP, although currently there is no "in" IXP setup; it
# is all done from outside.
#
my $inixp;

152 153 154 155 156 157
#
# Shadow mode. Run the client side against a remote tmcd.
#
my $shadow;
my $SHADOWDIR = "$VARDIR/shadow";

158 159 160 161 162 163 164
#
# Fileserver type.
# Default is "racy NFS" (the historical only choice) until proven otherwise
# (via "mounts" tmcc call).
#
my $fsrvtype = "NFS-RACY";

165 166 167
#
# The role of this pnode
#
168
my $role;
169

170 171 172
# Load up the paths. Its conditionalized to be compatabile with older images.
# Note this file has probably already been loaded by the caller.
BEGIN
173
{
174 175 176 177 178
    if (! -e "/etc/emulab/paths.pm") {
	die("Yikes! Could not require /etc/emulab/paths.pm!\n");
    }
    require "/etc/emulab/paths.pm";
    import emulabpaths;
179
    $SHADOWDIR = "$VARDIR/shadow";
180

181
    # Make sure these exist! They will not exist on a PLAB vserver initially.
182 183
    mkdir("$VARDIR", 0775);
    mkdir("$VARDIR/jails", 0775);
184
    mkdir("$VARDIR/vms", 0775);
185 186 187 188
    mkdir("$VARDIR/db", 0755);
    mkdir("$VARDIR/logs", 0775);
    mkdir("$VARDIR/boot", 0775);
    mkdir("$VARDIR/lock", 0775);
189 190 191 192 193
    mkdir("$SHADOWDIR", 0775);
    mkdir("$SHADOWDIR/db", 0755);
    mkdir("$SHADOWDIR/logs", 0775);
    mkdir("$SHADOWDIR/boot", 0775);
    mkdir("$SHADOWDIR/lock", 0775);
194

195 196 197 198 199
    #
    # Shadow mode allows the client side to run against remote tmcd.
    #
    if (exists($ENV{'SHADOW'})) {
	$shadow = $ENV{'SHADOW'};
200
	my ($server,$idkey) = split(',', $shadow);
201 202 203 204 205 206 207 208 209
	#
	# Need to taint check these to avoid breakage later.
	#
	if ($server =~ /^([-\w\.]+)$/) {
	    $server = $1;
	}
	else {
	    die("Bad data in server: $server");
	}
210 211
	if ($idkey =~ /^([-\w\+\:\.]*)$/) {
	    $idkey = $1;
212 213
	}
	else {
214
	    die("Bad data in urn: $idkey");
215
	}
Ryan Jackson's avatar
Ryan Jackson committed
216

217 218 219
	# The cache needs to go in a difference location.
	libtmcc::configtmcc("cachedir", $SHADOWDIR);
	libtmcc::configtmcc("server", $server);
220
	libtmcc::configtmcc("idkey", $idkey);
221 222
	# No proxy.
	libtmcc::configtmcc("noproxy", 1);
223
    }
224 225 226
    #
    # Determine if running inside a jail. This affects the paths below.
    #
227
    if (-e "$BOOTDIR/jailname") {
228
	open(VN, "$BOOTDIR/jailname");
229
	my $vid = <VN>;
230 231
	close(VN);

232
	libsetup_setvnodeid($vid);
233
	$injail = 1;
234 235 236
	if ($^O eq "linux") {
	    $inlinuxjail = 1;
	}
237
    }
238 239 240 241 242 243 244
    elsif (exists($ENV{'FAKEJAIL'})) {
	# Fake jail.
	libsetup_setvnodeid($ENV{'FAKEJAIL'});
	$nojail = 1;
    }
    elsif (-e "$BOOTDIR/plabname") {
	# Running inside a Plab vserver.
245
	open(VN, "$BOOTDIR/plabname");
246
	my $vid = <VN>;
247 248
	close(VN);

249
	libsetup_setvnodeid($vid);
250 251
	$inplab = 1;
    }
252 253 254 255 256 257 258 259 260
    elsif (-e "$BOOTDIR/vmname") {
	open(VN, "$BOOTDIR/vmname");
	my $vid = <VN>;
	close(VN);

	libsetup_setvnodeid($vid);
	$ingenvnode = 1;

    }
261

262
    $role = "";
Ryan Jackson's avatar
Ryan Jackson committed
263
    # Get our role.
264 265 266 267
    if (-e "$BOOTDIR/role") {
	open(VN, "$BOOTDIR/role");
	$role = <VN>;
	close(VN);
268
	chomp($role);
269
    }
270 271
}

272
#
Ryan Jackson's avatar
Ryan Jackson committed
273
# This "local" library provides the OS dependent part.
274
#
275
use liblocsetup;
276

277 278 279 280
#
# These are the paths of various files and scripts that are part of the
# setup library.
#
281
sub TMCC()		{ "$BINDIR/tmcc"; }
282
sub TMCCBIN()		{ "$BINDIR/tmcc.bin"; }
283
sub FINDIF()		{ "$BINDIR/findif"; }
284
sub TMUSESFS()		{ "$BOOTDIR/usesfs"; }
285 286
sub ISSIMTRAFGENPATH()	{ "$BOOTDIR/simtrafgen"; }
sub ISDELAYNODEPATH()	{ "$BOOTDIR/isdelaynode"; }
287
sub TMTOPOMAP()		{ "$BOOTDIR/topomap";}
Timothy Stack's avatar
 
Timothy Stack committed
288
sub TMLTMAP()		{ "$BOOTDIR/ltmap";}
289
sub TMLTPMAP()		{ "$BOOTDIR/ltpmap";}
290 291 292
sub TMSTORAGEMAP()	{ "$BOOTDIR/storagemap";}
sub TMDISKINFO()	{ "$BOOTDIR/diskinfo";}
sub TMEXTRAFS()		{ "$BOOTDIR/extrafs";}
293

294
#
295
# This path is valid only *outside* the jail when its setup.
Ryan Jackson's avatar
Ryan Jackson committed
296
#
297 298
sub JAILDIR()		{ "$VARDIR/jails/$vnodeid"; }

299 300 301 302
#
# This path is valid only *outside* the vm.  Sucks, but this is the best we
# can do.  Probably the only way to make this vm-specific if necessary is to
# have them create their dir and symlink.
Ryan Jackson's avatar
Ryan Jackson committed
303
#
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
sub GENVNODEDIR()	{ "$VARDIR/vms/$vnodeid"; }

#
# XXX: eventually this needs to come from tmcd, but that's not ready yet.
#
sub GENVNODETYPE() {
    if (-e "$ETCDIR/genvmtype") {
	my $vmtype = `cat $ETCDIR/genvmtype`;
	chomp($vmtype);
	return $vmtype;
    }

    return undef;
}

319 320 321
#
# Also valid outside the jail, this is where we put local project storage.
#
322
sub LOCALROOTFS() {
Ryan Jackson's avatar
Ryan Jackson committed
323
    return "/users/local"
324
	if (REMOTE());
Ryan Jackson's avatar
Ryan Jackson committed
325
    return "$VARDIR/jails/local"
326
	if (JAILED());
Ryan Jackson's avatar
Ryan Jackson committed
327
    return "$VARDIR/vms/local"
328
	if (GENVNODE());
329
    return "/local";
330
}
331

332 333 334
#
# Okay, here is the path mess. There are three environments.
# 1. A local node where everything goes in one place ($VARDIR/boot).
335
# 2. A virtual node inside a jail or a Plab vserver ($VARDIR/boot).
Ryan Jackson's avatar
Ryan Jackson committed
336
# 3. A virtual (or sub) node, from the outside.
337
#
338
# As for #3, whether setting up a old-style (fake) virtual node or a new style
339 340 341
# jailed node, the code that sets it up needs a different per-vnode path.
#
sub CONFDIR() {
342 343
    return "$SHADOWDIR/boot"
	if ($shadow);
344
    return $BOOTDIR
345
	if ($injail || $inplab || $ingenvnode);
Ryan Jackson's avatar
Ryan Jackson committed
346
    return GENVNODEDIR()
347
	if ($vnodeid && GENVNODETYPE());
348 349
    return JAILDIR()
	if ($vnodeid);
350
    return $BOOTDIR;
351
}
352 353
# Cause of fakejails, we want log files in the right place.
sub LOGDIR() {
354 355
    return "$SHADOWDIR/logs"
	if ($shadow);
356
    return $LOGDIR
357
	if ($injail || $inplab || $ingenvnode);
Ryan Jackson's avatar
Ryan Jackson committed
358
    return GENVNODEDIR()
359
	if ($vnodeid && GENVNODETYPE());
Ryan Jackson's avatar
Ryan Jackson committed
360
    return JAILDIR()
361 362 363
	if ($vnodeid);
    return $LOGDIR;
}
364

365 366
#
# The rest of these depend on the environment running in (inside/outside jail).
Ryan Jackson's avatar
Ryan Jackson committed
367
#
368 369
sub TMNICKNAME()	{ CONFDIR() . "/nickname";}
sub TMJAILNAME()	{ CONFDIR() . "/jailname";}
370
sub TMFAKEJAILNAME()	{ CONFDIR() . "/fakejail";}
371
sub TMJAILCONFIG()	{ CONFDIR() . "/jailconfig";}
372
sub TMGENVNODECONFIG()  { CONFDIR() . "/genvnodeconfig";}
373 374
sub TMSTARTUPCMD()	{ CONFDIR() . "/startupcmd";}
sub TMROUTECONFIG()     { CONFDIR() . "/rc.route";}
375
sub TMGATEDCONFIG()     { CONFDIR() . "/gated.conf";}
376
sub TMBRIDGES()		{ CONFDIR() . "/rc.bridges";}
377 378
sub TMDELAY()		{ CONFDIR() . "/rc.delay";}
sub TMLINKDELAY()	{ CONFDIR() . "/rc.linkdelay";}
379
sub TMDELMAP()		{ CONFDIR() . "/delay_mapping";}
380
sub TMSYNCSERVER()	{ CONFDIR() . "/syncserver";}
381
sub TMKEYHASH()		{ CONFDIR() . "/keyhash";}
382
sub TMEVENTKEY()	{ CONFDIR() . "/eventkey";}
383
sub TMNODEID()		{ CONFDIR() . "/nodeid";}
384
sub TMNODEUUID()	{ CONFDIR() . "/nodeuuid";}
385 386
sub TMROLE()		{ CONFDIR() . "/role";}
sub TMSIMRC()		{ CONFDIR() . "/rc.simulator";}
387
sub TMCREATOR()		{ CONFDIR() . "/creator";}
388
sub TMSWAPPER()		{ CONFDIR() . "/swapper";}
389
sub TMFWCONFIG()	{ CONFDIR() . "/rc.fw";}
390 391 392

#
# This is a debugging thing for my home network.
393 394 395 396 397 398 399 400 401 402
#
my $NODE = "";
if (defined($ENV{'TMCCARGS'})) {
    if ($ENV{'TMCCARGS'} =~ /^([-\w\s]*)$/) {
	$NODE .= " $1";
    }
    else {
	die("Tainted TMCCARGS from environment: $ENV{'TMCCARGS'}!\n");
    }
}
403 404

# Locals
405 406 407
my $pid		= "";
my $eid		= "";
my $vname	= "";
408 409 410 411 412 413
my $TIMESTAMPS  = 0;

# Allow override from the environment;
if (defined($ENV{'TIMESTAMPS'})) {
    $TIMESTAMPS = $ENV{'TIMESTAMPS'};
}
414

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
#
# Any reason NOT to hardwire these?
#
sub PROJDIR() {
    my $p = $pid;
    if (!$p) {
	($p, undef, undef) = check_nickname();
	return ""
	    if (!$p);
    }
    return "/proj/$p";
}

sub EXPDIR() {
    my $p = $pid;
    my $e = $eid;
    if (!$p || !$e) {
	($p, $e, undef) = check_nickname();
	return ""
	    if (!$p || !$e);
    }
    return "/proj/$p/exp/$e";
}

439 440 441
# When on the MFS, we do a much smaller set of stuff.
# Cause of the way the packages are loaded (which I do not understand),
# this is computed on the fly instead of once.
442
sub MFS()	{ if (-e "$ETCDIR/ismfs") { return 1; } else { return 0; } }
443

444 445 446
#
# Same for a remote node.
#
447 448
sub REMOTE()	{ if (-e "$ETCDIR/isrem") { return 1; } else { return 0; } }

449 450 451 452 453
#
# Same for a dedicated remote node.
#
sub REMOTEDED()	{ if (-e "$ETCDIR/isremded") { return 1; } else { return 0; } }

454 455 456 457 458
#
# Same for a control node.
#
sub CONTROL()	{ if (-e "$ETCDIR/isctrl") { return 1; } else { return 0; } }

459 460 461 462 463
#
# Same for an FS node.
#
sub FSNODE()	{ if (-e "$ETCDIR/isfs") { return 1; } else { return 0; } }

464 465 466
#
# Same for a Windows (CygWinXP) node.
#
467
# XXX  If you change this, look in libtmcc::tmccgetconfig() as well.
468 469
sub WINDOWS()	{ if (-e "$ETCDIR/iscygwin") { return 1; } else { return 0; } }

Kirk Webb's avatar
 
Kirk Webb committed
470 471 472 473 474
#
# Same for a stargate/garcia node.
#
sub STARGATE()  { if (-e "$ETCDIR/isstargate") { return 1; } else { return 0; } }

475 476 477 478
#
# Are we jailed? See above.
#
sub JAILED()	{ if ($injail) { return $vnodeid; } else { return 0; } }
479
sub FAKEJAILED(){ if ($nojail) { return $vnodeid; } else { return 0; } }
480
sub LINUXJAILED(){ if ($injail && $inlinuxjail) { return $vnodeid; } else { return 0; } }
481

482 483 484 485 486
#
# Are we using the generic vm abstraction for this vnode?  See above.
#
sub GENVNODE()  { if ($ingenvnode) { return $vnodeid; } else { return 0; } }

487 488 489 490 491
#
# Are we on plab?
#
sub PLAB()	{ if ($inplab) { return $vnodeid; } else { return 0; } }

492 493 494 495 496
#
# Are we on an IXP
#
sub IXP()	{ if ($inixp) { return $vnodeid; } else { return 0; } }

497 498 499 500 501
#
# Are we a firewall node
#
sub ISFW()	{ if (-e TMFWCONFIG()) { return 1; } else { return 0; } }

502
#
503
# Are we hosting a simulator or maybe just a NSE based trafgen.
504 505
#
sub SIMHOST()   { if ($role eq "simhost") { return 1; } else { return 0; } }
506
sub SIMTRAFGEN(){ if (-e ISSIMTRAFGENPATH())  { return 1; } else { return 0; } }
507

Ryan Jackson's avatar
Ryan Jackson committed
508 509 510 511 512
#
# Are we a subboss?
#
sub SUBBOSS()   { if ($role eq "subboss") { return 1; } else { return 0; } }

513
# A jail host?
514 515 516 517
sub JAILHOST()     { return (($role eq "virthost" ||
			      $role eq "sharedhost") ? 1 : 0); }
sub GENVNODEHOST() { if ($role eq "virthost") { return 1; } else { return 0; }}
sub SHAREDHOST()   { return ($role eq "sharedhost" ? 1 : 0); }
518
sub STORAGEHOST()  { return ($role eq "storagehost" ? 1 : 0); }
519

520 521 522
# A delay host?  Either a delay node or a node using linkdelays
sub DELAYHOST()	{ if (-e ISDELAYNODEPATH()) { return 1; } else { return 0; } }

523 524 525
# A shadow?
sub SHADOW()	   { return (defined($shadow) ? 1 : 0); }

526
#
527
# Is this node using SFS. Several scripts need to know this.
528
#
529
sub USESFS()	{ if (-e TMUSESFS()) { return 1; } else { return 0; } }
530

531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558
#
# What type of fileserver is this node using.  Choices are:
#
# NFS-RACY	FreeBSD NFS server with mountd race (the default)
# NFS		NFS server
# LOCAL		No shared filesystems
#
# XXX should come from tmcd
#
sub FSRVTYPE() {
    if (-e "$BOOTDIR/fileserver") {
	open(FD, "$BOOTDIR/fileserver");
	$fsrvtype = <FD>;
	close(FD);
	chomp($fsrvtype);
    }
    return $fsrvtype;
}

# XXX fer now hack: comes from rc.mounts
sub setFSRVTYPE($) {
    $fsrvtype = shift;
    if (open(FD, ">$BOOTDIR/fileserver")) {
	print FD "$fsrvtype\n";
	close(FD);
    }
}

Mike Hibler's avatar
Mike Hibler committed
559
#
Leigh B Stoller's avatar
Leigh B Stoller committed
560
# XXX fernow hack so I can readily identify code that is special to VMs
Mike Hibler's avatar
Mike Hibler committed
561 562
#
sub INXENVM()	{ return ($ingenvnode && GENVNODETYPE() eq "xen"); }
Leigh B Stoller's avatar
Leigh B Stoller committed
563
sub INVZVM()	{ return ($ingenvnode && GENVNODETYPE() eq "openvz"); }
Mike Hibler's avatar
Mike Hibler committed
564

565 566 567
#
# Reset to a moderately clean state.
#
568 569
sub cleanup_node ($) {
    my ($scrub) = @_;
Ryan Jackson's avatar
Ryan Jackson committed
570

571
    print STDOUT "Cleaning node; removing configuration files\n";
572
    unlink TMUSESFS, TMROLE, ISSIMTRAFGENPATH, ISDELAYNODEPATH;
573
    unlink TMSTORAGEMAP, TMDISKINFO;
574

575
    #
576 577
    # If scrubbing, also remove the password/group files and DBs so
    # that we revert to base set.
Ryan Jackson's avatar
Ryan Jackson committed
578
    #
579
    if ($scrub) {
580
	unlink TMNICKNAME;
581 582
	# XXX !scrub allows this to be initialized from outside (libvnode)
	unlink TMEXTRAFS;
583 584 585 586
    }
}

#
587 588
# Check node allocation. If the nickname file has been created, use
# that to avoid load on tmcd.
589 590 591 592 593
#
# Returns 0 if node is free. Returns list (pid/eid/vname) if allocated.
#
sub check_status ()
{
594 595 596
    my @tmccresults;

    if (tmcc(TMCCCMD_STATUS, undef, \@tmccresults) < 0) {
597 598
	warn("*** WARNING: Could not get status from server!\n");
	return -1;
599
    }
600 601 602 603
    #
    # This is possible if the boss node does not now about us yet.
    # We want to appear free. Specifically, it could happen on the
    # MFS when trying to bring in brand new nodes. tmcd will not know
Ryan Jackson's avatar
Ryan Jackson committed
604
    # anything about us, and return no info.
605 606 607 608 609
    #
    return 0
	if (! @tmccresults);

    my $status = $tmccresults[0];
610

611
    if ($status =~ /^FREE/) {
612
	unlink TMNICKNAME;
613 614
	return 0;
    }
Ryan Jackson's avatar
Ryan Jackson committed
615

616
    if ($status =~ /ALLOCATED=([-\@\w]*)\/([-\@\w]*) NICKNAME=([-\@\w]*)/) {
617 618 619 620 621 622
	$pid   = $1;
	$eid   = $2;
	$vname = $3;
    }
    else {
	warn "*** WARNING: Error getting reservation status\n";
623
	return -1;
624
    }
Ryan Jackson's avatar
Ryan Jackson committed
625

626 627
    #
    # Stick our nickname in a file in case someone wants it.
628 629
    # Do not overwrite; we want to save the original info until later.
    # See bootsetup; indicates project change!
630
    #
631
    if (! -e TMNICKNAME()) {
632 633
	system("echo '$vname.$eid.$pid' > " . TMNICKNAME());
    }
Ryan Jackson's avatar
Ryan Jackson committed
634

635 636 637 638
    return ($pid, $eid, $vname);
}

#
639 640
# Check cached nickname. Its okay if we have been deallocated and the info
# is stale. The node will notice that later.
Ryan Jackson's avatar
Ryan Jackson committed
641
#
642
sub check_nickname()
643
{
644 645 646
    if (-e TMNICKNAME) {
	my $nickfile = TMNICKNAME;
	my $nickinfo = `cat $nickfile`;
647

648 649 650 651 652 653 654 655 656
	if ($nickinfo =~ /([-\@\w]*)\.([-\@\w]*)\.([-\@\w]*)/) {
	    $vname = $1;
	    $eid   = $2;
	    $pid   = $3;

	    return ($pid, $eid, $vname);
	}
    }
    return check_status();
657 658 659
}

#
660 661
# Do SFS hostid setup. If we have an SFS host key and we can get a hostid
# from the SFS daemon, then send it to TMCD.
Austin Clements's avatar
Austin Clements committed
662
#
663
sub initsfs()
Austin Clements's avatar
Austin Clements committed
664 665 666
{
    my $myhostid;

667 668 669
    # Default to no SFS unless we can determine we have it running.
    unlink TMUSESFS()
	if (-e TMUSESFS());
Ryan Jackson's avatar
Ryan Jackson committed
670

671
    # Do I have a host key?
Austin Clements's avatar
Austin Clements committed
672
    if (! -e "/etc/sfs/sfs_host_key") {
673
	return;
Austin Clements's avatar
Austin Clements committed
674 675 676
    }

    # Give hostid to TMCD
677 678 679 680 681 682
    if (-d "/usr/local/lib/sfs-0.6") {
	$myhostid = `sfskey hostid - 2>/dev/null`;
    }
    else {
	$myhostid = `sfskey hostid -s authserv - 2>/dev/null`;
    }
683
    if (! $?) {
Austin Clements's avatar
Austin Clements committed
684 685 686
	if ( $myhostid =~ /^([-\.\w_]*:[a-z0-9]*)$/ ) {
	    $myhostid = $1;
	    print STDOUT "  Hostid: $myhostid\n";
687
	    tmcc(TMCCCMD_SFSHOSTID, "$myhostid");
Austin Clements's avatar
Austin Clements committed
688
	}
689 690 691
	elsif ( $myhostid =~ /^(@[-\.\w_]*,[a-z0-9]*)$/ ) {
	    $myhostid = $1;
	    print STDOUT "  Hostid: $myhostid\n";
692
	    tmcc(TMCCCMD_SFSHOSTID, "$myhostid");
693
	}
Austin Clements's avatar
Austin Clements committed
694 695
	else {
	    warn "*** WARNING: Invalid hostid\n";
696
	    return;
Austin Clements's avatar
Austin Clements committed
697
	}
698
	system("touch " . TMUSESFS());
Austin Clements's avatar
Austin Clements committed
699 700
    }
    else {
701
	warn "*** WARNING: Could not retrieve this node's SFShostid!\n";
Austin Clements's avatar
Austin Clements committed
702
    }
703 704 705
}

#
Ryan Jackson's avatar
Ryan Jackson committed
706 707
# Get the role of the node and stash it for future libsetup load.
#
708 709 710
sub dorole()
{
    my @tmccresults;
Austin Clements's avatar
Austin Clements committed
711

712 713 714 715 716 717
    if (tmcc(TMCCCMD_ROLE, undef, \@tmccresults) < 0) {
	warn("*** WARNING: Could not get role from server!\n");
	return -1;
    }
    return 0
	if (! @tmccresults);
Ryan Jackson's avatar
Ryan Jackson committed
718

719 720 721 722 723 724 725 726 727 728 729 730 731 732 733
    #
    # There should be just one string. Ignore anything else.
    #
    if ($tmccresults[0] =~ /([\w]*)/) {
	# Storing the value into the global variable
	$role = $1;
    }
    else {
	warn "*** WARNING: Bad role line: $tmccresults[0]";
	return -1;
    }
    system("echo '$role' > " . TMROLE());
    if ($?) {
	warn "*** WARNING: Could not write role to " . TMROLE() . "\n";
    }
Austin Clements's avatar
Austin Clements committed
734 735 736
    return 0;
}

737 738
#
# Get the nodeid
Ryan Jackson's avatar
Ryan Jackson committed
739
#
740 741 742 743 744
sub donodeid()
{
    my $nodeid;
    my @tmccresults;

745 746 747
    # Do this too.
    donodeuuid();

748 749 750 751 752 753
    if (tmcc(TMCCCMD_NODEID, undef, \@tmccresults) < 0) {
	warn("*** WARNING: Could not get nodeid from server!\n");
	return -1;
    }
    return 0
	if (! @tmccresults);
Ryan Jackson's avatar
Ryan Jackson committed
754

755 756 757 758 759 760 761 762 763 764
    #
    # There should be just one string. Ignore anything else.
    #
    if ($tmccresults[0] =~ /([-\w]*)/) {
	$nodeid = $1;
    }
    else {
	warn "*** WARNING: Bad nodeid line: $tmccresults[0]";
	return -1;
    }
Ryan Jackson's avatar
Ryan Jackson committed
765

766 767 768 769 770 771 772
    system("echo '$nodeid' > ". TMNODEID);
    if ($?) {
	warn "*** WARNING: Could not write nodeid to " . TMNODEID() . "\n";
    }
    return 0;
}

773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
#
# Get the nodeuuid
#
sub donodeuuid()
{
    my $nodeuuid;
    my @tmccresults;

    if (tmcc(TMCCCMD_NODEUUID, undef, \@tmccresults) < 0) {
	warn("*** WARNING: Could not get nodeuuid from server!\n");
	return -1;
    }
    return 0
	if (! @tmccresults);

    #
    # There should be just one string. Ignore anything else.
    #
    if ($tmccresults[0] =~ /([-\w]*)/) {
	$nodeuuid = $1;
    }
    else {
	warn "*** WARNING: Bad nodeuuid line: $tmccresults[0]";
	return -1;
    }

    system("echo '$nodeuuid' > ". TMNODEUUID);
    if ($?) {
	warn "*** WARNING: Could not write nodeuuid to " . TMNODEUUID() . "\n";
    }
    return 0;
}

806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051
#
# Get the boot script manifest -- whether scripts are enabled, or hooked, and 
# how and when they or their hooks run!
#
sub getmanifest($;$)
{
    my ($rptr,$nofetch) = @_;
    my @tmccresults;
    my %manifest = ();

    print "Checking manifest...\n";

    if (tmcc(TMCCCMD_MANIFEST, undef, \@tmccresults) < 0) {
	warn("*** WARNING: Could not get manifest from server!\n");
	%$rptr = ();
	return -1;
    }
    if (@tmccresults == 0) {
	%$rptr = ();
	return 0;
    }

    my $servicepat = q(SERVICE NAME=([\w\.\-]+) ENV=(\w+) WHENCE=(\w+));
    $servicepat   .= q( ENABLED=(0|1) HOOKS_ENABLED=(0|1));
    $servicepat   .= q( FATAL=(0|1) BLOBID=([\w\-]*));

    my $hookpat = q(HOOK SERVICE=([\w\.\-]+) ENV=(\w+) WHENCE=(\w+));
    $hookpat   .= q( OP=(\w+) POINT=(\w+));
    $hookpat   .= q( FATAL=(0|1) BLOBID=([\w\-]+));
    $hookpat   .= q( ARGV="([^"]*)");

    my @loadinforesults = ();
    if (tmcc(TMCCCMD_LOADINFO, undef, \@loadinforesults) < 0) {
	warn("*** WARNING: getmanifest could not get loadinfo from server,\n".
	     "             unsure if node is in MFS and reloading, continuing!\n");
    }

    #
    # Are we in a loading environment?  If yes, filter the manifest
    # so that only the service and hook settings that apply to the 
    # loading MFS apply.
    #
    if (@loadinforesults && MFS()) {
	$manifest{'_ENV'} = 'load';
    }
    #
    # Otherwise, if we're not in an MFS, we must be booting!
    # NOTE: we don't do any configuration of the image in the 
    # admin MFS!
    #
    elsif (!MFS()) {
	$manifest{'_ENV'} = 'boot';
    }
    #
    # Otherwise, don't return *anything* -- the admin mfs doesn't do any
    # config of the node.
    #
    else {
	%$rptr = ();
	return 0;
    }

    #
    # Process our results.
    #
    for (my $i = 0; $i < @tmccresults; ++$i) {
	my $line = $tmccresults[$i];
	my %service;

	if ($line =~ /^$servicepat/) {
	    my %service = ( 'ENABLED' => $4,
			    'HOOKS_ENABLED' => $5,
			    'BLOBID' => $7,
			    'WHENCE' => $3,
			    'FATAL' => $6 );
	    #
	    # Filter the service part of the manifest so that only the 
	    # settings that apply here are passed to scripts.
	    #
	    if ($2 eq $manifest{'_ENV'}) {
		# assume that there might be a hook line that applied to this
		# service ahead of the service line!  Other possibility
		# is that there was already a service line for this env...
		# which is a bug -- so just silently stomp it in that case.
		#
		# anyway, just update the results pointer for this service
		foreach my $k (keys(%service)) {
		    $manifest{$1}{$k} = $service{$k};
		}

		if (!exists($manifest{$1}{'_PREHOOKS'})) {
		    $manifest{$1}{'_PREHOOKS'} = [];
		}
		if (!exists($manifest{$1}{'_POSTHOOKS'})) {
		    $manifest{$1}{'_POSTHOOKS'} = [];
		}
	    }
	    # Otherwise just skip this entry -- it doesn't apply to us.
	    else {
		next;
	    }
	}
	elsif ($line =~ /^$hookpat/) {
	    #
	    # Filter the service part of the manifest so that only the 
	    # settings that apply here are passed to scripts.
	    #
	    if ($2 eq $manifest{'_ENV'}) {
		my $hookstr = "_" . uc($5) . "HOOKS";
		if (!exists($manifest{$1}) || !exists($manifest{$1}{$hookstr})) {
		    $manifest{$1}{$hookstr} = [];
		}

		my $hook = { 'BLOBID' => $7,
			     'OP' => $4, 
			     'WHENCE' => $3, 
			     'FATAL' => $6, 
			     'ARGV' => $8 };

		$manifest{$1}{$hookstr}->[@{$manifest{$1}{$hookstr}}] = $hook;
	    }
	    # Otherwise just skip this entry -- it doesn't apply to us.
	    else {
		next;
	    }
	}
	else {
	    warn("*** WARNING: did not recognize manifest line '$line'," . 
		 " continuing!\n");
	}
    }

    my $retval = 0;

    if (!defined($nofetch) || $nofetch != 1) {
	print "Downloading any manifest blobs...\n";
	%$rptr = %manifest;
	$retval = fetchmanifestblobs($rptr,undef,'manifest');
    }

    %$rptr = %manifest;
    return $retval;
}

sub fetchmanifestblobs($;$$)
{
    my ($manifest,$savedir,$basename) = @_;
    if (!defined($savedir)) {
	$savedir = $BLOBDIR;
    }
    if (!defined($basename)) {
	$basename = '';
    }
    my $blobpath = "$savedir/$basename";
    my $retval;
    my $failed = 0;

    foreach my $script (keys(%$manifest)) {
	# first grab the script replacement...
	if (exists($manifest->{$script}{'BLOBID'})
	    && $manifest->{$script}{'BLOBID'} ne '') {
	    my $bpath = $blobpath . "." . $manifest->{$script}{'BLOBID'};
	    $retval = libtmcc::blob::getblob($manifest->{$script}{'BLOBID'},
					     $bpath);
	    if ($retval == -1) {
		print STDERR "ERROR(fetchmanifestblobs): could not fetch " . 
		    $manifest->{$script}{'BLOBID'} . "!\n";
		++$failed;
	    }
	    else {
		$manifest->{$script}{'BLOBPATH'} = $bpath;
		chmod(0755,$bpath);
	    }
	}

	# now do hooks...
	my @hooktypes = ('_PREHOOKS','_POSTHOOKS');
	foreach my $hooktype (@hooktypes) {
	    next 
		if (!exists($manifest->{$script}{$hooktype}));

	    foreach my $hook (@{$manifest->{$script}{$hooktype}}) {
		my $bpath = $blobpath . "." . $hook->{'BLOBID'};
		$retval = libtmcc::blob::getblob($hook->{'BLOBID'},$bpath);
		if ($retval == -1) {
		    print STDERR "ERROR(fetchmanifestblobs): could not fetch " . 
			$hook->{'BLOBID'} . "!\n";
		    ++$failed;
		}
		else {
		    $hook->{'BLOBPATH'} = $bpath;
		    chmod(0755,$bpath);
		}
	    }
	}
		
    }

    return $failed;
}

sub runhooks($$$$)
{
    my ($manifest,$which,$script,$what) = @_;
    my $hookstr = "_".uc($which)."HOOKS";
    my $failed = 0;

    if (exists($manifest->{$script}) 
	# if hooks are enabled because of a service line
	&& ((exists($manifest->{$script}{'HOOKS_ENABLED'}) 
	     && $manifest->{$script}{'HOOKS_ENABLED'} == 1)
	    # or if there was no service line, in which case hooks are
	    # enabled by default
	    || !exists($manifest->{$script}{'HOOKS_ENABLED'}))
	&& exists($manifest->{$script}{$hookstr})) {
	print "  Running $script $which hooks\n"
	    if ($debug);

	for (my $i = 0; $i < @{$manifest->{$script}{$hookstr}}; ++$i) {
	    my $hook = $manifest->{$script}{$hookstr}->[$i];
	    my $blobid = $hook->{'BLOBID'};
	    my $argv = $hook->{'ARGV'};
	    my $hookrunfile = "$VARDIR/db/$script.${which}hook.$blobid.run";

	    # if the path doesn't exist, probably we failed to fetch the blob
	    if (!exists($hook->{'BLOBPATH'})) {
		++$failed;
		if ($hook->{'FATAL'}) {
		    fatal("Failed running $script $which hook $blobid (no blobpath!)");
		}
		else {
		    warn("  $script $which hook $blobid failed! (no blobpath!)");
		}
		next;
	    }

	    my $blobpath = $hook->{'BLOBPATH'};

	    # Only run the hook if its operation matches the operation we're
	    # doing (boot,shutdown,reconfig,reset)
	    if ($hook->{'OP'} ne $what) {
		next;
	    }

	    # If this is a first-only hook, skip if we've already done it!
	    if ($hook->{'WHENCE'} eq 'first' && -e $hookrunfile) {
1052 1053
		print "  Not running $which hook $blobid (first config only)\n" 
		    if ($debug);
1054 1055 1056
		next;
	    }

1057
	    print "  Running $script $which hook $blobid\n";
1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107

	    # NOTE: the last arg is always $what (boot,shutdown,reconfig,reset)
	    system("$blobpath $argv $what");
	    if ($?) {
		++$failed;
		if ($hook->{'FATAL'} == 1) {
		    fatal("Failed running $script $which hook $blobid");
		}
		else {
		    warn("  $script $which hook $blobid failed! ($?)");
		}
		# Don't write the hook run file if the hook failed!
		next;
	    }

	    open(FD,">$hookrunfile")
		or warn("open($hookrunfile): $!");
	    close(FD);
	}
    }

    return $failed;
}

sub runbootscript($$$$;@)
{
    my ($manifest,$path,$script,$what,@args) = @_;
    my $failed = 0;
    my $runfile = "$VARDIR/db/$script.run";
    # do we have a manifest entry for this script, and is it more than
    # just hooks!
    my $havemanifest = 0;
    if (exists($manifest->{$script}) 
	&& exists($manifest->{$script}{'ENABLED'})) {
	$havemanifest = 1;
    }

    #
    # If the script does not exist, don't run it or any hooks specified for it!
    # If we don't have a path, it's a "virtual" script like TBSETUP or ISUP, so
    # don't skip the hooks.  Only skip the script.
    #
    return 0
	if (defined($path) && ! -x "$path/$script");

    #
    # Don't do anything if the script or its hooks are disabled!
    #
    if ($havemanifest && $manifest->{$script}{'ENABLED'} != 1
	&& $manifest->{$script}{'HOOKS_ENABLED'} != 1) {
1108
	print "Not running $script or hooks (disabled)\n";
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
	return 0;
    }

    TBDebugTimeStamp("Executing $script");

    #
    # Handle any pre hooks
    #
    $failed += runhooks($manifest,'pre',$script,$what);

    #
    # Handle the script itself -- if there is a path defined.  If $path is
    # undef, it means that we don't really have a script to run here -- we
    # just want to run pre and post hooks around a bit of code -- like in 
    # rc.bootsetup where we tell tmcd we're in TBSETUP or ISUP.
    #
    if (defined($path) && (!$havemanifest
			   || $manifest->{$script}{'ENABLED'} == 1)) {
	# If this is a first-only script, skip if we've already done it!
	if ($havemanifest && $manifest->{$script}{'WHENCE'} eq 'first' 
	    && -e $runfile) {
	    print "  Not running $script (first config only)\n";
	    return 0;
	}
	
	# If there are no args, we pass $what as the single arg!
	# XXX actually pass user defined args!
	my $argv = "";
	if (@args) {
	    $argv .= " " . join(' ',@args);
	}
	else {
	    $argv .= " $what";
	}
	if ($havemanifest && $manifest->{$script}{'BLOBID'} ne '') {
	    my $blobpath = $manifest->{$script}{'BLOBPATH'};
1145
	    print "  Running $blobpath (instead of $path/$script)\n";
1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159
	    system("$blobpath $argv");
	}
	else {
	    print "  Running $path/$script\n"
		if ($debug);
	    system("$path/$script $argv");
	}
	if ($?) {
	    ++$failed;
	    if (exists($manifest->{$script}) 
		&& exists($manifest->{$script}{'FATAL'}) 
		&& $manifest->{$script}{'FATAL'} == 1) {
		fatal("  Failed running $script ($?)!");
	    }
1160 1161 1162 1163
	    # XXX failure of the firewall script is always fatal
	    elsif ($script eq "rc.firewall") {
		fatal("  Failed running $script ($?)!");
	    }