]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/Debian.pl
86bf028c47e68988ff4571002bd3750fcd3be20e
[infobot.git] / src / Modules / Debian.pl
1 #
2 #   Debian.pl: Frontend to debian contents and packages files
3 #      Author: dms
4 #     Version: v0.8 (20000918)
5 #     Created: 20000106
6 #
7
8 package Debian;
9
10 use strict;
11
12 # format: "alias=real".
13 my $announce    = 0;
14 my $defaultdist = "sid";
15 my $refresh = &::getChanConfDefault("debianRefreshInterval",7)
16                         * 60 * 60 * 24;
17 my $debug       = 0;
18 my $debian_dir  = "$::bot_state_dir/debian";
19
20 my %dists       = (
21         "unstable"      => "sid",
22         "testing"       => "sarge",
23         "stable"        => "woody",
24         "old-stable"    => "potato",    # this still works?
25         "incoming"      => "incoming",
26 );
27
28 my %urlcontents = (
29         "Contents-##DIST-i386.gz" =>
30                 "ftp://ftp.us.debian.org".
31                 "/debian/dists/##DIST/Contents-i386.gz",
32         "Contents-##DIST-i386-non-US.gz" =>
33                 "ftp://non-us.debian.org".
34                 "/debian-non-US/dists/##DIST/non-US/Contents-i386.gz",
35 );
36
37 my %urlpackages = (
38         "Packages-##DIST-main-i386.gz" =>
39                 "ftp://ftp.us.debian.org".
40                 "/debian/dists/##DIST/main/binary-i386/Packages.gz",
41         "Packages-##DIST-contrib-i386.gz" =>
42                 "ftp://ftp.us.debian.org".
43                 "/debian/dists/##DIST/contrib/binary-i386/Packages.gz",
44         "Packages-##DIST-non-free-i386.gz" =>
45                 "ftp://ftp.us.debian.org".
46                 "/debian/dists/##DIST/non-free/binary-i386/Packages.gz",
47
48         "Packages-##DIST-non-US-main-i386.gz" =>
49                 "ftp://non-us.debian.org".
50                 "/debian-non-US/dists/##DIST/non-US/main/binary-i386/Packages.gz",
51         "Packages-##DIST-non-US-contrib-i386.gz" =>
52                 "ftp://non-us.debian.org".
53                 "/debian-non-US/dists/##DIST/non-US/contrib/binary-i386/Packages.gz",
54         "Packages-##DIST-non-US-non-free-i386.gz" =>
55                 "ftp://non-us.debian.org".
56                 "/debian-non-US/dists/##DIST/non-US/non-free/binary-i386/Packages.gz",
57 );
58
59 #####################
60 ### COMMON FUNCTION....
61 #######################
62
63 ####
64 # Usage: &DebianDownload($dist, %hash);
65 sub DebianDownload {
66     my ($dist, %urls)   = @_;
67     my $bad     = 0;
68     my $good    = 0;
69
70     if (! -d $debian_dir) {
71         &::status("Debian: creating debian dir.");
72         mkdir($debian_dir, 0755);
73     }
74
75     # fe dists.
76     # Download the files.
77     my $file;
78     foreach $file (keys %urls) {
79         my $url = $urls{$file};
80         $url  =~ s/##DIST/$dist/g;
81         $file =~ s/##DIST/$dist/g;
82         my $update = 0;
83
84         if ( -f $file ) {
85             my $last_refresh = (stat $file)[9];
86             $update++ if (time() - $last_refresh > $refresh);
87         } else {
88             $update++;
89         }
90
91         next unless ($update);
92
93         &::DEBUG("announce == $announce.") if ($debug);
94         if ($good + $bad == 0 and !$announce) {
95             &::status("Debian: Downloading files for '$dist'.");
96             &::msg($::who, "Updating debian files... please wait.");
97             $announce++;
98         }
99
100         if (exists $::debian{$url}) {
101             &::DEBUG("2: ".(time - $::debian{$url})." <= $refresh") if ($debug);
102             next if (time() - $::debian{$url} <= $refresh);
103             &::DEBUG("stale for url $url; updating!") if ($debug);
104         }
105
106         if ($url =~ /^ftp:\/\/(.*?)\/(\S+)\/(\S+)$/) {
107             my ($host,$path,$thisfile) = ($1,$2,$3);
108
109 ### HACK 1
110 #           if ($file =~ /Contents-woody-i386-non-US/) {
111 #               &::DEBUG("Skipping Contents-woody-i386-non-US.");
112 #               $file =~ s/woody/potato/;
113 #               $path =~ s/woody/potato/;
114 #               next;
115 #           }
116
117             if (!&::ftpGet($host,$path,$thisfile,$file)) {
118                 &::WARN("deb: down: $file == BAD.");
119                 $bad++;
120                 next;
121             }
122
123             if (! -f $file) {
124                 &::WARN("deb: down: ftpGet: !file");
125                 $bad++;
126                 next;
127             }
128
129 ### HACK2
130 #           if ($file =~ /Contents-potato-i386-non-US/) {
131 #               &::DEBUG("hack: using potato's non-US contents for woody.");
132 #               system("cp debian/Contents-potato-i386-non-US.gz debian/Contents-woody-i386-non-US.gz");
133 #           }
134
135             my $exit = CORE::system("/bin/gzip -t $file >/dev/null 2>&1");
136             if ($exit) {
137                 &::WARN("deb: $file is corrupted :/");
138                 unlink $file;
139                 next;
140             }
141
142             &::DEBUG("deb: download: good.") if ($debug);
143             $good++;
144         } else {
145             &::ERROR("Debian: invalid format of url => ($url).");
146             $bad++;
147             next;
148         }
149     }
150
151     # ok... lets just run this.
152     &::miscCheck() if (&::whatInterface() =~ /IRC/);
153
154     if ($good) {
155         &generateIndex($dist);
156         return 1;
157     } else {
158         return -1 unless ($bad);        # no download.
159         &::DEBUG("DD: !good and bad($bad). :(");
160         return 0;
161     }
162 }
163
164 ###########################
165 # DEBIAN CONTENTS SEARCH FUNCTIONS.
166 ########
167
168 ####
169 # Usage: &searchContents($query);
170 sub searchContents {
171     my ($dist, $query)  = &getDistroFromStr($_[0]);
172     &::status("Debian: Contents search for '$query' in '$dist'.");
173     my $dccsend = 0;
174
175     $dccsend++          if ($query =~ s/^dcc\s+//i);
176
177     $query =~ s/\\([\^\$])/$1/g;        # hrm?
178     $query =~ s/^\s+|\s+$//g;
179
180     if (!&::validExec($query)) {
181         &::msg($::who, "search string looks fuzzy.");
182         return;
183     }
184
185     if ($dist eq "incoming") {          # nothing yet.
186         &::DEBUG("sC: dist = 'incoming'. no contents yet.");
187         return;
188     } else {
189         my %urls = &fixDist($dist, %urlcontents);
190         # download contents file.
191         &::DEBUG("deb: download 1.") if ($debug);
192         if (!&DebianDownload($dist, %urls)) {
193             &::WARN("Debian: could not download files.");
194         }
195     }
196
197     # start of search.
198     my $start_time = &::timeget();
199
200     my $found   = 0;
201     my $front   = 0;
202     my %contents;
203     my $grepRE;
204     ### TODO: search properly if /usr/bin/blah is done.
205     if ($query =~ s/\$$//) {
206         &::DEBUG("deb: search-regex found.") if ($debug);
207         $grepRE = "$query\[ \t]";
208     } elsif ($query =~ s/^\^//) {
209         &::DEBUG("deb: front marker regex found.") if ($debug);
210         $front = 1;
211         $grepRE = $query;
212     } else {
213         $grepRE = "$query*\[ \t]";
214     }
215
216     # fix up grepRE for "*".
217     $grepRE =~ s/\*/.*/g;
218
219     my @files;
220     foreach (keys %urlcontents) {
221         s/##DIST/$dist/g;
222
223         next unless ( -f "$debian_dir/$_" );
224         push(@files, "$debian_dir/$_");
225     }
226
227     if (!scalar @files) {
228         &::ERROR("sC: no files?");
229         &::msg($::who, "failed.");
230         return;
231     }
232
233     my $files = join(' ', @files);
234
235     my $regex   = $query;
236     $regex      =~ s/\./\\./g;
237     $regex      =~ s/\*/\\S*/g;
238     $regex      =~ s/\?/./g;
239
240     open(IN,"zegrep -h '$grepRE' $files |");
241     # wonderful abuse of last and next and unless ;)
242     while (<IN>) {
243         last if ($found > 100);
244
245         next unless (/^\.?\/?(.*?)[\t\s]+(\S+)\n$/);
246         my ($file,$package) = ("/".$1,$2);
247
248         if ($query =~ /[\/\*\\]/) {
249             next unless (eval { $file =~ /$regex/ });
250             return unless &checkEval($@);
251         } else {
252             my ($basename) = $file =~ /^.*\/(.*)$/;
253             next unless (eval { $basename =~ /$regex/ });
254             return unless &checkEval($@);
255         }
256         next if ($query !~ /\.\d\.gz/ and $file =~ /\/man\//);
257         next if ($front and eval { $file !~ /^\/$query/ });
258         return unless &checkEval($@);
259
260         $contents{$package}{$file} = 1;
261         $found++;
262     }
263     close IN;
264
265     my $pkg;
266
267     ### send results with dcc.
268     if ($dccsend) {
269         if (exists $::dcc{'SEND'}{$::who}) {
270             &::msg($::who, "DCC already active!");
271             return;
272         }
273
274         if (!scalar %contents) {
275             &::msg($::who,"search returned no results.");
276             return;
277         }
278
279         my $file = "$::param{tempDir}/$::who.txt";
280         if (!open OUT, ">$file") {
281             &::ERROR("Debian: cannot write file for dcc send.");
282             return;
283         }
284
285         foreach $pkg (keys %contents) {
286             foreach (keys %{ $contents{$pkg} }) {
287                 # TODO: correct padding.
288                 print OUT "$_\t\t\t$pkg\n";
289             }
290         }
291         close OUT;
292
293         &::shmWrite($::shm, "DCC SEND $::who $file");
294
295         return;
296     }
297
298     &::status("Debian: $found contents results found.");
299
300     my @list;
301     foreach $pkg (keys %contents) {
302         my @tmplist = &::fixFileList(keys %{ $contents{$pkg} });
303         my @sublist = sort { length $a <=> length $b } @tmplist;
304
305         pop @sublist while (scalar @sublist > 3);
306
307         $pkg =~ s/\,/\037\,\037/g;      # underline ','.
308         push(@list, "(". join(', ',@sublist) .") in $pkg");
309     }
310     # sort the total list from shortest to longest...
311     @list = sort { length $a <=> length $b } @list;
312
313     # show how long it took.
314     my $delta_time = &::timedelta($start_time);
315     &::status(sprintf("Debian: %.02f sec to complete query.", $delta_time)) if ($delta_time > 0);
316
317     my $prefix = "Debian Search of '$query' ";
318     if (scalar @list) { # @list.
319         &::pSReply( &::formListReply(0, $prefix, @list) );
320
321     } else {            # !@list.
322         &::DEBUG("deb: ok, !\@list, searching desc for '$query'.") if ($debug);
323         my @list = &searchDesc($query);
324
325         if (!scalar @list) {
326             my $prefix = "Debian Package/File/Desc Search of '$query' ";
327             &::pSReply( &::formListReply(0, $prefix, ) );
328
329         } elsif (scalar @list == 1) {   # list = 1.
330             &::DEBUG("deb: list == 1; showing package info of '$list[0]'.");
331             &infoPackages("info", $list[0]);
332
333         } else {                                # list > 1.
334             my $prefix = "Debian Desc Search of '$query' ";
335             &::pSReply( &::formListReply(0, $prefix, @list) );
336         }
337     }
338 }
339
340 ####
341 # Usage: &searchAuthor($query);
342 sub searchAuthor {
343     my ($dist, $query)  = &getDistroFromStr($_[0]);
344     &::DEBUG("deb: searchAuthor: dist => '$dist', query => '$query'.") if ($debug);
345     $query =~ s/^\s+|\s+$//g;
346
347     # start of search.
348     my $start_time = &::timeget();
349     &::status("Debian: starting author search.");
350
351     my $files;
352     my ($bad,$good) = (0,0);
353     my %urls = %urlpackages;
354
355     foreach (keys %urlpackages) {
356         s/##DIST/$dist/g;
357
358         if (! -f "$debian_dir/$_" ) {
359             $bad++;
360             next;
361         }
362
363         $good++;
364         $files .= " ".$_;
365     }
366
367     &::DEBUG("deb: good = $good, bad = $bad...") if ($debug);
368
369     if ($good == 0 and $bad != 0) {
370         my %urls = &fixDist($dist, %urlpackages);
371         &::DEBUG("deb: download 2.");
372
373         if (!&DebianDownload($dist, %urls)) {
374             &::ERROR("Debian(sA): could not download files.");
375             return;
376         }
377     }
378
379     my (%maint, %pkg, $package);
380     open(IN,"zegrep -h '^Package|^Maintainer' $files |");
381     while (<IN>) {
382         if (/^Package: (\S+)$/) {
383             $package = $1;
384
385         } elsif (/^Maintainer: (.*) \<(\S+)\>$/) {
386             my($name,$email) = ($1,$2);
387             if ($package eq "") {
388                 &::DEBUG("deb: sA: package == NULL.");
389                 next;
390             }
391             $maint{$name}{$email} = 1;
392             $pkg{$name}{$package} = 1;
393             $package = "";
394
395         } else {
396             chop;
397             &::WARN("debian: invalid line: '$_' (1).");
398         }
399     }
400     close IN;
401
402     my %hash;
403     # TODO: can we use 'map' here?
404     foreach (grep /\Q$query\E/i, keys %maint) {
405         $hash{$_} = 1;
406     }
407
408     # TODO: should we only search email if '@' is used?
409     if (scalar keys %hash < 15) {
410         my $name;
411
412         foreach $name (keys %maint) {
413             my $email;
414
415             foreach $email (keys %{ $maint{$name} }) {
416                 next unless ($email =~ /\Q$query\E/i);
417                 next if (exists $hash{$name});
418                 $hash{$name} = 1;
419             }
420         }
421     }
422
423     my @list = keys %hash;
424     if (scalar @list != 1) {
425         my $prefix = "Debian Author Search of '$query' ";
426         &::pSReply( &::formListReply(0, $prefix, @list) );
427         return 1;
428     }
429
430     &::DEBUG("deb: showing all packages by '$list[0]'...") if ($debug);
431
432     my @pkg = sort keys %{ $pkg{$list[0]} };
433
434     # show how long it took.
435     my $delta_time = &::timedelta($start_time);
436     &::status(sprintf("Debian: %.02f sec to complete query.", $delta_time)) if ($delta_time > 0);
437
438     my $email   = join(', ', keys %{ $maint{$list[0]} });
439     my $prefix  = "Debian Packages by $list[0] \002<\002$email\002>\002 ";
440     &::pSReply( &::formListReply(0, $prefix, @pkg) );
441 }
442
443 ####
444 # Usage: &searchDesc($query);
445 sub searchDesc {
446     my ($dist, $query)  = &getDistroFromStr($_[0]);
447     &::DEBUG("deb: searchDesc: dist => '$dist', query => '$query'.") if ($debug);
448     $query =~ s/^\s+|\s+$//g;
449
450     # start of search.
451     my $start_time = &::timeget();
452     &::status("Debian: starting desc search.");
453
454     my $files;
455     my ($bad,$good) = (0,0);
456     my %urls = %urlpackages;
457
458     foreach (keys %urlpackages) {
459         s/##DIST/$dist/g;
460
461         if (! -f "$debian_dir/$_" ) {
462             $bad++;
463             next;
464         }
465
466         $good++;
467         $files .= " $debian_dir/$_";
468     }
469
470     &::DEBUG("deb(2): good = $good, bad = $bad...") if ($debug);
471
472     if ($good == 0 and $bad != 0) {
473         my %urls = &fixDist($dist, %urlpackages);
474         &::DEBUG("deb: download 2c.") if ($debug);
475
476         if (!&DebianDownload($dist, %urls)) {
477             &::ERROR("deb: sD: could not download files.");
478             return;
479         }
480     }
481
482     my $regex   = $query;
483     $regex      =~ s/\./\\./g;
484     $regex      =~ s/\*/\\S*/g;
485     $regex      =~ s/\?/./g;
486
487     my (%desc, $package);
488     open(IN,"zegrep -h '^Package|^Description' $files |");
489     while (<IN>) {
490         if (/^Package: (\S+)$/) {
491             $package = $1;
492         } elsif (/^Description: (.*)$/) {
493             my $desc = $1;
494             next unless (eval { $desc =~ /$regex/i });
495             return unless &checkEval($@);
496
497             if ($package eq "") {
498                 &::WARN("sD: package == NULL?");
499                 next;
500             }
501
502             $desc{$package} = $desc;
503             $package = "";
504
505         } else {
506             chop;
507             &::WARN("debian: invalid line: '$_'. (2)");
508         }
509     }
510     close IN;
511
512     # show how long it took.
513     my $delta_time = &::timedelta($start_time);
514     &::status(sprintf("Debian: %.02f sec to complete query.", $delta_time)) if ($delta_time > 0);
515
516     return keys %desc;
517 }
518
519 ####
520 # Usage: &generateIncoming();
521 sub generateIncoming {
522     my $pkgfile  = $debian_dir."/Packages-incoming";
523     my $idxfile  = $pkgfile.".idx";
524     my $stale    = 0;
525     $stale++ if (&::isStale($pkgfile.".gz", $refresh));
526     $stale++ if (&::isStale($idxfile, $refresh));
527     &::DEBUG("deb: gI: stale => '$stale'.") if ($debug);
528     return 0 unless ($stale);
529
530     ### STATIC URL.
531     my %ftp = &::ftpList("llug.sep.bnl.gov", "/pub/debian/Incoming/");
532
533     if (!open PKG, ">$pkgfile") {
534         &::ERROR("cannot write to pkg $pkgfile.");
535         return 0;
536     }
537     if (!open IDX, ">$idxfile") {
538         &::ERROR("cannot write to idx $idxfile.");
539         return 0;
540     }
541
542     print IDX "*$pkgfile.gz\n";
543     my $file;
544     foreach $file (sort keys %ftp) {
545         next unless ($file =~ /deb$/);
546
547         if ($file =~ /^(\S+)\_(\S+)\_(\S+)\.deb$/) {
548             print IDX "$1\n";
549             print PKG "Package: $1\n";
550             print PKG "Version: $2\n";
551             print PKG "Architecture: ", (defined $4) ? $4 : "all", "\n";
552         }
553         print PKG "Filename: $file\n";
554         print PKG "Size: $ftp{$file}\n";
555         print PKG "\n";
556     }
557     close IDX;
558     close PKG;
559
560     system("gzip -9fv $pkgfile");       # lame fix.
561
562     &::status("Debian: generateIncoming() complete.");
563 }
564
565
566 ##############################
567 # DEBIAN PACKAGE INFO FUNCTIONS.
568 #########
569
570 # Usage: &getPackageInfo($query,$file);
571 sub getPackageInfo {
572     my ($package, $file) = @_;
573
574     if (! -f $file) {
575         &::status("gPI: file $file does not exist?");
576         return 'NULL';
577     }
578
579     my $found = 0;
580     my (%pkg, $pkg);
581
582     open(IN, "zcat $file 2>&1 |");
583
584     my $done = 0;
585     while (!eof IN) {
586         $_ = <IN>;
587
588         next if (/^ \S+/);      # package long description.
589
590         # package line.
591         if (/^Package: (.*)\n$/) {
592             $pkg = $1;
593             if ($pkg =~ /^\Q$package\E$/i) {
594                 $found++;       # we can use pkg{'package'} instead.
595                 $pkg{'package'} = $pkg;
596             }
597
598             next;
599         }
600
601         if ($found) {
602             chop;
603
604             if (/^Version: (.*)$/) {
605                 $pkg{'version'}         = $1;
606             } elsif (/^Priority: (.*)$/) {
607                 $pkg{'priority'}        = $1;
608             } elsif (/^Section: (.*)$/) {
609                 $pkg{'section'}         = $1;
610             } elsif (/^Size: (.*)$/) {
611                 $pkg{'size'}            = $1;
612             } elsif (/^Installed-Size: (.*)$/i) {
613                 $pkg{'installed'}       = $1;
614             } elsif (/^Description: (.*)$/) {
615                 $pkg{'desc'}            = $1;
616             } elsif (/^Filename: (.*)$/) {
617                 $pkg{'find'}            = $1;
618             } elsif (/^Pre-Depends: (.*)$/) {
619                 $pkg{'depends'}         = "pre-depends on $1";
620             } elsif (/^Depends: (.*)$/) {
621                 if (exists $pkg{'depends'}) {
622                     $pkg{'depends'} .= "; depends on $1";
623                 } else {
624                     $pkg{'depends'} = "depends on $1";
625                 }
626             } elsif (/^Maintainer: (.*)$/) {
627                 $pkg{'maint'} = $1;
628             } elsif (/^Provides: (.*)$/) {
629                 $pkg{'provides'} = $1;
630             } elsif (/^Suggests: (.*)$/) {
631                 $pkg{'suggests'} = $1;
632             } elsif (/^Conflicts: (.*)$/) {
633                 $pkg{'conflicts'} = $1;
634             }
635
636 ###         &::DEBUG("=> '$_'.");
637         }
638
639         # blank line.
640         if (/^$/) {
641             undef $pkg;
642             last if ($found);
643             next;
644         }
645
646         next if (defined $pkg);
647     }
648
649     close IN;
650
651     %pkg;
652 }
653
654 # Usage: &infoPackages($query,$package);
655 sub infoPackages {
656     my ($query,$dist,$package) = ($_[0], &getDistroFromStr($_[1]));
657
658     &::status("Debian: Searching for package '$package' in '$dist'.");
659
660     # download packages file.
661     # hrm...
662     my %urls = &fixDist($dist, %urlpackages);
663     if ($dist ne "incoming") {
664         &::DEBUG("deb: download 3.") if ($debug);
665
666         if (!&DebianDownload($dist, %urls)) {   # no good download.
667             &::WARN("Debian(iP): could not download ANY files.");
668         }
669     }
670
671     # check if the package is valid.
672     my $incoming = 0;
673     my @files = &validPackage($package, $dist);
674     if (!scalar @files) {
675         &::status("Debian: no valid package found; checking incoming.");
676         @files = &validPackage($package, "incoming");
677         if (scalar @files) {
678             &::status("Debian: cool, it exists in incoming.");
679             $incoming++;
680         } else {
681             &::msg($::who, "Package '$package' does not exist.");
682             return 0;
683         }
684     }
685
686     if (scalar @files > 1) {
687         &::WARN("same package in more than one file; random.");
688         &::DEBUG("THIS SHOULD BE FIXED SOMEHOW!!!");
689         $files[0] = &::getRandom(@files);
690     }
691
692     if (! -f $files[0]) {
693         &::WARN("files[0] ($files[0]) doesn't exist.");
694         &::msg($::who, "WARNING: $files[0] does not exist? FIXME");
695         return 'NULL';
696     }
697
698     ### TODO: if specific package is requested, note down that a version
699     ###         exists in incoming.
700
701     my $found = 0;
702     my $file = $files[0];
703     my ($pkg);
704
705     ### TODO: use fe, dump to a hash. if only one version of the package
706     ###         exists. do as normal otherwise list all versions.
707     if (! -f $file) {
708         &::ERROR("D:iP: file '$file' DOES NOT EXIST!!! should never happen.");
709         return 0;
710     }
711     my %pkg = &getPackageInfo($package, $file);
712
713     # 'fm'-like output.
714     if ($query eq "info") {
715         if (scalar keys %pkg > 5) {
716             $pkg{'info'}  = "\002(\002". $pkg{'desc'} ."\002)\002";
717             $pkg{'info'} .= ", section ".$pkg{'section'};
718             $pkg{'info'} .= ", is ".$pkg{'priority'};
719 #           $pkg{'info'} .= ". Version: \002$pkg{'version'}\002";
720             $pkg{'info'} .= ". Version: \002$pkg{'version'}\002 ($dist)";
721             $pkg{'info'} .= ", Packaged size: \002". int($pkg{'size'}/1024) ."\002 kB";
722             $pkg{'info'} .= ", Installed size: \002$pkg{'installed'}\002 kB";
723
724             if ($incoming) {
725                 &::status("iP: info requested and pkg is in incoming, too.");
726                 my %incpkg = &getPackageInfo($query, $debian_dir ."/Packages-incoming");
727
728                 if (scalar keys %incpkg) {
729                    $pkg{'info'} .= ". Is in incoming ($incpkg{'file'}).";
730                 } else {
731                     &::ERROR("iP: pkg $query is in incoming but we couldn't get any info?");
732                 }
733             }
734         } else {
735             &::DEBUG("deb: running debianCheck() due to problems (".scalar(keys %pkg).").");
736             &debianCheck();
737             &::DEBUG("deb: end of debianCheck()");
738
739             &::msg($::who,"Debian: Package appears to exist but I could not retrieve info about it...");
740             return;
741         }
742     } 
743
744     if ($dist eq "incoming") {
745         $pkg{'info'} .= "Version: \002$pkg{'version'}\002";
746         $pkg{'info'} .= ", Packaged size: \002". int($pkg{'size'}/1024) ."\002 kB";
747         $pkg{'info'} .= ", is in incoming!!!";
748     }
749
750     if (!exists $pkg{$query}) {
751         if ($query eq "suggests") {
752             $pkg{$query} = "has no suggestions";
753         } elsif ($query eq "conflicts") {
754             $pkg{$query} = "does not conflict with any other package";
755         } elsif ($query eq "depends") {
756             $pkg{$query} = "does not depend on anything";
757         } elsif ($query eq "maint") {
758             $pkg{$query} = "has no maintainer";
759         } else {
760             $pkg{$query} = "has nothing about $query";
761         }
762     }
763
764     &::pSReply("$package: $pkg{$query}");
765 }
766
767 # Usage: &infoStats($dist);
768 sub infoStats {
769     my ($dist)  = @_;
770     $dist       = &getDistro($dist);
771     return unless (defined $dist);
772
773     &::DEBUG("deb: infoS: dist => '$dist'.");
774
775     # download packages file if needed.
776     my %urls = &fixDist($dist, %urlpackages);
777     &::DEBUG("deb: download 4.");
778     if (!&DebianDownload($dist, %urls)) {
779         &::WARN("Debian(iS): could not download ANY files.");
780         &::msg($::who, "Debian(iS): internal error.");
781         return;
782     }
783
784     my %stats;
785     my %total;
786     my $file;
787     foreach $file (keys %urlpackages) {
788         $file =~ s/##DIST/$dist/g;      # won't work for incoming.
789         &::DEBUG("deb: file => '$file'.");
790         if (exists $stats{$file}{'count'}) {
791             &::DEBUG("deb: hrm... duplicate open with $file???");
792             next;
793         }
794
795         open(IN, "zcat $file 2>&1 |");
796
797         if (! -e $file) {
798             &::DEBUG("deb: iS: $file does not exist.");
799             next;
800         }
801
802         while (!eof IN) {
803             $_ = <IN>;
804
805             next if (/^ \S+/);  # package long description.
806
807             if (/^Package: (.*)\n$/) {          # counter.
808                 $stats{$file}{'count'}++;
809                 $total{'count'}++;
810             } elsif (/^Maintainer: .* <(\S+)>$/) {
811                 $stats{$file}{'maint'}{$1}++;
812                 $total{'maint'}{$1}++;
813             } elsif (/^Size: (.*)$/) {          # compressed size.
814                 $stats{$file}{'csize'}  += $1;
815                 $total{'csize'}         += $1;
816             } elsif (/^i.*size: (.*)$/i) {      # installed size.
817                 $stats{$file}{'isize'}  += $1;
818                 $total{'isize'}         += $1;
819             }
820
821 ###         &::DEBUG("=> '$_'.");
822         }
823         close IN;
824     }
825
826     ### TODO: don't count ppl with multiple email addresses.
827
828     &::pSReply(
829         "Debian Distro Stats on $dist... ".
830         "\002$total{'count'}\002 packages, ".
831         "\002".scalar(keys %{ $total{'maint'} })."\002 maintainers, ".
832         "\002". int($total{'isize'}/1024)."\002 MB installed size, ".
833         "\002". int($total{'csize'}/1024/1024)."\002 MB compressed size."
834     );
835
836 ### TODO: do individual stats? if so, we need _another_ arg.
837 #    foreach $file (keys %stats) {
838 #       foreach (keys %{ $stats{$file} }) {
839 #           &::DEBUG("  '$file' '$_' '$stats{$file}{$_}'.");
840 #       }
841 #    }
842
843     return;
844 }
845
846 ###
847 # HELPER FUNCTIONS FOR INFOPACKAGES...
848 ###
849
850 # Usage: &generateIndex();
851 sub generateIndex {
852     my (@dists) = @_;
853     &::DEBUG("D: generateIndex($dists[0]) called!");
854     if (!scalar @dists or $dists[0] eq '') {
855         &::ERROR("gI: no dists to generate index.");
856         return 1;
857     }
858
859     foreach (@dists) {
860         my $dist = &getDistro($_); # incase the alias is returned, possible?
861         my $idx  = $debian_dir."/Packages-$dist.idx";
862
863         # TODO: check if any of the Packages file have been updated then
864         #       regenerate it, even if it's not stale.
865         # TODO: also, regenerate the index if the packages file is newer
866         #       than the index.
867         next unless (&::isStale($idx, $refresh));
868
869         if (/^incoming$/i) {
870             &::DEBUG("deb: gIndex: calling generateIncoming()!");
871             &generateIncoming();
872             next;
873         }
874
875         if (/^woody$/i) {
876             &::DEBUG("deb: Copying old index of woody to -old");
877             system("cp $idx $idx-old");
878         }
879
880         &::DEBUG("deb: gIndex: calling DebianDownload($dist, ...).") if ($debug);
881         &DebianDownload($dist, &fixDist($dist, %urlpackages) );
882
883         &::status("Debian: generating index for '$dist'.");
884         if (!open OUT, ">$idx") {
885             &::ERROR("cannot write to $idx.");
886             return 0;
887         }
888
889         my $packages;
890         foreach $packages (keys %urlpackages) {
891             $packages =~ s/##DIST/$dist/;
892             $packages =  "$debian_dir/$packages";
893
894             if (! -e $packages) {
895                 &::ERROR("gIndex: '$packages' does not exist?");
896                 next;
897             }
898
899             print OUT "*$packages\n";
900             open(IN,"zcat $packages |");
901
902             while (<IN>) {
903                 next unless (/^Package: (.*)\n$/);
904                 print OUT $1."\n";
905             }
906             close IN;
907         }
908         close OUT;
909     }
910
911     return 1;
912 }
913
914 # Usage: &validPackage($package, $dist);
915 sub validPackage {
916     my ($package,$dist) = @_;
917     my @files;
918     my $file;
919
920     ### this majorly sucks, we need some standard in place.
921     # why is this needed... need to investigate later.
922     my $olddist = $dist;
923     $dist = &getDistro($dist);
924
925     &::DEBUG("deb: validPackage($package, $dist) called.") if ($debug);
926
927     my $error = 0;
928     while (!open IN, $debian_dir."/Packages-$dist.idx") {
929         if ($error) {
930             &::ERROR("Packages-$dist.idx does not exist (#1).");
931             return;
932         }
933
934         &generateIndex($dist);
935
936         $error++;
937     }
938
939     my $count = 0;
940     while (<IN>) {
941         if (/^\*(.*)\n$/) {
942             $file = $1;
943             next;
944         }
945
946         if (/^\Q$package\E\n$/) {
947             push(@files,$file);
948         }
949         $count++;
950     }
951     close IN;
952
953     &::VERB("vP: scanned $count items in index.",2);
954
955     return @files;
956 }
957
958 sub searchPackage {
959     my ($dist, $query) = &getDistroFromStr($_[0]);
960     my $file = $debian_dir."/Packages-$dist.idx";
961     my @files;
962     my $error   = 0;
963     my $warn    = ($query =~ tr/A-Z/a-z/) ? 1 : 0;
964
965     &::status("Debian: Search package matching '$query' in '$dist'.");
966     unlink $file if ( -z $file );
967
968     while (!open IN, $file) {
969         if ($dist eq "incoming") {
970             &::DEBUG("deb: sP: dist == incoming; calling gI().");
971             &generateIncoming();
972         }
973
974         if ($error) {
975             &::ERROR("could not generate index ($file)!");
976             return;
977         }
978
979         $error++;
980         &::DEBUG("deb: should we be doing this?");
981         &generateIndex(($dist));
982     }
983
984     while (<IN>) {
985         chop;
986
987         if (/^\*(.*)$/) {
988             $file = $1;
989
990             if (&::isStale($file, $refresh)) {
991                 &::DEBUG("deb: STALE $file! regen.") if ($debug);
992                 &generateIndex(($dist));
993 ###             @files = searchPackage("$query $dist");
994                 &::DEBUG("deb: EVIL HACK HACK HACK.") if ($debug);
995                 last;
996             }
997
998             next;
999         }
1000
1001         if (/\Q$query\E/) {
1002             push(@files,$_);
1003         }
1004     }
1005     close IN;
1006
1007     if (scalar @files and $warn) {
1008         &::msg($::who, "searching for package name should be fully lowercase!");
1009     }
1010
1011     return @files;
1012 }
1013
1014 sub getDistro {
1015     my $dist = $_[0];
1016
1017     if (!defined $dist or $dist eq "") {
1018         &::DEBUG("deb: gD: dist == NULL; dist = defaultdist.");
1019         $dist = $defaultdist;
1020     }
1021
1022     if ($dist =~ /^(slink|hamm|rex|bo)$/i) {
1023         &::DEBUG("deb: deprecated version ($dist).");
1024         &::msg($::who, "Debian: deprecated distribution version.");
1025         return;
1026     }
1027
1028     if (exists $dists{$dist}) {
1029         &::VERB("gD: returning dists{$dist} ($dists{$dist})",2);
1030         return $dists{$dist};
1031
1032     } else {
1033         if (!grep /^\Q$dist\E$/i, %dists) {
1034             &::msg($::who, "invalid dist '$dist'.");
1035             return;
1036         }
1037
1038         &::VERB("gD: returning $dist (no change or conversion)",2);
1039         return $dist;
1040     }
1041 }
1042
1043 sub getDistroFromStr {
1044     my ($str) = @_;
1045     my $dists   = join '|', %dists;
1046     my $dist    = $defaultdist;
1047
1048     if ($str =~ s/\s+($dists)$//i) {
1049         $dist = &getDistro(lc $1);
1050         $str =~ s/\\+$//;
1051     }
1052     $str =~ s/\\([\$\^])/$1/g;
1053
1054     return($dist,$str);
1055 }
1056
1057 sub fixDist {
1058     my ($dist, %urls) = @_;
1059     my %new;
1060     my ($key,$val);
1061
1062     while (($key,$val) = each %urls) {
1063         $key =~ s/##DIST/$dist/;
1064         $val =~ s/##DIST/$dist/;
1065         ### TODO: what should we do if the sar wasn't done.
1066         $new{$debian_dir."/".$key} = $val;
1067     }
1068
1069     return %new;
1070 }
1071
1072 sub DebianFind {
1073     # HACK! HACK! HACK!
1074     my ($str) = @_;
1075     my ($dist, $query) = &getDistroFromStr($str);
1076     my @results = sort &searchPackage($str);
1077
1078     if (!scalar @results) {
1079         &::Forker("debian", sub { &searchContents($str); } );
1080     } elsif (scalar @results == 1) {
1081         &::status("searchPackage returned one result; getting info of package instead!");
1082         &::Forker("debian", sub { &infoPackages("info", "$results[0] $dist"); } );
1083     } else {
1084         my $prefix = "Debian Package Listing of '$query' ";
1085         &::pSReply( &::formListReply(0, $prefix, @results) );
1086     }
1087 }
1088
1089 sub debianCheck {
1090     my $error   = 0;
1091
1092     &::status("debianCheck() called.");
1093
1094     ### TODO: remove the following loop (check if dir exists before)
1095     while (1) {
1096         last if (opendir(DEBIAN, $debian_dir));
1097
1098         if ($error) {
1099             &::ERROR("dC: cannot opendir debian.");
1100             return;
1101         }
1102
1103         mkdir $debian_dir, 0755;
1104         $error++;
1105     }
1106
1107     my $retval = 0;
1108     my $file;
1109     while (defined($file = readdir DEBIAN)) {
1110         next unless ($file =~ /(gz|bz2)$/);
1111
1112         my $exit = system("gzip -t '$debian_dir/$file'");
1113         next unless ($exit);
1114         &::DEBUG("deb: hmr... => ".(time() - (stat($file))[8])."'.");
1115         next unless (time() - (stat($file))[8] > 3600);
1116
1117         &::DEBUG("deb: dC: exit => '$exit'.");
1118         &::WARN("dC: '$debian_dir/$file' corrupted? deleting!");
1119         unlink $debian_dir."/".$file;
1120         $retval++;
1121     }
1122
1123     return $retval;
1124 }
1125
1126 sub checkEval {
1127     my($str)    = @_;
1128
1129     if ($str) {
1130         &::WARN("cE: $str");
1131         return 0;
1132     } else {
1133         return 1;
1134     }
1135 }
1136
1137 sub searchDescFE {
1138 #    &::DEBUG("deb: FE called for searchDesc");
1139     my ($query) = @_;
1140     my @list = &searchDesc($query);
1141
1142     if (!scalar @list) {
1143         my $prefix = "Debian Desc Search of '$query' ";
1144         &::pSReply( &::formListReply(0, $prefix, ) );
1145     } elsif (scalar @list == 1) {       # list = 1.
1146         &::DEBUG("deb: list == 1; showing package info of '$list[0]'.");
1147         &infoPackages("info", $list[0]);
1148     } else {                            # list > 1.
1149         my $prefix = "Debian Desc Search of '$query' ";
1150         &::pSReply( &::formListReply(0, $prefix, @list) );
1151     }
1152 }
1153
1154 1;