1 # This module is part of debbugs, and is released
2 # under the terms of the GPL version 2, or any later
3 # version at your option.
4 # See the file README and COPYING for more information.
6 # [Other people have contributed to this file; their copyrights should
8 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
10 package Debbugs::Common;
14 Debbugs::Common -- Common routines for all of Debbugs
18 use Debbugs::Common qw(:url :html);
23 This module is a replacement for the general parts of errorlib.pl.
24 subroutines in errorlib.pl will be gradually phased out and replaced
25 with equivalent (or better) functionality here.
33 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
34 use base qw(Exporter);
38 $DEBUG = 0 unless defined $DEBUG;
41 %EXPORT_TAGS = (util => [qw(getbugcomponent getbuglocation getlocationpath get_hashname),
42 qw(appendfile buglog getparsedaddrs getmaintainers),
43 qw(getmaintainers_reverse)
45 misc => [qw(make_list)],
46 date => [qw(secs_to_english)],
48 lock => [qw(filelock unfilelock @cleanups lockpid)],
51 Exporter::export_ok_tags(qw(lock quit date util misc));
52 $EXPORT_TAGS{all} = [@EXPORT_OK];
55 #use Debbugs::Config qw(:globals);
56 use Debbugs::Config qw(:config);
58 use Debbugs::MIME qw(decode_rfc1522);
64 our $DEBUG_FH = \*STDERR if not defined $DEBUG_FH;
68 The following functions are exported by the C<:util> tag
70 =head2 getbugcomponent
72 my $file = getbugcomponent($bug_number,$extension,$location)
74 Returns the path to the bug file in location C<$location>, bug number
75 C<$bugnumber> and extension C<$extension>
80 my ($bugnum, $ext, $location) = @_;
82 if (not defined $location) {
83 $location = getbuglocation($bugnum, $ext);
84 # Default to non-archived bugs only for now; CGI scripts want
85 # archived bugs but most of the backend scripts don't. For now,
86 # anything that is prepared to accept archived bugs should call
87 # getbuglocation() directly first.
88 return undef if defined $location and
89 ($location ne 'db' and $location ne 'db-h');
91 my $dir = getlocationpath($location);
92 return undef if not defined $dir;
93 if (defined $location and $location eq 'db') {
94 return "$dir/$bugnum.$ext";
96 my $hash = get_hashname($bugnum);
97 return "$dir/$hash/$bugnum.$ext";
101 =head2 getbuglocation
103 getbuglocation($bug_number,$extension)
105 Returns the the location in which a particular bug exists; valid
106 locations returned currently are archive, db-h, or db. If the bug does
107 not exist, returns undef.
112 my ($bugnum, $ext) = @_;
113 my $archdir = get_hashname($bugnum);
114 return 'archive' if -r getlocationpath('archive')."/$archdir/$bugnum.$ext";
115 return 'db-h' if -r getlocationpath('db-h')."/$archdir/$bugnum.$ext";
116 return 'db' if -r getlocationpath('db')."/$bugnum.$ext";
121 =head2 getlocationpath
123 getlocationpath($location)
125 Returns the path to a specific location
129 sub getlocationpath {
131 if (defined $location and $location eq 'archive') {
132 return "$config{spool_dir}/archive";
133 } elsif (defined $location and $location eq 'db') {
134 return "$config{spool_dir}/db";
136 return "$config{spool_dir}/db-h";
145 Returns the hash of the bug which is the location within the archive
150 return "" if ( $_[ 0 ] < 0 );
151 return sprintf "%02d", $_[ 0 ] % 100;
158 Returns the path to the logfile corresponding to the bug.
164 my $location = getbuglocation($bugnum, 'log');
165 return getbugcomponent($bugnum, 'log', $location) if ($location);
166 $location = getbuglocation($bugnum, 'log.gz');
167 return getbugcomponent($bugnum, 'log.gz', $location);
173 appendfile($file,'data','to','append');
175 Opens a file for appending and writes data to it.
181 if (!open(AP,">>$file")) {
182 print $DEBUG_FH "failed open log<\n" if $DEBUG;
183 print $DEBUG_FH "failed open log err $!<\n" if $DEBUG;
184 &quit("opening $file (appendfile): $!");
186 print(AP @_) || &quit("writing $file (appendfile): $!");
187 close(AP) || &quit("closing $file (appendfile): $!");
190 =head2 getparsedaddrs
192 my $address = getparsedaddrs($address);
193 my @address = getparsedaddrs($address);
195 Returns the output from Mail::Address->parse, or the cached output if
196 this address has been parsed before. In SCALAR context returns the
197 first address parsed.
205 return () unless defined $addr;
206 return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0]
207 if exists $_parsedaddrs{$addr};
208 @{$_parsedaddrs{$addr}} = Mail::Address->parse($addr);
209 return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0];
213 our $_maintainer_rev;
215 return $_maintainer if $_maintainer;
218 for my $file (@config{qw(maintainer_file maintainer_file_override)}) {
219 next unless defined $file;
220 my $maintfile = new IO::File $file,'r' or
221 &quitcgi("Unable to open $file: $!");
222 while(<$maintfile>) {
223 next unless m/^(\S+)\s+(\S.*\S)\s*$/;
227 for my $maint (map {lc($_->address)} getparsedaddrs($b)) {
228 push @{$maintainer_rev{$maint}},$a;
233 $_maintainer = \%maintainer;
234 $_maintainer_rev = \%maintainer_rev;
237 sub getmaintainers_reverse{
238 return $_maintainer_rev if $_maintainer_rev;
240 return $_maintainer_rev;
245 my $english = secs_to_english($seconds);
246 my ($days,$english) = secs_to_english($seconds);
248 XXX This should probably be changed to use Date::Calc
255 my $days = int($seconds / 86400);
256 my $years = int($days / 365);
260 push @age, "1 year" if ($years == 1);
261 push @age, "$years years" if ($years > 1);
262 push @age, "1 day" if ($days == 1);
263 push @age, "$days days" if ($days > 1);
264 $result .= join(" and ", @age);
266 return wantarray?(int($seconds/86400),$result):$result;
272 These functions are exported with the :lock tag
278 FLOCKs the passed file. Use unfilelock to unlock it.
286 # NB - NOT COMPATIBLE WITH `with-lock'
288 if ($lockfile !~ m{^/}) {
289 $lockfile = cwd().'/'.$lockfile;
292 $count= 10; $errors= '';
295 my $fh2 = IO::File->new($lockfile,'w')
296 or die "Unable to open $lockfile for writing: $!";
297 flock($fh2,LOCK_EX|LOCK_NB)
298 or die "Unable to lock $lockfile $!";
305 push @filelocks, {fh => $fh, file => $lockfile};
310 &quit("failed to get lock on $lockfile -- $errors");
314 push(@cleanups,\&unfilelock);
322 Unlocks the file most recently locked.
324 Note that it is not currently possible to unlock a specific file
325 locked with filelock.
330 if (@filelocks == 0) {
331 warn "unfilelock called with no active filelocks!\n";
334 my %fl = %{pop(@filelocks)};
336 flock($fl{fh},LOCK_UN)
337 or warn "Unable to unlock lockfile $fl{file}: $!";
339 or warn "Unable to close lockfile $fl{file}: $!";
341 or warn "Unable to unlink lockfile $fl{file}: $!";
346 lockpid('/path/to/pidfile');
348 Creates a pidfile '/path/to/pidfile' if one doesn't exist or if the
349 pid in the file does not respond to kill 0.
351 Returns 1 on success, false on failure; dies on unusual errors.
358 my $pidfh = IO::File->new($pidfile, 'r') or
359 die "Unable to open pidfile $pidfile: $!";
362 ($pid) = $pid =~ /(\d+)/;
363 if (defined $pid and kill(0,$pid)) {
368 die "Unable to unlink stale pidfile $pidfile $!";
370 my $pidfh = IO::File->new($pidfile,'w') or
371 die "Unable to open $pidfile for writing: $!";
372 print {$pidfh} $$ or die "Unable to write to $pidfile $!";
373 close $pidfh or die "Unable to close $pidfile $!";
380 These functions are exported with the :quit tag.
386 Exits the program by calling die after running some cleanups.
388 This should be replaced with an END handler which runs the cleanups
389 instead. (Or possibly a die handler, if the cleanups are important)
394 print $DEBUG_FH "quitting >$_[0]<\n" if $DEBUG;
396 while ($u= $cleanups[$#cleanups]) { &$u; }
402 These functions are exported with the :misc tag
406 LIST = make_list(@_);
408 Turns a scalar or an arrayref into a list; expands a list of arrayrefs
411 That is, make_list([qw(a b c)]); returns qw(a b c); make_list([qw(a
412 b)],[qw(c d)] returns qw(a b c d);
417 return map {(ref($_) eq 'ARRAY')?@{$_}:$_} @_;