Handle hostnames with upper-case letters
[webmin.git] / grub / grub-lib.pl
1 # grub-lib.pl
2 # Functions for parsing and editing a grub menu file
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7
8 %title_order = ( 'lock', 10,
9                  'root', 5,
10                  'kernel', 4,
11                  'chainloader', -1,
12                  'initrd', 2,
13                  'boot', 1 );
14
15 # get_menu_config()
16 # Parses the config file into a list of title structures
17 sub get_menu_config
18 {
19 local $lnum = 0;
20 local (@rv, $title);
21 open(CONF, $config{'menu_file'});
22 while(<CONF>) {
23         s/#.*$//;
24         s/\r|\n//g;
25         if (/^\s*(\S+)\s*=\s*(.*)/ || /^\s*(\S+)\s*(.*)/) {
26                 if ($title && $1 ne 'title') {
27                         # directive in an existing section
28                         if (defined($title->{$1})) {
29                                 # Multiple values!
30                                 $title->{$1} .= "\0".$2;
31                                 }
32                         else {
33                                 $title->{$1} = $2;
34                                 }
35                         $title->{'eline'} = $lnum;
36                         }
37                 else {
38                         # top-level title or option
39                         local $d = { 'name' => $1,
40                                      'value' => $2,
41                                      'line' => $lnum,
42                                      'eline' => $lnum,
43                                      'index' => scalar(@rv) };
44                         push(@rv, $d);
45                         $title = $d if ($1 eq 'title');
46                         }
47                 }
48         $lnum++;
49         }
50 close(CONF);
51 return \@rv;
52 }
53
54 # save_directive(&config, &old|name, &new)
55 sub save_directive
56 {
57 local $old;
58 if (!$_[1] || ref($_[1])) {
59         $old = $_[1];
60         }
61 else {
62         $old = &find($_[1], $_[0]);
63         }
64 local @lines;
65 if (defined($_[2])) {
66         @lines = ( "$_[2]->{'name'} $_[2]->{'value'}" );
67         foreach $k (sort { $title_order{$b} <=> $title_order{$a} }
68                          keys %{$_[2]}) {
69                 if ($k !~ /^(name|value|line|eline|index)$/) {
70                         if ($_[2]->{$k} eq '') {
71                                 push(@lines, $k);
72                                 }
73                         else {
74                                 foreach my $v (split(/\0/, $_[2]->{$k})) {
75                                         push(@lines, $k." ".$v);
76                                         }
77                                 }
78                         }
79                 }
80         }
81 local $lref = &read_file_lines($config{'menu_file'});
82 if ($old) {
83         # Replace one entry in the file
84         splice(@$lref, $old->{'line'}, $old->{'eline'} - $old->{'line'} + 1,
85                @lines);
86         }
87 elsif ($_[2]->{'name'} eq 'title') {
88         # Append to file
89         push(@$lref, "", @lines);
90         }
91 else {
92         # Insert before titles
93         local $t = &find("title", $_[0]);
94         if ($t) {
95                 splice(@$lref, $t->{'line'}, 0, @lines);
96                 }
97         else {
98                 push(@$lref, "", @lines);
99                 }
100         }
101 }
102
103 # swap_directives(&dir1, &dir2)
104 # Swaps two blocks in the config file
105 sub swap_directives
106 {
107 my ($dir1, $dir2) = @_;
108 local $lref = &read_file_lines($config{'menu_file'});
109 if ($dir1->{'line'} > $dir2->{'line'}) {
110         ($dir1, $dir2) = ($dir2, $dir1);
111         }
112 my @lines1 = @$lref[$dir1->{'line'} .. $dir1->{'eline'}];
113 my @lines2 = @$lref[$dir2->{'line'} .. $dir2->{'eline'}];
114 my $len1 = $dir1->{'eline'} - $dir1->{'line'} + 1;
115 my $len2 = $dir2->{'eline'} - $dir2->{'line'} + 1;
116 splice(@$lref, $dir2->{'line'}, $len2, @lines1);
117 splice(@$lref, $dir1->{'line'}, $len1, @lines2);
118 }
119
120 # find(name, &config)
121 sub find
122 {
123 local @rv;
124 foreach $c (@{$_[1]}) {
125         push(@rv, $c) if ($c->{'name'} eq $_[0]);
126         }
127 return wantarray ? @rv : $rv[0];
128 }
129
130 # find_value(name, &config)
131 sub find_value
132 {
133 local @rv = &find($_[0], $_[1]);
134 return !@rv ? undef : wantarray ? map { $_->{'value'} } @rv : $rv[0]->{'value'};
135 }
136
137 # linux_to_bios(device)
138 # Converts a Linux device file like /dev/hda into a GRUB bios disk like (hd0)
139 sub linux_to_bios
140 {
141 if ($_[0] =~ /^(\/dev\/[hs]d[a-z])(\d+)$/ ||
142     $_[0] =~ /^(\/dev\S+\/)part(\d+)$/ ||
143     $_[0] =~ /^(\/dev\S+c\d+d\d+)p(\d+)$/) {
144         # A partition on a disk .. get the disk's device, and then add the part
145         local ($dev, $part) = ($1, $2-1);
146         $dev .= "disc" if ($dev =~ /\/$/);
147         local $dsk = &linux_to_bios($dev);
148         $dsk =~ /^\(([a-z]+\d+)\)$/ || return undef;
149         return "($1,$part)";
150         }
151 local @map = &get_device_map();
152 local @st = stat($_[0]);
153 if (@map) {
154         foreach $m (@map) {
155                 local @mst = stat($m->[1]);
156                 if ($m->[1] eq $_[0] ||
157                     @mst && @st && $mst[0] == $st[0] && $mst[1] == $st[1]) {
158                         return $m->[0];
159                         }
160                 }
161         }
162
163 # Have to guess based on the device name :(
164 return $_[0] =~ /\/dev\/hd([a-d])$/ ? "(hd".(ord($1)-97).")" :
165        $_[0] =~ /\/dev\/fd([0-4])$/ ? "(fd$1)" : undef;
166 }
167
168 # bios_to_linux(device)
169 # Converts a GRUB bios disk like (hd0) to a Linux device file like /dev/hda
170 sub bios_to_linux
171 {
172 if ($_[0] =~ /^\(([a-z]+\d+),(\d+)\)$/) {
173         # A partition on a BIOS disk .. get the disk device, and add the part
174         local ($dev, $part) = ($1, $2+1);
175         local $dsk = &bios_to_linux("($dev)");
176         if ($dsk =~ /^(\/dev\/[hs]d[a-z])$/) {
177                 return $dsk.$part;
178                 }
179         elsif ($dsk =~ /^(\/dev\S+\/)disc$/) {
180                 return $1."part".$part;
181                 }
182         elsif ($dsk =~ /^(\/dev\S+c\d+d\d+)$/) {
183                 return $dsk.$part;
184                 }
185         else {
186                 return undef;
187                 }
188         }
189 local @map = &get_device_map();
190 if (@map) {
191         foreach $m (@map) {
192                 if ($m->[0] eq $_[0]) {
193                         return $m->[1];
194                         }
195                 }
196         }
197
198 # Have to guess from BIOS name :(
199 return $_[0] =~ /^\(hd(\d+)\)$/ ? "/dev/hd".chr($1+97) :
200        $_[0] =~ /^\(fd([0-4])\)$/ ? "/dev/fd$1" : undef;
201 }
202
203 # get_device_map()
204 # Returns the device.map file contents, or an empty list if there is none
205 sub get_device_map
206 {
207 local ($dm, $temp, @rv);
208 if (!$config{'device_map'} || !-r $config{'device_map'}) {
209         # Run GRUB to build the map now
210         $dm = $temp = &transname();
211         open(GRUB, "|$config{'grub_path'} --batch --device-map=$temp >/dev/null 2>&1");
212         print GRUB "quit\n";
213         close(GRUB);
214         }
215 else {
216         # Just use the existing file
217         $dm = $config{'device_map'};
218         }
219 open(MAP, $dm);
220 while(<MAP>) {
221         s/\r|\n//g;
222         s/#.*$//;
223         if (/^(\S+)\s+(\S+)/) {
224                 push(@rv, [ $1, $2 ]);
225                 }
226         }
227 close(MAP);
228 unlink($temp) if ($temp);
229 return @rv;
230 }
231