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

#
# Update the system with new accounts/mounts/etc. Use -i for immediate
# (no waiting if it cannot get the lock).
#
sub usage()
{
    print "Usage: update [-i] [-r] [-l] [-t]\n";
    print "Options:\n";
    print "-i    - Immediate mode; quit if lock already taken\n";
    print "-r    - Reconfig; all testbed configs updated and restarted\n";
    print "-l    - Update only mounts and accounts (default)\n";
    print "-t    - Update tarballs and rpms\n";
    exit(1);
}
my $optlist   = "iltr";
my $batchmode = 1;
my $reconfig  = 0;
my $accounts  = 0;
my $tarballs  = 0;
my $immediate = 0;

# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }

#
# Load the OS independent support library. It will load the OS dependent
# library and initialize itself. 
# 
use libsetup;
use libtestbed qw(TBBackGround);

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

#
# Must either be setuid root (widearea), or it must run as root.
# 
if ($UID && $EUID) {
    if ($UID) {
	die("*** $0:\n".
	    "    Must be run as root!\n");
    }
    else {
	die("*** $0:\n".
	    "    Must be setuid root! Maybe not installed properly?\n");
    }
}

#
# If not invoked as real root, then must be invoked as emulabman.
#
if ($UID) {
    my ($pwname) = getpwuid($UID) or
	die("*** $0:\n".
	    "    $UID is not in the password file!\n");

    if ($pwname ne "emulabman") {
	die("*** $0:\n".
	    "    You do not have permission to run this script!\n");
    }

    #
    # But must run as real root!
    #
    $UID = 0;
}

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"i"})) {
    $batchmode = 0;
    $immediate = 1;
}
if (defined($options{"r"})) {
    $reconfig = 1;
}
if (defined($options{"l"})) {
    $accounts = 1;
}
if (defined($options{"t"})) {
    $tarballs = 1;
}
if (@ARGV) {
    usage();
}
# Backwards compat; no options means mounts and accounts.
if (!$accounts && !$tarballs && !$reconfig) {
    $accounts = 1;
}

#
# Local config.
#
my $lockfile  = "$LOCKDIR/node_update_lockfile";

#
# Reuse the same log file so we can track errors.
#
my $logname   = "$LOGDIR/account_update.debug";

#
# Put this into the background so that the ssh can return immediately.
# 
if ($batchmode && TBBackGround($logname)) {
    #
    # Parent exits normally
    #
    exit(0);
}

if ($batchmode) {
    my $date = POSIX::strftime("20%y/%m/%d %H:%M:%S", localtime());

    print "------------------------------------------------------------\n";
    print "$date\n";
    print "------------------------------------------------------------\n";
}

#
# We do not want multiple invocations of this script running concurrently!
# Use a lock file to serialze.
#
open(LOCK, ">>$lockfile") ||
    die("Could not open $lockfile\n");

my $count = 0;
while (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
    if ($immediate) {
	print "Another update in progress. Exiting ...\n";
	close(LOCK);
	exit(1);
    }
    print "Another update in progress. Waiting a moment ...\n";
   
    if ($count++ > 20) {
	die("Could not get the lock after a long time!\n");
    }
    sleep(5);
}

#
# Full reconfig. 
#
if ($reconfig) {
    system("$BINDIR/rc/rc.config reconfig");
    my $exitval = $? >> 8;
    close(LOCK);
    exit($exitval);
}

#
# Really, its the tmcc cache that is refreshed.
#
libsetup_refresh();

#
# Order matters.
#
if ($accounts) {
    # Accounts implies mounts too, which are done first.
    print "Updating mounts and accounts ... \n";
    
    system("$BINDIR/rc/rc.mounts reconfig") == 0 and
	system("$BINDIR/rc/rc.accounts reconfig");
    
    if ($?) {
	close(LOCK);
	die("*** $0:\n".
	    "    Failed to update mounts and accounts!\n");
    }
}

if ($tarballs) {
    # We do both tarballs and rpms.
    print "Updating tarballs and rpms ... \n";

    system("$BINDIR/rc/rc.tarfiles reconfig") == 0 and
	system("$BINDIR/rc/rc.rpms reconfig");
    
    if ($?) {
	close(LOCK);
	die("*** $0:\n".
	    "    Failed to update tarballs and rpms!\n");
    }
}

close(LOCK);
exit(0);
