dhcpd_makeconf.in 8.2 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2 3
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2006 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5 6
# All rights reserved.
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
7
use English;
8
use Getopt::Std;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
9
use Fcntl ':flock';
Robert Ricci's avatar
Robert Ricci committed
10

11 12 13 14
#
# dhcpd_makeconf - helper script to create dhcpd.conf files from the database.
# The template file should look like an ordinary dhcpd.conf file, but have
# the string %%nodetype=<type> where you want entries for a set of nodes
Leigh B. Stoller's avatar
Leigh B. Stoller committed
15
# filled out. See the template file in the dhcp directory for an example.
16
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
17 18 19 20 21
sub usage {
    print "Usage: $0 [-h] [-v] [-i] [-t <templatefile>]\n";
    print "-h	Show this message\n";
    print "-v	Use virtual names, when possible, for hostnames\n";
    print "-i 	Install new config file to standard location.\n";
22
    print "-r 	Restart DHCPD after config file regeneration (with -i only)\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
23 24 25 26
    print "Without -i, spits out a dhcpd config file on stdout, as in:\n";
    print "  $0 > dhcpd.conf\n";
    exit(1);
}
27
my $optlist = "ihvt:r";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
28 29
my $install = 0;
my $vnames  = 0;
30
my $restart = 0;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
31

32
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
33
# Configure variables
34
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
my $TBOPS       = "@TBOPSEMAIL@";

# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

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

use lib "@prefix@/lib";
use libdb;
use libtestbed;

my $CRTLTAG	    = TBDB_IFACEROLE_CONTROL();
my $DHCPD_CONF	    = "/usr/local/etc/dhcpd.conf";
my $DHCPD_TEMPLATE  = "/usr/local/etc/dhcpd.conf.template";
my $lockfile	    = "/var/tmp/testbed_dhcpd_lockfile";
52
my %servernodes	    = ();
Leigh B. Stoller's avatar
Leigh B. Stoller committed
53 54 55 56
my $template        = $DHCPD_TEMPLATE;
my $outfile	    = "/tmp/dhcpd_makeconf.$$";
my $OUT		    = *STDOUT; 

57
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
58 59
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
60
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
61 62 63 64 65 66
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"i"})) {
    $install = 1;
67

Leigh B. Stoller's avatar
Leigh B. Stoller committed
68 69 70 71 72
    # We don't want to run this script unless its the real version.
    if ($EUID != 0) {
	die("*** $0:\n".
	    "    Must be root! Maybe its a development version?\n");
    }
73 74 75
    if (defined($options{"r"})) {
	$restart = 1;
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
}
if (defined($options{"h"})) {
    usage();
}
if (defined($options{"v"})) {
    $vnames = 1;
}
if (defined($options{"t"})) {
    $template = $options{"t"};

    #
    # Untaint argument; Allow slash.
    #
    if ($template =~ /^([-\w\.\/]+)$/) {
	$template = $1;
    }
    else {
	die("Tainted template name: $template\n");
    }
}
96

Robert Ricci's avatar
Robert Ricci committed
97
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
98 99 100
# If we are going to actually install this file, must serialize to
# avoid a trashed config file. Use a dummy file in /var/tmp, opened for
# writing and flock'ed.
Robert Ricci's avatar
Robert Ricci committed
101
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
if ($install) {
    open(LOCK, ">>$lockfile") || fatal("Couldn't open $lockfile\n");
    my $count = 0;
    if (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
	#
	# If we don't get it the first time, we wait for:
	# 1) The lock to become free, in which case we do our thing
	# 2) The time on the lock to change, in which case we wait for
	#    that process to finish
	#
	my $oldlocktime = (stat(LOCK))[9];
	my $gotlock = 0;
	while (1) {
	    print "Another dhcpd config update in progress, ".
		"waiting for it to finish\n";
	    if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		# OK, got the lock, we can do what we're supposed to
		$gotlock = 1;
		last;
	    }
	    my $locktime = (stat(LOCK))[9];
	    if ($locktime != $oldlocktime) {
		$oldlocktime = $locktime;
		last;
	    }
	    if ($count++ > 30)  {
		fatal("Could not get the lock after a long time!\n");
	    }
	    sleep(1);
	}
Robert Ricci's avatar
Robert Ricci committed
132

Leigh B. Stoller's avatar
Leigh B. Stoller committed
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	$count = 0;
	#
	# If we didn't get the lock, wait for the processes that did to finish
	#
	if (!$gotlock) {
	    while (1) {
		if ((stat(LOCK))[9] != $oldlocktime) {
		    exit(0);
		}
		if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		    close(LOCK);
		    exit(0);
		}
		if ($count++ > 30)  {
		    fatal("Process with the lock didn't finish after a ".
			  "long time!\n");
		}
		sleep(1); 
	    }
	}
    }
    #
    # Perl-style touch(1)
    #
    my $now = time;
    utime $now, $now, $lockfile;
Robert Ricci's avatar
Robert Ricci committed
159

Leigh B. Stoller's avatar
Leigh B. Stoller committed
160 161 162 163 164 165 166
    #
    # Open temporary output file.
    # 
    open(OF, ">$outfile") or
	fatal("Could not open $outfile\n");
    $OUT = *OF;
}
Robert Ricci's avatar
Robert Ricci committed
167

Leigh B. Stoller's avatar
Leigh B. Stoller committed
168 169
open(IF,"<$template") or
    fatal("Unable to open $template for reading");
Robert Ricci's avatar
Robert Ricci committed
170 171 172 173
while (<IF>) {
	if (/^(\s*)\%\%nodetype=(\w+)/) {
		my $spaces = $1;
		my $nodetype = $2;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
174
		my $query_result =
175
		    DBQueryWarn("select n.node_id,n.pxe_boot_path, ".
176
				"       t.pxe_boot_path as nt_pxe_boot_path, ".
177 178 179
				"       i.IP,i.MAC,r.pid,r.eid,r.vname, ".
				"       r.inner_elab_role,r.inner_elab_boot, ".
				"       r.plab_role,r.plab_boot ".
Leigh B. Stoller's avatar
Leigh B. Stoller committed
180 181 182 183 184 185 186 187 188 189 190 191
				"from nodes as n ".
				"left join interfaces as i on ".
				"     n.node_id=i.node_id ".
				"left join node_types as t on n.type=t.type ".
				"left join reserved as r on ".
				"     n.node_id=r.node_id ".
				"where (n.type='$nodetype' or ".
				"       t.class='$nodetype') and ".
				"      i.role='$CRTLTAG' ".
				"order BY n.priority");
		if (! $query_result) {
		    fatal("Could not dhcpd data from DB!");
Robert Ricci's avatar
Robert Ricci committed
192
		}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
193 194

		#
195 196 197
		# First go through and find any server (boss or plc) nodes.
		# We need these to create the next-server line for elabinelab
		# and plabinelab nodes.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
198 199 200
		#
		while (my %row = $query_result->fetchhash()) {
		    if (defined($row{"pid"}) &&
201 202 203 204 205
			((defined($row{"inner_elab_role"}) &&
			  ($row{"inner_elab_role"} eq "boss" ||
			   $row{"inner_elab_role"} eq "boss+router")) ||
			 (defined($row{"plab_role"}) &&
			  $row{"plab_role"} eq "plc"))) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
206 207
			my $tag = $row{"pid"} . ":" . $row{"eid"};
			
208
			$servernodes{$tag} = $row{"IP"};
Leigh B. Stoller's avatar
Leigh B. Stoller committed
209
		    }
Robert Ricci's avatar
Robert Ricci committed
210
		}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
211 212 213 214 215 216 217 218
		$query_result->dataseek(0);
		
		while (my %row = $query_result->fetchhash()) {
		    my $ip  = $row{"IP"};
		    my $mac = $row{"MAC"};
		    my $node_id;
		    my $next_server = "";
		    my $hostname = "";
219
		    my $dns = "";
220
		    my $filename = "";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
221 222 223 224 225 226 227 228 229

		    if ($vnames && defined($row{"vname"})) {
			$node_id = $row{"vname"};
		    }
		    else {
			$node_id = $row{"node_id"};
		    }

		    if (defined($row{"pid"}) &&
230 231 232 233 234 235
			(($row{"inner_elab_boot"} == 1 &&
			  defined($row{"inner_elab_role"}) &&
			  $row{"inner_elab_role"} eq "node") ||
			 ($row{"plab_boot"} == 1 &&
			  defined($row{"plab_role"}) &&
			  $row{"plab_role"} eq "node"))) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
236 237 238
			my $tag = $row{"pid"} . ":" . $row{"eid"};
			
			$next_server = "${spaces}\tnext-server " .
239 240 241 242 243
			    $servernodes{$tag} . ";\n";
			if ($row{"inner_elab_boot"} == 1) {
			    $dns = "${spaces}\toption domain-name-servers ".
				"1.1.1.1;\n";
			}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
244 245 246 247 248 249
		    }
		    else {
			$hostname =
			    "${spaces}\toption host-name \"$node_id\";\n";
		    }
		    
250 251 252 253 254 255 256
		    #
		    # Handle alternate boot program filename if it exists.
		    # Use mutable nodes.pxe_boot_path if it is defined.
		    # Otherwise use the node_types.pxe_boot_path if it is
		    # defined.  Otherwise don't set anything (use the global
		    # default).
		    #
257 258 259 260 261 262 263
		    if (defined($row{"pxe_boot_path"})) {
			my $fn = $row{"pxe_boot_path"};
			# make sure it is pretty constrained
			if ($fn =~ /^\/tftpboot\// && $fn !~ /\.\./) {
			    $filename = "${spaces}\tfilename \"$fn\";\n";
			}
		    }
264 265 266 267 268 269 270
		    if (!$filename && defined($row{"nt_pxe_boot_path"})) {
			my $fn = $row{"nt_pxe_boot_path"};
			# make sure it is pretty constrained
			if ($fn =~ /^\/tftpboot\// && $fn !~ /\.\./) {
			    $filename = "${spaces}\tfilename \"$fn\";\n";
			}
		    }
271

Leigh B. Stoller's avatar
Leigh B. Stoller committed
272 273 274 275
		    # Need to make MAC look right..
		    $mac =~ s/(..)\B/$1:/g;

		    print $OUT "${spaces}host $ip {\n";
276
		    print $OUT $filename;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
277
		    print $OUT $next_server;
278
		    print $OUT $dns;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
279 280 281 282
		    print $OUT "${spaces}\thardware ethernet $mac;\n";
		    print $OUT $hostname;
		    print $OUT "${spaces}\tfixed-address $ip;\n";
		    print $OUT "${spaces}}\n\n";
Robert Ricci's avatar
Robert Ricci committed
283 284
		}
	} else {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
285 286
	    # It's a regular line
	    print $OUT $_;
Robert Ricci's avatar
Robert Ricci committed
287 288
	}
}
289
close(IF);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
290 291 292 293 294 295 296 297 298 299 300

if ($install) {
    close(OF) or
	fatal("Could not close $outfile");

    if (-e $DHCPD_CONF) {
	system("cp -fp $DHCPD_CONF ${DHCPD_CONF}.old") == 0 or
	    fatal("Could not backup copy of ${DHCPD_CONF}");
    }
    system("mv -f $outfile $DHCPD_CONF") == 0 or
	    fatal("Could not install new ${DHCPD_CONF}");
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315

    if ($restart) {
	$dpid = `cat /var/run/dhcpd.pid`;
	chomp($dpid);
        # untaint
	if ($dpid =~ /^([\d]+)$/) {
	    $dpid = $1;
	}
	else {
	    fatal("Bad pid for DHCPD: $dpid");
	}
	if (kill('TERM', $dpid) == 0) {
	    fatal("Could not kill(TERM) process $dpid (dhcpd): $!");
	}
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
316
}
317
exit(0);
Robert Ricci's avatar
Robert Ricci committed
318

Leigh B. Stoller's avatar
Leigh B. Stoller committed
319 320 321 322 323 324
#
# Die.
#
sub fatal {
    my $msg = $_[0];
    die($msg);
Robert Ricci's avatar
Robert Ricci committed
325
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
326