#!/usr/bonsaitools/bin/perl -w # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Bug Tracking System. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): Terry Weissman , # Bryce Nesbitt # To recreate the shadow database, run "processmail regenerate" . use diagnostics; use strict; require "globals.pl"; $| = 1; umask(0); $::lockcount = 0; my $regenerate = 0; my $nametoexclude = ""; my @forcecc; sub Lock { if ($::lockcount <= 0) { $::lockcount = 0; if (!open(LOCKFID, ">>data/maillock")) { mkdir "data", 0777; chmod 0777, "data"; open(LOCKFID, ">>data/maillock") || die "Can't open lockfile."; } my $val = flock(LOCKFID,2); if (!$val) { # '2' is magic 'exclusive lock' const. print "Lock failed: $val\n"; } chmod 0666, "data/maillock"; } $::lockcount++; } sub Unlock { $::lockcount--; if ($::lockcount <= 0) { flock(LOCKFID,8); # '8' is magic 'unlock' const. close LOCKFID; } } sub FileSize { my ($filename) = (@_); my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); if (defined $size) { return $size; } return -1; } sub Different { my ($file1, $file2) = (@_); my $size1 = FileSize($file1); my $size2 = FileSize($file2); if ($size1 != $size2) { return 1; } open(FID1, "<$file1") || die "Can't open $file1"; open(FID2, "<$file2") || die "Can't open $file2"; my $d1; my $d2; if (read(FID1, $d1, $size1) ne $size1) { die "Can't read $size1 bytes from $file1"; } if (read(FID2, $d2, $size2) ne $size2) { die "Can't read $size2 bytes from $file2"; } close FID1; close FID2; return ($d1 ne $d2); } sub DescCC { my ($cclist) = (@_); if (scalar(@$cclist) <= 0) { return ""; } return "Cc: " . join(", ", @$cclist) . "\n"; } sub DescDependencies { my ($id) = (@_); if (!Param("usedependencies")) { return ""; } my $result = ""; my $me = "blocked"; my $target = "dependson"; my $title = "BugsThisDependsOn"; for (1..2) { SendSQL("select $target from dependencies where $me = $id order by $target"); my @list; while (MoreSQLData()) { push(@list, FetchOneColumn()); } if (@list) { my @verbose; my $count = 0; foreach my $i (@list) { SendSQL("select bug_status, resolution from bugs where bug_id = $i"); my ($bug_status, $resolution) = (FetchSQLData()); my $desc; if ($bug_status eq "NEW" || $bug_status eq "ASSIGNED" || $bug_status eq "REOPENED") { $desc = ""; } else { $desc = "[$resolution]"; } push(@verbose, $i . "$desc"); $count++; } if ($count > 5) { $result .= "$title: Big list (more than 5) has been omitted\n"; } else { $result .= "$title: " . join(', ', @verbose) . "\n"; } } my $tmp = $me; $me = $target; $target = $tmp; $title = "OtherBugsDependingOnThis"; } return $result; } sub GetBugText { my ($id) = (@_); undef %::bug; my @collist = ("bug_id", "product", "version", "rep_platform", "op_sys", "bug_status", "resolution", "priority", "bug_severity", "assigned_to", "reporter", "bug_file_loc", "short_desc", "component", "qa_contact", "target_milestone", "status_whiteboard", "groupset"); my $query = "select " . join(", ", @collist) . " from bugs where bug_id = $id"; SendSQL($query); my @row; if (!(@row = FetchSQLData())) { return ""; } foreach my $field (@collist) { $::bug{$field} = shift @row; if (!defined $::bug{$field}) { $::bug{$field} = ""; } } $::bug{'assigned_to'} = DBID_to_name($::bug{'assigned_to'}); $::bug{'reporter'} = DBID_to_name($::bug{'reporter'}); my $qa_contact = ""; my $target_milestone = ""; my $status_whiteboard = ""; if (Param('useqacontact') && $::bug{'qa_contact'} > 0) { $::bug{'qa_contact'} = DBID_to_name($::bug{'qa_contact'}); $qa_contact = "QAContact: $::bug{'qa_contact'}\n"; } else { $::bug{'qa_contact'} = ""; } if (Param('usetargetmilestone') && $::bug{'target_milestone'} ne "") { $target_milestone = "TargetMilestone: $::bug{'target_milestone'}\n"; } if (Param('usestatuswhiteboard') && $::bug{'status_whiteboard'} ne "") { $status_whiteboard = "StatusWhiteboard: $::bug{'status_whiteboard'}\n"; } $::bug{'long_desc'} = GetLongDescription($id); my @cclist; @cclist = split(/,/, ShowCcList($id)); my @voterlist; SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who"); while (MoreSQLData()) { my $v = FetchOneColumn(); push(@voterlist, $v); } $::bug{'cclist'} = join(',', @cclist); $::bug{'voterlist'} = join(',', @voterlist); if (Param("prettyasciimail")) { my $temp = formline <<'END',$::bug{'short_desc'},$id,$::bug{'product'},$::bug{'bug_status'},$::bug{'version'},$::bug{'resolution'},$::bug{'rep_platform'},$::bug{'bug_severity'},$::bug{'op_sys'},$::bug{'priority'},$::bug{'component'},$::bug{'assigned_to'},$::bug{'reporter'},$qa_contact,DescCC(\@cclist),$target_milestone,${status_whiteboard},$::bug{'bug_file_loc'},DescDependencies($id); +============================================================================+ | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | +----------------------------------------------------------------------------+ | Bug #: @<<<<<<<<<<< Product: @<<<<<<<<<<<<<<<<<<<<<< | | Status: @<<<<<<<<<<<<<<<<<< Version: @<<<<<<<<<<<<<<<<<<<<<< | | Resolution: @<<<<<<<<<<<<<<<<<< Platform: @<<<<<<<<<<<<<<<<<<<<<< | | Severity: @<<<<<<<<<<<<<<<<<< OS/Version: @<<<<<<<<<<<<<<<<<<<<<< | | Priority: @<<<<<<<<<<<<<<<<<< Component: @<<<<<<<<<<<<<<<<<<<<<< | +----------------------------------------------------------------------------+ | Assigned To: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | | Reported By: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | | ~QA Contact: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | | ~ CC list: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | +----------------------------------------------------------------------------+ | ~ Milestone: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | |~ Whiteboard: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | | URL: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | |~Dependencies: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | +============================================================================+ | DESCRIPTION | END my $prettymail = $^A . $::bug{'long_desc'}; return $prettymail; } else { return "Bug\#: $id Product: $::bug{'product'} Version: $::bug{'version'} Platform: $::bug{'rep_platform'} OS/Version: $::bug{'op_sys'} Status: $::bug{'bug_status'} Resolution: $::bug{'resolution'} Severity: $::bug{'bug_severity'} Priority: $::bug{'priority'} Component: $::bug{'component'} AssignedTo: $::bug{'assigned_to'} ReportedBy: $::bug{'reporter'} $qa_contact$target_milestone${status_whiteboard}URL: $::bug{'bug_file_loc'} " . DescCC(\@cclist) . "Summary: $::bug{'short_desc'} " . DescDependencies($id) . " $::bug{'long_desc'} "; } } my $didexclude = 0; my %seen; sub fixaddresses { my ($field, $list) = (@_); my @result; foreach my $i (@$list) { if (!defined $i || $i eq "") { next; } SendSQL("select emailnotification, groupset & $::bug{'groupset'} from profiles where login_name = " . SqlQuote($i)); my ($emailnotification, $groupset) = (FetchSQLData()); if ($groupset ne $::bug{'groupset'}) { next; } if ($emailnotification eq "CConly") { if ($field ne "cc") { next; } } if ($emailnotification eq "ExcludeSelfChanges" && (lc($i) eq $nametoexclude)) { $didexclude = 1; next; } if (!defined $::nomail{$i} && !defined $seen{$i}) { push(@result, $i . Param('emailsuffix')); $seen{$i} = 1; } } return join(", ", @result); } sub Log { my ($str) = (@_); Lock(); open(FID, ">>data/maillog") || die "Can't write to data/maillog"; print FID time2str("%D %H:%M", time()) . ": $str\n"; close FID; Unlock(); } sub ProcessOneBug { my $i = $_[0]; my $old = "shadow/$i"; my $new = "shadow/$i.tmp.$$"; my $diffs = "shadow/$i.diffs.$$"; my $verb = "Changed"; if (!stat($old)) { mkdir "shadow", 0777; chmod 0777, "shadow"; open(OLD, ">$old") || die "Couldn't create null $old"; close OLD; $verb = "New"; } my $text = GetBugText($i); if ($text eq "") { die "Couldn't find bug $i."; } open(FID, ">$new") || die "Couldn't create $new"; print FID $text; close FID; if (Different($old, $new)) { system("diff -c -b $old $new > $diffs"); my $tolist = fixaddresses("to", [$::bug{'assigned_to'}, $::bug{'reporter'}, $::bug{'qa_contact'}]); my @combinedcc; foreach my $v (split(/,/, "$::bug{'cclist'},$::bug{'voterlist'}")) { push @combinedcc, $v; } push (@combinedcc, (@forcecc)); my $cclist = fixaddresses("cc", \@combinedcc); my $logstr = "Bug $i $verb"; if ($tolist ne "" || $cclist ne "") { my %substs; $substs{"fullbugreport"} = $text; # added ability to include the full bug report $substs{"to"} = $tolist; $substs{"cc"} = $cclist; $substs{"bugid"} = $i; $substs{"diffs"} = ""; open(DIFFS, "<$diffs") || die "Can't open $diffs"; while () { $substs{"diffs"} .= $_; } close DIFFS; $substs{"neworchanged"} = $verb; $substs{"summary"} = $::bug{'short_desc'}; my $msg = PerformSubsts(Param("changedmail"), \%substs); if (!$regenerate) { # Note: fixaddresses may result in a Cc: only. This seems # harmless. open(SENDMAIL, "|/usr/lib/sendmail -t") || die "Can't open sendmail"; print SENDMAIL $msg; close SENDMAIL; $logstr = "$logstr; mail sent to $tolist, $cclist"; print "Email sent to: $tolist $cclist\n"; if ($didexclude) { print "Excluding: $nametoexclude (change your preferences if you wish not to be excluded)\n"; } } } unlink($diffs); Log($logstr); } rename($new, $old) || die "Can't rename $new to $old"; chmod 0666, $old; if ($regenerate) { print "$i "; } %seen = (); } # Code starts here ConnectToDatabase(); Lock(); if (open(FID, ") { $::nomail{trim($_)} = 1; } close FID; } # To recreate the shadow database, run "processmail regenerate" . if ($ARGV[0] eq "regenerate") { $regenerate = 1; shift @ARGV; SendSQL("select bug_id from bugs order by bug_id"); my @regenerate_list; while (my @row = FetchSQLData()) { push @regenerate_list, $row[0]; } foreach my $i (@regenerate_list) { ProcessOneBug($i); Unlock(); Lock(); } print("\n"); exit; } if ($ARGV[0] eq "-forcecc") { shift(@ARGV); foreach my $i (split(/,/, shift(@ARGV))) { push(@forcecc, trim($i)); } } if (($#ARGV < 0) || ($#ARGV > 1)) { print "Usage error: processmail {bugid} {nametoexclude}\nOr: processmail regenerate\n"; exit; } if ($#ARGV == 1) { $nametoexclude = lc($ARGV[1]); } if ($ARGV[0] eq "rescanall") { print "
Collecting bug ids...\n"; SendSQL("select bug_id from bugs where to_days(now()) - to_days(delta_ts) <= 2 order by bug_id"); my @list; while (my @row = FetchSQLData()) { push @list, $row[0]; } foreach my $id (@list) { $ARGV[0] = $id; print "
Doing bug $id\n"; ProcessOneBug($ARGV[0]); } } else { ProcessOneBug($ARGV[0]); } exit;