Commit 99346dc0 authored by Leigh Stoller's avatar Leigh Stoller

Another round of widearea node hacking for CMU. These changes add

widearea reloading support.

* New slot in the images table to store an access key which remote
  sites must provide in order to download an image (via https).

* tmcd returns a different kind of ADDRESS field from doloadinfo.
  Instead of the multicast stuff, return a URL that points to boss'
  web server. The URL is of the form:

   https://www.myemulab.net/spewimage.php?imageid=10013&access_key=abcdef

  which as you can see is fully specified; the client does not need
  to know anything else.

* New webpage and backend scripts appropriately called "spewimage"
  which also includes support for the http HEAD request (from wget) to
  avoid downloading images that are already on the node. I just
  learned about this HEAD request stuff today ... but otherwise these
  operate as expected, spewing the image if the access key is provided.

* Changes to rc.frisbee to deal with remote loading. In addition to
  URL support, I also added support for simple paths, the intent being
  that we will probably distribute images offline (say, at night) so
  that when a node reboots it doesn't actually have to wait 60 minutes
  for an image to download. I have not added any server side support
  for this yet though. Maybe later this week.

* Other bits and pieces and fixes to make this work.
parent 6f998011
......@@ -5,6 +5,7 @@
#undef VERSION
#undef TBROOT
#undef TBBASE
#undef TBDBNAME
#undef TBADMINGROUP
#undef IPBASE
......
......@@ -1854,6 +1854,9 @@ fi
if test -z "$TBWWW"; then
TBWWW="<$TBBASE/>"
fi
cat >> confdefs.h <<EOF
#define TBBASE "$TBBASE"
EOF
if test -z "$THISHOMEBASE"; then
THISHOMEBASE="Emulab.Net"
fi
......
......@@ -500,6 +500,8 @@ fi
if test -z "$TBWWW"; then
TBWWW="<$TBBASE/>"
fi
AC_DEFINE_UNQUOTED(TBBASE, "$TBBASE")
if test -z "$THISHOMEBASE"; then
THISHOMEBASE="Emulab.Net"
fi
......
......@@ -139,6 +139,7 @@ sub ezid($) { return field($_[0], "ezid"); }
sub shared($) { return field($_[0], "shared"); }
sub global($) { return field($_[0], "global"); }
sub updated($) { return field($_[0], "updated"); }
sub access_key($) { return field($_[0], "access_key"); }
#
# Refresh a class instance by reloading from the DB.
......
......@@ -451,6 +451,9 @@ sub issimnode($) { return NodeTypeInfo($_[0])->issimnode(); }
sub default_osid($;$) {
return NodeTypeInfo($_[0])->default_osid($_[1]);
}
sub default_imageid($;$) {
return NodeTypeInfo($_[0])->default_imageid($_[1]);
}
sub imageable($;$) {
return NodeTypeInfo($_[0])->imageable($_[1]);
}
......
......@@ -248,6 +248,7 @@ sub GetAttribute($$;$$)
}
# Shortcuts for typical attributes.
sub default_osid($;$) {return GetAttribute($_[0], "default_osid", $_[1]); }
sub default_imageid($;$){return GetAttribute($_[0], "default_imageid",$_[1]); }
sub delay_osid($;$) {return GetAttribute($_[0], "delay_osid", $_[1]); }
sub jail_osid($;$) {return GetAttribute($_[0], "jail_osid", $_[1]); }
sub imageable($;$) {return GetAttribute($_[0], "imageable", $_[1]); }
......
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2000-2006 University of Utah and the Flux Group.
* Copyright (c) 2000-2007 University of Utah and the Flux Group.
* All rights reserved.
*/
......@@ -22,6 +22,7 @@
#define TBDB_FLEN_PRIVKEY 64
#define TBDB_FLEN_SFSHOSTID 128
#define TBDB_FLEN_RPMS 4096
#define TBDB_FLEN_TINYTEXT (256 + 1)
/*
* Event system stuff.
......
......@@ -1311,6 +1311,7 @@ CREATE TABLE `images` (
`global` tinyint(4) NOT NULL default '0',
`mbr_version` tinyint(4) NOT NULL default '1',
`updated` datetime default NULL,
`access_key` varchar(64) default NULL,
PRIMARY KEY (`imageid`),
UNIQUE KEY `pid` (`pid`,`imagename`),
KEY `gid` (`gid`),
......
......@@ -4164,3 +4164,8 @@ last_net_act,last_cpu_act,last_ext_act);
add KEY `IP` (`IP`),
add KEY `privkey` (`privkey`);
4.132: Add an access key to images table for distribution to
widearea nodes.
alter table images add `access_key` varchar(64) default NULL;
......@@ -204,11 +204,16 @@ sub osload ($$) {
foreach my $node (@nodes) {
# All nodes start out as being successful; altered later as needed.
$result->{$node} = 0;
my $nodeobject = Node->Lookup($node);
if (!defined($nodeobject)) {
tberror "$node: Could not map to object!";
goto failednode;
}
# Get default imageid for this node.
my $default_imageid;
if (! DefaultImageID($node, \$default_imageid) ||
! defined($default_imageid)) {
my $default_imageid = $nodeobject->default_imageid();
if (! defined($default_imageid)) {
tberror "$node: No default imageid defined!";
goto failednode;
}
......@@ -244,6 +249,7 @@ sub osload ($$) {
my $defosid = $rowref->{'default_osid'};
my $maxwait = $rowref->{'maxloadwait'};
my $imagepid = $rowref->{'pid'};
my $access_key = $rowref->{'access_key'};
my $osinfo = OSinfo->Lookup($defosid);
if (!defined($osinfo)) {
......@@ -320,7 +326,8 @@ sub osload ($$) {
# become an entry in node_capabilities or something like that in the
# future - that would be cleaner)
#
my ($type, $class) = TBNodeType($node);
my $type = $nodeobject->type();
my $class = $nodeobject->class();
my $reload_mode;
my $reload_func;
my $reboot_required;
......@@ -333,6 +340,18 @@ sub osload ($$) {
$reload_mode = "Frisbee";
$reload_func = \&SetupReloadFrisbee;
$reboot_required = !$noreboot; # Reboot unless $noreboot flag set
# This is passed along so that remote node can request the file.
# Make sure the image object has an access key defined.
if ($nodeobject->isremotenode() !defined($access_key)) {
$access_key = TBGenSecretKey();
$rowref->{'access_key'} = $access_key;
if ($image->Update({'access_key' => $access_key}) != 0) {
tberror "$node: Could not initialize image access key";
goto failednode;
}
}
}
#
......@@ -340,19 +359,20 @@ sub osload ($$) {
# later, we'll know how to handle it
#
$reload_info{$node} = {
'node' => $node,
'mode' => $reload_mode,
'func' => $reload_func,
'imageid' => $imageid,
'osid' => $defosid,
'reboot' => $reboot_required,
'zerofree'=> $zerofree,
'maxwait' => $maxwait
'maxwait' => $maxwait,
};
print "Setting up reload for $node (mode: $reload_mode)\n";
if (!$TESTMODE) {
if (&$reload_func($node, $imageid, $defosid, $zerofree) < 0) {
if (&$reload_func($reload_info{$node}) < 0) {
tberror("$node: Could not set up reload. Skipping.");
goto failednode;
}
......@@ -470,8 +490,7 @@ sub osload ($$) {
my $reload_info = $reload_info{$node};
# Possible race with reboot?
if (&{$reload_info->{'func'}}($node, $reload_info->{'imageid'},
$reload_info->{'osid'}, $reload_info->{'zerofree'}) < 0) {
if (&{$reload_info->{'func'}}($reload_info) < 0) {
tberror("$node: Could not set up reload. Skipping.");
$result->{$node} = -1;
$failures++;
......@@ -722,10 +741,13 @@ sub WaitTillReloadDone($$$@)
}
# Setup a reload.
sub SetupReloadFrisbee($$$$)
sub SetupReloadFrisbee($)
{
my ($node, $imageid, $osid_notused, $zerofree) = @_;
my $osid = TBNodeDiskloadOSID($node);
my $reload_info = $_[0];
my $node = $reload_info->{'node'};
my $imageid = $reload_info->{'imageid'};
my $zerofree = $reload_info->{'zerofree'};
my $osid = TBNodeDiskloadOSID($node);
#
# Put it in the current_reloads table so that nodes can find out which
......@@ -756,9 +778,12 @@ sub SetupReloadFrisbee($$$$)
# this differs from a Frisbee reload in one key way - it does the reload
# right here in this code, rather than setting up a reload for later.
#
sub SetupReloadUISP($$$$)
sub SetupReloadUISP($)
{
my ($node, $imageid, $osid, $zerofree_unused) = @_;
my $reload_info = $_[0];
my $node = $reload_info->{'node'};
my $imageid = $reload_info->{'imageid'};
my $osid = $reload_info->{'osid'};
#
# Get the path to the image
......
......@@ -201,6 +201,33 @@ sub doboot()
fatal("Could not determine the name of the boss server!");
}
#
# We need a hostname that resolves to something. If we get here and
# there is no hostname, set it.
#
if (REMOTE()) {
my $curname = `hostname -s`;
if (! ($curname =~ /.+/)) {
my ($domain) = ($bossname =~ /^[^\.]+\.(.*)$/);
my @tmccresults;
if (tmcc(TMCCCMD_NODEID, undef, \@tmccresults) < 0) {
fatal("Could not get nodeid from tmccd!");
}
my $nodeid = $tmccresults[0];
chomp($nodeid);
print STDERR
"Hostname not set. Setting it to ${nodeid}.${domain}\n";
if (system("hostname", "${nodeid}.${domain}")) {
fatal("sethostname failed!");
}
# Delay a moment to let things settle down.
sleep(5);
}
}
#
# Check for an os dependent initialization script.
#
......
......@@ -97,7 +97,7 @@ if ($action eq "boot" || $action eq "reconfig") {
# We want to save all of the output off, but also dup it to the console.
#
open(LOG, "> $LOGFILE") or
BootFatal(-1, "Could not open $LOGFILE!");
BootNotify(-1, "Could not open $LOGFILE!");
LOG->autoflush(1);
#
......@@ -319,9 +319,9 @@ sub doboot()
system("reboot");
sleep(10000);
}
elsif ($bootwhat =~ /^mfs:[-\w\.]*:(.*)$/) {
print("Bootinfo says to boot MFS $1!\n");
my $mfs = basename($1);
elsif ($bootwhat =~ /^mfs:([-\w\.]*:)?(.*)$/) {
print("Bootinfo says to boot MFS $2!\n");
my $mfs = basename($2);
#
# We know about a couple of different MFSs, but thats it!
......@@ -346,8 +346,9 @@ sub doboot()
sleep(10);
goto bootinfo;
}
# Failed, drop into MFS for easy debug and reboot later.
goto mfs;
fatal("Failed to reload the disk. Sending data to Emulab.");
# Drop into the shell to debug.
return 1;
}
elsif ($mfs =~ /^freebsd.*$/) {
goto mfs;
......
......@@ -22,6 +22,13 @@ if [ -r /etc/emulab/paths.sh ]; then
else
BINDIR=/etc/testbed
BOOTDIR=/etc/testbed
ETCDIR=/etc/testbed
fi
# Behave a little different on widearea nodes.
isrem=0
if [ -e $ETCDIR/isrem ]; then
isrem=1
fi
#
......@@ -129,17 +136,17 @@ if [ -x /usr/sbin/ntpdate ]; then
/usr/sbin/ntpdate -b $BOSSIP >/dev/null 2>&1
fi
ADDRESS=`echo $LOADINFO | awk -F= '{ printf $2 }' | awk -F' ' '{ print $1 }'`
PARTITION=`echo $LOADINFO | awk -F= '{ printf $3 }' | awk -F' ' '{ print $1 }'`
ADDRESS=`echo \"$LOADINFO\" | sed -e 's/.*ADDR=\([^[:space:]]*\).*/\1/'`
PARTITION=`echo \"$LOADINFO\" | sed -e 's/.*PART=\([^[:space:]]*\).*/\1/'`
PARTITION=${PARTITION:-'0'}
PARTOS=`echo $LOADINFO | awk -F= '{ printf $4 }' | awk -F' ' '{ print $1 }'`
DISK=`echo $LOADINFO | awk -F= '{ printf $5 }' | awk -F' ' '{ print $1 }'`
PARTOS=`echo \"$LOADINFO\" | sed -e 's/.*PARTOS=\([^[:space:]]*\).*/\1/'`
DISK=`echo \"$LOADINFO\" | sed -e 's/.*DISK=\([^[:space:]]*\).*/\1/'`
DISK=${DISK:-'ad0'}
ZFILL=`echo $LOADINFO | awk -F= '{ printf $6 }' | awk -F' ' '{ print $1 }'`
ZFILL=`echo \"$LOADINFO\" | sed -e 's/.*ZFILL=\([^[:space:]]*\).*/\1/'`
ZFILL=${ZFILL:-'0'}
ACPI=`echo $LOADINFO | awk -F= '{ printf $7 }' | awk -F' ' '{ print $1 }'`
ACPI=`echo \"$LOADINFO\" | sed -e 's/.*ACPI=\([^[:space:]]*\).*/\1/'`
ACPI=${ACPI:-'unknown'}
MBR=`echo $LOADINFO | awk -F= '{ printf $8 }' | awk -F' ' '{ print $1 }'`
MBR=`echo \"$LOADINFO\" | sed -e 's/.*MBRVERS=\([^[:space:]]*\).*/\1/'`
MBR=${MBR:-'1'}
if [ "$PARTITION" != "0" ]; then
......@@ -205,14 +212,56 @@ if [ -x /dev/MAKEDEV -a ! -e /dev/$DISK ]; then
fi
if [ x"$ADDRESS" != x ]; then
PORT=`echo $ADDRESS | awk -F: '{ printf $2 }'`
MCAST=`echo $ADDRESS | awk -F: '{ printf $1 }'`
if [ -e $BOOTDIR/myip ]; then
MCASTIF="-i `cat $BOOTDIR/myip`"
isurl=`echo \"$ADDRESS\" | grep http -`
ispath=`echo \"$ADDRESS\" | egrep \"^/\" -`
if [ x"$isurl" != x ]; then
echo "Need to download $ADDRESS"
isurl=1
if [ ! -d /images ]; then
echo "Need to create or mount /images directory!"
exit 1
fi
#
# This needs a lot more work ...
#
imagefile=`echo ${ADDRESS} | sed -e 's,^http[s]*://[^/]*/,,'`
imagefile="/images/$imagefile"
wget -nv -N -P /images "${ADDRESS}"
wstat=$?
case $wstat in
0)
echo "wget succeeded getting the image"
;;
*)
echo "wget failed, status $wstat"
exit 1
;;
esac
elif [ x"$ispath" != x ]; then
ispath=1
if [ ! -e $ADDRESS ]; then
echo "$ADDRESS does not exist!"
exit 1
fi
imagefile="$ADDRESS"
else
PORT=`echo $ADDRESS | awk -F: '{ printf $2 }'`
MCAST=`echo $ADDRESS | awk -F: '{ printf $1 }'`
if [ -e $BOOTDIR/myip ]; then
MCASTIF="-i `cat $BOOTDIR/myip`"
else
MCASTIF=""
fi
MCASTADDR="-m $MCAST -p $PORT"
isurl=0
ispath=0
fi
MCASTADDR="-m $MCAST -p $PORT"
#
# ZFILL==1: use frisbee
# ZFILL==2: separate disk-wipe pass (not yet implemented)
......@@ -258,15 +307,20 @@ if [ x"$ADDRESS" != x ]; then
# a reasonable assumption in general, it mostly works in our
# environment and at least won't hurt anything if not true.
#
if [ x"$ZFILL" = x -a "$PARTITION" = "0" ]; then
if [ $isrem -eq 0 -a x"$ZFILL" = x -a "$PARTITION" = "0" ]; then
zapsuperblocks $DISK
fi
echo "Running $BINDIR/frisbee $LOADIP $MEMARGS $ZFILL $SLICE $MCASTIF $MCASTADDR /dev/$DISK at `date`"
$BINDIR/tmcc state RELOADING
$BINDIR/tmcc state RELOADING
$BINDIR/frisbee $LOADIP $MEMARGS $ZFILL $SLICE $MCASTIF $MCASTADDR /dev/$DISK
fstat=$?
if [ x"$imagefile" != x ]; then
echo "Running /usr/local/bin/imageunzip -o -O -W 32 $SLICE $ZFILL $imagefile /dev/$DISK"
/usr/local/bin/imageunzip -o -O -W 32 $SLICE $ZFILL $imagefile /dev/$DISK
else
echo "Running $BINDIR/frisbee $LOADIP $MEMARGS $ZFILL $SLICE $MCASTIF $MCASTADDR /dev/$DISK at `date`"
$BINDIR/frisbee $LOADIP $MEMARGS $ZFILL $SLICE $MCASTIF $MCASTADDR /dev/$DISK
fi
fstat=$?
#
# Turn the cache back off if we turned it on.
......@@ -304,7 +358,11 @@ if [ x"$ADDRESS" != x ]; then
if [ $reboot -eq 1 ]; then
$BINDIR/tmcc state RELOADDONEV2
echo "Waiting for server to reboot us ..."
sleep 240
if [ $isrem -eq 1 ]; then
sleep 30
else
sleep 240
fi
echo "No response from server, rebooting myself ..."
/sbin/reboot
sleep 100
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# Copyright (c) 2000-2004, 2007 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
......@@ -36,6 +36,12 @@ $| = 1;
use libsetup;
use libtmcc;
# Do not do this on remote nodes. Let their DHCP server or static hostname
# take precedence. We will check to make sure there is a hostname later
# in the bootsetup.
exit(0)
if (REMOTE());
#
# Check to see if hostname actually set yet. The dhclient exit hook gets
# invoked no matter what, and typically the first or second DHCP fails,
......
......@@ -182,7 +182,7 @@ typedef struct {
char keyhash[TBDB_FLEN_PRIVKEY];
char eventkey[TBDB_FLEN_PRIVKEY];
char sfshostid[TBDB_FLEN_SFSHOSTID];
char testdb[256];
char testdb[TBDB_FLEN_TINYTEXT];
} tmcdreq_t;
static int iptonodeid(struct in_addr, tmcdreq_t *);
static int checkdbredirect(tmcdreq_t *);
......@@ -3619,18 +3619,19 @@ COMMAND_PROTOTYPE(doloadinfo)
MYSQL_ROW row;
char buf[MYBUFSIZE];
char *bufp = buf, *ebufp = &buf[sizeof(buf)];
char *disktype, *useacpi;
char *disktype, *useacpi, address[MYBUFSIZE];
int disknum, zfill, mbrvers;
/*
* Get the address the node should contact to load its image
*/
res = mydb_query("select load_address,loadpart,OS,frisbee_pid,mustwipe,mbr_version"
res = mydb_query("select load_address,loadpart,OS,frisbee_pid,"
" mustwipe,mbr_version,access_key,imageid"
" from current_reloads as r "
"left join images as i on i.imageid = r.image_id "
"left join os_info as o on i.default_osid = o.osid "
"where node_id='%s'",
6, reqp->nodeid);
8, reqp->nodeid);
if (!res) {
error("doloadinfo: %s: DB Error getting loading address!\n",
......@@ -3642,30 +3643,50 @@ COMMAND_PROTOTYPE(doloadinfo)
mysql_free_result(res);
return 0;
}
row = mysql_fetch_row(res);
/*
* Simple text string.
* Remote nodes get a URL for the address.
*/
row = mysql_fetch_row(res);
if (! row[0] || !row[0][0]) {
mysql_free_result(res);
return 0;
if (!reqp->islocal) {
if (!row[6] || !row[6][0]) {
error("doloadinfo: %s: "
"No access key associated with imageid %s\n",
reqp->nodeid, row[7]);
mysql_free_result(res);
return 1;
}
OUTPUT(address, sizeof(address),
"%s/spewimage.php?imageid=%s&access_key=%s",
TBBASE, row[7], row[6]);
}
else {
/*
* Simple text string.
*/
if (! row[0] || !row[0][0]) {
mysql_free_result(res);
return 0;
}
strcpy(address, row[0]);
/*
* Sanity check
*/
if (!row[3] || !row[3][0]) {
error("doloadinfo: %s: No pid associated with address %s\n",
reqp->nodeid, row[0]);
return 1;
/*
* Sanity check
*/
if (!row[3] || !row[3][0]) {
error("doloadinfo: %s: "
"No pid associated with address %s\n",
reqp->nodeid, row[0]);
mysql_free_result(res);
return 1;
}
}
bufp += OUTPUT(bufp, ebufp - bufp,
"ADDR=%s PART=%s PARTOS=%s", row[0], row[1], row[2]);
"ADDR=%s PART=%s PARTOS=%s", address, row[1], row[2]);
/*
* Remember zero-fill free space, mbr version fields
* Remember zero-fill free space, mbr version fields, and access_key
*/
zfill = 0;
if (row[4] && row[4][0])
......@@ -3674,8 +3695,6 @@ COMMAND_PROTOTYPE(doloadinfo)
if (row[5] && row[5][0])
mbrvers = atoi(row[5]);
mysql_free_result(res);
/*
* Get disk type and number
*/
......@@ -3717,8 +3736,9 @@ COMMAND_PROTOTYPE(doloadinfo)
nrows--;
}
}
OUTPUT(bufp, ebufp - bufp, " DISK=%s%d ZFILL=%d ACPI=%s MBRVERS=%d\n",
disktype, disknum, zfill, useacpi, mbrvers);
bufp += OUTPUT(bufp, ebufp - bufp,
" DISK=%s%d ZFILL=%d ACPI=%s MBRVERS=%d\n",
disktype, disknum, zfill, useacpi, mbrvers);
mysql_free_result(res);
client_writeback(sock, buf, strlen(buf), tcp);
......@@ -7273,8 +7293,10 @@ COMMAND_PROTOTYPE(dobootwhat)
char buf[MYBUFSIZE];
char *bufp = buf, *ebufp = &buf[sizeof(buf)];
if (query_bootinfo_db(reqp->client,
BIVERSION_CURRENT, boot_whatp)) {
boot_info.opcode = BIOPCODE_BOOTWHAT_REQUEST;
boot_info.version = BIVERSION_CURRENT;
if (bootinfo(reqp->client, &boot_info, (void *) reqp)) {
OUTPUT(buf, sizeof(buf), "STATUS=failed\n");
}
else {
......
......@@ -23,10 +23,10 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
opsreboot deletenode node_statewait grabwebcams \
grabswitchconfig backupswitches cvsinit checkquota \
spewconlog opsdb_control newnode suchown archive_list \
wanodecheckin wanodecreate
wanodecheckin wanodecreate spewimage
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin
webwanodecheckin webspewimage
WEB_BIN_SCRIPTS = webcreate_image websetdest weblinkmon_ctl webspewevents \
webdelay_config
LIBEXEC_SCRIPTS = spewleds webcopy spewsource webcvsweb xlogin webviewvc \
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
use POSIX qw(mktime strftime);
#
# Spew a tar/rpm file to stdout.
#
# The script is setuid and run from the webserver.
#
sub usage()
{
print STDERR
"Usage: spewimage [-h] [-t timestamp] -k access_key <imageid>\n".
"Spew an image file to a (widearea) node.\n";
exit(-1);
}
my $optlist = "t:k:h";
my $debug = 0;
my $headonly = 0;
my $access_key;
my $timestamp; # GM Time.
#
# Exit codes are important; they tell the web page what has happened so
# it can say something useful to the user. Fatal errors are mostly done
# with die(), but expected errors use this routine. At some point we will
# use the DB to communicate the actual error.
#
# $status < 0 - Fatal error. Something went wrong we did not expect.
# $status = 0 - Proceeding.
# $status > 0 - Expected error. No such file, not modified, etc.
# 1. File could not be verified.
# 2. File has not changed since timestamp.
# 3. File could not be opened for reading.
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $TBLOGS = "@TBLOGSEMAIL@";
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use Image;
# Locals
my $bytelen = 0;
# Protos
sub SpewImage();
sub VerifyImage();
sub fatal($);
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Turn off line buffering on output
$| = 1;
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"h"})) {
$headonly = 1;
}
if (defined($options{"k"})) {
$access_key = $options{"k"};
if ($access_key =~ /^([\w]+)$/) {
$access_key = $1;
}
else {
die("*** Bad data in access_key: $access_key\n");
}
}
if (defined($options{"t"})) {
$timestamp = $options{"t"};
if ($timestamp =~ /^([\d]+)$/) {
$timestamp = $1;
}
else {
die("*** Bad data in timestamp: $timestamp\n");
}
}
if (@ARGV != 1 || !defined($access_key)) {
usage();
}
my $imageid = $ARGV[0];
my $image = Image->Lookup($ARGV[0]);
if (!defined($image)) {
die("*** $0:\n".
" $imageid does not exist!\n");
}
if (my $retval = VerifyImage()) {
exit($retval);
}
exit(3)
if (SpewImage() != 0);
exit(0);
#
# Spew out a file.
#
sub SpewImage()
{
my $file = $image->path();
open(FD, "< $file")
or fatal("Could not open $file!\n");
#
# Deal with NFS read failures
#
my $foffset = 0;
my $retries = 5;
my $buf;
while ($bytelen) {
my $rlen = sysread(FD, $buf, 8192);
if (! defined($rlen)) {
#
# Retry a few times on error to avoid the
# changing-exports-file server problem.
#
if ($retries > 0 && sysseek(FD, $foffset, 0)) {
$retries--;
sleep(1);
next;
}
fatal("Error reading $file: $!");
}
if ($rlen == 0) {
last;
}
if (! syswrite(STDOUT, $buf, $rlen)) {
fatal("Error writing file to stdout: $!");
}