Annotation of loncom/build/check-rpms, revision 1.6
1.1 harris41 1: #!/usr/bin/perl
2: #
1.3 harris41 3: # check-rpms, version 2.1.1
1.1 harris41 4: # Martin Siegert, SFU, siegert@sfu.ca, Feb 02
5: #
1.4 harris41 6: # documentation and minor patches,
7: # Scott Harrison sharrison@users.sourceforge.net 2002
8:
1.2 harris41 9: =pod
10:
11: =head1 NAME
12:
13: check-rpms - compare installed rpms with up-to-date distribution
14:
15: =head1 DESCRIPTION
16:
17: I<check-rpms> compares installed RPM packages (listed by the command
18: "rpm -qa") on a Linux system with an up-to-date distribution. That
19: distribution may either reside in a local directory (possibly NFS
20: mounted) or on a ftp server. If the B<-ftp> option is specified,
21: I<check-rpms> retrieves directory listings from the I<ftpserver>'s
22: I<directory>/<arch> directories, where <arch> is set to noarch, i386,
23: i586, i686, and athlon consecutively. If I<ftpserver/directory> is
24: not specified, $FTPSERVER/$FTPUPDATES is used. The $FTPSERVER and
25: $FTPUPDATES variables can be set in the configuration file. If
26: either of the two is not set, the default server "updates.redhat.com"
27: and the default directory "$RHversion/en/os" is used,
28: where $RHversion is obtained from the /etc/redhat-release file. If
29: run with the B<-ftp> option, all rpm packages that need to be downloaded
30: (see the B<--download>, B<--recheck>, and B<--update> options) will
31: be downloaded into the directory specified by the B<-d> directory
32: option. If that option is omitted the $RPMDIR directory is used.
33: The $RPMDIR variable that can be set in the configuration file. If
34: $RPMDIR variable is not set either, the default directory
35: "/mnt/redhat/RedHat/RPMS" is used.
36:
37: If the B<-ftp> is omitted, it is assumed that B<-d> I<directory> specifies
38: a local directory that contains up-to-date rpm packages. If B<-d>
39: I<directory> is omitted as well, the $RPMDIR directory is used. If
40: $RPMDIR is not set, the default directory "/mnt/redhat/Red-
41: Hat/RPMS" is used.
42:
43: I<check-rpms> uses a lexical sort on the version string and the
44: release string of the package in order to decide whether the
45: installed package or the package form the distribution is newer.
46: I<check-rpms> lists packages of the distribution that are found to be
47: newer than the installed packages or, if B<--update> is specified,
48: will update the packages using the "rpm -Fvh <list of packages>"
49: command. In the latter case I<check-rpms> must be run as root. Fur-
50: thermore, the $RPMUSER variable should be set to a non-root user-
51: name (see the B<-c> option below). I<check-rpms> will switch to that
52: user and run most of the script under that user id.Only the
53: final "rpm -Fvh ..." command will be run as root. If $RPMUSER is
54: not set, the "nobody" user id will be used. It is recommended to
55: set $RPMUSER to an ordinary username (such as yourself). Further-
56: more, if a ftp server is used, create the download directory
57: (which is specified in the B<-d> directory option or in the $RPMDIR
58: variable), change the owner ship of that directory to that user,
59: and set the permissions to 700 before running I<check-rpms> with the
60: B<--update> option. Note, that B<--update> implies the B<--no-kernel>
61: option, i.e., I<check-rpms> refuses to update the kernel directly.
62:
63: =cut
64:
1.1 harris41 65: # ************ WARNING *****************************************************
66: # THIS PROGRAM IS PROVIDED "AS IS" WITHOUT
67: # WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLICIT.
68: # IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
69: # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
70: # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
71: # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
72: # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
73: # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
74: # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
75: # SUCH DAMAGE.
76: # **************************************************************************
77:
78: # check-rpms.pl is free software; you can redistribute it and/or modify
79: # it under the terms of the GNU General Public License as published by
80: # the Free Software Foundation; either version 2 of the License, or
81: # (at your option) any later version.
82: #
83: # check-rpms.pl is distributed in the hope that it will be useful,
84: # but WITHOUT ANY WARRANTY; without even the implied warranty of
85: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86: # GNU General Public License for more details:
87: # http://www.gnu.org/licenses/gpl.html
88:
89: use Getopt::Long;
90:
91: my $retval = &GetOptions("verbose|v","lm|list-missing","lq|list-questionable",
92: "dir|d=s","ftp:s","noftp","download|dl","recheck|r",
1.5 harris41 93: "nk|no-kernel","update","c=s","rpmuser=s");
1.1 harris41 94:
1.2 harris41 95: =pod
96:
97: =head1 OPTIONS
98:
99: =over 4
100:
101: =item B<-v> B<--verbose>
102:
103: verbose mode: prints additional progress information on
104: standard output
105:
106: =item B<-ftp> [I<ftpserver/directory>]
107:
108: compare the installed packages with the rpm packages found
109: on the ftp server I<ftpserver> in the directories I<directory>/<arch>,
110: where arch is set to noarch, i386, i586, i686,
111: and athlon consecutively. If I<ftpserver/directory> is not
112: specified, the $FTPSERVER and $FTPUPDATES variables are
113: checked. These variables can be set in the configuration
114: file (see the B<-c> option below). If those variables are not
115: set either, the default server "updates.redhat.com" and the
116: default directory "$RHversion/en/os" is used, where $RHversion
117: is obtained from the I</etc/redhat-release> file.
118:
119: =item B<-noftp>
120:
121: use a local directory as the source for new rpm packages
122: even if the $FTP veriable is set to 1 in the configuration
123: file.
124:
125: =item B<-d> I<directory> B<--rpm-directory> I<directory>
126:
127: if B<-ftp> is specified download all rpm packages that need to
128: be downloaded into I<directory>. If B<-ftp> is not specified,
129: regard the rpm packages found in I<directory> as an up-to-date
130: distribution against which the installed packages are
131: compared to.
132:
133: =item B<-lm> B<--list-missing>
134:
135: list installed packages that do not have an equivalent in
136: the up-to-date distribution. This will generate lots of
137: output when the comparison is made with the updates directory
138: of a ftp server.
139:
140: =item B<-lq> B<--list-questionable>
141:
142: list packages for which the lexical sort algorithm does not
143: give a conclusive result on whether the installed package
144: is older than the package in the distribution. These are
145: packages that have version and/or release strings that contain
146: letters. For example, it is not absolutely clear
147: whether the version 1.2.3b is actually newer or older than
148: 1.2.3. The lexical sort would classify 1.2.3b to be newer
149: than 1.2.3; with B<-lq> specified the package would be listed
150: in any case. See also B<--recheck> below.
151:
152: =item B<-dl> B<--download>
153:
154: download packages from the remote ftp server that are found
155: to be newer than installed packages into the directory that
156: is specified in the B<-d> I<directory> option or in the $RPMDIR
157: variable or, if neither of the two are specified, into
158: "/mnt/redhat/RedHat/RPMS". If the download directory does
159: not exist, I<check-rpms> will create it.
160:
161: =item B<-r> B<--recheck>
162:
163: Use the "rpm -Uvh --test --nodeps <package>" command to
164: check all packages that have letters in their version
165: and/or release string; B<--recheck> implies B<--list-questionable>
166: (see above). At the time of writing (Feb. 2002) there
167: is one known case for which the lexical sort algorithm
168: fails to detect a new package: mutt-1.2.5.1 was released to
169: replace mutt-1.2.5i, however, the lexical sort algorithm
170: incorrectly classifies mutt-1.2.5i to be newer than
171: mutt-1.2.5.1. In this case using the B<--recheck> option is
172: essential. In all other cases it is not. It is nevertheless
173: probably a good idea to use B<--recheck> at least once in a
174: while. B<--recheck> can increase the run-time of I<check-rpms>
175: substantially, particularly if a ftp server is used. In
176: that case the questionable packages must be downloaded from
177: the server into a directory I<directory> (as specified in the
178: -d option or the $RPMDIR variable) which will be created,
179: if it does not exist.
180:
181: =item B<-nk> B<--no-kernel>
182:
183: do not list kernel packages. That is, kernel, kernel-smp,
184: kernel-enterprise, kernel-BOOT, and kernel-debug will not
185: be checked and listed. However, kernel-headers and kernel-source
186: will be checked. The B<--update> option (see below)
187: implies B<--no-kernel>.
188:
189: =item B<--update>
190:
191: update all packages that were found to have newer versions.
192: For this to work I<check-rpms> must be run as root and a suitable
193: $RPMUSER must exist (see DESCRIPTION above). It is
194: strongly advisable to do a dry run B<check-rpms -v -lq> before
195: running B<check-rpms --update>.
196:
197: =item B<-c> I<configurationfile>
198:
199: The optional configuration file to use. This file can be
200: used to specify the $RPMDIR variable, the $FTP, $FTPSERVER,
201: and $FTPUPDATES, variables, and the $RPMUSER variable. An
202: example configuration file is given below. If the B<-c> option
203: is omitted, I<check-rpms> will use the default configuration
204: file I</usr/local/etc/check-rpms.conf>, if it exists.
205:
1.5 harris41 206: =item B<--rpmuser> I<user name>
207:
208: Specifying $RPMUSER on the command line.
209:
1.2 harris41 210: =back
211:
212: =head1 EXAMPLES
213:
214: =over 4
215:
216: =item check-rpms
217:
218: will 1) check whether /usr/local/etc/check-rpms.conf exists; 2) if
219: it does it will read the variables specified in that file, if it
220: doesn't exist, $RPMDIR is set to /mnt/redhat/RedHat/RPMS; 3) if
221: $RPMDIR is set, this directory will be regarded as the source of
222: the up-to-date distribution, unless $FTP is set to 1. In that latter
223: case the $FTPSERVER and $FTPUPDATES are used, if those variables are
224: set. Otherwise "updates.redhat.com" and "<RHversion>/en/os"
225: will be used; 4) the installed packages are compared
226:
227: =item check-rpms -v -lq -d /mnt/redhat/7.1/RedHat/RPMS
228:
229: will use the distribution in the directory /mnt/redhat/7.1/RedHat/RPMS
230: for comparison with the installed packages. The command
231: will give more detailed information on its progress and will list
232: the packages that need upgrading and in another section it will
233: list packages they may need to be upgraded.
234:
235: =item check-rpms -v -lq -ftp updates.redhat.com/7.1/en/os
236:
237: same as above, but the directories 7.1/en/os/noarch,
238: 7.1/en/os/i386, 7.1/en/os/i586, 7.1/en/os/i686, and
239: 7.1/en/os/athlon on updates.redhat.com will be searched for new
240: packages.
241:
242: =item check-rpms -v -r --updates
243:
244: will use the default location for updated packages (determined as
245: indicated in the first example); if a ftp server is used, it will
246: download all newer and all packages with letters in the version
247: and/or release strings (i.e., "questionable" packages) from that
248: ftp server, recheck the questionable packages, and finally update
249: all packages that need to be updated.
250:
251: =back
252:
253: =cut
254:
1.1 harris41 255: if ( $retval == 0 ) {
256: usage();
257: }
258:
259: # executables
260: $FTPLS = "ncftpls";
261: $FTPGET = "ncftpget";
262: $GREP = "grep";
263:
264: # default values
265: $RHversion = (split /\s/, `cat /etc/redhat-release`)[4];
266: $DEFCONF = "/usr/local/etc/check-rpms.conf";
267: $DEFRPMDIR = "/mnt/redhat/RedHat/RPMS";
268: $DEFFTPSERVER = "updates.redhat.com";
269: $DEFFTPUPDATES = "$RHversion/en/os";
1.5 harris41 270: $DEFRPMUSER = "nobody";
1.1 harris41 271:
272: $RPMDIR=$DEFRPMDIR;
273:
274: # configuration
275: # the configuration file should set the $RPMDIR variable and/or $FTPSERVER,
276: # $FTPUPDATES and $DOWNLOADDIR variables, and the $RPMUSER variable.
277: if ($opt_c) {
278: $CONF = $opt_c;
279: } else {
280: $CONF = $DEFCONF;
281: }
282:
1.2 harris41 283: =pod
284:
285: =head1 The Configuration File
286:
287: All variables must be defined using perl syntax, i.e., in the form
288:
289: $variable = value;
290:
291: (do not forget the semicolon at the end of a line). Comments
292: start with "#" and blank lines may be included as well.
293:
294: Example configuration file:
295:
296: # check-rpms configuration file
297:
298: # $RPMDIR is the directory where up-to-date RPMs can be found and/or
299: # rpm packages are downloaded into.
300: $RPMDIR = "/mnt/redhat/RedHat/RPMS";
301:
302: # $RPMUSER is the user name that check-rpms switches to for most of
303: # the script when run as root
1.5 harris41 304: $RPMUSER = "adminjoe";
1.2 harris41 305:
306: # $FTPSERVER and $FTPUPDATES are the hostname of a ftp server and the
307: # directory where RPM updates can be found without the <arch> directory.
308: # I.e., $FTPUPDATES should be set to something like pub/7.2, if the RPMs
309: # are located in pub/7.2/i386, pub/7.2/i686, etc.
310: # $FTPSERVER and $FTPUPDATES are used if -ftp is specified or if the following
311: # line is uncommented.
312: # $FTP = 1;
313: $FTPSERVER = "updates.redhat.com";
314: $FTPUPDATES = "7.2/en/os";
315:
316: =cut
317:
1.1 harris41 318: if ( -f $CONF) {
319: require($CONF);
320: } else {
321: $FTPSERVER = $DEFFTPSERVER;
322: $FTPUPDATES = $DEFFTPUPDATES;
1.5 harris41 323: }
324:
325: if ($opt_rpmuser) {
326: $DEFRPMUSER = $opt_rpmuser;
1.1 harris41 327: }
328:
329: # check whether we are running as root
330: if ($< == 0){
331: if (! $RPMUSER) {
332: $RPMUSER = $DEFRPMUSER;
333: }
334: $RPMUID = getpwnam($RPMUSER);
335: if (! $RPMUID) {
336: die "You do not seem to have a $RPMUSER user on your system.\nSet the \$RPMUSER variable in the $CONF configuration file to a non-root user.\n";
337: }
338: if ($RPMUID == 0) {
339: die "You must set the \$RPMUSER variable in $CONF to a non-root user.\n";
340: }
341: # switch to $RPMUID
342: $> = $RPMUID;
343: if ($> != $RPMUID) { die "switching to $RPMUID uid failed.\n" }
344: }
345:
346: # command-line arguments
347: $verbose = $opt_verbose;
348: $list_missing = $opt_lm;
349: $questionable = $opt_lq;
350: $no_kernel = $opt_nk;
351: $download = $opt_download;
352: $recheck = $opt_recheck;
353: $update = $opt_update;
354:
355: if (defined $opt_update && $< != 0) {
356: die "You must be root in order to update rpms.\n";
357: }
358:
359: if ( defined $opt_dir ){
360: $RPMDIR = $opt_dir;
361: }
362:
363: if (defined $opt_ftp && defined $opt_noftp) {
364: die "Setting -ftp and -noftp does not make sense, does it?\n";
365: }
366:
367: if (defined $opt_noftp) { $FTP = 0; }
368:
369: if (defined $opt_ftp || $FTP) {
370: $ftp = 1;
371: if ( $opt_ftp ) {
372: $_ = $opt_ftp;
373: ($FTPSERVER, $FTPUPDATES) = m/^([^\/]+)\/(.*)$/;
374: } elsif ( ! ($FTPSERVER && $FTPUPDATES)) {
375: $FTPSERVER = $DEFFTPSERVER;
376: $FTPUPDATES = $DEFFTPUPDATES;
377: }
378:
379: if (defined $opt_update){
380: $download=1;
381: }
382:
383: if ($download || $recheck) {
384: if ( ! -d $RPMDIR) {
1.3 harris41 385: if ($verbose) { print "Creating $RPMDIR ...\n"; }
386: if ($< == 0) {
387: $retval = system("su $RPMUSER -c \'mkdir -p $RPMDIR\'; chmod 700 $RPMDIR");
388: } else {
389: $retval = system("mkdir -p $RPMDIR; chmod 700 $RPMDIR");
390: }
1.1 harris41 391: if ($retval) { die "error: could not create $RPMDIR\n"; }
392: }
393: }
394: } elsif ( (! -d $RPMDIR) || system("ls $RPMDIR/*.rpm > /dev/null 2>&1")) {
395: die "Either $RPMDIR does not exist or it does not contain any packages.\n";
396: }
397:
1.6 ! harris41 398: my $FTPSERVER_auth = $FTPSERVER;
! 399: $FTPSERVER_auth =~ s/^(\w+)\:(\w+)\@/-u $1 -p $2 /;
! 400:
1.1 harris41 401: if ($recheck) {
402: $questionable=1;
403: }
404:
405: if (defined $opt_update || defined $opt_nk) {
406: $no_kernel=1;
407: }
408:
1.4 harris41 409: $PROC = `grep -i "athlon\|amd" /proc/cpuinfo`;
1.1 harris41 410: if ( ! "$PROC" ) {
411: $PROC = `uname -m`;
412: chomp($PROC);
413: } else {
414: $PROC = "athlon";
415: }
416:
417: @ARCHITECTURES = ("noarch", "i386", "i586", "i686");
418: if ( $RHversion > 7.0 ){
419: push(@ARCHITECTURES, "athlon");
420: }
421:
422: # get the local list of installed packages
423:
424: if ($verbose) {
425: print "updates for $PROC processor, RH $RHversion\n";
426: print "Getting list of installed packages\n";
427: }
428:
429: if ($< == 0) {
430: @local_rpm_list = `su $RPMUSER -c 'rpm -qa'`;
431: } else {
432: @local_rpm_list = `rpm -qa`;
433: }
434: chop(@local_rpm_list);
435:
436: %local_rpm = %remote_rpm = ();
437:
438: for (@local_rpm_list) {
439: # good place to test the regular expressions...
440: # ($pkg, $ver, $release) = m/^(.*)-([^-]*)-([^-]+)/;
441: # print "$_\t->$pkg, $ver, $release\n";
442:
443: my ($pkg, $pver) = m/([^ ]*)-([^-]+-[^-]+)/;
444: $local_rpm{$pkg} = $pver;
445: }
446:
447: # now connect to the remote host
448:
449: my @templist;
450: if ($ftp) {
451: if ( `rpm -q ncftp --pipe "grep 'not installed'"` ) {
452: die "you must have the ncftp package installed in order to use a\n",
453: "ftp server with check-rpms.\n";
454: }
455: $SOURCE = $FTPSERVER;
456: for (@ARCHITECTURES) {
457: my $FTPDIR = "$FTPUPDATES/$_";
458: if ($verbose) {
459: print ("Getting package lists from $FTPSERVER/$FTPDIR ...\n");
460: }
461: push(@templist, grep(/\.rpm$/, `$FTPLS -x "-1a" "ftp://$FTPSERVER/$FTPDIR/"`));
462: if ($?) { print STDERR "$FTPLS failed with status ",$?/256,".\n"; }
463: }
464: } else {
465: $SOURCE = $RPMDIR;
466: if ($verbose) {
467: print ("Getting package lists from $RPMDIR ...\n");
468: }
469: @templist = grep(/\.rpm$/, `(cd $RPMDIR;ls -1)`);
470: }
471:
472: #
473: # If two versions of the same RPM appear with different architectures
474: # and/or different versions, the right one must be found.
475: #
476:
477: $giveup = 0;
478: for (@templist) {
479: ($rpm, $pkg, $pver, $arch) = m/(([^ ]*)-([^- ]+-[^-]+\.(\w+)\.rpm))/;
1.3 harris41 480: if (! defined $local_rpm{$pkg}) { next; }
1.1 harris41 481: if ($remote_rpm{$pkg}) {
482: # problem: there are several versions of the same package.
483: # this means that the package exists for different architectures
484: # (e.g., kernel, glibc, etc.) and/or that the remote server
485: # has several versions of the same package in which case the
486: # latest version must be picked.
487: my ($pkg1) = ($remote_rpm{$pkg} =~ m/([^-]+-[^-]+)\.\w+.rpm/);
488: my ($pkg2) = ($pver =~ m/([^-]+-[^-]+)\.\w+.rpm/);
489: my ($vcmp, $qflag) = cmp_versions($pkg1, $pkg2);
490: if ($qflag && $questionable) {
491: # cannot decide which of the two is newer - what should we do?
492: # print a warning that lists the two rpms.
493: # If running with --update, both packages must be rechecked with
494: # rpm -qp --queryformat '%{SERIAL}' <pkg>
495: if ($recheck || $update) {
496: my $decision = pkg_compare("$pkg-$remote_rpm{$pkg}",$rpm, $vcmp);
497: if ($decision < 0) {
498: # an error in the ftp download routine accured: giveup
499: $remote_rpm{$pkg} = undef;
500: $giveup = 1;
501: } elsif ($decision > 0) {
502: # second package is newer
503: $remote_rpm{$pkg} = $pver;
504: }
505: next;
506: } else {
507: mulpkg_msg("$pkg-$remote_rpm{$pkg}", $rpm, $vcmp);
508: print "** check whether this is correct or rerun with --recheck option.\n";
509: if ($vcmp < 0) {
510: $remote_rpm{$pkg} = $pver;
511: }
512: }
513: }
514: if ($vcmp == 0) {
515: # versions are equal: must be different architecture
516: # procedure to select the correct architecture:
517: # if $PROC = athlon: if available use $arch = athlon (exist for
518: # RH 7.1 or newer) otherwise use i686
519: # if $PROC = ix86: choose pkg with $PROC cmp $arch >= 0 and
520: # $arch cmp $prev_arch = 1
521: $_ = $remote_rpm{$pkg};
522: ($prev_arch) = m/.*\.(\w+)\.rpm$/;
523: if (cmp_arch($arch,$prev_arch)) { $remote_rpm{$pkg} = $pver };
524: } elsif ($vcmp < 0) { # second rpm is newer
525: $remote_rpm{$pkg} = $pver;
526: }
527: } else {
528: $remote_rpm{$pkg} = $pver;
529: }
530: }
531:
532: if ($giveup && defined $opt_update) {
533: die "Multiple versions of the same package were found on the server.\n",
534: "However, due to ftp download problems it could not be verified\n",
535: "which of the packages are the most recent ones.\n",
536: "If the choices specified above appear to be correct, rerun check-rpms\n",
537: "without the -lq (or --list-questionable) option. Otherwise, fix the download\n",
538: "problems or install those packages separately first.\n";
539: }
540:
541: #
542: # check for UPDated and DIFferent packages...
543: #
544:
545: for (@local_rpm_list) {
546: my ($pkg, $version) = m/^([^ ]*)-([^-]+-[^-]+)$/;
547: if (! $pkg) { print "Couldn't parse $_\n"; next; }
548: if ($no_kernel) {
549: if ($pkg eq 'kernel' || $pkg eq 'kernel-smp'
550: || $pkg eq 'kernel-enterprise' || $pkg eq 'kernel-BOOT'
551: || $pkg eq 'kernel-debug') { next; }
552: }
553: if (defined $remote_rpm{$pkg}) {
554: # this package has an update
555: my ($rversion) = ($remote_rpm{$pkg} =~ m/([^-]+-[^-]+)\.\w+.rpm/);
556: my $rpm = ($pkg . '-' . $remote_rpm{$pkg});
557: my ($vcmp,$qflag) = cmp_versions($version, $rversion);
558: if ( $qflag && $questionable ) {
559: # at least one of the version strings contains letters
560: push(@q_updates, $rpm);
561: } elsif ( $vcmp < 0 ) {
562: # local version is lower
1.3 harris41 563: push(@updates, $rpm);
1.1 harris41 564: }
565: } elsif ($list_missing) {
566: print "Package '$pkg' missing from remote repository\n";
567: }
568: }
569:
1.6 ! harris41 570:
1.1 harris41 571: if ($recheck && @q_updates) {
572: if ($ftp) {
573: for (@q_updates) {
574: ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
575: push(@ftp_files, "$FTPUPDATES/$arch/$_");
576: }
577: if ($verbose) {
578: print "Getting questionable packages form $FTPSERVER ...\n";
579: }
1.6 ! harris41 580: my $status = system("$FTPGET $FTPSERVER_auth $RPMDIR @ftp_files");
1.1 harris41 581: if ($status) {
582: if ($< == 0) {
583: # if we are running as root exit to avoid symlink attacks, etc.
1.6 ! harris41 584: print("DEBUG DEBUG $FTPGET $FTPSERVER_auth $RPMDIR @ftp_files");
1.1 harris41 585: die "$FTPGET failed with status ", $status/256, ".\n";
586: } else {
587: print STDERR "warning: $FTPGET failed with status ", $status/256, ".\n";
588: }
589: }
590: }
591: for (@q_updates) {
592: if ($verbose) {print "** rechecking $_ ... ";}
593: my $errmsg = `rpm -Uvh --test --nodeps --pipe 'grep -v ^Preparing' $RPMDIR/$_ 2>&1`;
594: if (! $errmsg) {
595: # no error message, i.e., the rpm is needed.
596: push(@updates,$_);
597: if ($verbose) {print "needed!\n";}
598: } elsif ($verbose) {
599: print "not needed:\n$errmsg\n";
600: }
601: }
602: @q_updates=();
603: }
604:
605: #
606: # print list of new files and download ...
607: #
608:
609: @updates = sort @updates;
610: if (@updates) {
611: if ($verbose) {
612: print "\nRPM files to be updated:\n\n";
613: }
614: for (@updates) {
615: print "$_\n";
616: }
617: if ($download) {
618: @ftp_files=();
619: for (@updates) {
620: ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
621: push(@ftp_files, "$FTPUPDATES/$arch/$_");
622: }
623: if ($verbose) {
624: print "starting downloads ... \n";
625: }
1.6 ! harris41 626: my $status = system("$FTPGET $FTPSERVER_auth $RPMDIR @ftp_files");
1.1 harris41 627: if ($status) {
628: if ($< == 0) {
629: # if we are running as root exit to avoid symlink attacks, etc.
630: die "$FTPGET failed with status ", $status/256, ".\n";
631: } else {
632: print STDERR "warning: $FTPGET failed with status ", $status/256, ".\n";
633: }
634: } elsif ($verbose) {
635: print "... done.\n";
636: }
637: }
638: }
639:
640: @q_updates = sort @q_updates;
641: if (@q_updates && $questionable) {
642: if ($verbose) {
643: print "\nRPM files that may need to be updated:\n\n";
644: for (@q_updates) {
645: my ($old) = m/^([^ ]*)-[^-]+-[^-]+\.\w+\.rpm$/;
646: $old = `rpm -q $old`;
647: chomp($old);
648: print "upgrade ", $old, " to ", $_, " ?\n";
649: }
650: } else {
651: for (@q_updates) {
652: print "$_\n";
653: }
654: }
655: if ($download) {
656: @ftp_files=();
657: for (@updates) {
658: ($arch) = m/[^ ]*-[^- ]+-[^-]+\.(\w+)\.rpm/;
659: push(@ftp_files, $FTPUPDATES/$arch/$_);
660: }
661: if ($verbose) {
662: print "starting downloads ... \n";
1.6 ! harris41 663: system("$FTPGET $FTPSERVER_auth $$RPMDIR @ftp_files");
1.1 harris41 664: print "... done.\n";
665: } else {
1.6 ! harris41 666: system("$FTPGET $FTPSERVER_auth $$RPMDIR @ftp_files");
1.1 harris41 667: }
668: }
669: }
670:
671: if ($verbose && !(@updates || @q_updates)) {
672: print "No new updates are available in $SOURCE\n";
673: }
674:
675: if ($opt_update) {
676: if (@q_updates){
677: push(@updates,@q_updates);
678: }
679: if (@updates) {
680: if ($verbose) {
681: print "Running rpm -Fvh ...\n";
682: }
683: # switch to UID=0
684: $> = $<;
685: system("(cd $RPMDIR;rpm -Fvh @updates)");
686: }
687: }
688:
689: # download routine
690: sub ftp_download {
691: my ($FTPSERVER, $FTPDIR, $downloaddir, @packages) = @_;
692: my @ftp_packages=();
693: for (@packages) {
694: my ($arch) = m/[^ ]*-[^-]+-[^-]*\.(\w+)\.rpm$/;
695: push(@ftp_packages,"$FTPDIR/$arch/$_");
696: }
1.6 ! harris41 697: my $status = system("$FTPGET $FTPSERVER_auth $downloaddir @ftp_packages");
1.1 harris41 698: return $status;
699: }
700:
701: sub pkg_compare($$$) {
702: my ($pkg1, $pkg2, $cmp) = @_;
703: if (defined $opt_ftp) {
704: if ($verbose) {
705: my ($pkg) = ($pkg1 =~ /([^ ]*)-[^-]+-[^-]+\.\w+\.rpm/);
706: print "The ftp server provides multiple versions of the $pkg package.\n",
707: "Downloading $pkg1 and $pkg2 in order to find out which is newer.\n";
708: }
709: my $status = ftp_download($FTPSERVER, $FTPUPDATES, $RPMDIR, ($pkg1, $pkg2));
710: if ($status) {
711: # at this point just give up ...
712: print STDERR "** $FTPGET failed with status ", $status/256, ".\n";
713: mulpkg_msg($pkg1, $pkg2, $cmp);
714: return -1;
715: }
716: }
717: my $serial1 = `rpm -qp --queryformat '%{SERIAL}' $RPMDIR/$pkg1`;
718: my $serial2 = `rpm -qp --queryformat '%{SERIAL}' $RPMDIR/$pkg2`;
1.3 harris41 719: if ($serial2 > $serial1) {
720: remove_pkg("$RPMDIR/$pkg1");
721: return 1;
722: } else {
723: remove_pkg("$RPMDIR/$pkg2");
724: return 0;
725: }
726: }
727:
728: sub remove_pkg($) {
729: my ($pkg) = @_;
730: if ($verbose) {
731: print "Removing $pkg ...\n";
732: }
733: my $status = system("rm -f $pkg");
734: if ($status) {
735: printf STDERR "error: could not remove $pkg. You must remove this file before updating.\n";
736: if ($update) { $giveup = 1; }
737: }
1.1 harris41 738: }
739:
740: sub mulpkg_msg($$$) {
741: my ($pkg1, $pkg2, $cmp) = @_;
742: print "** The server provides two versions of the same package:\n",
743: "** $pkg1 and $pkg2.\n";
744: if ($cmp > 0) {
745: print "** It appears that $pkg-$remote_rpm{$pkg} is newer.\n"
746: } else {
747: print "** It appears that $pkg-$pver is newer.\n";
748: }
749: }
750:
751: #############################################################################
752: #
753: # Version comparison utilities
754: #
755:
756: sub hack_version($) {
757: my ($pver) = @_;
758: $pver =~ s/(\d+)/sprintf("%08d", $1)/eg; # pad numbers with leading zeros to make alphabetical sort do the right thing
759: $pver = (sprintf "%-80s", $pver); # pad with spaces so that "3.2.1" is greater than "3.2"
760: return $pver;
761: }
762:
763: sub cmp_versions($$) {
764: my ($pkg1, $pkg2) = @_;
765:
766: # shortcut if they're obviously the same.
767: return (0,0) if ($pkg1 eq $pkg2);
768:
769: # split into version and release
770: my ($ver1, $rel1) = ($pkg1 =~ m/([^-]+)-([^-]+)/);
771: my ($ver2, $rel2) = ($pkg2 =~ m/([^-]+)-([^-]+)/);
772:
773: if ($ver1 ne $ver2) {
774: my $qflag = ((grep /[A-z]/, $ver1) || (grep /[A-z]/, $ver2));
775: $ver1 = hack_version($ver1);
776: $ver2 = hack_version($ver2);
777: return ($ver1 cmp $ver2, $qflag);
778: } else {
779: my $qflag = ((grep /[A-z]/, $rel1) || (grep /[A-z]/, $rel2));
780: $rel1 = hack_version($rel1);
781: $rel2 = hack_version($rel2);
782: return ($rel1 cmp $rel2, $qflag);
783: }
784: }
785:
786: sub cmp_arch($$) {
787: my ($arch1, $arch2) = @_;
788: my $retval = 0;
789: $archcmp = ($arch1 cmp $arch2) > 0;
790: if ( "$PROC" eq "athlon" ) {
791: if ( "$arch2" ne "athlon"
792: && ( "$arch1" eq "athlon" || $archcmp )){
793: $retval = 1;
794: }
795: } elsif ( $archcmp && ($PROC cmp $arch1) >= 0 ) {
796: $retval = 1;
797: }
798: return $retval;
799: }
800:
801: # @tests = ('3.2', '3.2',
802: # '3.2a', '3.2a',
803: # '3.2', '3.2a',
804: # '3.2', '3.3',
805: # '3.2', '3.2.1',
806: # '1.2.5i', '1.2.5.1',
807: # '1.6.3p6', '1.6.4');
808: #
809: # while (@tests) {
810: # $a = shift(@tests);
811: # $b = shift(@tests);
812: # printf "%-10s < %-10s = %d\n", $a, $b, cmp_versions($a, $b);
813: # }
814: #
815: # And the correct output is...
816: #
817: # 3.2 < 3.2 = 0
818: # 3.2a < 3.2a = 0
819: # 3.2 < 3.2a = -1
820: # 3.2 < 3.3 = -1
821: # 3.2 < 3.2.1 = -1
822: # 1.2.5i < 1.2.5.1 = -1
823: # 1.6.3p6 < 1.6.4 = -1
824: #
825: # the lexical sort does not give the correct result in the second to last case.
826:
827:
828: sub usage(){
829: die "usage: check-rpms [-v | --verbose] [-d directory | --dir directory]\n",
830: " [-ftp [server/directory]] [-noftp] [-lm | --list-missing]\n",
831: " [-lq | --list-questionable] [-r | --recheck ]\n",
832: " [-nk | --no-kernel] [--update] [-c configurationfile]\n";
833: }
1.2 harris41 834:
835: =pod
836:
837: =head1 SEE ALSO
838:
839: rpm(8), ncftpls(1), ncftpget(1)
840:
841: =head1 AUTHOR
842:
843: Martin Siegert, Simon Fraser University, siegert@sfu.ca
844:
845: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>