Merge commit 'erwan/master' into hdt-0.3.5
[profile/ivi/syslinux.git] / utils / pxelinux-options
1 #!/usr/bin/perl
2 #
3 # Set PXELINUX hard-coded options
4 #
5
6 use Socket;                     # For gethostbyname
7 use Fcntl;
8 use 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
34 sub 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
42 sub 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
63 sub 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
81 sub parse_string($)
82 {
83     return $_[0];
84 }
85
86 sub 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
105 sub 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
116 sub 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
127 sub 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
145 sub 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
158 sub 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
171 sub 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
184 sub 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
197 sub 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
230 sub 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
261 sub 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
288 sub 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
303 sub 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
328 sub 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 = ();
351 foreach $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;
362 undef $file;
363
364 while (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
453 if (!defined($file) && !$usage) {
454     $err = $usage = 1;
455 }
456 if ($usage) {
457     usage();
458 }
459 if ($err || $usage) {
460     exit($err);
461 }
462
463 if (!scalar(@clear)) {
464     $no_write = 1;              # No modifications requested
465 }
466
467 $mode = $no_write ? '<' : '+<';
468
469 open(FILE, $mode, $file)
470     or die "$0: cannot open: $file: $!\n";
471 ($patch_start, @data) = read_optsets(\*FILE);
472 if (!defined($patch_start)) {
473     die "$0: $file: patch block not found or file corrupt\n";
474 }
475
476 foreach $o (@clear) {
477     $data[0] = delete_option($o, $data[0]);
478     $data[1] = delete_option($o, $data[1]);
479 }
480 foreach $o (keys(%before)) {
481     $data[0] = add_option($o, $before{$o}, $data[0]);
482 }
483 foreach $o (keys(%after)) {
484     $data[1] = add_option($o, $after{$o}, $data[1]);
485 }
486
487 if ($list) {
488     list_options('-b ', $data[0]);
489     list_options('-a ', $data[1]);
490 }
491
492 if (!$no_write) {
493     if (!write_optsets(\*FILE, $patch_start, @data)) {
494         die "$0: $file: failed to write options: $!\n";
495     }
496 }
497
498 close(FILE);
499 exit 0;