diff options
Diffstat (limited to 'utils')
-rwxr-xr-x | utils/pxelinux-options | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/utils/pxelinux-options b/utils/pxelinux-options new file mode 100755 index 00000000..0d45f26c --- /dev/null +++ b/utils/pxelinux-options @@ -0,0 +1,482 @@ +#!/usr/bin/perl +# +# Set PXELINUX hard-coded options +# + +use Socket; # For gethostbyname +use Fcntl; +use bytes; + +%option_names = ( + 6 => 'domain-name-servers', + 15 => 'domain-name', + 54 => 'next-server', + 209 => 'config-file', + 210 => 'path-prefix', + 211 => 'reboottime' + ); + +@fmt_oneip = (\&parse_oneip, \&show_ip); +@fmt_multiip = (\&parse_multiip, \&show_ip); +@fmt_string = (\&parse_string, \&show_string); +@fmt_uint32 = (\&parse_uint32, \&show_uint32); + +%option_format = ( + 6 => \@fmt_multiip, + 15 => \@fmt_string, + 54 => \@fmt_oneip, + 67 => \@fmt_string, + 209 => \@fmt_string, + 210 => \@fmt_string, + 211 => \@fmt_uint32 + ); + +sub parse_oneip($) +{ + my($s) = @_; + my($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($s); + + return ($addrtype == AF_INET) ? $addrs[0] : undef; +} + +sub parse_multiip($) +{ + my($l) = @_; + my $s; + my @a = (); + my $addr; + my $d = ''; + + foreach $s (split(/,/, $l)) { + my($name,$aliases,$addrtype,$length,@addrs) + = gethostbyname($s); + if ($addrtype == AF_INET) { + foreach $addr (@addrs) { + $d .= $addr; + } + } + } + + return $d ne '' ? $d : undef; +} + +sub show_ip($) +{ + my($l) = @_; + + if (length($l) & 3) { + return undef; + } else { + my @h = (); + my $i; + + for ($i = 0; $i < length($l); $i += 4) { + push(@h, inet_ntoa(substr($l, $i, 4))); + } + + return join(',', @h); + } +} + +sub parse_string($) +{ + return $_[0]; +} + +sub show_string($) +{ + my($s) = @_; + my $o, $i, $c; + + $o = "\'"; + for ($i = 0; $i < length($s); $i++) { + $c = substr($s, $i, 1); + if ($c eq "\'" || $c eq '!') { + $o .= "\'\\$c\'"; + } else { + $o .= $c; + } + } + $o .= "\'"; + + return $o; +} + +sub parse_uint32($) +{ + my($s) = @_; + + if ($s =~ /^[0-9]+$/) { + return pack("N", $s); + } else { + return undef; + } +} + +sub show_uint32($) +{ + my($l) = @_; + + if (length($l) == 4) { + return unpack("N", $l); + } else { + return undef; + } +} + +sub parse_generic($) +{ + my($s) = @_; + + if ($s =~ /^[0-9a-f]{1,2}(:[0-9a-f]{1,2})*$/) { + my $h; + my @b = (); + + foreach $h (split(/\:/, $s)) { + push(@b, hex $h); + } + + return pack("C", @b); + } else { + return undef; + } +} + +sub show_generic($) +{ + my($l) = @_; + my $i; + my @h; + + for ($i = 0; $i < length($l); $i++) { + push(@h, sprintf("%02x", unpack("C", substr($l, $i, $1)))); + } + + return join(':', @h); +} + +sub parse_option($$) +{ + my($opt, $arg) = @_; + my $v; + + if (defined($option_format{$opt})) { + $v = $option_format{$opt}[0]($arg); + return $v if (defined($v)); + } + + return parse_generic($arg); +} + +sub show_option($$) +{ + my($opt, $arg) = @_; + my $v; + + if (defined($option_format{$opt})) { + $v = $option_format{$opt}[1]($arg); + return $v if (defined($v)); + } + + return show_generic($arg); +} + +sub option_number($) +{ + my($n) = @_; + + if (defined($option_rnames{$n})) { + return $option_rnames{$n}; + } elsif ($n =~ /^[0-9]+$/ && $n >= 1 && $n <= 254) { + return $n+0; + } else { + return undef; + } +} + +sub read_optsets($) +{ + my($file) = @_; + my $data, $bdata, $adata; + my $patch_start = (stat($file))[7]; + + return undef unless (seek($file, 8, SEEK_SET)); + return undef unless (read($file, $data, 7*4) == 7*4); + + my($magic, $len, $flags, $boff, $blen, $aoff, $alen) + = unpack("VVVVVVV", $data); + return undef if ($magic != 0x2983c8ac); + return undef if ($len < 7*4); + + if ($blen == 0) { + $bdata = ''; + } else { + return undef unless (seek($file, $boff, SEEK_SET)); + return undef unless (read($file, $bdata, $blen) == $blen); + $patch_start = $boff if ($boff < $patch_start); + } + + if ($alen == 0) { + $adata = ''; + } else { + return undef unless (seek($file, $aoff, SEEK_SET)); + return undef unless (read($file, $adata, $alen) == $alen); + $patch_start = $aoff if ($aoff < $patch_start); + } + + return ($patch_start, $bdata, $adata); +} + +sub write_optsets($$@) +{ + my($file, $patch_start, $bdata, $adata) = @_; + my $boff = 0; + my $aoff = 0; + + if (length($bdata) > 0) { + $bdata .= "\xff"; + $boff = $patch_start; + return undef unless (seek($file, $boff, SEEK_SET)); + return undef unless (print $file $bdata); + $patch_start += length($bdata); + } + + if (length($adata) > 0) { + $adata .= "\xff"; + $aoff = $patch_start; + return undef unless (seek($file, $aoff, SEEK_SET)); + return undef unless (print $file $adata); + $patch_start += length($adata); + } + + my $hdr = pack("VVVV", $boff, length($bdata), $aoff, length($adata)); + + return undef unless (seek($file, 8+3*4, SEEK_SET)); + return undef unless (print $file $hdr); + + truncate($file, $patch_start); + return 1; +} + +sub delete_option($$) +{ + my ($num, $block) = @_; + my $o, $l, $c, $x; + + $x = 0; + while ($x < length($block)) { + ($o, $l) = unpack("CC", substr($block, $x, 2)); + if ($o == $num) { + # Delete this option + substr($block, $x, $l+2) = ''; + } elsif ($o == 0) { + # Delete a null option + substr($block, $x, 1) = ''; + } elsif ($o == 255) { + # End marker - truncate block + $block = substr($block, 0, $x); + last; + } else { + # Skip to the next option + $x += $l+2; + } + } + + return $block; +} + +sub add_option($$$) +{ + my ($num, $data, $block) = @_; + + $block = delete_option($num, $block); + + if (length($data) == 0) { + return $block; + } elsif (length($data) > 255) { + die "$0: option $num has too much data (max 255 bytes)\n"; + } else { + return $block . pack("CC", $num, length($data)) . $data; + } +} + +sub list_options($$) +{ + my($pfx, $data) = @_; + my $x, $o, $l; + + while ($x < length($data)) { + ($o, $l) = unpack("CC", substr($data, $x, 2)); + + if ($o == 0) { + $x++; + } elsif ($o == 255) { + last; + } else { + my $odata = substr($data, $x+2, $l); + last if (length($odata) != $l); # Incomplete option + + printf "%s%-20s %s\n", $pfx, + $option_names{$o} || sprintf("%d", $o), + show_option($o, $odata); + + $x += $l+2; + } + } +} + +sub usage() +{ + print STDERR "FIX USAGE MESSAGE\n"; +} + +%option_rnames = (); +foreach $opt (keys(%option_names)) { + $option_rnames{$option_names{$opt}} = $opt; +} + +%before = (); +%after = (); +@clear = (); +$usage = 0; +$err = 0; +$list = 0; +$no_write = 0; +undef $file; + +while (defined($opt = shift(@ARGV))) { + if ($opt !~ /^-/) { + if (defined($file)) { + $err = $usage = 1; + last; + } + $file = $opt; + } elsif ($opt eq '-b' || $opt eq '--before') { + $oname = shift(@ARGV); + $odata = shift(@ARGV); + + if (!defined($odata)) { + $err = $usage = 1; + last; + } + + $onum = option_number($oname); + if (!defined($onum)) { + print STDERR "$0: unknown option name: $oname\n"; + $err = 1; + next; + } + + $odata = parse_option($onum, $odata); + if (!defined($odata)) { + print STDERR "$0: unable to parse data for option $oname\n"; + $err = 1; + next; + } + + delete $after{$onum}; + $before{$onum} = $odata; + push(@clear, $onum); + } elsif ($opt eq '-a' || $opt eq '--after') { + $oname = shift(@ARGV); + $odata = shift(@ARGV); + + if (!defined($odata)) { + $err = $usage = 1; + last; + } + + $onum = option_number($oname); + if (!defined($onum)) { + print STDERR "$0: unknown option name: $oname\n"; + $err = 1; + next; + } + + $odata = parse_option($onum, $odata); + if (!defined($odata)) { + print STDERR "$0: unable to parse data for option $oname\n"; + $err = 1; + next; + } + + delete $before{$onum}; + $after{$onum} = $odata; + push(@clear, $onum); + } elsif ($opt eq '-d' || $opt eq '--delete') { + $oname = shift(@ARGV); + + if (!defined($oname)) { + $err = $usage = 1; + last; + } + + $onum = option_number($oname); + if (!defined($onum)) { + print STDERR "$0: unknown option name: $oname\n"; + $err = 1; + next; + } + + push(@clear, $onum); + delete $before{$onum}; + delete $after{$onum}; + } elsif ($opt eq '-n' || $opt eq '--no-write' || $opt eq '--dry-run') { + $no_write = 1; + } elsif ($opt eq '-l' || $opt eq '--list') { + $list = 1; + } elsif ($opt eq '-h' || $opt eq '--help') { + $usage = 1; + } else { + print STDERR "Invalid option: $opt\n"; + $err = $usage = 1; + } +} + +if (!defined($file) && !$usage) { + $err = $usage = 1; +} +if ($usage) { + usage(); +} +if ($err || $usage) { + exit($err); +} + +if (!scalar(@clear)) { + $no_write = 1; # No modifications requested +} + +$mode = $no_write ? '<' : '+<'; + +open(FILE, $mode, $file) + or die "$0: cannot open: $file: $!\n"; +($patch_start, @data) = read_optsets(\*FILE); +if (!defined($patch_start)) { + die "$0: $file: patch block not found or file corrupt\n"; +} + +foreach $o (@clear) { + $data[0] = delete_option($o, $data[0]); + $data[1] = delete_option($o, $data[1]); +} +foreach $o (keys(%before)) { + $data[0] = add_option($o, $before{$o}, $data[0]); +} +foreach $o (keys(%after)) { + $data[1] = add_option($o, $after{$o}, $data[1]); +} + +if ($list) { + list_options('-b ', $data[0]); + list_options('-a ', $data[1]); +} + +if (!$no_write) { + if (!write_optsets(\*FILE, $patch_start, @data)) { + die "$0: $file: failed to write options: $!\n"; + } +} + +close(FILE); +exit 0; |