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 Exporter qw(import);
38 $DEBUG = 0 unless defined $DEBUG;
41 %EXPORT_TAGS = (util => [qw(getbugcomponent getbuglocation getlocationpath get_hashname),
42 qw(appendfile overwritefile buglog getparsedaddrs getmaintainers),
43 qw(getsourcemaintainers getsourcemaintainers_reverse),
45 qw(getmaintainers_reverse),
47 qw(package_maintainer),
49 qw(open_compressed_file),
51 misc => [qw(make_list globify_scalar english_join checkpid),
52 qw(cleanup_eval_fail),
55 date => [qw(secs_to_english)],
57 lock => [qw(filelock unfilelock lockpid simple_filelock simple_unlockfile)],
60 Exporter::export_ok_tags(keys %EXPORT_TAGS);
61 $EXPORT_TAGS{all} = [@EXPORT_OK];
64 #use Debbugs::Config qw(:globals);
69 use Debbugs::Config qw(:config);
72 use Debbugs::MIME qw(decode_rfc1522);
75 use Storable qw(dclone);
76 use Time::HiRes qw(usleep);
78 use Params::Validate qw(validate_with :types);
80 use Fcntl qw(:DEFAULT :flock);
81 use Encode qw(is_utf8 decode_utf8);
83 our $DEBUG_FH = \*STDERR if not defined $DEBUG_FH;
87 The following functions are exported by the C<:util> tag
89 =head2 getbugcomponent
91 my $file = getbugcomponent($bug_number,$extension,$location)
93 Returns the path to the bug file in location C<$location>, bug number
94 C<$bugnumber> and extension C<$extension>
99 my ($bugnum, $ext, $location) = @_;
101 if (not defined $location) {
102 $location = getbuglocation($bugnum, $ext);
103 # Default to non-archived bugs only for now; CGI scripts want
104 # archived bugs but most of the backend scripts don't. For now,
105 # anything that is prepared to accept archived bugs should call
106 # getbuglocation() directly first.
107 return undef if defined $location and
108 ($location ne 'db' and $location ne 'db-h');
110 my $dir = getlocationpath($location);
111 return undef if not defined $dir;
112 if (defined $location and $location eq 'db') {
113 return "$dir/$bugnum.$ext";
115 my $hash = get_hashname($bugnum);
116 return "$dir/$hash/$bugnum.$ext";
120 =head2 getbuglocation
122 getbuglocation($bug_number,$extension)
124 Returns the the location in which a particular bug exists; valid
125 locations returned currently are archive, db-h, or db. If the bug does
126 not exist, returns undef.
131 my ($bugnum, $ext) = @_;
132 my $archdir = get_hashname($bugnum);
133 return 'archive' if -r getlocationpath('archive')."/$archdir/$bugnum.$ext";
134 return 'db-h' if -r getlocationpath('db-h')."/$archdir/$bugnum.$ext";
135 return 'db' if -r getlocationpath('db')."/$bugnum.$ext";
140 =head2 getlocationpath
142 getlocationpath($location)
144 Returns the path to a specific location
148 sub getlocationpath {
150 if (defined $location and $location eq 'archive') {
151 return "$config{spool_dir}/archive";
152 } elsif (defined $location and $location eq 'db') {
153 return "$config{spool_dir}/db";
155 return "$config{spool_dir}/db-h";
164 Returns the hash of the bug which is the location within the archive
169 return "" if ( $_[ 0 ] < 0 );
170 return sprintf "%02d", $_[ 0 ] % 100;
177 Returns the path to the logfile corresponding to the bug.
179 Returns undef if the bug does not exist.
185 my $location = getbuglocation($bugnum, 'log');
186 return getbugcomponent($bugnum, 'log', $location) if ($location);
187 $location = getbuglocation($bugnum, 'log.gz');
188 return getbugcomponent($bugnum, 'log.gz', $location) if ($location);
197 Returns the path to the summary file corresponding to the bug.
199 Returns undef if the bug does not exist.
205 my $location = getbuglocation($bugnum, 'summary');
206 return getbugcomponent($bugnum, 'summary', $location) if ($location);
212 appendfile($file,'data','to','append');
214 Opens a file for appending and writes data to it.
219 my ($file,@data) = @_;
220 my $fh = IO::File->new($file,'a') or
221 die "Unable top open $file for appending: $!";
222 print {$fh} @data or die "Unable to write to $file: $!";
223 close $fh or die "Unable to close $file: $!";
228 ovewritefile($file,'data','to','append');
230 Opens file.new, writes data to it, then moves file.new to file.
235 my ($file,@data) = @_;
236 my $fh = IO::File->new("${file}.new",'w') or
237 die "Unable top open ${file}.new for writing: $!";
238 print {$fh} @data or die "Unable to write to ${file}.new: $!";
239 close $fh or die "Unable to close ${file}.new: $!";
240 rename("${file}.new",$file) or
241 die "Unable to rename ${file}.new to $file: $!";
244 =head2 open_compressed_file
246 my $fh = open_compressed_file('foo.gz') or
247 die "Unable to open compressed file: $!";
250 Opens a file; if the file ends in .gz, .xz, or .bz2, the appropriate
251 decompression program is forked and output from it is read.
253 This routine by default opens the file with UTF-8 encoding; if you want some
254 other encoding, specify it with the second option.
257 sub open_compressed_file {
258 my ($file,$encoding) = @_;
259 $encoding //= ':encoding(UTF-8)';
261 my $mode = "<$encoding";
263 if ($file =~ /\.gz$/) {
264 $mode = "-|$encoding";
265 push @opts,'gzip','-dc';
267 if ($file =~ /\.xz$/) {
268 $mode = "-|$encoding";
269 push @opts,'xz','-dc';
271 if ($file =~ /\.bz2$/) {
272 $mode = "-|$encoding";
273 push @opts,'bzip2','-dc';
275 open($fh,$mode,@opts,$file);
281 =head2 getparsedaddrs
283 my $address = getparsedaddrs($address);
284 my @address = getparsedaddrs($address);
286 Returns the output from Mail::Address->parse, or the cached output if
287 this address has been parsed before. In SCALAR context returns the
288 first address parsed.
296 return () unless defined $addr;
297 return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0]
298 if exists $_parsedaddrs{$addr};
300 # don't display the warnings from Mail::Address->parse
301 local $SIG{__WARN__} = sub { };
302 @{$_parsedaddrs{$addr}} = Mail::Address->parse($addr);
304 return wantarray?@{$_parsedaddrs{$addr}}:$_parsedaddrs{$addr}[0];
307 =head2 getmaintainers
309 my $maintainer = getmaintainers()->{debbugs}
311 Returns a hashref of package => maintainer pairs.
315 our $_maintainer = undef;
316 our $_maintainer_rev = undef;
318 return $_maintainer if defined $_maintainer;
319 package_maintainer(rehash => 1);
323 =head2 getmaintainers_reverse
325 my @packages = @{getmaintainers_reverse->{'don@debian.org'}||[]};
327 Returns a hashref of maintainer => [qw(list of packages)] pairs.
331 sub getmaintainers_reverse{
332 return $_maintainer_rev if defined $_maintainer_rev;
333 package_maintainer(rehash => 1);
334 return $_maintainer_rev;
337 =head2 getsourcemaintainers
339 my $maintainer = getsourcemaintainers()->{debbugs}
341 Returns a hashref of src_package => maintainer pairs.
345 our $_source_maintainer = undef;
346 our $_source_maintainer_rev = undef;
347 sub getsourcemaintainers {
348 return $_source_maintainer if defined $_source_maintainer;
349 package_maintainer(rehash => 1);
350 return $_source_maintainer;
353 =head2 getsourcemaintainers_reverse
355 my @src_packages = @{getsourcemaintainers_reverse->{'don@debian.org'}||[]};
357 Returns a hashref of maintainer => [qw(list of source packages)] pairs.
361 sub getsourcemaintainers_reverse{
362 return $_source_maintainer_rev if defined $_source_maintainer_rev;
363 package_maintainer(rehash => 1);
364 return $_source_maintainer_rev;
367 =head2 package_maintainer
369 my @s = package_maintainer(source => [qw(foo bar baz)],
370 binary => [qw(bleh blah)],
375 =item source -- scalar or arrayref of source package names to return
376 maintainers for, defaults to the empty arrayref.
378 =item binary -- scalar or arrayref of binary package names to return
379 maintainers for; automatically returns source package maintainer if
380 the package name starts with 'src:', defaults to the empty arrayref.
382 =item reverse -- whether to return the source/binary packages a
383 maintainer maintains instead
385 =item rehash -- whether to reread the maintainer and source maintainer
392 sub package_maintainer {
393 my %param = validate_with(params => \@_,
394 spec => {source => {type => SCALAR|ARRAYREF,
397 binary => {type => SCALAR|ARRAYREF,
400 maintainer => {type => SCALAR|ARRAYREF,
403 rehash => {type => BOOLEAN,
406 reverse => {type => BOOLEAN,
411 my @binary = make_list($param{binary});
412 my @source = make_list($param{source});
413 my @maintainers = make_list($param{maintainer});
414 if ((@binary or @source) and @maintainers) {
415 croak "It is nonsensical to pass both maintainers and source or binary";
417 if ($param{rehash}) {
418 $_source_maintainer = undef;
419 $_source_maintainer_rev = undef;
420 $_maintainer = undef;
421 $_maintainer_rev = undef;
423 if (not defined $_source_maintainer or
424 not defined $_source_maintainer_rev) {
425 $_source_maintainer = {};
426 $_source_maintainer_rev = {};
427 for my $fn (@config{('source_maintainer_file',
428 'source_maintainer_file_override',
429 'pseudo_maint_file')}) {
430 next unless defined $fn and length $fn;
432 warn "Missing source maintainer file '$fn'";
435 __add_to_hash($fn,$_source_maintainer,
436 $_source_maintainer_rev);
439 if (not defined $_maintainer or
440 not defined $_maintainer_rev) {
442 $_maintainer_rev = {};
443 for my $fn (@config{('maintainer_file',
444 'maintainer_file_override',
445 'pseudo_maint_file')}) {
446 next unless defined $fn and length $fn;
448 warn "Missing maintainer file '$fn'";
451 __add_to_hash($fn,$_maintainer,
456 for my $binary (@binary) {
457 if (not $param{reverse} and $binary =~ /^src:/) {
458 push @source,$binary;
461 push @return,grep {defined $_} make_list($_maintainer->{$binary});
463 for my $source (@source) {
464 $source =~ s/^src://;
465 push @return,grep {defined $_} make_list($_source_maintainer->{$source});
467 for my $maintainer (grep {defined $_} @maintainers) {
468 push @return,grep {defined $_}
469 make_list($_maintainer_rev->{$maintainer});
470 push @return,map {$_ !~ /^src:/?'src:'.$_:$_}
472 make_list($_source_maintainer_rev->{$maintainer});
477 #=head2 __add_to_hash
479 # __add_to_hash($file,$forward_hash,$reverse_hash,'address');
481 # Reads a maintainer/source maintainer/pseudo desc file and adds the
482 # maintainers from it to the forward and reverse hashref; assumes that
483 # the forward is unique; makes no assumptions of the reverse.
488 my ($fn,$forward,$reverse,$type) = @_;
489 if (ref($forward) ne 'HASH') {
490 croak "__add_to_hash must be passed a hashref for the forward";
492 if (defined $reverse and not ref($reverse) eq 'HASH') {
493 croak "if reverse is passed to __add_to_hash, it must be a hashref";
496 my $fh = IO::File->new($fn,'r') or
497 croak "Unable to open $fn for reading: $!";
498 binmode($fh,':encoding(UTF-8)');
501 next unless m/^(\S+)\s+(\S.*\S)\s*$/;
502 my ($key,$value)=($1,$2);
504 $forward->{$key}= $value;
505 if (defined $reverse) {
506 if ($type eq 'address') {
507 for my $m (map {lc($_->address)} (getparsedaddrs($value))) {
508 push @{$reverse->{$m}},$key;
512 push @{$reverse->{$value}}, $key;
521 my $pseudopkgdesc = getpseudodesc(...);
523 Returns the entry for a pseudo package from the
524 $config{pseudo_desc_file}. In cases where pseudo_desc_file is not
525 defined, returns an empty arrayref.
527 This function can be used to see if a particular package is a
528 pseudopackage or not.
532 our $_pseudodesc = undef;
534 return $_pseudodesc if defined $_pseudodesc;
536 __add_to_hash($config{pseudo_desc_file},$_pseudodesc) if
537 defined $config{pseudo_desc_file} and
538 length $config{pseudo_desc_file};
544 sort_versions('1.0-2','1.1-2');
546 Sorts versions using AptPkg::Versions::compare if it is available, or
547 Debbugs::Versions::Dpkg::vercmp if it isn't.
553 use Debbugs::Versions::Dpkg;
554 $vercmp=\&Debbugs::Versions::Dpkg::vercmp;
556 # eventually we'll use AptPkg:::Version or similar, but the current
557 # implementation makes this *super* difficult.
560 # use AptPkg::Version;
561 # $vercmp=\&AptPkg::Version::compare;
566 return sort {$vercmp->($a,$b)} @_;
572 my $english = secs_to_english($seconds);
573 my ($days,$english) = secs_to_english($seconds);
575 XXX This should probably be changed to use Date::Calc
582 my $days = int($seconds / 86400);
583 my $years = int($days / 365);
587 push @age, "1 year" if ($years == 1);
588 push @age, "$years years" if ($years > 1);
589 push @age, "1 day" if ($days == 1);
590 push @age, "$days days" if ($days > 1);
591 $result .= join(" and ", @age);
593 return wantarray?(int($seconds/86400),$result):$result;
599 These functions are exported with the :lock tag
604 filelock($lockfile,$locks);
606 FLOCKs the passed file. Use unfilelock to unlock it.
608 Can be passed an optional $locks hashref, which is used to track which
609 files are locked (and how many times they have been locked) to allow
610 for cooperative locking.
619 # NB - NOT COMPATIBLE WITH `with-lock'
620 my ($lockfile,$locks) = @_;
621 if ($lockfile !~ m{^/}) {
622 $lockfile = cwd().'/'.$lockfile;
624 # This is only here to allow for relocking bugs inside of
625 # Debbugs::Control. Nothing else should be using it.
626 if (defined $locks and exists $locks->{locks}{$lockfile} and
627 $locks->{locks}{$lockfile} >= 1) {
628 if (exists $locks->{relockable} and
629 exists $locks->{relockable}{$lockfile}) {
630 $locks->{locks}{$lockfile}++;
631 # indicate that the bug for this lockfile needs to be reread
632 $locks->{relockable}{$lockfile} = 1;
633 push @{$locks->{lockorder}},$lockfile;
638 confess "Locking already locked file: $lockfile\n".Data::Dumper->Dump([$lockfile,$locks],[qw(lockfile locks)]);
641 my ($fh,$t_lockfile,$errors) =
642 simple_filelock($lockfile,10,1);
644 push @filelocks, {fh => $fh, file => $lockfile};
645 if (defined $locks) {
646 $locks->{locks}{$lockfile}++;
647 push @{$locks->{lockorder}},$lockfile;
651 croak "failed to get lock on $lockfile -- $errors".
652 (defined $locks?Data::Dumper->Dump([$locks],[qw(locks)]):'');
656 =head2 simple_filelock
658 my ($fh,$t_lockfile,$errors) =
659 simple_filelock($lockfile,$count,$wait);
661 Does a flock of lockfile. If C<$count> is zero, does a blocking lock.
662 Otherwise, does a non-blocking lock C<$count> times, waiting C<$wait>
665 In list context, returns the lockfile filehandle, lockfile name, and
666 any errors which occured.
668 When the lockfile filehandle is undef, locking failed.
670 These lockfiles must be unlocked manually at process end.
675 sub simple_filelock {
676 my ($lockfile,$count,$wait) = @_;
677 if (not defined $count) {
683 if (not defined $wait) {
690 my $fh2 = IO::File->new($lockfile,'w')
691 or die "Unable to open $lockfile for writing: $!";
692 # Do a blocking lock if count is zero
693 flock($fh2,LOCK_EX|($count == 0?0:LOCK_NB))
694 or die "Unable to lock $lockfile $!";
703 # use usleep for fractional wait seconds
704 usleep($wait * 1_000_000);
706 last unless (--$count > 0);
709 return wantarray?($fh,$lockfile,$errors):$fh
711 return wantarray?(undef,$lockfile,$errors):undef;
714 # clean up all outstanding locks at end time
721 =head2 simple_unlockfile
723 simple_unlockfile($fh,$lockfile);
728 sub simple_unlockfile {
729 my ($fh,$lockfile) = @_;
731 or warn "Unable to unlock lockfile $lockfile: $!";
733 or warn "Unable to close lockfile $lockfile: $!";
735 or warn "Unable to unlink lockfile $lockfile: $!";
744 Unlocks the file most recently locked.
746 Note that it is not currently possible to unlock a specific file
747 locked with filelock.
753 if (@filelocks == 0) {
754 carp "unfilelock called with no active filelocks!\n";
757 if (defined $locks and ref($locks) ne 'HASH') {
758 croak "hash not passsed to unfilelock";
760 if (defined $locks and exists $locks->{lockorder} and
761 @{$locks->{lockorder}} and
762 exists $locks->{locks}{$locks->{lockorder}[-1]}) {
763 my $lockfile = pop @{$locks->{lockorder}};
764 $locks->{locks}{$lockfile}--;
765 if ($locks->{locks}{$lockfile} > 0) {
768 delete $locks->{locks}{$lockfile};
770 my %fl = %{pop(@filelocks)};
771 simple_unlockfile($fl{fh},$fl{file});
777 lockpid('/path/to/pidfile');
779 Creates a pidfile '/path/to/pidfile' if one doesn't exist or if the
780 pid in the file does not respond to kill 0.
782 Returns 1 on success, false on failure; dies on unusual errors.
789 my $pid = checkpid($pidfile);
790 die "Unable to read pidfile $pidfile: $!" if not defined $pid;
791 return 0 if $pid != 0;
793 die "Unable to unlink stale pidfile $pidfile $!";
795 my $pidfh = IO::File->new($pidfile,O_CREAT|O_EXCL|O_WRONLY) or
796 die "Unable to open $pidfile for writing: $!";
797 print {$pidfh} $$ or die "Unable to write to $pidfile $!";
798 close $pidfh or die "Unable to close $pidfile $!";
804 checkpid('/path/to/pidfile');
806 Checks a pid file and determines if the process listed in the pidfile
807 is still running. Returns the pid if it is, 0 if it isn't running, and
808 undef if the pidfile doesn't exist or cannot be read.
815 my $pidfh = IO::File->new($pidfile, 'r') or
820 ($pid) = $pid =~ /(\d+)/;
821 if (defined $pid and kill(0,$pid)) {
834 These functions are exported with the :quit tag.
840 Exits the program by calling die.
842 Usage of quit is deprecated; just call die instead.
847 print {$DEBUG_FH} "quitting >$_[0]<\n" if $DEBUG;
848 carp "quit() is deprecated; call die directly instead";
854 These functions are exported with the :misc tag
858 LIST = make_list(@_);
860 Turns a scalar or an arrayref into a list; expands a list of arrayrefs
863 That is, make_list([qw(a b c)]); returns qw(a b c); make_list([qw(a
864 b)],[qw(c d)] returns qw(a b c d);
869 return map {(ref($_) eq 'ARRAY')?@{$_}:$_} @_;
875 print english_join(list => \@list);
876 print english_join(\@list);
878 Joins list properly to make an english phrase.
882 =item normal -- how to separate most values; defaults to ', '
884 =item last -- how to separate the last two values; defaults to ', and '
886 =item only_two -- how to separate only two values; defaults to ' and '
888 =item list -- ARRAYREF values to join; if the first argument is an
889 ARRAYREF, it's assumed to be the list of values to join
893 In cases where C<list> is empty, returns ''; when there is only one
894 element, returns that element.
899 if (ref $_[0] eq 'ARRAY') {
900 return english_join(list=>$_[0]);
902 my %param = validate_with(params => \@_,
903 spec => {normal => {type => SCALAR,
906 last => {type => SCALAR,
909 only_two => {type => SCALAR,
912 list => {type => ARRAYREF,
916 my @list = @{$param{list}};
918 return @list?$list[0]:'';
921 return join($param{only_two},@list);
923 my $ret = $param{last} . pop(@list);
924 return join($param{normal},@list) . $ret;
928 =head2 globify_scalar
930 my $handle = globify_scalar(\$foo);
932 if $foo isn't already a glob or a globref, turn it into one using
933 IO::Scalar. Gives a new handle to /dev/null if $foo isn't defined.
935 Will carp if given a scalar which isn't a scalarref or a glob (or
936 globref), and return /dev/null. May return undef if IO::Scalar or
937 IO::File fails. (Check $!)
939 The scalar will fill with octets, not perl's internal encoding, so you
940 must use decode_utf8() after on the scalar, and encode_utf8() on it
941 before. This appears to be a bug in the underlying modules.
948 if (defined $scalar) {
949 if (defined ref($scalar)) {
950 if (ref($scalar) eq 'SCALAR' and
951 not UNIVERSAL::isa($scalar,'GLOB')) {
952 if (is_utf8(${$scalar})) {
953 ${$scalar} = decode_utf8(${$scalar});
954 carp(q(\$scalar must not be in perl's internal encoding));
956 open $handle, '>:scalar:utf8', $scalar;
963 elsif (UNIVERSAL::isa(\$scalar,'GLOB')) {
967 carp "Given a non-scalar reference, non-glob to globify_scalar; returning /dev/null handle";
970 return IO::File->new('/dev/null','>:encoding(UTF-8)');
973 =head2 cleanup_eval_fail()
975 print "Something failed with: ".cleanup_eval_fail($@);
977 Does various bits of cleanup on the failure message from an eval (or
978 any other die message)
980 Takes at most two options; the first is the actual failure message
981 (usually $@ and defaults to $@), the second is the debug level
982 (defaults to $DEBUG).
984 If debug is non-zero, the code at which the failure occured is output.
988 sub cleanup_eval_fail {
989 my ($error,$debug) = @_;
990 if (not defined $error or not @_) {
991 $error = $@ // 'unknown reason';
994 $debug = $DEBUG // 0;
996 $debug = 0 if not defined $debug;
1001 # ditch the "at foo/bar/baz.pm line 5"
1002 $error =~ s/\sat\s\S+\sline\s\d+//;
1003 # ditch croak messages
1004 $error =~ s/^\t+.+\n?//mg;
1005 # ditch trailing multiple periods in case there was a cascade of
1007 $error =~ s/\.+$/\./;
1013 hash_slice(%hash,qw(key1 key2 key3))
1015 For each key, returns matching values and keys of the hash if they exist
1020 # NB: We use prototypes here SPECIFICALLY so that we can be passed a
1021 # hash without uselessly making a reference to first. DO NOT USE
1022 # PROTOTYPES USELESSLY ELSEWHERE.
1023 sub hash_slice(\%@) {
1024 my ($hashref,@keys) = @_;
1025 return map {exists $hashref->{$_}?($_,$hashref->{$_}):()} @keys;