Handle hostnames with upper-case letters
[webmin.git] / OsChooser.pm
1 #!/usr/local/bin/perl -w
2 use strict;
3 # Detect the operating system and version.
4
5 package OsChooser;
6
7 # Package scoped for mapping short names to long "proper" names
8 my %NAMES_TO_REAL;
9
10 # main
11 sub main {
12         if ($#ARGV < 1) { die "Usage: $0 os_list.txt outfile [0|1|2|3] [issue]\n"; }
13         my ($oslist, $out, $auto, $issue) = @ARGV;
14         return write_file($out, oschooser($oslist, $auto, $issue));
15         }
16 main() unless caller(); # make it testable and usable as a library
17
18 $| = 1;
19
20 sub oschooser {
21 my ($oslist, $auto, $issue) = @_;
22 my $ver_ref;
23
24 my ($list_ref, $names_ref) = parse_patterns($oslist);
25
26 if ($auto && ($ver_ref = auto_detect($oslist, $issue, $list_ref, $names_ref))) {
27         return ($ver_ref->[2], $ver_ref->[3], $ver_ref->[0], $ver_ref->[1]);
28         }
29 elsif (!$auto || ($auto == 3 && have_tty()) || $auto == 2) {
30         $ver_ref = ask_user($names_ref, $list_ref);
31         return ($ver_ref->[2], $ver_ref->[3], $ver_ref->[0], $ver_ref->[1]);
32         }
33 else {
34         print "Failed to detect operating system\n";
35         exit 1;
36         }
37 }
38
39 # Return a reference to a pre-parsed list array, and a ref to a names array
40 sub parse_patterns {
41 my ($oslist) = @_;
42 my @list;
43 my @names;
44 my %donename;
45 # Parse the patterns file
46 open(OS, "<$oslist") || die "failed to open $oslist : $!";
47 while(<OS>) {
48         chop;
49         if (/^([^\t]+)\t+([^\t]+)\t+([^\t]+)\t+([^\t]+)\t*(.*)$/) {
50                 push(@list, [ $1, $2, $3, $4, $5 ]);
51                 push(@names, $1) if (!$donename{$1}++);
52                 $NAMES_TO_REAL{$1} ||= $3;
53                 }
54         }
55 close(OS);
56 return (\@list, \@names);
57 }
58
59 # auto_detect($oslist, $issue)
60 # Returns detected OS details in a hash ref
61 sub auto_detect {
62 my ($oslist, $issue, $list_ref) = @_;
63 my $ver_ref;
64 my @list = @$list_ref;
65
66 # Try to guess the OS name and version
67 my $etc_issue;
68 my $uname = `uname -a`;
69
70 if ($issue) {
71         $etc_issue = `cat $issue`;
72         $uname = $etc_issue; # Strangely, I think this will work fine.
73         }
74 elsif (-r "/etc/.issue") {
75         $etc_issue = `cat /etc/.issue`;
76         }
77 elsif (-r "/etc/issue") {
78         $etc_issue = `cat /etc/issue`;
79         }
80
81 foreach my $o_ref (@list) {
82         if ($issue && $o_ref->[4]) {
83                 $o_ref->[4] =~ s#cat [/a-zA-Z\-\s]*\s2#cat $issue 2#g;
84                 } # Testable, but this regex substitution is dumb.XXX
85         local $^W = 0; # Disable warnings for evals, which may have undefined vars
86         if ($o_ref->[4] && eval "$o_ref->[4]") {
87                 # Got a match! Resolve the versions
88                 print "$o_ref->[4]\n";
89                 $ver_ref = $o_ref;
90                 if ($ver_ref->[1] =~ /\$/) {
91                         $ver_ref->[1] = eval "($o_ref->[4]); $ver_ref->[1]";
92                         }
93                 if ($ver_ref->[3] =~ /\$/) {
94                         $ver_ref->[3] = eval "($o_ref->[4]); $ver_ref->[3]";
95                         }
96                 last;
97                 }
98         if ($@) {
99                 print STDERR "Error parsing $o_ref->[4]\n";
100                 }
101         }
102         return $ver_ref;
103 }
104
105 sub ask_user {
106 my ($names_ref, $list_ref) = @_;
107 my @names = @$names_ref;
108 my @list = @$list_ref;
109 my $vnum;
110 my $osnum;
111 # ask for the operating system name ourselves
112 my $dashes = "-" x 75;
113 print <<EOF;
114 For Webmin to work properly, it needs to know which operating system
115 type and version you are running. Please select your system type by
116 entering the number next to it from the list below
117 $dashes
118 EOF
119 {
120 my $i;
121 for($i=0; $i<@names; $i++) {
122         printf " %2d) %-20.20s ", $i+1, $names[$i];
123         print "\n" if ($i%3 == 2);
124         }
125 print "\n" if ($i%3);
126 }
127 print $dashes,"\n";
128 print "Operating system: ";
129 chop($osnum = <STDIN>);
130 if ($osnum !~ /^\d+$/) {
131         print "ERROR: You must enter the number next to your operating\n";
132         print "system, not its name or version number.\n\n";
133         exit 9;
134         }
135 if ($osnum < 1 || $osnum > @names) {
136         print "ERROR: $osnum is not a valid operating system number.\n\n";
137         exit 10;
138         }
139 print "\n";
140
141 # Ask for the operating system version
142 my $name = $names[$osnum-1];
143 print <<EOF;
144 Please enter the version of $name you are running
145 EOF
146 print "Version: ";
147 chop($vnum = <STDIN>);
148 if ($vnum !~ /^\S+$/) {
149         print "ERROR: An operating system number cannot contain\n\n";
150         print "spaces. It must be like 2.1 or ES4.0.\n";
151         exit 10;
152         }
153 print "\n";
154 return [ $name, $vnum,
155           $NAMES_TO_REAL{$name}, $vnum ];
156 }
157
158 # write_file($out, $os_type, $os_version, $real_os_type, $real_os_version)
159 # Write the name, version and real name and version to a file
160 sub write_file {
161 my ($out, $os_type, $os_version, $real_os_type, $real_os_version) = @_;
162 open(OUT, ">$out") or die "Failed to open $out for writing.";
163 print OUT "os_type='",$os_type,"'\n";
164 print OUT "os_version='",$os_version,"'\n";
165 print OUT "real_os_type='",$real_os_type,"'\n";
166 print OUT "real_os_version='",$real_os_version,"'\n";
167 return close(OUT);
168 }
169
170 sub have_tty
171 {
172 # Do we have a tty?
173 my $rv = system("tty >/dev/null 2>&1");
174 if ($?) {
175         return 0;
176         }
177 else {
178         return 1;
179         }
180 }
181
182 1;
183
184 __END__
185
186 =head1 OsChooser.pm
187
188 Attempt to detect operating system and version, or ask the user to select
189 from a list.  Works from the command line, for usage from shell scripts,
190 or as a library for use within Perl scripts.
191
192 =head2 COMMAND LINE USE
193
194 OsChooser.pm os_list.txt outfile [auto] [issue]
195
196 Where "auto" can be the following values:
197
198 =over 4
199
200 =item 0
201
202 always ask user
203
204 =item 1
205
206 automatic, give up if fails
207
208 =item 2
209
210 automatic, ask user if fails
211
212 =item 3
213
214 automatic, ask user if fails and if a TTY
215
216 =back
217
218 =head2 SYNOPSIS
219
220     use OsChooser;
221     my ($os_type, $version, $real_os_type, $real_os_version) =
222        OsChooser->oschooser("os_list.txt", "outfile", $auto, [$issue]);
223
224 =cut
225