#!/usr/local/bin/perl # wwwgnats.pl - a WWW interface to the GNATS bug tracking system # Thanks to Larry Wall, CERN, and NCSA for Perl, WWW, and Mosaic! require "/usr/local/lib/gnats/libgnats.pl"; use POSIX; #### Configuration begins here # Miscellanous data $software = "Apache"; $databasetitle = "$software Problem Report Database"; $EDITOR_FILE = "$GNATS_LIB/gnats-db/gnats-adm/responsible"; # Lifetime (in seconds) of displayed info before it expires. $life_time = 30 * 60; $CCNOTE = < in the Cc line ] [and leave the subject line UNCHANGED. This is not done] [automatically because of the potential for mail loops. ] EOT # # Canned closure messages. These are used in one-click "close this # sucker" operations. The text isn't passed directly to the handle_edit_pr # function; instead, the StateReason field is set to "_C_" plus the # appropriate message key (e.g., "_C_Stale"). handle_edit_pr senses # this and substitutes the actual text. # $CANNED_TEXT{"Quiet"} = "[This is a standard response.]\n" . "This Apache problem report has not been updated recently.\n" . "Please reply to this message if you have any additional\n" . "information about this issue, or if you have answers to\n" . "any questions that have been posed to you. If we don't\n" . "hear from you, this report will be closed."; $CANNED_TEXT{"Stale"} = "[This is a standard response.]\n" . "No response from submitter, assuming issue has been resolved."; $CANNED_TEXT{"Duplicate"} = "[This is a standard response.]\n" . "This issue has been reported before;\n" . "please search the FAQ and the bug database.\n" . "Thanks for using Apache!"; $CANNED_TEXT{"Inappropriate"} = "[This is a standard response.]\n" . "This is a CGI programming or basic configuration issue.\n" . "As mentioned on the main bug database page, we must refer\n" . "all such basic or non-Apache-related questions to the\n" . "comp.infosystems.www.servers.unix and related newsgroups.\n" . "Please ask your question there.\n" . "Please also search the FAQ and the bug database.\n" . "Thanks for using Apache!"; # Outside commands $MAILER = "/usr/sbin/sendmail -oi -t"; $DATEPROG = "/bin/date"; $LSPROG = "/bin/ls"; $GNUINFOSCRIPT = "http://www.hyperreal.com/info/gnuinfo/index"; ### Configuration ends here ### Modification log: # 7/14/94 Dan Kegel (dank@adventure.com) - Originally written # 7/15/94 Huy Le - Created front-end, added quick-query output restrictions # 7/16/94 Dan Kegel - added config section & read responsible file # 7/18/94 Huy Le - Added send-pr function # 8/19/94 Huy Le - Added edit-pr function # 1/17/95 Dan Kegel - Rewrote edit-pr for robustness # 1/20/95 Dan Kegel - cleanup, added way to look for bugs waiting on person # 1/20/95 Dan Kegel - added a button to report summary of active bugs # 1/30/95 Dan Kegel - Moved some code into sub parse_pr() # 2/1/95 Dan kegel - moved all non-WWW code into libgnats.pl # 3/1/95 Dan Kegel - Edit code now uses associative array to represent # current PR. PR is output in fixed order, not input order. # Fewer hardcoded paths to external programs. Moved pr_addr code to # subroutine in libgnats.pl. # 3/2/95 Dan Kegel - Fixed small bug in timestamp; : was confusing browsers. # 8/24/96 Brian behlendorf - Extensive hacking for Apache # 5/1/1997 (let's be Y2K-safe, now! ;-) Enhancements for Apache # 6/6/1997 Ken Coar - Added form widgets for searching header or content # fields; edit notification now has GNATS' preferred subject format; # added PR# to when editing; added ability to annotate w/o # changing state. # 6/11/1997 Ken Coar - Added ability to edit the synopsis, release, and # originator - very useful for pulling "pending" PRs into the # mainstream db. # 7/11/1997 Ken Coar - added warning that the submitter had better not # be duplicating an existing PR or FAQ item, or the response he gets # may be unsatisfying. # 7/30/1997 Ken Coar - add PR# link at extreme right if there are more # than 4 columns - easier to follow link without having to # horizontally scroll back. Also added field to let you go directly # to a particular PR from the main menu (prompted by Dean Gaudet). # 8/13/1997 Ken Coar - fix problem causing check for "any" keyword to # occlude things like "mod_auth-any". # 11/9/1997 Ken Coar - Make "general" the default category on new PRs, # and "non-critical" the default severity. # 11/10/1997 Ken Coar - Added some "canned response" buttons on the # full display page seen by authorised editors. # 11/11/1997 Ken Coar - Change the subject of edit mail sent to match the # actual PR category; if it was changed as part of the edit, the message # would be unreplyable because of a category mismatch between the # new database value and the old subject line. # 11/13/1997 Ken Coar - Add another button for "closing due to no response," # and fix a mis-set Class value from one of the other buttons. # 11/14/1997 Ken Coar - Add a note to mail messages about how to get # replies to go into the database. Also add an Expires: header field # (Dirk-Willem van Gulik). # 12/30/1997 Ken Coar - Correct a pointer to the bugdb page, and add the # actual closure text to the editing display so editors know what will # be sent. # 01/03/1998 Ken Coar - Add a checkbox to the edit form to prevent # email from being sent. This allows cosmetic changes to be made, # even to closed PRs, without spamming people. # 01/20/1998 Ken Coar - Simplify the canned message handling by using # sentinels. # 04/24/1998 Ken Coar - change "chop" to "chomp" in a few places, add # email syntax validation (Dean Gaudet's suggestion). # 05/24/1998 Ken Coar - change my address and put a 'Go' SUBMIT button right # next to the 'go directly to' option (saves mouse motion). # ### End Modification log ### Environment variables $SCRIPT_NAME = $ENV{"SCRIPT_NAME"}; $PATH_INFO = $ENV{"PATH_INFO"}; $QUERY_STRING = $ENV{"QUERY_STRING"}; $CONTENT_LENGTH = $ENV{"CONTENT_LENGTH"}; $HTTP_AGENT = $ENV{"HTTP_USER_AGENT"}; $SERVER_NAME = $ENV{"SERVER_NAME"}; #################### Array definitions # Query-pr's -i option outputs the following fields numerically. # Define arrays to map numbers to name. ### Arrays for quick output display with restricted output fields @quickSeverity = ("", "Crit", "Ser", "Noncr"); @quickPriority = ("", "Hi", "Med", "Low"); @quickfmt = ("brief","regular","verbose"); # The first field is the quickfmt, the second is the field width %field = ( "CATEGORY", "0:15", "RELEASE", "0:7", "SEVERITY", "1:5", "PRIORITY", "4:3", # Not displayed "RESPONSIBLE", "1:8", "STATE", "0:9", "CLASS", "1:8", "SUBMITTER", "4:7", # Not displayed "ARRIVAL_DATE", "2:14", "ORIGINATOR", "1:15", "SYNOPSIS", "0:0", ); # Defines order of field display in quick output @field = ( "CATEGORY", "RELEASE", "SEVERITY", "PRIORITY", "RESPONSIBLE", "STATE", "CLASS", "SUBMITTER", "ARRIVAL_DATE", "ORIGINATOR", "SYNOPSIS", ); ### Arrays for quick output of PR's with restricted characteristics # The first field is the restriction, the second field is the default option %quickrestr = ( "Category", "any", "Severity", "any", "Responsible", "any", # "Originator", "any", "Class", "any", "State", "open", ); # Defines order of quick output restrictions @quickrestr = ( "Category", "Severity", "Responsible", # "Originator", "Class", "State", ); $new_default_category = "general"; $new_default_severity = "non-critical"; #################### Main routine # Main Program #&read_originator; &read_editor; ### Submit a new PR if ($PATH_INFO =~ m,^/send_pr,) { &send_pr(); } elsif ($PATH_INFO =~ m,^/handle_send_pr,) { &handle_send_pr(); ### Edit an existing PR } elsif ($PATH_INFO =~ m,^/edit_pr,) { $QUERY_STRING =~ m,^pr=(\d*),; &edit_pr($1); } elsif ($PATH_INFO =~ m,^/handle_edit_pr/,) { if ($PATH_INFO =~ m,^/handle_edit_pr/(\d+)&([^&]+)&([^&]+)$,) { &handle_edit_pr($1, $2, $3); } else { print "Bad args for handle_edit_pr!\n"; print "Args are $PATH_INFO.\n"; if ($PATH_INFO =~ m,^/handle_edit_pr/(\d+)&,) { print "Doesn't have digits&\n"; } if ($PATH_INFO =~ m,^/handle_edit_pr/(\d+)&([^&]+)&,) { print "Doesn't have digits&stuff\n"; } } ### Display the entire PR } elsif ($PATH_INFO =~ m,^/full,) { local($subdir); $subdir = $PATH_INFO; if ($subdir =~ m,^/full/(\d+),) { # Called from quick query &query_full($1); } else { $QUERY_STRING =~ m,^pr=(\w+)$,; &query_full($1); } ### Query a number of PR's and display in quick output form } elsif ($PATH_INFO =~ m,^/quick,) { # Get arg of quickfmt= $QUERY_STRING =~ m,quickfmt=(\w+)&,; $QUICKFMT = $1; # Get all restrictions. First, strip off quickfmt prefix. ($RESTR = $QUERY_STRING) =~ s/quickfmt=\w+&//; # Then, split into words. @RESTR=split(/&/,$RESTR); &query_quick($QUICKFMT,@RESTR); ### Get count of pending bugs by category } elsif ($PATH_INFO =~ m,^/summary_cat,) { &query_summary_cat(); ### Get count of pending bugs by person } elsif ($PATH_INFO =~ m,^/summary,) { &query_summary(); ### Main menu } elsif ($PATH_INFO eq "") { &main_menu(); print "<HR><SMALL>Version: 24 April 1998<BR>Authors: ", "<A HREF=\"http://alumni.caltech.edu/~dank/gnats.html\">Dan Kegel ", "and Huy Le</A>, <BR> ", "with revamp work by ", "<A HREF=\"http://www.organic.com\">Brian Behlendorf</A> ", "brian\@organic.com & ", "<A HREF=\"http://Web.Golux.Com/coar/\">Ken Coar</A> ", "coar\@Apache.Org</SMALL>\n"; } else { &emit_preamble (0); print "<head><title>SPR Front End

SPR Front End

"; print "Bad subdirectory/parameters specified in URL.\n"; } #&dumpenv; print "\n\n"; print "\n"; exit(0); #################### Miscellaneous functions # Format a date according to RFC 822 rules. sub RFC822_date { local ($s) = @_; local (@fdate) = gmtime ($s); local (@MNAME) = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); local (@WDAY) = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); $fdate[5] += ($fdate[5] > 90) ? 1900 : 2000; return sprintf ("%s, %02d %s %04d %02d:%02d:%02d GMT", $WDAY[$fdate[6]], $fdate[3], $MNAME[$fdate[4]], $fdate[5], $fdate[2], $fdate[1], $fdate[0]); } # Dumps the environment sub dumpenv { print "\n


\n"; print "

\n";
    foreach (sort(keys(%ENV))) {
    	print "$_ ", $ENV{$_}, "\n";
    }
    print "
\n"; } # # Emits either a redirect or the preamble to some HTML. # sub emit_preamble { local ($redirect, $target, $oldsel) = @_; $oldsel = select (STDOUT); $| = 1; if ($redirect) { print "Location: $target\n\n"; } else { print "Content-type: text/html\n"; print "Expires: " . &RFC822_date (time + $life_time) . "\n\n"; print "\n"; } $| = 0; select ($oldsel); return 0; } # Translates '+' to ' ' and '%##' to 'chr(0x##)' sub cgi_trans { local($str) = $_[0]; $str =~ s/\+/ /g; $str =~ s/%([\dA-Fa-f][\dA-Fa-f])/sprintf("%c",hex($1))/eg; return $str; } # Make text safe to display in an HTML stream sub html_escape { local($tmp) = $_[0]; $tmp =~ s//>/g; return $tmp; } # Reads in possible PR originators # Note: @nOriginator has an extra entry at the top #sub read_originator { # Full_name e-mail@address of originators # Huy_Le Huy_Le@ccmail.adventure.com # open(ORIGINATOR, "$ORIGIN_FILE") || die "Couldn't get originator file\n"; # while () { # if (!/^\s*(#|\n)/) { # ($name, $email) = split(/ /); # chop($email); # $nOriginator{$name} = $email; # } # } # close(ORIGINATOR); # @nOriginator = sort(keys(%nOriginator)); # unshift(@nOriginator, ""); # needed for 'any' #} # Reads in possible PR editors sub read_editor { # Full_name e-mail@address of editors # Huy_Le Huy_Le@ccmail.adventure.com open(EDITOR, "$EDITOR_FILE") || die "Couldn't open editor file $EDITOR_FILE\n"; while () { if (!/^\s*(#|\n)/) { chop; ($name, $realname, $email) = split(/\:/); $nEditor{$name} = $email; } } close(EDITOR); @nEditor = sort(keys(%nEditor)); } #################### PR submission # Sends the new PR sub handle_send_pr { # Display title &emit_preamble (0); print "Apache: New Problem Report Submission"; # Get arguments local($_)=scalar(); chomp; s/\r$//; local($i,%input); undef(%fieldvalues); # Global! foreach (split(/&/)) { ++$i; local($key,$value)=split(/=/); $value = &cgi_trans($value); # By convention, multi-line fields have newlines at end of # each line. I think some browsers forget the last newline? if ($fieldnames_multi{$key} > 0) { # # Convert any CR-LFs or CRs to \n (different browsers use # different line terminators). # $value =~ s/\r\n/\n/g; $value =~ s/\r/\n/g; if ($value !~ /\n$/) { $value .= "\n"; } } $fieldvalues{$key}=$value; } # Verify arguments local($field, $ok, @emsgs) = (0, 1); foreach $field ("Synopsis", "emailaddr", "Environment", "Category", "Description") { local ($fieldval) = $fieldvalues{$field}; $fieldval =~ s/\s//g; if ($fieldval eq "") { push (@emsgs, $field . " is blank"); $ok = 0; } } if ($fieldvalues{'emailaddr'} !~ m:^\S+\@\S+\.\S+$:) { push (@emsgs, "Email address is invalid"); $ok = 0; } if (! $ok) { print "

Problem report not sent!

\n"; print "Your problem report has missing or invalid required information:\n"; print "
    \n"; foreach $field (@emsgs) { print "
  • $field\n
  • \n"; } print "
"; print "

Please go back to the form and correct this.

\n"; return; } # kludge $fieldvalues{"Confidential"} = "no"; $fieldvalues{"Priority"} = "medium"; # megakludge. $fieldvalues{"Submitter-Id"} = $fieldvalues{"Organization"}; $fieldvalues{"Originator"} = $fieldvalues{"emailaddr"}; # Send the PR $prtext = &unparse_pr("send"); open(MAIL, "|$MAILER") || die "Error while invoking $MAILER"; print MAIL < Reply-To: $fieldvalues{"emailaddr"} X-send-pr-version: $GNATS_VER $CCNOTE $prtext EOM close(MAIL); print < Your problem report has been sent. It will take about 3 minutes to show up in the system.
Important Note:

When your report has been recorded in the database, you will get a mail message about it. Although you cannot use the Web to make changes to your report, you can do so through email.

To add information to your problem report, just REPLY to the mail message you get, and be sure that the address <apbugs\@Apache.Org>is referenced in either the To: or Cc: lines. That way, the text of your message will be attached to the existing information in the PR.

Likewise, if you receive private email from the Apache developers, you should CC the <apbugs\@Apache.Org> address so your reply will be recorded in the database for others to see.

Important! DO NOT change the subject of your message! It must match the pattern "Re: component/PR-number" (e.g., "Re: general/911: demo"), or else it won't make it into the database.

The only time you will see these instructions is when you enter a new problem report.

EOHT } # Gives interface for new PR submission sub send_pr { # Display title &emit_preamble (0); print <Apache: New Problem Report submission

New Problem Report form:

Please be aware that the Apache problem report database is PUBLICLY ACCESSIBLE. The information you submit on this form will be available to anyone on the Internet. Do not send information that you wish to remain private.

Please also be aware that you may get a curt and unsatisfying response if you report a problem that has already been reported or that is covered in the FAQ. The developers do not have time to answer the same question again and again. Please search the FAQ and the problem report database before you enter this report!

EOHT print "
\n"; # print "Organization: \n"; # for ("kadev", "kaqa", "kaworlds") { # print "$_ \n"; # } # print "
\n\n"; print ""; # print "Originator (you): \n"; # print "\n"; print < Your real name, first and last: Your email address: Category of problem: \n"; print "$software release: \n"; foreach ("Class", "Severity") { print "$_:\n"; print "\n"; } print "\n"; # Maximize box dimensions local($width)= 80; local($height)= 4; local($height2)=$height*2; print < Environment - What operating system, at what patchlevel? What compiler? Etc. (The output from uname -a is very useful.)

Synopsis - a brief description of the problem at hand.

Full Description
If you are getting a core dump, such as a SIGSEGV, please provide a backtrace (see the FAQ for instructions).

How can we repeat this problem? Give example URL's, etc.

Do you have any suggested way to fix it?

EOM } #################### PR editing # Gets the timestamp of the given file sub timestamp { local($fname) = shift(@_); open(TIMESTAMP, "$LSPROG -l $fname|") || die "Error: can't record the timestamp of the PR ($fname)"; ($_=)=~ /\s(\S+\s+\S+\s+\S+)\s+\S+$/ || die "Error: can't record the timestamp of the PR ($fname)"; close(TIMESTAMP); local($ts)=$1; $ts =~ tr/ /+/; $ts =~ tr/:/_/; $ts; } # Sends the PR changes sub handle_edit_pr { # Get the PR, editor, old state, and timestamp local($pr, $oldstate, $timestamp) = @_; # Display title &emit_preamble (0); print "Problem Report editing for PR#$pr "; # Initialization $errmsg="

Your problem report changes have not been sent.

\n"; # Get arguments local($_)=scalar(); chomp; s/\r$//; local($i,%input); foreach (split(/&/)) { ++$i; local($key,$value)=split(/=/); $value=&cgi_trans($value); if ($fieldnames_multi{$key} > 0) { # # Convert any CR-LFs or CRs to \n (different browsers use # different line terminators). # $value =~ s/\r\n/\n/g; $value =~ s/\r/\n/g; if ($value !~ /\n$/) { $value .= "\n"; } } $input{$key}=$value; } # Verify arguments if (!$input{"Editor"}) { print $errmsg, "You must register your name.\n"; return; } # Lock and read the PR into @oldpr and %fieldvalues # Also sets $semipr and $fullpr $err = &read_pr($pr, $input{"Editor"}); if ($err ne "") { print $errmsg, $err; return; } #local($oldstate) = $fieldvalues{"State"}; local($oldsyn) = $fieldvalues{"Synopsis"}; local($oldresp) = $fieldvalues{"Responsible"}; $oldresp =~ s/\s*\(.*$//; # Get rid of comment in responsible party name local($reply_to) = $fieldvalues{"Reply-To"}; if ($reply_to eq "") { # "Reply-To:" takes precedence over "From:". $reply_to = $fieldvalues{"From"}; } local($ed_err); local(%mail_to); local($change_msg) = ""; LOCKED: { # Now that pr has been locked, if any errors are encountered, # set $ed_err to the reason and jump to UNLOCK. # Check that the timestamp hasn't changed since the form was generated if ($timestamp ne ×tamp($fullpr)) { $ed_err = "$errmsg\nThis PR has been modified since you started" . " editing it.\n"; last LOCKED; } # Another sanity check if ($reply_to eq "" || $oldresp eq "") { $ed_err = "$errmsg\nHey! Old responsible is '$oldresp', reply address on pr is '$reply_to'!\n"; last LOCKED; } # Get the date local($date); if (!open(DATE, "$DATEPROG|")) { $ed_err = "$errmsg\nError: can't run $DATEPROG"; last LOCKED; } chomp($date=); close(DATE); $mail_to{&tolower($nEditor{$input{'Editor'}})} = 1; local($to_subm, $to_old, $to_new); # # If the StateReason begins with "_C_" the remainder of the # value is a key into the canned-closure-messages hash. # Make the substitution. # if ($input{'StateReason'} =~ /^_C_/) { local ($key) = $input{'StateReason'}; $key =~ s/^_C_//; $input{'StateReason'} = $CANNED_TEXT{$key}; } # Update the audit trail #print "if ($input{'State'} ne $fieldvalues{'State'})\n"; if ($input{"State"} ne $fieldvalues{'State'}) { if ($input{'StateReason'} eq "") { $ed_err = "$errmsg\nYou must tell why the State changed.\n"; last LOCKED; } $change_msg .= "State-Changed-From-To: $fieldvalues{'State'}-$input{'State'} State-Changed-By: $input{'Editor'} State-Changed-When: $date State-Changed-Why: $input{'StateReason'} "; $to_old = $to_subm = 1; $fieldvalues{'State'} = $input{'State'}; } elsif ($input{'StateReason'} ne "") { $change_msg .= "Comment-Added-By: $input{'Editor'} Comment-Added-When: $date Comment-Added: $input{'StateReason'} "; $to_old = $to_subm = 1; $fieldvalues{'State'} = $input{'State'}; } if ($input{"Synopsis"} && ($input{"Synopsis"} ne $fieldvalues{'Synopsis'})) { $change_msg .= "Synopsis-Changed-From: $fieldvalues{'Synopsis'} Synopsis-Changed-To: $input{'Synopsis'} Synopsis-Changed-By: $input{'Editor'} Synopsis-Changed-When: $date "; $to_old = $to_subm = 1; $fieldvalues{'Synopsis'} = $input{'Synopsis'}; } if ($input{"Originator"} && ($input{"Originator"} ne $fieldvalues{'Originator'})) { $change_msg .= "Originator-Changed-From-To: $fieldvalues{'Originator'}-$input{'Originator'} Originator-Changed-By: $input{'Editor'} Originator-Changed-When: $date "; $to_old = $to_subm = 1; $fieldvalues{'Originator'} = $input{'Originator'}; } if ($input{"Release"} ne $fieldvalues{'Release'}) { $change_msg .= "Release-Changed-From-To: $fieldvalues{'Release'}-$input{'Release'} Release-Changed-By: $input{'Editor'} Release-Changed-When: $date "; $to_old = $to_subm = 1; $fieldvalues{'Release'} = $input{'Release'}; } if ($input{"Class"} ne $fieldvalues{'Class'}) { $change_msg .= "Class-Changed-From-To: $fieldvalues{'Class'}-$input{'Class'} Class-Changed-By: $input{'Editor'} Class-Changed-When: $date "; $to_old = $to_subm = 1; $fieldvalues{'Class'} = $input{'Class'}; } if ($input{"Severity"} ne $fieldvalues{'Severity'}) { $change_msg .= "Severity-Changed-From-To: $fieldvalues{'Severity'}-$input{'Severity'} Severity-Changed-By: $input{'Editor'} Severity-Changed-When: $date "; $to_old = $to_subm = 1; $fieldvalues{'Severity'} = $input{'Severity'}; } #print "if ($input{'Responsible'} ne $oldresp)\n"; if ($input{'Responsible'} ne $oldresp) { if ($input{'ResponsibleReason'} eq "") { $ed_err = "$errmsg\nYou must tell why the Responsible person changed.\n"; last LOCKED; } $change_msg .= "Responsible-Changed-From-To: $fieldvalues{'Responsible'}-$input{'Responsible'} Responsible-Changed-By: $input{'Editor'} Responsible-Changed-When: $date Responsible-Changed-Why: $input{'ResponsibleReason'} "; $to_old = $to_new = 1; $fieldvalues{'Responsible'} = $input{'Responsible'}; } if ($input{"Category"} ne $fieldvalues{"Category"}) { # Gnats' original edit-pr command didn't generate an audit # trail for this change # Note: category names might have dashes in them, making # the following line hard to parse! $change_msg .= "Category-Changed-From-To: $fieldvalues{'Category'}-$input{'Category'} Category-Changed-By: $input{'Editor'} Category-Changed-When: $date \n"; $fieldvalues{'Category'} = $input{'Category'}; } # Check that changes were actually made if ($change_msg eq "") { $ed_err = "$errmsg\nNothing was changed.\n"; last LOCKED; } # Add the change log to the PR $fieldvalues{'Audit-Trail'} .= $change_msg; # Generate the mailing list if ($to_subm) { $mail_to{&tolower($reply_to)} = 1; } if ($to_old) { ($adr, $err) = &pr_addr($oldresp); if ($err ne "") { $ed_err = "$errmsg\n$err"; last LOCKED; } $mail_to{&tolower($adr)} = 1; } if ($to_new) { ($adr, $err) = &pr_addr($input{'Responsible'}); if ($err ne "") { $ed_err = "$errmsg\n$err"; last LOCKED; } $mail_to{&tolower($adr)} = 1; } # Apply the changes and send the new PR $newpr = &unparse_pr(""); #print "opening |$PR_EDIT > /tmp/wwwgnats.$$ 2>&1\n"; unlink("/tmp/wwwgnats.$$"); if (!open(PREDIT, "|$PR_EDIT > /tmp/wwwgnats.$$ 2>&1")) { $ed_err = "$errmsg\nError: can't invoke pr-edit\n"; last LOCKED; } print PREDIT $newpr; close(PREDIT); if ($?) { $ed_err = "$errmsg\nError: pr-edit returns status $?, and reports:\n"; if (!open(PREDIT, "/tmp/wwwgnats.$$")) { $ed_err .= "(whoops, no output from pr-edit found; couldn't open /tmp/wwwgnats.$$)\n"; } else { $ed_err .= join("\n",); close(PREDIT); } unlink("/tmp/wwwgnats.$$"); last LOCKED; } unlink("/tmp/wwwgnats.$$"); #print ""; } # Unlock the PR #print "unlocking\n"; system("$PR_EDIT --unlock $semipr"); # if we got here via a last, report the error and quit if ($ed_err ne "") { print "
";
	print $ed_err;
	print "
"; return; } # Email-notify all concerned parties #print "---------------------------------\n"; local($mail_to, $mailed) = ("", 0); $mail_to = join(", ", sort(keys(%mail_to))); if (($mail_to ne "") && (!$input{"NoNotify"})) { if (open(MAILER, "|$MAILER")) { $msg = "To: $mail_to From: $input{'Editor'} Subject: Re: $input{'Category'}/$pr: $oldsyn $CCNOTE Synopsis: $oldsyn $change_msg "; print MAILER $msg; close(MAILER); $mailed = 1; } else { print "Error: can't run $MAILER\n"; } } # Display message $msg =~ s/\/\>\;/g; print "

Your changes to PR $pr were filed to the database.

"; if ($mailed) { print <
$msg
EOHT } } # Gives the interface to change the PR sub edit_pr { $pr = $_[0]; # Display title &emit_preamble (0); print "Problem Report editing for PR#$pr\n"; if ($pr eq "") { print "

Sorry

\n"; print "You must specify the number of the problem report to edit.\n"; return; } $err = &read_pr($pr, ""); if ($err ne "") { print "$err\n"; return; } local($oldsyn) = $fieldvalues{"Synopsis"}; local($oldstate) = $fieldvalues{"State"}; local($oldresp) = $fieldvalues{"Responsible"}; local($oldrel) = $fieldvalues{"Release"}; local($oldorig) = $fieldvalues{"Originator"}; local($oldclass) = $fieldvalues{"Class"}; local($oldsev) = $fieldvalues{"Severity"}; $oldresp =~ s/\s*\(.*$//; # Get rid of comment in responsible party name local($timestamp)=×tamp($fullpr); # The modification form print "

Modification form for PR number $pr

Synopsis: " . &html_escape($oldsyn) . "
Click here to view the full text of PR #$pr

"; # Maximize box dimensions local($width)= 60; local($height)= 4; print <
DO NOT send mail about this change
EOHT print "
Editor (you):

\n"; print "
New synopsis:
\n"; print "
Originator:
\n"; print "
Class:

\n"; print "
Release:
\n"; print "
Severity:

\n"; print "
New state:\n"; print "
"; print "
If state changed, give the reason here. To add a comment to the case, enter text here without changing the state:
"; print "
New category:\n"; print "
"; print "
\n"; print "
New responsible person:\n"; print "
"; print "
\n"; if (! $somebody_resp) { print "Error! No known person responsible for this bug!\n"; } print "
If responsible person changed, give the reason here:"; print "
\n"; print "
\n"; print "\n"; print "\n"; } #################### Quick query # Truncate a string to the given width, replacing last shown char with $ if truncated. # Usage: $fstr = &truncstr("long string", $width); sub truncstr { local($str) = shift(@_); local($WIDTH) = shift(@_); local($W, $fstring); # Truncate or pad the variable to the desired width. $W=$WIDTH; if (length($str)>$WIDTH && $WIDTH) { --$W }; $fstring = sprintf("%-${W}s",$WIDTH?substr($str,0,$W):$str); # Add a $ if we truncated it. if (length($str)>$WIDTH && $WIDTH) { $fstring .= "\$"; } return $fstring; } sub numerically { $a <=> $b; } sub query_quick { #print "args = (@_)\n"; local($quickfmt,@restrict)=@_; # Convert $quickfmt to index into @quickfmt LOOP: for ($i=0; $i<@quickfmt; $i++) { if ($quickfmt eq $quickfmt[$i]) { $quickfmt=$i; last LOOP; } } # Split restrictions into key,value pairs # Collapse multiple selections with same key # Store collapsed restrictions in %restrict local($oldkey, $oldval); local(%restrict); foreach (@restrict) { $_ = &tolower($_); $_ = &cgi_trans($_); local($key,$val) = split(/=/); if ($key ne $oldkey) { if ($oldval ne "") { $restrict{$oldkey} = $oldval; } $oldkey = $key; $oldval = ""; $oldval = $val if ($val ne ""); } else { # just continue adding to old restriction $oldval .= "|" if ($oldval ne ""); $oldval .= "$val" if ($val ne ""); } } # Could have put a sentinal on the end of @restrict, but let's duplicate # code instead. if ($oldval ne "") { $restrict{$oldkey} = $oldval; } # # If we were given an explicit PR number, redirect the browser # there. # if ($oldval = $restrict{'pr'}) { local ($port) = $ENV{'SERVER_PORT'}; $port = ($port eq '80') ? "" : ":$port"; return &emit_preamble (1, "http://$SERVER_NAME$port$SCRIPT_NAME/full/$oldval"); } delete ($restrict{'pr'}); # # Nope, it's a real query. Now let's remove form-fields that don't # equate to GNATS-fields. # $oldval = $restrict{"search"}; if ($oldval && $restrict{"qstring"}) { $restrict{$oldval} = $restrict{"qstring"}; } delete $restrict{"string"}; delete $restrict{"qstring"}; delete $restrict{"search"}; # Print title &emit_preamble (0); print "Quick summary of PR's

Quick summary of PR's:

"; local($opts); local(@prs); # Read in quick format list of pr's matching query # If querying by person, then do two queries: originator and responsible if ($restrict{"person"} ne "") { $fullname = $restrict{"person"}; # Look up bugs for which this person is the originator. if ($fullname eq "any") { $opts = "--state=\"open|analyzed|feedback\""; } else { $oldval = $fullname; # Turn underscores and spaces into regular expression # that match either underscores or spaces # (Our database sometimes puts underscores instead of spaces). $oldval =~ s/[\s_]/[ _]/g; # Handle bugs with no known originator if ($oldval eq "nobody") { $oldval = "nobody|^\$"; } # Convert this key into a query-pr option $opts = " --originator=\"$oldval\""; # The originator cares about bugs which are in feedback state. $opts .= " --state=\"feedback\""; } print "
query-pr -i $opts\n
\n"; open(PIPE,"$GNATS_BIN/query-pr -i $opts|") || die "Can't open"; @prs = ; close(PIPE); # Look up bugs for which this person is the responsible party. # Convert person's name into nickname $nickname = $fullname2nametag{$fullname}; # Golly, maybe the spaces have been replaced with underscores. if ($nickname eq "") { $fullname =~ s/_/\040/g; $nickname = $fullname2nametag{$fullname}; } if ($nickname ne "") { # Convert this key into a query-pr option $opts = " --responsible=\"$nickname\""; # Responsible person cares about bugs which are open or analyzed. $opts .= " --state=\"open|analyzed\""; print "
query-pr -i $opts\n
\n"; open(PIPE,"$GNATS_BIN/query-pr -i $opts|") || die "Can't open"; @prs = (@prs,); close(PIPE); } else { #print "Warning: $fullname has no nickname.\n"; } } else { # Output restrictions as query-pr options foreach (keys(%restrict)) { $oldkey = $_; $oldval = $restrict{$oldkey}; # If "any" was given as a distinct keyword, alone or in # combination, don't bother using this key if (($oldval ne "any") && ($oldval !~ /^any\|/) && ($oldval !~ /\|any$/)) { # Turn underscores and spaces into regular expression # that match either underscores or spaces # (Our database sometimes puts underscores instead of spaces). $oldval =~ s/[\s_]/[ _]/g; # Convert this key into a query-pr option $opts .= " --$oldkey=\"$oldval\""; } } print "
query-pr -i $opts\n
\n"; open(PIPE,"$GNATS_BIN/query-pr -i $opts|") || die "Can't open"; @prs = ; close(PIPE); } @prs = sort numerically (@prs); if (@prs == 0) { print "

No bugs match your query.

\n"; return; } print "\n\n"; # Print field headers. local($QUICKFMT, $WIDTH, $fstring, $str); $fstring = ""; foreach (@field) { ($QUICKFMT, $WIDTH)=split(/:/,$field{$_}); if ($QUICKFMT <= $quickfmt) { # $fstring .= &truncstr($_, $WIDTH) . " "; $fstring .= " "; } } if ($#field > 4) { $fstring .= ""; } print "$fstring\n"; # Print each PR in result as link to full text foreach (@prs) { s/\s*\|\s*/|/go; ( $NUMBER, $CATEGORY, $SYNOPSIS, $CONFIDENTIAL, $SEVERITY, $PRIORITY, $RESPONSIBLE, $STATE, $CLASS, $SUBMITTER, $ARRIVAL_DATE, $ORIGINATOR, $RELEASE ) = split(/\|/, $_); $SEVERITY = $quickSeverity[$SEVERITY]; $PRIORITY = $quickPriority[$PRIORITY]; $STATE = $nState[$STATE]; $CLASS = $nClass[$CLASS]; $fstring = sprintf("%s", substr(" ", 0, (length($NUMBER)<5)?4-length("$NUMBER"):1)); # @field is an array of the variable names $NUMBER, etc., in presentation order. # %field tells which quickfmt level and ?below? to print this variable # in, and how wide to print it. foreach (@field) { ($QUICKFMT, $WIDTH)=split(/:/,$field{$_}); if ($QUICKFMT <= $quickfmt) { $str = eval "\$$_"; $str =~ s/\s+/ /g; # $fstring .= &truncstr($str, $WIDTH) . " "; $fstring .= ""; } } print "\n"; print " "; print "$fstring\n"; if ($#field > 4) { print ""; } print "\n\n"; } print "
PR#$_PR#
" . &html_escape($str) . "
$NUMBER$NUMBER
\n"; } sub query_summary { # Print title &emit_preamble (0); print "Summary of active PR's by person and status

Summary of active PR's by person and status:

"; #print "
query-pr -i --state=\"open|analyzed|feedback\" \n
\n"; open(PIPE,"$GNATS_BIN/query-pr -i --state=\"open|analyzed|feedback\" |") || die "Can't open"; @prs = ; close(PIPE); if (@prs == 0) { print "

No bugs match your query.

\n"; return; } # Count bugs by person. # Print each PR in result as link to full text local(%counts,%names); foreach (@prs) { s/\s*\|\s*/|/go; ( $NUMBER, $CATEGORY, $SYNOPSIS, $CONFIDENTIAL, $SEVERITY, $PRIORITY, $RESPONSIBLE, $STATE, $CLASS, $SUBMITTER, $ARRIVAL_DATE, $ORIGINATOR, $RELEASE ) = split(/\|/, $_); $STATE = $nState[$STATE]; #print "$STATE, $ORIGINATOR, $RESPONSIBLE "; # Figure out which person this bug is waiting on, if any if ($STATE eq "open" || $STATE eq "analyzed") { # Waiting on responsible person # Convert nickname to fullname $nickname = $RESPONSIBLE; $nickname = &tolower($nickname); $fullname = $nametag2fullname{$nickname}; if ($fullname eq "") { $fullname = $nickname; } } elsif ($STATE eq "feedback") { $fullname = $ORIGINATOR; $fullname = &tolower($fullname); $fullname =~ tr/_/\040/; } if ($fullname eq "") { $fullname = "nobody"; } if ($STATE eq "open" || $STATE eq "analyzed" || $STATE eq "feedback") { #print "counts{$fullname._.$STATE}++"; $counts{$fullname."_".$STATE}++; $names{$fullname}++; } #print "\n"; } #print "\n"; # Print field headers. print "
\n";
    local(@states) = ("open", "analyzed", "feedback");
    local($fstring, $str);
    $fstring = &truncstr("", 20)."   ";
    foreach (@states) {
	$fstring .= &truncstr($_, 10) . " ";
    }
    print $fstring,"\n"; 
    # Print counts per person.
    foreach $fullname (sort(keys(%names))) {
	$fstring = &truncstr($fullname, 20)."   ";
	local($_fullname) = $fullname;
	$_fullname =~ tr/ /_/;
	$fstring =~ s/(\s*)$//;
	print "$fstring$1"; 
	$fstring = "";
	foreach (@states) {
	    $str = $counts{$fullname."_".$_}+0;
	    $fstring .= &truncstr($str, 10) . " ";
	}
	print "$fstring\n";
    }
    print "
\n"; } sub query_summary_cat { # Print title &emit_preamble (0); print "Summary of PR's by category and status

Summary of PR's by category and status:

"; #print "
query-pr -i \n
\n"; open(PIPE,"$GNATS_BIN/query-pr -i |") || die "Can't open"; @prs = ; close(PIPE); if (@prs == 0) { print "

No bugs match your query.

\n"; return; } # Count bugs by category. # Print each PR in result as link to full text local(%counts,%names,%states); foreach (@prs) { s/\s*\|\s*/|/go; ( $NUMBER, $CATEGORY, $SYNOPSIS, $CONFIDENTIAL, $SEVERITY, $PRIORITY, $RESPONSIBLE, $STATE, $CLASS, $SUBMITTER, $ARRIVAL_DATE, $ORIGINATOR, $RELEASE ) = split(/\|/, $_); $STATE = $nState[$STATE]; #print "$STATE, $ORIGINATOR, $RESPONSIBLE "; #print "counts{$CATEGORY._.$STATE}++"; $counts{$CATEGORY."_".$STATE}++; $names{$CATEGORY}++; $states{$STATE}++; #print "\n"; } #print "\n"; # Print field headers. print "
\n";
    local(@states) = sort(keys(%states));
    local($fstring, $str);
    $fstring = &truncstr("", 20)."   ";
    foreach (@states) {
	$fstring .= &truncstr($_, 10) . " ";
    }
    print $fstring,"\n"; 
    # Print counts per person.
    foreach $CATEGORY (sort(keys(%names))) {
	$fstring = &truncstr($CATEGORY, 20)."   ";
	$fstring =~ s/(\s*)$//;
	print "$fstring$1"; 
	$fstring = "";
	foreach (@states) {
	    next if ($_ eq "");
	    $str = $counts{$CATEGORY."_".$_}+0;
	    if ($str > 0) {
		# Who's gonna answer the following query?
		$fstring .= "$str"; 
		$fstring .= " " x (11-length($str));
	    } else {
		$fstring .= &truncstr($str, 10) . " ";
	    }
	}
	print "$fstring\n";
    }
    print "
\n"; } #################### Full query sub query_full { local($pr) = $_[0]; &emit_preamble (0); print "Full Problem Report Text for PR#$pr "; if ($pr eq "") { print "

Sorry

\n"; print "You must specify the number of the problem report to view.\n"; return; } $err = &read_pr($pr, ""); if ($err ne "") { print "$err\n"; } else { if ($ENV{'REMOTE_USER'}) { local ($timestamp) = ×tamp($fullpr); print <
Edit this report
Outstanding request(s) for information
$CANNED_TEXT{'Quiet'}
   
No response from submitter
$CANNED_TEXT{'Stale'}
   
Already reported
$CANNED_TEXT{'Duplicate'}
   
Inappropriate report
$CANNED_TEXT{'Inappropriate'}
   
EOM } print "

Full text of PR number $pr:

\n
\n";
	$prtext = &html_escape(join("",@oldpr));
	print $prtext;
	print "
\n"; } } #################### Main Menu # Very first page of front end sub main_menu { &emit_preamble (0); print <$databasetitle

$databasetitle

Built on top of the GNU tool GNATS.


EOM #| Summary of active bugs by status and person #| Summary of all bugs by status and category #EOM #------------------------------------------------------------------ # Quick query print "


\n"; print "Use the following form to search the problem report database. "; print "Narrow your search by specifying a value for one or more problem report fields.\n"; print "

\n"; # Choose quick output format. print "\n"; print "\n"; print "
Go directly to the full display "; print "for PR# \n"; print "\n"; print "
OR

Compose a database query

"; print ""; print "\n
\nOutput format: \n"; print "
\n"; local($mode); # Loop over output restrictions (Category, State, Responsible). foreach (@quickrestr) { # Make sure choices fit within window #$mode = $HTTP_AGENT=~/X Win/ && eval"\@n$_">20 ? "SIZE=10" : ""; #$mode = eval"\@n$_">30 ? "SIZE=20" : ""; $mode = ""; $separator = ""; # Let user select more than one choice for "State". if ($_ eq "State") { $mode = "SIZE=". eval"\@n$_"; $mode .= " MULTIPLE"; $separator = "
"; } print "$separator $_:\n"; print "
\n" if ($_ eq "State"); print "
\n"; } print <
and/or specifying a freeform text search on any field:
Header fields
Report text

EOM }