1 # Functions for checking for updates to packages from YUM, APT or some other
4 BEGIN { push(@INC, ".."); };
5 eval "use WebminCore;";
7 &foreign_require("software", "software-lib.pl");
8 &foreign_require("cron", "cron-lib.pl");
9 &foreign_require("webmin", "webmin-lib.pl");
12 $available_cache_file = "$module_config_directory/available.cache";
13 $current_cache_file = "$module_config_directory/current.cache";
14 $updates_cache_file = "$module_config_directory/updates.cache";
15 $cron_cmd = "$module_config_directory/update.pl";
17 $yum_cache_file = "$module_config_directory/yumcache";
18 $apt_cache_file = "$module_config_directory/aptcache";
19 $yum_changelog_cache_dir = "$module_config_directory/yumchangelog";
21 # get_software_packages()
22 # Fills in software::packages with list of installed packages (if missing),
24 sub get_software_packages
26 if (!$get_software_packages_cache) {
27 %software::packages = ( );
28 $get_software_packages_cache = &software::list_packages();
30 return $get_software_packages_cache;
33 # list_current(nocache)
34 # Returns a list of packages and versions for installed software. Keys are :
35 # name - The my package name (ie. CSWapache2)
36 # update - Name used to refer to it by the updates system (ie. apache2)
37 # version - Version number
38 # epoch - Epoch part of the version
39 # desc - Human-readable description
43 if ($nocache || &cache_expired($current_cache_file)) {
44 my $n = &get_software_packages();
46 for(my $i=0; $i<$n; $i++) {
47 push(@rv, { 'name' => $software::packages{$i,'name'},
48 'update' => $software::packages{$i,'name'},
50 $software::packages{$i,'version'},
52 $software::packages{$i,'epoch'},
54 $software::packages{$i,'desc'},
55 'system' => $software::update_system,
57 &fix_pkgadd_version($rv[$#rv]);
60 # Filter out dupes and sort by name
61 @rv = &filter_duplicates(\@rv);
63 &write_cache_file($current_cache_file, \@rv);
67 return &read_cache_file($current_cache_file);
71 # list_available(nocache)
72 # Returns the names and versions of packages available from the update system
76 my $expired = &cache_expired($available_cache_file);
77 if ($nocache || $expired == 2 ||
78 $expired == 1 && !&check_available_lock()) {
79 # Get from update system
81 my @avail = &packages_available();
82 foreach my $avail (@avail) {
83 $avail->{'update'} = $avail->{'name'};
84 $avail->{'name'} = &csw_to_pkgadd($avail->{'name'});
88 # Filter out dupes and sort by name
89 @rv = &filter_duplicates(\@rv);
92 # Failed .. fall back to cache
93 @rv = &read_cache_file($available_cache_file);
95 &write_cache_file($available_cache_file, \@rv);
99 return &read_cache_file($available_cache_file);
103 # check_available_lock()
104 # Returns 1 if the package update system is currently locked
105 sub check_available_lock
107 if ($software::update_system eq "yum") {
108 return &check_pid_file("/var/run/yum.pid");
113 # filter_duplicates(&packages)
114 # Given a list of package structures, orders them by name and version number,
115 # and removes dupes with the same name
116 sub filter_duplicates
119 my @rv = sort { $a->{'name'} cmp $b->{'name'} ||
120 &compare_versions($b, $a) } @$pkgs;
122 return grep { !$done{$_->{'name'},$_->{'system'}}++ } @rv;
125 # cache_expired(file)
126 # Checks if some cache has expired. Returns 0 if OK, 1 if expired, 2 if
131 my @st = stat($file);
133 if (!$config{'cache_time'} || time()-$st[9] > $config{'cache_time'}*60*60) {
141 my ($file, $arr) = @_;
142 &open_tempfile(FILE, ">$file");
143 &print_tempfile(FILE, Dumper($arr));
144 &close_tempfile(FILE);
145 $read_cache_file_cache{$file} = $arr;
148 # read_cache_file(file)
149 # Returns the contents of some cache file, as an array ref
153 if (defined($read_cache_file_cache{$file})) {
154 return @{$read_cache_file_cache{$file}};
158 $read_cache_file_cache{$file} = $VAR1;
164 # compare_versions(&pkg1, &pk2)
165 # Returns -1 if the version of pkg1 is older than pkg2, 1 if newer, 0 if same.
168 my ($pkg1, $pkg2) = @_;
169 if ($pkg1->{'system'} eq 'webmin' && $pkg2->{'system'} eq 'webmin') {
170 # Webmin module version compares are always numeric
171 return $pkg1->{'version'} <=> $pkg2->{'version'};
173 my $ec = $pkg1->{'epoch'} <=> $pkg2->{'epoch'};
174 if ($ec && ($pkg1->{'epoch'} eq '' || $pkg2->{'epoch'} eq '') &&
175 $pkg1->{'system'} eq 'apt') {
176 # On some Debian systems, we don't have a my epoch
180 &software::compare_versions($pkg1->{'version'}, $pkg2->{'version'});
185 my @jobs = &cron::list_cron_jobs();
186 my ($job) = grep { $_->{'user'} eq 'root' &&
187 $_->{'command'} eq $cron_cmd } @jobs;
191 # packages_available()
192 # Returns a list of all available packages, as hash references with name and
193 # version keys. These come from the APT, YUM or CSW update system, if available.
194 # If not, nothing is returned.
195 sub packages_available
197 if (@packages_available_cache) {
198 return @packages_available_cache;
200 if (defined(&software::update_system_available)) {
201 # From a decent package system
202 my @rv = software::update_system_available();
204 foreach my $p (@rv) {
205 $p->{'system'} = $software::update_system;
206 $p->{'version'} =~ s/,REV=.*//i; # For CSW
207 if ($p->{'system'} eq 'apt' && !$p->{'source'}) {
209 $p->{'file'} =~ /virtualmin/i ? 'virtualmin' :
210 $p->{'file'} =~ /debian/i ? 'debian' :
211 $p->{'file'} =~ /ubuntu/i ? 'ubuntu' : undef;
213 $done{$p->{'name'}} = $p;
215 if ($software::update_system eq "yum" &&
216 &has_command("up2date")) {
217 # YUM is the package system select, but up2date is installed
218 # too (ie. RHEL). Fetch its packages too..
219 if (!$done_rhn_lib++) {
220 do "../software/rhn-lib.pl";
222 my @rhnrv = &update_system_available();
223 foreach my $p (@rhnrv) {
224 $p->{'system'} = "rhn";
225 my $d = $done{$p->{'name'}};
227 # Seen already .. but is this better?
228 if (&compare_versions($p, $d) > 0) {
230 @rv = grep { $_ ne $d } @rv;
232 $done{$p->{'name'}} = $p;
237 $done{$p->{'name'}} = $p;
241 @packages_available_cache = @rv;
247 sub supports_updates_available
249 return defined(&software::update_system_updates);
252 # updates_available(no-cache)
253 # Returns an array of hash refs of package updates available, according to
254 # the update system, with caching.
255 sub updates_available
258 if (!scalar(@updates_available_cache)) {
259 if ($nocache || &cache_expired($updates_cache_file)) {
260 # Get from original source
261 @updates_available_cache = &software::update_system_updates();
262 foreach my $a (@updates_available_cache) {
263 $a->{'update'} = $a->{'name'};
264 $a->{'system'} = $software::update_system;
266 &write_cache_file($updates_cache_file,
267 \@updates_available_cache);
271 @updates_available_cache =
272 &read_cache_file($updates_cache_file);
275 return @updates_available_cache;
278 # package_install(package-name, [system])
279 # Install some package, either from an update system or from Webmin. Returns
280 # a list of updated package names.
283 my ($name, $system) = @_;
287 # First get from list of updates
288 ($pkg) = grep { $_->{'update'} eq $name &&
289 ($_->{'system'} eq $system || !$system) }
290 sort { &compare_versions($b, $a) }
291 &list_possible_updates(0);
293 # Then try list of all available packages
294 ($pkg) = grep { $_->{'update'} eq $name &&
295 ($_->{'system'} eq $system || !$system) }
296 sort { &compare_versions($b, $a) }
300 print &text('update_efindpkg', $name),"<p>\n";
303 if (defined(&software::update_system_install)) {
304 # Using some update system, like YUM or APT
305 &clean_environment();
306 if ($software::update_system eq $pkg->{'system'}) {
307 # Can use the default system
308 @rv = &software::update_system_install($name, undef, 1);
311 # Another update system exists!! Use it..
312 if (!$done_rhn_lib++) {
313 do "../software/$pkg->{'system'}-lib.pl";
315 if (!$done_rhn_text++) {
316 %text = ( %text, %software::text );
318 @rv = &update_system_install($name, undef, 1);
320 &reset_environment();
323 &error("Don't know how to install package $pkg->{'name'} with type $pkg->{'type'}");
325 # Flush installed cache
326 unlink($current_cache_file);
330 # package_install_multiple(&package-names, system)
331 # Install multiple packages, either from an update system or from Webmin.
332 # Returns a list of updated package names.
333 sub package_install_multiple
335 my ($names, $system) = @_;
339 if (defined(&software::update_system_install)) {
340 # Using some update system, like YUM or APT
341 &clean_environment();
342 if ($software::update_system eq $system) {
343 # Can use the default system
344 @rv = &software::update_system_install(
345 join(" ", @$names), undef, 1);
348 # Another update system exists!! Use it..
349 if (!$done_rhn_lib++) {
350 do "../software/$pkg->{'system'}-lib.pl";
352 if (!$done_rhn_text++) {
353 %text = ( %text, %software::text );
355 @rv = &update_system_install(join(" ", @$names), undef, 1);
357 &reset_environment();
360 &error("Don't know how to install packages");
362 # Flush installed cache
363 unlink($current_cache_file);
367 # list_package_operations(package|packages, system)
368 # Given a package (or space-separate package list), returns a list of all
369 # dependencies that will be installed
370 sub list_package_operations
372 my ($name, $system) = @_;
373 if (defined(&software::update_system_operations)) {
374 my @rv = &software::update_system_operations($name);
375 foreach my $p (@rv) {
376 $p->{'system'} = $system;
383 # list_possible_updates([nocache])
384 # Returns a list of updates that are available. Each element in the array
385 # is a hash ref containing a name, version, description and severity flag.
386 # Intended for calling from themes. Nocache 0=cache everything, 1=flush all
387 # caches, 2=flush only current
388 sub list_possible_updates
392 my @current = &list_current($nocache);
393 if (&supports_updates_available()) {
394 # Software module supplies a function that can list just packages
397 foreach my $c (@current) {
398 $currentmap{$c->{'name'},$c->{'system'}} ||= $c;
400 foreach my $a (&updates_available($nocache == 1)) {
401 my $c = $currentmap{$a->{'name'},$a->{'system'}};
403 next if ($a->{'version'} eq $c->{'version'});
404 push(@rv, { 'name' => $a->{'name'},
405 'update' => $a->{'update'},
406 'system' => $a->{'system'},
407 'version' => $a->{'version'},
408 'oldversion' => $c->{'version'},
409 'epoch' => $a->{'epoch'},
410 'oldepoch' => $c->{'epoch'},
411 'security' => $a->{'security'},
412 'source' => $a->{'source'},
413 'desc' => $c->{'desc'} || $a->{'desc'} });
417 # Compute from current and available list
418 my @avail = &list_available($nocache == 1);
420 foreach my $a (@avail) {
421 my $oa = $availmap{$a->{'name'},$a->{'system'}};
422 if (!$oa || &compare_versions($a, $oa) > 0) {
423 $availmap{$a->{'name'},$a->{'system'}} = $a;
426 foreach my $c (sort { $a->{'name'} cmp $b->{'name'} } @current) {
427 # Work out the status
428 my $a = $availmap{$c->{'name'},$c->{'system'}};
429 if ($a->{'version'} && &compare_versions($a, $c) > 0) {
430 # A regular update is available
431 push(@rv, { 'name' => $a->{'name'},
432 'update' => $a->{'update'},
433 'system' => $a->{'system'},
434 'version' => $a->{'version'},
435 'oldversion' => $c->{'version'},
436 'epoch' => $a->{'epoch'},
437 'oldepoch' => $c->{'epoch'},
438 'security' => $a->{'security'},
439 'source' => $a->{'source'},
440 'desc' => $c->{'desc'} || $a->{'desc'},
448 # list_possible_installs([nocache])
449 # Returns a list of packages that could be installed, but are not yet
450 sub list_possible_installs
454 my @current = &list_current($nocache);
455 my @avail = &list_available($nocache == 1);
456 foreach my $a (sort { $a->{'name'} cmp $b->{'name'} } @avail) {
457 ($c) = grep { $_->{'name'} eq $a->{'name'} &&
458 $_->{'system'} eq $a->{'system'} } @current;
459 if (!$c && &installation_candiate($a)) {
460 push(@rv, { 'name' => $a->{'name'},
461 'update' => $a->{'update'},
462 'system' => $a->{'system'},
463 'version' => $a->{'version'},
464 'epoch' => $a->{'epoch'},
465 'desc' => $a->{'desc'},
472 # csw_to_pkgadd(package)
473 # On Solaris systems, convert a CSW package name like ap2_modphp5 to a
474 # real package name like CSWap2modphp5
478 if ($gconfig{'os_type'} eq 'solaris') {
485 # fix_pkgadd_version(&package)
486 # If this is Solaris and the package version is missing, we need to make
487 # a separate pkginfo call to get it.
488 sub fix_pkgadd_version
491 if ($gconfig{'os_type'} eq 'solaris') {
492 if (!$pkg->{'version'}) {
493 # Make an extra call to get the version
494 my @pinfo = &software::package_info($pkg->{'name'});
495 $pinfo[4] =~ s/,REV=.*//i;
496 $pkg->{'version'} = $pinfo[4];
500 $pkg->{'version'} =~ s/,REV=.*//i;
503 $pkg->{'desc'} =~ s/^\Q$pkg->{'update'}\E\s+\-\s+//;
506 # installation_candiate(&package)
507 # Returns 1 if some package can be installed, even when it currently isn't.
508 # Always true for now.
509 sub installation_candiate
515 # clear_repository_cache()
516 # Clear any YUM or APT caches
517 sub clear_repository_cache
519 if ($software::update_system eq "yum") {
520 &execute_command("yum clean all");
522 elsif ($software::update_system eq "apt") {
523 &execute_command("apt-get update");
527 # split_epoch(version)
528 # Splits a version formatted like 5:x.yy into an epoch and real version
532 if ($ver =~ /^(\d+):(\S+)$/) {
535 return (undef, $ver);
538 # get_changelog(&pacakge)
539 # If possible, returns information about what has changed in some update
543 if ($pkg->{'system'} eq 'yum') {
544 # See if yum supports changelog
545 if (!defined($supports_yum_changelog)) {
546 my $out = &backquote_command("yum -h 2>&1 </dev/null");
547 $supports_yum_changelog = $out =~ /changelog/ ? 1 : 0;
549 return undef if (!$supports_yum_changelog);
551 # Check if we have this info cached
552 my $cfile = $yum_changelog_cache_dir."/".
553 $pkg->{'name'}."-".$pkg->{'version'};
554 my $cl = &read_file_contents($cfile);
556 # Run it for this package and version
558 &open_execute_command(YUMCL, "yum changelog all ".
559 quotemeta($pkg->{'name'}), 1, 1);
562 if (/^\Q$pkg->{'name'}-$pkg->{'version'}\E/) {
565 elsif (/^==========/ || /^changelog stats/) {
575 if (!-d $yum_changelog_cache_dir) {
576 &make_dir($yum_changelog_cache_dir, 0700);
578 &open_tempfile(CACHE, ">$cfile");
579 &print_tempfile(CACHE, $cl);
580 &close_tempfile(CACHE);
587 sub flush_package_caches
589 unlink($current_cache_file);
590 unlink($updates_cache_file);
591 unlink($available_cache_file);
592 unlink($available_cache_file.'0');
593 unlink($available_cache_file.'1');
594 @packages_available_cache = ( );
595 %read_cache_file_cache = ( );
598 # list_for_mode(mode, nocache)
599 # If not is 'updates' or 'security', return just updates. Othewise, return
600 # all available packages.
603 my ($mode, $nocache) = @_;
604 return $mode eq 'updates' || $mode eq 'security' ?
605 &list_possible_updates($nocache) : &list_available($nocache);