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),
44 qw(getmaintainers_reverse),
47 misc => [qw(make_list globify_scalar english_join checkpid),
48 qw(cleanup_eval_fail),
50 date => [qw(secs_to_english)],
52 lock => [qw(filelock unfilelock lockpid)],
55 Exporter::export_ok_tags(qw(lock quit date util misc));
56 $EXPORT_TAGS{all} = [@EXPORT_OK];
59 #use Debbugs::Config qw(:globals);
63 use Debbugs::Config qw(:config);
66 use Debbugs::MIME qw(decode_rfc1522);
70 use Params::Validate qw(validate_with :types);
74 our $DEBUG_FH = \*STDERR if not defined $DEBUG_FH;
78 The following functions are exported by the C<:util> tag
80 =head2 getbugcomponent
82 my $file = getbugcomponent($bug_number,$extension,$location)
84 Returns the path to the bug file in location C<$location>, bug number
85 C<$bugnumber> and extension C<$extension>
90 my ($bugnum, $ext, $location) = @_;
92 if (not defined $location) {
93 $location = getbuglocation($bugnum, $ext);
94 # Default to non-archived bugs only for now; CGI scripts want
95 # archived bugs but most of the backend scripts don't. For now,
96 # anything that is prepared to accept archived bugs should call
97 # getbuglocation() directly first.
98 return undef if defined $location and
99 ($location ne 'db' and $location ne 'db-h');
101 my $dir = getlocationpath($location);
102 return undef if not defined $dir;
103 if (defined $location and $location eq 'db') {
104 return "$dir/$bugnum.$ext";
106 my $hash = get_hashname($bugnum);
107 return "$dir/$hash/$bugnum.$ext";
111 =head2 getbuglocation
113 getbuglocation($bug_number,$extension)
115 Returns the the location in which a particular bug exists; valid
116 locations returned currently are archive, db-h, or db. If the bug does
117 not exist, returns undef.
122 my ($bugnum, $ext) = @_;
123 my $archdir = get_hashname($bugnum);
124 return 'archive' if -r getlocationpath('archive')."/$archdir/$bugnum.$ext";
125 return 'db-h' if -r getlocationpath('db-h')."/$archdir/$bugnum.$ext";
126 return 'db' if -r getlocationpath('db')."/$bugnum.$ext";
131 =head2 getlocationpath
133 getlocationpath($location)
135 Returns the path to a specific location
139 sub getlocationpath {
141 if (defined $location and $location eq 'archive') {
142 return "$config{spool_dir}/archive";
143 } elsif (defined $location and $location eq 'db') {
144 return "$config{spool_dir}/db";
146 return "$config{spool_dir}/db-h";
155 Returns the hash of the bug which is the location within the archive
160 return "" if ( $_[ 0 ] < 0 );
161 return sprintf "%02d", $_[ 0 ] % 100;
168 Returns the path to the logfile corresponding to the bug.
170 Returns undef if the bug does not exist.
176 my $location = getbuglocation($bugnum, 'log');
177 return getbugcomponent($bugnum, 'log', $location) if ($location);
178 $location = getbuglocation($bugnum, 'log.gz');
179 return getbugcomponent($bugnum, 'log.gz', $location) if ($location);
188 Returns the path to the summary file corresponding to the bug.
190 Returns undef if the bug does not exist.
196 my $location = getbuglocation($bugnum, 'summary');
197 return getbugcomponent($bugnum, 'summary', $location) if ($location);
203 appendfile($file,'data','to','append');
205 Opens a file for appending and writes data to it.
210 my ($file,@data) = @_;
211 my $fh = IO::File->new($file,'a') or
212 die "Unable top open $file for appending: $!";
213 print {$fh} @data or die "Unable to write to $file: $!";
214 close $fh or die "Unable to close $file: $!";
217 =head2 getparsedaddrs
219 my $address = getparsedaddrs($address);
220 my @address = getparsedaddrs($address);
222 Returns the output from Mail::Address->parse, or the cached output if
223 this address has been parsed before. In SCALAR context returns the
224 first address parsed.
232 return () unless defined $addr;
233 return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0]
234 if exists $_parsedaddrs{$addr};
236 # don't display the warnings from Mail::Address->parse
237 local $SIG{__WARN__} = sub { };
238 @{$_parsedaddrs{$addr}} = Mail::Address->parse($addr);
240 return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0];
243 =head2 getmaintainers
245 my $maintainer = getmaintainers()->{debbugs}
247 Returns a hashref of package => maintainer pairs.
252 our $_maintainer_rev;
254 return $_maintainer if $_maintainer;
257 for my $file (@config{qw(maintainer_file maintainer_file_override pseduo_maint_file)}) {
258 next unless defined $file;
259 my $maintfile = IO::File->new($file,'r') or
260 die "Unable to open maintainer file $file: $!";
261 while(<$maintfile>) {
262 next unless m/^(\S+)\s+(\S.*\S)\s*$/;
266 for my $maint (map {lc($_->address)} getparsedaddrs($b)) {
267 push @{$maintainer_rev{$maint}},$a;
272 $_maintainer = \%maintainer;
273 $_maintainer_rev = \%maintainer_rev;
277 =head2 getmaintainers_reverse
279 my @packages = @{getmaintainers_reverse->{'don@debian.org'}||[]};
281 Returns a hashref of maintainer => [qw(list of packages)] pairs.
285 sub getmaintainers_reverse{
286 return $_maintainer_rev if $_maintainer_rev;
288 return $_maintainer_rev;
293 my $pseudopkgdesc = getpseudodesc(...);
295 Returns the entry for a pseudo package from the
296 $config{pseudo_desc_file}. In cases where pseudo_desc_file is not
297 defined, returns an empty arrayref.
299 This function can be used to see if a particular package is a
300 pseudopackage or not.
306 return $_pseudodesc if $_pseudodesc;
309 if (not defined $config{pseudo_desc_file}) {
313 my $pseudo = IO::File->new($config{pseudo_desc_file},'r')
314 or die "Unable to open $config{pseudo_desc_file}: $!";
316 next unless m/^(\S+)\s+(\S.*\S)\s*$/;
317 $pseudodesc{lc $1} = $2;
320 $_pseudodesc = \%pseudodesc;
327 my $english = secs_to_english($seconds);
328 my ($days,$english) = secs_to_english($seconds);
330 XXX This should probably be changed to use Date::Calc
337 my $days = int($seconds / 86400);
338 my $years = int($days / 365);
342 push @age, "1 year" if ($years == 1);
343 push @age, "$years years" if ($years > 1);
344 push @age, "1 day" if ($days == 1);
345 push @age, "$days days" if ($days > 1);
346 $result .= join(" and ", @age);
348 return wantarray?(int($seconds/86400),$result):$result;
354 These functions are exported with the :lock tag
360 FLOCKs the passed file. Use unfilelock to unlock it.
367 # NB - NOT COMPATIBLE WITH `with-lock'
369 if ($lockfile !~ m{^/}) {
370 $lockfile = cwd().'/'.$lockfile;
373 $count= 10; $errors= '';
376 my $fh2 = IO::File->new($lockfile,'w')
377 or die "Unable to open $lockfile for writing: $!";
378 flock($fh2,LOCK_EX|LOCK_NB)
379 or die "Unable to lock $lockfile $!";
386 push @filelocks, {fh => $fh, file => $lockfile};
391 die "failed to get lock on $lockfile -- $errors";
397 # clean up all outstanding locks at end time
409 Unlocks the file most recently locked.
411 Note that it is not currently possible to unlock a specific file
412 locked with filelock.
417 if (@filelocks == 0) {
418 warn "unfilelock called with no active filelocks!\n";
421 my %fl = %{pop(@filelocks)};
422 flock($fl{fh},LOCK_UN)
423 or warn "Unable to unlock lockfile $fl{file}: $!";
425 or warn "Unable to close lockfile $fl{file}: $!";
427 or warn "Unable to unlink lockfile $fl{file}: $!";
433 lockpid('/path/to/pidfile');
435 Creates a pidfile '/path/to/pidfile' if one doesn't exist or if the
436 pid in the file does not respond to kill 0.
438 Returns 1 on success, false on failure; dies on unusual errors.
445 my $pid = checkpid($pidfile);
446 die "Unable to read pidfile $pidfile: $!" if not defined $pid;
447 return 0 if $pid != 0;
449 die "Unable to unlink stale pidfile $pidfile $!";
451 my $pidfh = IO::File->new($pidfile,'w') or
452 die "Unable to open $pidfile for writing: $!";
453 print {$pidfh} $$ or die "Unable to write to $pidfile $!";
454 close $pidfh or die "Unable to close $pidfile $!";
460 checkpid('/path/to/pidfile');
462 Checks a pid file and determines if the process listed in the pidfile
463 is still running. Returns the pid if it is, 0 if it isn't running, and
464 undef if the pidfile doesn't exist or cannot be read.
471 my $pidfh = IO::File->new($pidfile, 'r') or
476 ($pid) = $pid =~ /(\d+)/;
477 if (defined $pid and kill(0,$pid)) {
490 These functions are exported with the :quit tag.
496 Exits the program by calling die.
498 Usage of quit is deprecated; just call die instead.
503 print {$DEBUG_FH} "quitting >$_[0]<\n" if $DEBUG;
504 carp "quit() is deprecated; call die directly instead";
510 These functions are exported with the :misc tag
514 LIST = make_list(@_);
516 Turns a scalar or an arrayref into a list; expands a list of arrayrefs
519 That is, make_list([qw(a b c)]); returns qw(a b c); make_list([qw(a
520 b)],[qw(c d)] returns qw(a b c d);
525 return map {(ref($_) eq 'ARRAY')?@{$_}:$_} @_;
531 print english_join(list => \@list);
532 print english_join(\@list);
534 Joins list properly to make an english phrase.
538 =item normal -- how to separate most values; defaults to ', '
540 =item last -- how to separate the last two values; defaults to ', and '
542 =item only_two -- how to separate only two values; defaults to ' and '
544 =item list -- ARRAYREF values to join; if the first argument is an
545 ARRAYREF, it's assumed to be the list of values to join
549 In cases where C<list> is empty, returns ''; when there is only one
550 element, returns that element.
555 if (ref $_[0] eq 'ARRAY') {
556 return english_join(list=>$_[0]);
558 my %param = validate_with(params => \@_,
559 spec => {normal => {type => SCALAR,
562 last => {type => SCALAR,
565 only_two => {type => SCALAR,
568 list => {type => ARRAYREF,
572 my @list = @{$param{list}};
574 return @list?$list[0]:'';
577 return join($param{only_two},@list);
579 my $ret = $param{last} . pop(@list);
580 return join($param{normal},@list) . $ret;
584 =head2 globify_scalar
586 my $handle = globify_scalar(\$foo);
588 if $foo isn't already a glob or a globref, turn it into one using
589 IO::Scalar. Gives a new handle to /dev/null if $foo isn't defined.
591 Will carp if given a scalar which isn't a scalarref or a glob (or
592 globref), and return /dev/null. May return undef if IO::Scalar or
593 IO::File fails. (Check $!)
600 if (defined $scalar) {
601 if (defined ref($scalar)) {
602 if (ref($scalar) eq 'SCALAR' and
603 not UNIVERSAL::isa($scalar,'GLOB')) {
604 return IO::Scalar->new($scalar);
610 elsif (UNIVERSAL::isa(\$scalar,'GLOB')) {
614 carp "Given a non-scalar reference, non-glob to globify_scalar; returning /dev/null handle";
617 return IO::File->new('/dev/null','w');
620 =head2 cleanup_eval_fail()
622 print "Something failed with: ".cleanup_eval_fail($@);
624 Does various bits of cleanup on the failure message from an eval (or
625 any other die message)
627 Takes at most two options; the first is the actual failure message
628 (usually $@ and defaults to $@), the second is the debug level
629 (defaults to $DEBUG).
631 If debug is non-zero, the code at which the failure occured is output.
635 sub cleanup_eval_fail {
636 my ($error,$debug) = @_;
637 if (not defined $error or not @_) {
638 $error = $@ || 'unknown reason';
641 $debug = $DEBUG || 0;
643 $debug = 0 if not defined $debug;
648 # ditch the "at foo/bar/baz.pm line 5"
649 $error =~ s/\sat\s\S+\sline\s\d+//;
650 # ditch trailing multiple periods in case there was a cascade of
652 $error =~ s/\.+$/\./;