root/utils/pxelinux-options

Revision 1c8d53bf85b1f570d1dc69b1498b880a3ade836a, 9.7 KB (checked in by H. Peter Anvin <hpa@…>, 3 years ago)

pxelinux-options: add help text

Add help text to pxelinux-options.

Signed-off-by: H. Peter Anvin <hpa@…>

  • Property mode set to 100755
Line 
1#!/usr/bin/perl
2#
3# Set PXELINUX hard-coded options
4#
5
6use Socket;                     # For gethostbyname
7use Fcntl;
8use bytes;
9
10%option_names = (
11      6 => 'domain-name-servers',
12     15 => 'domain-name',
13     54 => 'next-server',
14    209 => 'config-file',
15    210 => 'path-prefix',
16    211 => 'reboottime'
17    );
18
19@fmt_oneip   = ("ip-address", \&parse_oneip, \&show_ip);
20@fmt_multiip = ("ip-address-list", \&parse_multiip, \&show_ip);
21@fmt_string  = ("string", \&parse_string, \&show_string);
22@fmt_uint32  = ("uint32", \&parse_uint32, \&show_uint32);
23
24%option_format = (
25      6 => \@fmt_multiip,
26     15 => \@fmt_string,
27     54 => \@fmt_oneip,
28     67 => \@fmt_string,
29    209 => \@fmt_string,
30    210 => \@fmt_string,
31    211 => \@fmt_uint32
32    );
33
34sub parse_oneip($)
35{
36    my($s) = @_;
37    my($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($s);
38
39    return ($addrtype == AF_INET) ? $addrs[0] : undef;
40}
41
42sub parse_multiip($)
43{
44    my($l) = @_;
45    my $s;
46    my @a = ();
47    my $addr;
48    my $d = '';
49
50    foreach $s (split(/,/, $l)) {
51        my($name,$aliases,$addrtype,$length,@addrs)
52            = gethostbyname($s);
53        if ($addrtype == AF_INET) {
54            foreach $addr (@addrs) {
55                $d .= $addr;
56            }
57        }
58    }
59
60    return $d ne '' ? $d : undef;
61}
62
63sub show_ip($)
64{
65    my($l) = @_;
66
67    if (length($l) & 3) {
68        return undef;
69    } else {
70        my @h = ();
71        my $i;
72
73        for ($i = 0; $i < length($l); $i += 4) {
74            push(@h, inet_ntoa(substr($l, $i, 4)));
75        }
76
77        return join(',', @h);
78    }
79}
80
81sub parse_string($)
82{
83    return $_[0];
84}
85
86sub show_string($)
87{
88    my($s) = @_;
89    my $o, $i, $c;
90
91    $o = "\'";
92    for ($i = 0; $i < length($s); $i++) {
93        $c = substr($s, $i, 1);
94        if ($c eq "\'" || $c eq '!') {
95            $o .= "\'\\$c\'";
96        } else {
97            $o .= $c;
98        }
99    }
100    $o .= "\'";
101
102    return $o;
103}
104
105sub parse_uint32($)
106{
107    my($s) = @_;
108
109    if ($s =~ /^[0-9]+$/) {
110        return pack("N", $s);
111    } else {
112        return undef;
113    }
114}
115
116sub show_uint32($)
117{
118    my($l) = @_;
119
120    if (length($l) == 4) {
121        return unpack("N", $l);
122    } else {
123        return undef;
124    }
125}
126
127sub parse_generic($)
128{
129    my($s) = @_;
130
131    if ($s =~ /^[0-9a-f]{1,2}(:[0-9a-f]{1,2})*$/) {
132        my $h;
133        my @b = ();
134
135        foreach $h (split(/\:/, $s)) {
136            push(@b, hex $h);
137        }
138
139        return pack("C", @b);
140    } else {
141        return undef;
142    }
143}
144
145sub show_generic($)
146{
147    my($l) = @_;
148    my $i;
149    my @h;
150
151    for ($i = 0; $i < length($l); $i++) {
152        push(@h, sprintf("%02x", unpack("C", substr($l, $i, $1))));
153    }
154
155    return join(':', @h);
156}
157
158sub parse_option($$)
159{
160    my($opt, $arg) = @_;
161    my $v;
162
163    if (defined($option_format{$opt})) {
164        $v = $option_format{$opt}[1]($arg);
165        return $v if (defined($v));
166    }
167
168    return parse_generic($arg);
169}
170
171sub show_option($$)
172{
173    my($opt, $arg) = @_;
174    my $v;
175
176    if (defined($option_format{$opt})) {
177        $v = $option_format{$opt}[2]($arg);
178        return $v if (defined($v));
179    }
180
181    return show_generic($arg);
182}
183
184sub option_number($)
185{
186    my($n) = @_;
187
188    if (defined($option_rnames{$n})) {
189        return $option_rnames{$n};
190    } elsif ($n =~ /^[0-9]+$/ && $n >= 1 && $n <= 254) {
191        return $n+0;
192    } else {
193        return undef;
194    }
195}
196
197sub read_optsets($)
198{
199    my($file) = @_;
200    my $data, $bdata, $adata;
201    my $patch_start = (stat($file))[7];
202
203    return undef unless (seek($file, 8, SEEK_SET));
204    return undef unless (read($file, $data, 7*4) == 7*4);
205
206    my($magic, $len, $flags, $boff, $blen, $aoff, $alen)
207        = unpack("VVVVVVV", $data);
208    return undef if ($magic != 0x2983c8ac);
209    return undef if ($len < 7*4);
210
211    if ($blen == 0) {
212        $bdata = '';
213    } else {
214        return undef unless (seek($file, $boff, SEEK_SET));
215        return undef unless (read($file, $bdata, $blen) == $blen);
216        $patch_start = $boff if ($boff < $patch_start);
217    }
218
219    if ($alen == 0) {
220        $adata = '';
221    } else {
222        return undef unless (seek($file, $aoff, SEEK_SET));
223        return undef unless (read($file, $adata, $alen) == $alen);
224        $patch_start = $aoff if ($aoff < $patch_start);
225    }
226
227    return ($patch_start, $bdata, $adata);
228}
229
230sub write_optsets($$@)
231{
232    my($file, $patch_start, $bdata, $adata) = @_;
233    my $boff = 0;
234    my $aoff = 0;
235
236    if (length($bdata) > 0) {
237        $bdata .= "\xff";
238        $boff = $patch_start;
239        return undef unless (seek($file, $boff, SEEK_SET));
240        return undef unless (print $file $bdata);
241        $patch_start += length($bdata);
242    }
243
244    if (length($adata) > 0) {
245        $adata .= "\xff";
246        $aoff = $patch_start;
247        return undef unless (seek($file, $aoff, SEEK_SET));
248        return undef unless (print $file $adata);
249        $patch_start += length($adata);
250    }
251
252    my $hdr = pack("VVVV", $boff, length($bdata), $aoff, length($adata));
253
254    return undef unless (seek($file, 8+3*4, SEEK_SET));
255    return undef unless (print $file $hdr);
256
257    truncate($file, $patch_start);
258    return 1;
259}
260
261sub delete_option($$)
262{
263    my ($num, $block) = @_;
264    my $o, $l, $c, $x;
265
266    $x = 0;
267    while ($x < length($block)) {
268        ($o, $l) = unpack("CC", substr($block, $x, 2));
269        if ($o == $num) {
270            # Delete this option
271            substr($block, $x, $l+2) = '';
272        } elsif ($o == 0) {
273            # Delete a null option
274            substr($block, $x, 1) = '';
275        } elsif ($o == 255) {
276            # End marker - truncate block
277            $block = substr($block, 0, $x);
278            last;
279        } else {
280            # Skip to the next option
281            $x += $l+2;
282        }
283    }
284
285    return $block;
286}
287
288sub add_option($$$)
289{
290    my ($num, $data, $block) = @_;
291
292    $block = delete_option($num, $block);
293
294    if (length($data) == 0) {
295        return $block;
296    } elsif (length($data) > 255) {
297        die "$0: option $num has too much data (max 255 bytes)\n";
298    } else {
299        return $block . pack("CC", $num, length($data)) . $data;
300    }
301}
302
303sub list_options($$)
304{
305    my($pfx, $data) = @_;
306    my $x, $o, $l;
307
308    while ($x < length($data)) {
309        ($o, $l) = unpack("CC", substr($data, $x, 2));
310
311        if ($o == 0) {
312            $x++;
313        } elsif ($o == 255) {
314            last;
315        } else {
316            my $odata = substr($data, $x+2, $l);
317            last if (length($odata) != $l); # Incomplete option
318
319            printf "%s%-20s %s\n", $pfx,
320                $option_names{$o} || sprintf("%d", $o),
321                show_option($o, $odata);
322
323            $x += $l+2;
324        }
325    }
326}
327
328sub usage()
329{
330    my $i;
331
332    print STDERR "Usage: $0 options pxelinux.0\n";
333    print STDERR "Options:\n";
334    print STDERR "--before option value   -b   Add an option before DHCP data\n";
335    print STDERR "--after  option value   -a   Add an option after DHCP data\n";
336    print STDERR "--delete option         -d   Delete an option\n";
337    print STDERR "--list                  -l   List set options\n";
338    print STDERR "--dry-run               -n   Don't modify the target file\n";
339    print STDERR "--help                  -h   Display this help text\n";
340    print STDERR "\n";
341    print STDERR "The following DHCP options are currently recognized:\n";
342    printf STDERR "%-23s %-3s  %s\n", 'Name', 'Num', 'Value Format';
343
344    foreach $i (sort { $a <=> $b } keys(%option_names)) {
345        printf STDERR "%-23s %3d  %s\n",
346                $option_names{$i}, $i, $option_format{$i}[0];
347    }
348}
349
350%option_rnames = ();
351foreach $opt (keys(%option_names)) {
352    $option_rnames{$option_names{$opt}} = $opt;
353}
354
355%before   = ();
356%after    = ();
357@clear    = ();
358$usage    = 0;
359$err      = 0;
360$list     = 0;
361$no_write = 0;
362undef $file;
363
364while (defined($opt = shift(@ARGV))) {
365    if ($opt !~ /^-/) {
366        if (defined($file)) {
367            $err = $usage = 1;
368            last;
369        }
370        $file = $opt;
371    } elsif ($opt eq '-b' || $opt eq '--before') {
372        $oname = shift(@ARGV);
373        $odata = shift(@ARGV);
374
375        if (!defined($odata)) {
376            $err = $usage = 1;
377            last;
378        }
379
380        $onum = option_number($oname);
381        if (!defined($onum)) {
382            print STDERR "$0: unknown option name: $oname\n";
383            $err = 1;
384            next;
385        }
386
387        $odata = parse_option($onum, $odata);
388        if (!defined($odata)) {
389            print STDERR "$0: unable to parse data for option $oname\n";
390            $err = 1;
391            next;
392        }
393
394        delete $after{$onum};
395        $before{$onum} = $odata;
396        push(@clear, $onum);
397    } elsif ($opt eq '-a' || $opt eq '--after') {
398        $oname = shift(@ARGV);
399        $odata = shift(@ARGV);
400
401        if (!defined($odata)) {
402            $err = $usage = 1;
403            last;
404        }
405
406        $onum = option_number($oname);
407        if (!defined($onum)) {
408            print STDERR "$0: unknown option name: $oname\n";
409            $err = 1;
410            next;
411        }
412
413        $odata = parse_option($onum, $odata);
414        if (!defined($odata)) {
415            print STDERR "$0: unable to parse data for option $oname\n";
416            $err = 1;
417            next;
418        }
419
420        delete $before{$onum};
421        $after{$onum} = $odata;
422        push(@clear, $onum);
423    } elsif ($opt eq '-d' || $opt eq '--delete') {
424        $oname = shift(@ARGV);
425
426        if (!defined($oname)) {
427            $err = $usage = 1;
428            last;
429        }
430
431        $onum = option_number($oname);
432        if (!defined($onum)) {
433            print STDERR "$0: unknown option name: $oname\n";
434            $err = 1;
435            next;
436        }
437
438        push(@clear, $onum);
439        delete $before{$onum};
440        delete $after{$onum};
441    } elsif ($opt eq '-n' || $opt eq '--no-write' || $opt eq '--dry-run') {
442        $no_write = 1;
443    } elsif ($opt eq '-l' || $opt eq '--list') {
444        $list = 1;
445    } elsif ($opt eq '-h' || $opt eq '--help') {
446        $usage = 1;
447    } else {
448        print STDERR "Invalid option: $opt\n";
449        $err = $usage = 1;
450    }
451}
452
453if (!defined($file) && !$usage) {
454    $err = $usage = 1;
455}
456if ($usage) {
457    usage();
458}
459if ($err || $usage) {
460    exit($err);
461}
462
463if (!scalar(@clear)) {
464    $no_write = 1;              # No modifications requested
465}
466
467$mode = $no_write ? '<' : '+<';
468
469open(FILE, $mode, $file)
470    or die "$0: cannot open: $file: $!\n";
471($patch_start, @data) = read_optsets(\*FILE);
472if (!defined($patch_start)) {
473    die "$0: $file: patch block not found or file corrupt\n";
474}
475
476foreach $o (@clear) {
477    $data[0] = delete_option($o, $data[0]);
478    $data[1] = delete_option($o, $data[1]);
479}
480foreach $o (keys(%before)) {
481    $data[0] = add_option($o, $before{$o}, $data[0]);
482}
483foreach $o (keys(%after)) {
484    $data[1] = add_option($o, $after{$o}, $data[1]);
485}
486
487if ($list) {
488    list_options('-b ', $data[0]);
489    list_options('-a ', $data[1]);
490}
491
492if (!$no_write) {
493    if (!write_optsets(\*FILE, $patch_start, @data)) {
494        die "$0: $file: failed to write options: $!\n";
495    }
496}
497
498close(FILE);
499exit 0;
Note: See TracBrowser for help on using the browser.