54da632bab3e2041c2a9578af3443096f6283fe3
[bugscan.git] / scanlib.pm
1 #! /usr/bin/perl
2 # vim: ts=4 sw=4 nowrap
3 #
4 # General functions for scanning the BTS-database.
5 # Based on bugscan, written by Richard Braakman <dark@debian.org>,
6 # which was based on an unknown other script.
7 #
8 # Global variables:
9 #   %exclude        - list of bugreports to exclude from the report
10 #   %maintainer     - map from packagename to maintainer
11 #   %section        - map from packagename to section in the FTP-site
12 #   %packagelist    - map from packagename to bugreports
13
14 use lib qw(/org/bugs.debian.org/perl);
15 use LWP::UserAgent;
16 use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522);
17 use Debbugs::Packages;
18 use Debbugs::Versions;
19 use Debbugs::Status;
20 use Fcntl qw(O_RDONLY);
21 use strict;
22 use warnings;
23 require bugcfg;
24 package scanlib;
25
26 our (%exclude,%maintainer,%section,%packagelist,%debbugssection,%bugs);
27
28
29 # Read the list of maintainer 
30 sub readmaintainers() {
31         my $pkg;                                        # Name of package
32         my $mnt;                                        # Maintainer name & email
33
34         open(M, $bugcfg::maintainerlist) or die "open $bugcfg::maintainerlist: $!\n";
35         while (<M>) {
36                 chomp;
37                 m/^(\S+)\s+(\S.*\S)\s*$/ or die "Maintainers: $_ ?";
38                 ($pkg, $mnt) = ($1, $2);
39                 $pkg =~ y/A-Z/a-z/;                     # Normalize package-name. why???
40                 $_=$mnt;
41                 if (not m/</) {
42                         $mnt="$2 <$1>" if ( m/(\S+)\s+\(([^)]+)\)/ );
43                 }
44                 $maintainer{$pkg}= $mnt;
45         }
46         close(M);
47 }
48
49
50 sub readsources() {
51         my $root;                                       # Root of archive we are scanning
52         my $archive;                            # Name of archive we are scanning
53         my $sect;                                       # Name of current section
54
55         $root=shift;
56         $archive=shift;
57         for $sect (@bugcfg::sections) {
58                 open(P, "zcat $root/$sect/source/Sources.gz|")
59                         or die open "open: $sect sourcelist: $!\n";
60                 while (<P>) {
61                         chomp;
62                         next unless m/^Package:\s/;
63                         s/^Package:\s*//;                       # Strip the fieldname
64                         $section{$_} = "$archive/$sect";
65                 }
66                 close (P);
67         }
68 }
69
70 sub readpackages() {
71         my $root;                                       # Root of archive we are scanning
72         my $archive;                            # Name of archive we are scanning
73         my $sect;                                       # Name of current section
74         my $arch;                                       # Name of current architecture
75
76         $root=shift;
77         $archive=shift;
78         for $arch ( @bugcfg::architectures ) {
79                 for $sect ( @bugcfg::sections) {
80                         open(P, "zcat $root/$sect/binary-$arch/Packages.gz|")
81                                 or die "open: $root/$sect/binary-$arch/Packages.gz: $!\n";
82                         while (<P>) {
83                                 chomp;
84                                 next unless m/^Package:\s/;     # We're only interested in the packagenames
85                                 s/^Package:\s*//;                       # Strip the fieldname
86                                 $section{$_} = "$archive/$sect";
87                         }
88                         close(P);
89                 }
90         }
91 }
92
93 sub readdebbugssources() {
94         my $file;
95         my $archive;
96
97         $file=shift;
98         $archive=shift;
99         open(P, $file)
100                 or die "open: $file: $!\n";
101         while (<P>) {
102                 chomp;
103                 my ($host, $bin, $sect, $ver, $src) = split /\s+/;
104                 my $sectname = ($sect =~ /^\Q$archive/) ? $sect : "$archive/$sect";
105                 $debbugssection{$bin} = $sectname;
106                 $debbugssection{$src} = $sectname;
107         }
108         close(P);
109 }
110
111 sub readpseudopackages() {
112         open(P, $bugcfg::pseudolist) or die("open $bugcfg::pseudolist: $!\n");
113         while (<P>) {
114                 chomp;
115                 s/\s.*//;
116                 $section{$_} = "pseudo";
117         }
118         close(P);
119 }
120
121
122 sub scanspool() {
123         my @dirs;
124         my $dir;
125
126         chdir($bugcfg::spooldir) or die "chdir $bugcfg::spooldir: $!\n";
127
128         opendir(DIR, $bugcfg::spooldir) or die "opendir $bugcfg::spooldir: $!\n";
129         @dirs=grep(m/^\d+$/,readdir(DIR));
130         closedir(DIR);
131
132         for $dir (@dirs) {
133                 scanspooldir("$bugcfg::spooldir/$dir");
134         }
135
136 }
137
138 sub scanspooldir() {
139         my ($dir)               = @_;
140         my $f;                  # While we're currently processing
141         my @list;               # List of files to process
142         my $skip;               # Flow control
143         my $walk;               # index variable
144         my $taginfo;    # Tag info
145
146         chdir($dir) or die "chdir $dir: $!\n";
147
148         opendir(DIR, $dir) or die "opendir $dir: $!\n";
149         @list = grep { s/\.summary$// }
150                         grep { m/^\d+\.summary$/ } 
151                         readdir(DIR);
152         closedir(DIR);
153
154         for $f (@list) {
155                 next if $exclude{$f};                   # Check the list of bugs to skip
156         
157                 my $bug = Debbugs::Status::read_bug(summary => "$f.summary");
158                 next if (!defined($bug));
159                 
160                 $skip=1;
161                 for $walk (@bugcfg::priorities) {
162                         $skip=0 if $walk eq $bug->{'severity'};
163                 }
164
165                 my @tags = split(' ', $bug->{'keywords'});
166                 for my $tag (@tags) {
167                         for my $s (@bugcfg::skiptags) {
168                                 $skip=1 if $tag eq $s;
169                         }
170                 }
171                 next if $skip==1;
172         
173                 my %disttags = ();      
174                 $disttags{'oldstable'}    = grep(/^woody$/, @tags);
175                 $disttags{'stable'}       = grep(/^sarge$/, @tags);
176                 $disttags{'testing'}      = grep(/^etch$/, @tags);
177                 $disttags{'unstable'}     = grep(/^sid$/, @tags);
178                 $disttags{'experimental'} = grep(/^experimental$/, @tags);
179                         
180                 # default according to dondelelcaro 2006-11-11
181                 if (!$disttags{'oldstable'} && !$disttags{'stable'} && !$disttags{'testing'} && !$disttags{'unstable'} && !$disttags{'experimental'}) {
182                         $disttags{'testing'} = 1;
183                         $disttags{'unstable'} = 1;
184                         $disttags{'experimental'} = 1;
185                 }
186                 
187                 my $bi = {};
188                 if (defined($section{$bug->{'package'}}) && $section{$bug->{'package'}} eq 'pseudo') {
189                         # versioning information makes no sense for pseudo packages,
190                         # just use the tags
191                         for my $dist qw(oldstable stable testing unstable experimental) {
192                                 $bi->{$dist} = $disttags{$dist};
193                         }
194                         next if (length($bug->{'done'}));
195                 } else {
196                         my $affects_any = 0;
197                 
198                         # only bother to check the versioning status for the distributions indicated by the tags 
199                         for my $dist qw(oldstable stable testing unstable experimental) {
200                                 local $SIG{__WARN__} = sub {};
201
202                                 $bi->{$dist} = 0;
203                                 next if (!$disttags{$dist});
204
205                                 my $presence = Debbugs::Status::bug_presence(
206                                         bug => $f, 
207                                         status => $bug, 
208                                         dist => $dist, 
209                                         arch => \@bugcfg::architectures
210                                 );
211
212                                 # ignore bugs that are absent/fixed in this distribution, include everything
213                                 # else (that is, "found" which says that the bug is present, and undef, which
214                                 # indicates that no versioning information is present and it's not closed
215                                 # unversioned)
216                                 if (!defined($presence) || ($presence ne 'absent' && $presence ne 'fixed')) {
217                                         $bi->{$dist} = 1;
218                                         $affects_any = 1;
219                                 }
220                         }
221                         
222                         next if !$affects_any;
223                 }
224
225                 for my $keyword qw(pending patch help moreinfo unreproducible security upstream etch-ignore) {
226                         $bi->{$keyword} = ($bug->{'keywords'} =~ /\b$keyword\b/) ? 1 : 0;
227                 }
228
229                 if (length($bug->{'mergedwith'})) {
230                         my @merged = split(' ', $bug->{'mergedwith'});
231                         next if ($merged[0] < $f);
232                 }
233
234                 for my $package (split /[,\s]+/, $bug->{'package'}) {
235                         $_= $package; y/A-Z/a-z/; $_= $` if m/[^-+._a-z0-9]/;
236                         push @{$packagelist{$_}}, $f;
237                 }
238
239                 my $taginfo = get_taginfo($bi);
240                 my $relinfo = get_relinfo($bi);
241
242                 $bugs{$f} = "$f [$taginfo] [$relinfo] " . $bug->{'subject'};
243         }
244 }
245
246
247 sub readstatus() {
248         my $bug;                # Number of current bug
249         my $subject;    # Subject for current bug
250         my $pkg;                # Name of current package
251         my $file;               # Name of statusfile
252         my $sect;               # Section of current package
253         my $mnt;                # Maintainer of current package
254
255         $file=shift;
256         open(P, $file) or die "open $file: $!";
257         while (<P>) {
258                 chomp;
259                 if (m/^[0-9]+ \[/) {
260                         ($bug,$subject)=split(/ /, $_, 2);
261                         $bugs{$bug}=$subject;
262                         push @{$packagelist{$pkg}}, $bug;
263                 } else {
264                         ($pkg,$sect, $mnt)=split(/ /, $_, 3);
265                         next if (!defined($pkg));
266                         $section{$pkg}=$sect;
267                         $maintainer{$pkg}=$mnt;
268                 }
269         }
270         close P;
271 }
272
273
274 sub urlsanit {
275         my $url = shift;
276         $url =~ s/%/%25/g;
277         $url =~ s/\+/%2b/g;
278         my %saniarray = ('<','lt', '>','gt', '&','amp', '"','quot');
279         $url =~ s/([<>&"])/\&$saniarray{$1};/g;
280         return $url;
281 }
282
283 sub htmlsanit {
284     my %saniarray = ('<','lt', '>','gt', '&','amp', '"','quot');
285     my $in = shift || "";
286     $in =~ s/([<>&"])/\&$saniarray{$1};/g;
287     return $in;
288 }
289
290 sub wwwnumber() {
291         my $number = shift;             # Number of bug to html-ize
292
293         "<A HREF=\"http://bugs.debian.org/cgi-bin/bugreport.cgi?archive=no&amp;bug=" .
294                 urlsanit($number) . '">' . htmlsanit($number) . '</A>';
295 }
296
297 sub wwwname() {
298         my $name = shift;                       # Name of package
299
300         "<A HREF=\"http://bugs.debian.org/cgi-bin/pkgreport.cgi?archive=no&amp;pkg=" .
301                 urlsanit($name) . '">' . htmlsanit($name) . '</A>';
302 }
303
304 sub check_worry {
305         my ($status) = @_;
306
307         if ($status =~ m/^\[[^]]*I/ or
308             $status !~ m/ \[[^]]*T/) {
309                 return 0;
310         }
311         return 1;
312 }
313
314 sub check_worry_stable {
315         my ($status) = @_;
316
317         if ($status !~ m/ \[[^]]*S/) {
318                 return 0;
319         }
320         return 1;
321 }
322
323 sub get_taginfo {
324     my $bi = shift;
325
326         my $taginfo = "";
327         $taginfo .= $bi->{'pending'}        ? "P" : " ";
328         $taginfo .= $bi->{'patch'}          ? "+" : " ";
329         $taginfo .= $bi->{'help'}           ? "H" : " ";
330         $taginfo .= $bi->{'moreinfo'}       ? "M" : " ";
331         $taginfo .= $bi->{'unreproducible'} ? "R" : " ";
332         $taginfo .= $bi->{'security'}       ? "S" : " ";
333         $taginfo .= $bi->{'upstream'}       ? "U" : " ";
334         $taginfo .= $bi->{'etch-ignore'}    ? "I" : " ";
335
336         return $taginfo;
337 }
338
339 sub get_relinfo {
340     my $bi = shift;
341
342     my $relinfo = "";
343         for my $dist qw(oldstable stable testing unstable experimental) {
344             $relinfo .= uc(substr($dist, 0, 1)) if $bi->{$dist};
345         }
346
347         return $relinfo;
348 }
349
350
351 1;