Handle hostnames with upper-case letters
[webmin.git] / software / apt-lib.pl
1 # apt-lib.pl
2 # Functions for installing packages from debian APT
3
4 $apt_get_command = $config{'apt_mode'} ? "aptitude" : "apt-get";
5 $apt_search_command = $config{'apt_mode'} ? "aptitude" : "apt-cache";
6
7 sub list_update_system_commands
8 {
9 return ($apt_get_command, $apt_search_command);
10 }
11
12 # update_system_install([package], [&in], [no-force])
13 # Install some package with apt
14 sub update_system_install
15 {
16 local $update = $_[0] || $in{'update'};
17 local $force = !$_[2];
18 local (@rv, @newpacks);
19
20 # Build the command to run
21 $ENV{'DEBIAN_FRONTEND'} = 'noninteractive';
22 local $cmd = $apt_get_command eq "apt-get" ?
23   "$apt_get_command -y ".($force ? " --force-yes -f" : "")." install $update" :
24   "$apt_get_command -y".($force ? " -f" : "")." install $update";
25 $update = join(" ", map { quotemeta($_) } split(/\s+/, $update));
26 print "<b>",&text('apt_install', "<tt>$cmd</tt>"),"</b><p>\n";
27 print "<pre>";
28 &additional_log('exec', undef, $cmd);
29
30 # Run dpkg --configure -a to clear any un-configured packages
31 $SIG{'TERM'} = 'ignore';        # This may cause a Webmin re-config!
32 local $out = &backquote_logged("dpkg --configure -a 2>&1 </dev/null");
33 print &html_escape($out);
34
35 # Create an input file of 'yes'
36 local $yesfile = &transname();
37 &open_tempfile(YESFILE, ">$yesfile", 0, 1);
38 foreach (0..100) {
39         &print_tempfile(YESFILE, "Yes\n");
40         }
41 &close_tempfile(YESFILE);
42
43 # Run the command
44 &open_execute_command(CMD, "$cmd <$yesfile", 2);
45 while(<CMD>) {
46         if (/setting\s+up\s+(\S+)/i && !/as\s+MDA/i) {
47                 push(@rv, $1);
48                 }
49         elsif (/packages\s+will\s+be\s+upgraded/i ||
50                /new\s+packages\s+will\s+be\s+installed/i) {
51                 print;
52                 $line = $_ = <CMD>;
53                 $line =~ s/^\s+//; $line =~ s/\s+$//;
54                 push(@newpacks, split(/\s+/, $line));
55                 }
56         print &html_escape("$_");
57         }
58 close(CMD);
59 if (!@rv && $config{'package_system'} ne 'debian' && !$?) {
60         # Other systems don't list the packages installed!
61         @rv = @newpacks;
62         }
63 print "</pre>\n";
64 if ($?) { print "<b>$text{'apt_failed'}</b><p>\n"; }
65 else { print "<b>$text{'apt_ok'}</b><p>\n"; }
66 return @rv;
67 }
68
69 # update_system_operations(packages)
70 # Given a list of packages, returns a list containing packages that will
71 # actually get installed, each of which is a hash ref with name and version.
72 sub update_system_operations
73 {
74 my ($packages) = @_;
75 $ENV{'DEBIAN_FRONTEND'} = 'noninteractive';
76 my $cmd = "apt-get -s install ".
77           join(" ", map { quotemeta($_) } split(/\s+/, $packages)).
78           " </dev/null 2>&1";
79 my $out = &backquote_command($cmd);
80 my @rv;
81 foreach my $l (split(/\r?\n/, $out)) {
82         if ($l =~ /Inst\s+(\S+)\s+\[(\S+)\]\s+\(([^ \)]+)/ ||
83             $l =~ /Inst\s+(\S+)\s+\[(\S+)\]/) {
84                 my $pkg = { 'name' => $1,
85                             'version' => $3 || $2 };
86                 if ($pkg->{'version'} =~ s/^(\S+)://) {
87                         $pkg->{'epoch'} = $1;
88                         }
89                 push(@rv, $pkg);
90                 }
91         }
92 return @rv;
93 }
94
95 # update_system_form()
96 # Shows a form for updating all packages on the system
97 sub update_system_form
98 {
99 print &ui_subheading($text{'apt_form'});
100 print &ui_form_start("apt_upgrade.cgi");
101 print &ui_table_start($text{'apt_header'}, undef, 2);
102
103 print &ui_table_row($text{'apt_update'},
104         &ui_yesno_radio("update", 1));
105
106 print &ui_table_row($text{'apt_mode'},
107         &ui_radio("mode", 0, [ [ 2, $text{'apt_mode2'} ],
108                                [ 1, $text{'apt_mode1'} ],
109                                [ 0, $text{'apt_mode0'} ] ]));
110
111 print &ui_table_row($text{'apt_sim'},
112         &ui_yesno_radio("sim", 0));
113
114 print &ui_table_end();
115 print &ui_form_end([ [ undef, $text{'apt_apply'} ] ]);
116 }
117
118 # update_system_resolve(name)
119 # Converts a standard package name like apache, sendmail or squid into
120 # the name used by APT.
121 sub update_system_resolve
122 {
123 local ($name) = @_;
124 return $name eq "dhcpd" ? "dhcp3-server" :
125        $name eq "bind" ? "bind9" :
126        $name eq "mysql" ? "mysql-client mysql-server mysql-admin" :
127        $name eq "apache" ? "apache2" :
128        $name eq "postgresql" ? "postgresql postgresql-client" :
129        $name eq "openssh" ? "ssh" :
130        $name eq "openldap" ? "slapd" :
131        $name eq "dovecot" ? "dovecot-common dovecot-imapd dovecot-pop3d" :
132                                $name;
133 }
134
135 # update_system_available()
136 # Returns a list of package names and versions that are available from APT
137 sub update_system_available
138 {
139 local (@rv, $pkg, %done);
140
141 # Use dump to get versions
142 &execute_command("$apt_get_command update");
143 &clean_language();
144 &open_execute_command(DUMP, "apt-cache dump 2>/dev/null", 1, 1);
145 while(<DUMP>) {
146         if (/^\s*Package:\s*(\S+)/) {
147                 $pkg = { 'name' => $1 };
148                 push(@rv, $pkg);
149                 $done{$1} = $pkg;
150                 }
151         elsif (/^\s*Version:\s*(\S+)/ && $pkg && !$pkg->{'version'}) {
152                 $pkg->{'version'} = $1;
153                 if ($pkg->{'version'} =~ /^(\d+):(.*)$/) {
154                         $pkg->{'epoch'} = $1;
155                         $pkg->{'version'} = $2;
156                         }
157                 }
158         elsif (/^\s*File:\s*(\S+)/ && $pkg) {
159                 $pkg->{'file'} ||= $1;
160                 }
161         }
162 close(DUMP);
163 &reset_environment();
164
165 # Use search to get descriptions
166 foreach my $s (&update_system_search('.*')) {
167         my $pkg = $done{$s->{'name'}};
168         if ($pkg) {
169                 $pkg->{'desc'} = $s->{'desc'};
170                 }
171         }
172
173 &set_pinned_versions(\@rv);
174 return @rv;
175 }
176
177 # update_system_search(text)
178 # Returns a list of packages matching some search
179 sub update_system_search
180 {
181 local (@rv, $pkg);
182 &clean_language();
183 &open_execute_command(DUMP, "$apt_search_command search ".
184                             quotemeta($_[0])." 2>/dev/null", 1, 1);
185 while(<DUMP>) {
186         if (/^(\S+)\s*-\s*(.*)/) {
187                 push(@rv, { 'name' => $1, 'desc' => $2 });
188                 }
189         elsif (/^(\S)\s+(\S+)\s+-\s*(.*)/) {
190                 push(@rv, { 'name' => $2, 'desc' => $3 });
191                 }
192         }
193 close(DUMP);
194 &reset_environment();
195 return @rv;
196 }
197
198 # update_system_updates()
199 # Returns a list of available package updates
200 sub update_system_updates
201 {
202 &execute_command("$apt_get_command update");
203
204 # Find held packages by dpkg
205 local %holds;
206 if ($config{'package_system'} eq 'debian') {
207         &clean_language();
208         &open_execute_command(HOLDS, "dpkg --get-selections", 1, 1);
209         while(<HOLDS>) {
210                 if (/^(\S+)\s+hold/) {
211                         $holds{$1}++;
212                         }
213                 }
214         close(HOLDS);
215         &reset_environment();
216         }
217
218 if (&has_command("apt-show-versions")) {
219         # This awesome command can give us all updates in one hit, and takes
220         # pinned versions and backports into account
221         local @rv;
222         &clean_language();
223         &open_execute_command(PKGS, "apt-show-versions 2>/dev/null", 1, 1);
224         while(<PKGS>) {
225                 if (/^(\S+)\/(\S+)\s+upgradeable\s+from\s+(\S+)\s+to\s+(\S+)/ &&
226                     !$holds{$1}) {
227                         local $pkg = { 'name' => $1,
228                                        'source' => $2,
229                                        'version' => $4 };
230                         if ($pkg->{'version'} =~ s/^(\S+)://) {
231                                 $pkg->{'epoch'} = $1;
232                                 }
233                         push(@rv, $pkg);
234                         }
235                 }
236         close(PKGS);
237         &reset_environment();
238         return @rv;
239         }
240 else {
241         # Need to manually compose by calling dpkg and apt-cache showpkg
242         local %packages;
243         local $n = &list_packages();
244         local %currentmap;
245         for(my $i=0; $i<$n; $i++) {
246                 local $pkg = { 'name' => $packages{$i,'name'},
247                                'oldversion' => $packages{$i,'version'},
248                                'desc' => $packages{$i,'desc'},
249                                'oldepoch' => $packages{$i,'epoch'} };
250                 $currentmap{$pkg->{'name'}} ||= $pkg;
251                 }
252         local @rv;
253         local @names = grep { !$holds{$_} } keys %currentmap;
254         while(scalar(@names)) {
255                 local @somenames;
256                 if (scalar(@names) > 100) {
257                         # Do 100 at a time
258                         @somenames = @names[0..99];
259                         @names = @names[100..$#names];
260                         }
261                 else {
262                         # Do the rest
263                         @somenames = @names;
264                         @names = ( );
265                         }
266                 &clean_language();
267                 &open_execute_command(PKGS, "apt-cache showpkg ".
268                         join(" ", @somenames)." 2>/dev/null", 1, 1);
269                 local $pkg = undef;
270                 while(<PKGS>) {
271                         s/\r|\n//g;
272                         if (/^\s*Package:\s*(\S+)/) {
273                                 $pkg = $currentmap{$1};
274                                 }
275                         elsif (/^Versions:\s*$/ && $pkg && !$pkg->{'version'}) {
276                                 # Newest version is on next line
277                                 local $ver = <PKGS>;
278                                 $ver =~ s/\s.*\r?\n//;
279                                 local $epoch;
280                                 if ($ver =~ s/^(\d+)://) {
281                                         $epoch = $1;
282                                         }
283                                 $pkg->{'version'} = $ver;
284                                 $pkg->{'epoch'} = $epoch;
285                                 local $newer =
286                                     $pkg->{'epoch'} <=> $pkg->{'oldepoch'} ||
287                                     &compare_versions($pkg->{'version'},
288                                                       $pkg->{'oldversion'});
289                                 if ($newer > 0) {
290                                         push(@rv, $pkg);
291                                         }
292                                 }
293                         }
294                 close(PKGS);
295                 &reset_environment();
296                 }
297         &set_pinned_versions(\@rv);
298         return @rv;
299         }
300 }
301
302 # set_pinned_versions(&package-list)
303 # Updates the version and epoch fields in a list of available packages based
304 # on pinning.
305 sub set_pinned_versions
306 {
307 local ($pkgs) = @_;
308 local %pkgmap = map { $_->{'name'}, $_ } @$pkgs;
309 &clean_language();
310 &open_execute_command(PKGS, "apt-cache policy 2>/dev/null", 1, 1);
311 while(<PKGS>) { 
312         s/\r|\n//g;
313         if (/\s+(\S+)\s+\-\>\s+(\S+)/) {
314                 my ($name, $pin) = ($1, $2);
315                 my $pkg = $pkgmap{$name};
316                 if ($pkg) {
317                         $pkg->{'version'} = $pin;
318                         $pkg->{'epoch'} = undef;
319                         if ($pkg->{'version'} =~ s/^(\S+)://) {
320                                 $pkg->{'epoch'} = $1;
321                                 }
322                         }
323                 }
324         }
325 close(PKGS);
326 &reset_environment();
327 }
328