#! /usr/bin/perl

# $Id: fai-chboot 3027 2005-11-10 22:37:16Z lange $
#*********************************************************************
#
# fai-chboot -- manage configuration for network boot
#
# This script is part of FAI (Fully Automatic Installation)
# Copyright (C) 2003-2005 Thomas Lange, lange@informatik.uni-koeln.de
# Universitaet zu Koeln
#
#*********************************************************************
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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
# General Public License for more details.
#
# A copy of the GNU General Public License is available as
# '/usr/share/common-licences/GPL' in the Debian GNU/Linux distribution
# or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html.  You
# can also obtain it by writing to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#*********************************************************************

$version="version 1.5.3, 5-apr-2005";

use Socket;
use Net::hostent;
use Getopt::Std;

$0=~ s#.+/##; # remove path from program name
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub ip2hex {

  my $ipadr = shift;
  my $hex = sprintf("%02X%02X%02X%02X", split(/\./,$ipadr));
  return ($ipadr,$hex);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub host2hex {

  my $host = shift;

  if ($host =~ /^\d+\.\d+\.\d+\.\d+$/) {
    # hostname is already an IP address
    return ip2hex($host);
  }
  return ('no IP','default') if ($host =~ /^default/);

  my $h = gethost($host);
  die "$0: unknown host: $host\n" unless $h;

  if ( @{$h->addr_list} > 1 ) {
    my $i;
    for my $addr ( @{$h->addr_list} ) {
      $ipadr = inet_ntoa($addr);
      printf "$host \taddr #%d is [%s]\n", $i++, $ipadr if $debug;
    }
  } else {
    $ipadr = inet_ntoa($h->addr);
    printf "$host \taddress is [%s]\n", $ipadr if $debug;
  }
  ip2hex($ipadr);
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub lsdir {

  my (@n,$host,$ip,$iaddr,$hex,$kernelname,$append);

  opendir(DIR, $pxedir) || die "can't opendir $pxedir: $!";
  foreach (readdir(DIR)) {
#    prtipa if /^[0-9A-F]{2}$/;
#    prtipb if /^[0-9A-F]{4}$/;
#    prthostname if /^[0-9A-F]{6}/;
    next unless /^[0-9A-F]+|default/;
    $hex = $_;

    # hex to ip address
    @n = $_ =~ m/[0-9A-F][0-9A-F]/g;
    $ip = join ('.', map {$_ = hex $_} @n);

    if ($hex =~ /^default/) {
      $host = 'unknown IP';
    } elsif ($#n < 3) {
      # Don't fail if not a complete IP
      $host = "Subnet: $ip";
    } else {
      # ip to hostname
      $iaddr = inet_aton($ip);
      if ($h = gethostbyaddr($iaddr, AF_INET)) {
	$host = $h->name;
      } else {
	$host = "unknown host: $ip";
      }
    }

    # read pxe config file for a host
    undef $kernelname;
    open (CFG,"$pxedir/$hex") || die "$! $@\n";
    while (<CFG>) {
      /^kernel (\S+)/ and $kernelname = $1;
      /^(localboot.+)/ and $kernelname = $1;
      /^append (.+)/ and $append = $1;
    }
    close (CFG);

    $opt_L or undef $append;
    printf "%-8s %-12s $kernelname $append\n",$hex,$host;
    undef $append;
    undef $kernelname;
  }
  exit;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub usage {

  print "$0 $version\n";
  print << "EOM";
 Please read the manual pages fai-chboot(8).
EOM
  exit 0;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub mkpxecfg {

  my ($host,$kernel,$rootfs) = @_;
  my ($ipadr,$hex) = host2hex($host);

  warn "$host has $ipadr in hex $hex\n" if $verbose;
  warn "Writing file $pxedir/$hex for $host\n" if $verbose;
  return if $opt_n;

  if ($opt_p && -e "$pxedir/$hex") {
    warn "WARNING: $pxedir/$hex already exists. Skipping file creation. ";
    return;
  }
  open (FILE,"> $pxedir/$hex") or warn "$0 $@ $!";
  print FILE << "EOM";
# generated by fai-chboot for host $host with IP $ipadr
default fai-generated

label fai-generated
$kernel
EOM

  $append="append $bootprot root=$rootfs $opt_k $flags\n";
  print FILE $append if $rootfs;
  close FILE;
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub disable {

  # rename network config file
  my ($host) = shift;
  my ($ipadr,$hex) = host2hex($host);
  print "disable pxe config for $host in hex $hex\n" if $verbose;
  return if $opt_n;
  rename "$pxedir/$hex","$pxedir/$hex.disable" or $error .= "\nRename for $hex failed.$! $@";
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub enable {

  # rename network config file
  my ($host) = shift;
  my ($ipadr,$hex) = host2hex($host);
  print "reenable pxe config for $host in hex $hex\n" if $verbose;
  return if $opt_n;
  rename "$pxedir/$hex.disable","$pxedir/$hex" or $error .= "\nRename for $hex failed.$! $@";
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

getopts('Bd:ehnvlLiIp:f:Frk:So');
$opt_h and usage;
defined @ARGV or usage;

$opt_n and $opt_v = 1;
$opt_v and $verbose = 1;
$pxedir = $opt_d || '/boot/fai/pxelinux.cfg';
$opt_L and $opt_l = 1;
$opt_l and lsdir;
($opt_B and $opt_F) && die "ERROR: use only one option out of -B and -F\n";
($opt_S and $opt_I) && die "ERROR: use only one option out of -I and -S\n";

if ($opt_r) {
  die "Missing host name(s). Can't disable network booting.\n" unless @ARGV;
  foreach (@ARGV) {
    disable("$_");
  }
  $error and die "$0: $error\n";
  exit 0;
}

if ($opt_e) {
  die "Missing host name(s). Can't reenable network booting.\n" unless @ARGV;
  foreach (@ARGV) {
    enable("$_");
  }
  $error and die "$0: $error\n";
  exit 0;
}

if ($opt_S) {
  $opt_i = 1;
  $action="FAI_ACTION=sysinfo";
}
if ($opt_I) {
  $opt_i = 1;
  $action="FAI_ACTION=install";
}

if ($opt_i) {
  # create config so host will boot the install kernel
  $kernelname = 'kernel vmlinuz-install';
  $rootfs     = '/dev/nfs';
  $bootprot   = "ip=dhcp devfs=nomount $action";
} elsif ($opt_o) {
  $kernelname = 'localboot 0';
  $rootfs   = '';
  $bootprot = '';
  $flags    = '';
} else {
  $kernelname = shift;
  $kernelname = "kernel $kernelname";
  $rootfs = shift;
}

$opt_F and $opt_f="verbose,sshd,createvt,syslogd";
$opt_B and $opt_f="verbose,sshd,syslogd,reboot";
$opt_f and $flags="FAI_FLAGS=$opt_f";
warn "append parameters: $bootprot $opt_k $flags\n" if $verbose;
warn "Booting $kernelname\nrootfs is $rootfs\n" if $verbose;
$opt_k && print "Kernel parameters: $opt_k\n";

die "No host specified.\n" unless @ARGV;
foreach (@ARGV) { mkpxecfg("$_",$kernelname,$rootfs); }
