This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
perlbug: don't run editor when noninteractive
[perl5.git] / utils / perlbug.PL
index 955e98f..720cf12 100644 (file)
@@ -22,39 +22,14 @@ $file .= '.com' if $^O eq 'VMS';
 
 open OUT, ">$file" or die "Can't create $file: $!";
 
-# extract patchlevel.h information
+# get patchlevel.h timestamp
 
-open PATCH_LEVEL, "<" . catfile(updir, "patchlevel.h")
-    or die "Can't open patchlevel.h: $!";
+-e catfile(updir, "patchlevel.h")
+    or die "Can't find patchlevel.h: $!";
 
-my $patchlevel_date = (stat PATCH_LEVEL)[9];
+my $patchlevel_date = (stat _)[9];
 
-while (<PATCH_LEVEL>) {
-    last if $_ =~ /^\s*static\s+(?:const\s+)?char.*?local_patches\[\]\s*=\s*{\s*$/;
-}
-
-if (! defined($_)) {
-    warn "Warning: local_patches section not found in patchlevel.h\n";
-}
-
-my @patches;
-while (<PATCH_LEVEL>) {
-    last if /^\s*}/;
-    next if /^\s*#/;  # preprocessor stuff
-    next if /PERL_GIT_UNPUSHED_COMMITS/;    # XXX expand instead
-    next if /"uncommitted-changes"/;        # XXX determine if active instead
-    chomp;
-    s/^\s+,?\s*"?//;
-    s/"?\s*,?$//;
-    s/(['\\])/\\$1/g;
-    push @patches, $_ unless $_ eq 'NULL';
-}
-my $patch_desc = "'" . join("',\n    '", @patches) . "'";
-my $patch_tags = join "", map /(\S+)/ ? "+$1 " : (), @patches;
-
-close(PATCH_LEVEL) or die "Error closing patchlevel.h: $!";
-
-# TO DO (prehaps): store/embed $Config::config_sh into perlbug. When perlbug is
+# TO DO (perhaps): store/embed $Config::config_sh into perlbug. When perlbug is
 # used, compare $Config::config_sh with the stored version. If they differ then
 # append a list of individual differences to the bug report.
 
@@ -74,18 +49,15 @@ $Config{startperl}
 my \$config_tag1 = '$extract_version - $Config{cf_time}';
 
 my \$patchlevel_date = $patchlevel_date;
-my \$patch_tags = '$patch_tags';
-my \@patches = (
-    $patch_desc
-);
 !GROK!THIS!
 
 # In the following, perl variables are not expanded during extraction.
 
 print OUT <<'!NO!SUBS!';
+my @patches = Config::local_patches();
+my $patch_tags = join "", map /(\S+)/ ? "+$1 " : (), @patches;
 
 use warnings;
-no warnings 'once'; # Eventually, the $::opt_ stuff should get cleaned up
 use strict;
 use Config;
 use File::Spec;                # keep perlbug Perl 5.005 compatible
@@ -104,62 +76,12 @@ BEGIN {
     $::HaveTemp = ($@ eq "");
     eval { require Module::CoreList; };
     $::HaveCoreList = ($@ eq "");
+    eval { require Text::Wrap; };
+    $::HaveWrap = ($@ eq "");
 };
 
-my $Version = "1.39";
-
-# Changed in 1.06 to skip Mail::Send and Mail::Util if not available.
-# Changed in 1.07 to see more sendmail execs, and added pipe output.
-# Changed in 1.08 to use correct address for sendmail.
-# Changed in 1.09 to close the REP file before calling it up in the editor.
-#                 Also removed some old comments duplicated elsewhere.
-# Changed in 1.10 to run under VMS without Mail::Send; also fixed
-#                 temp filename generation.
-# Changed in 1.11 to clean up some text and removed Mail::Send deactivator.
-# Changed in 1.12 to check for editor errors, make save/send distinction
-#                 clearer and add $ENV{REPLYTO}.
-# Changed in 1.13 to hopefully make it more difficult to accidentally
-#                 send mail
-# Changed in 1.14 to make the prompts a little more clear on providing
-#                 helpful information. Also let file read fail gracefully.
-# Changed in 1.15 to add warnings to stop people using perlbug for non-bugs.
-#                 Also report selected environment variables.
-# Changed in 1.16 to include @INC, and allow user to re-edit if no changes.
-# Changed in 1.17 Win32 support added.  GSAR 97-04-12
-# Changed in 1.18 add '-ok' option for reporting build success. CFR 97-06-18
-# Changed in 1.19 '-ok' default not '-v'
-#                 add local patch information
-#                 warn on '-ok' if this is an old system; add '-okay'
-# Changed in 1.20 Added patchlevel.h reading and version/config checks
-# Changed in 1.21 Added '-nok' for reporting build failure DFD 98-05-05
-# Changed in 1.22 Heavy reformatting & minor bugfixes HVDS 98-05-10
-# Changed in 1.23 Restore -ok(ay): say 'success'; don't prompt
-# Changed in 1.24 Added '-F<file>' to save report HVDS 98-07-01
-# Changed in 1.25 Warn on failure to open save file. HVDS 98-07-12
-# Changed in 1.26 Don't require -t STDIN for -ok. HVDS 98-07-15
-# Changed in 1.27 Added Mac OS and File::Spec support CNANDOR 99-07-27
-# Changed in 1.28 Additional questions for Perlbugtron RFOLEY 20.03.2000
-# Changed in 1.29 Perlbug(tron): auto(-ok), short prompts RFOLEY 05-05-2000
-# Changed in 1.30 Added warnings on failure to open files MSTEVENS 13-07-2000
-# Changed in 1.31 Add checks on close().Fix my $var unless. TJENNESS 26-07-2000
-# Changed in 1.32 Use File::Spec->tmpdir TJENNESS 20-08-2000
-# Changed in 1.33 Don't require -t STDOUT for -ok.
-# Changed in 1.34 Added Message-Id RFOLEY 18-06-2002 
-# Changed in 1.35 Use File::Temp (patch from Solar Designer) NWCLARK 28-02-2004
-# Changed in 1.36 Initial Module::CoreList support Alexandr Ciornii 11-07-2007
-# Changed in 1.37 Killed some string evals, rewrote most prose JESSE 2008-06-08
-# Changed in 1.38 Actually enforce the CoreList check,
-#                 Record the module the user enters if they do so
-#                 Refactor prompts to use common code           JESSE 2008-06-08
-# Changed in 1.39 Trap mail sending failures (simple ones) so   JESSE 2008-06-08
-#                 users might be able to recover their bug reports
-#                 Refactor mail sending routines
-#                 Unify message building code
-#                 Unify message header building
-#                 Fix "module" prompting to not squish "category" prompting 
-#                 use warnings; (except 'once' warnings)
-#                 Unified report fingerprint/change detection code
-#                 Removed some labeled 'gotos'
+my $Version = "1.40";
+
 #TODO:
 #       make sure failure (transmission-wise) of Mail::Send is accounted for.
 #       (This may work now. Unsure of the original author's issue -JESSE 2008-06-08)
@@ -168,20 +90,22 @@ my $Version = "1.39";
 my( $file, $usefile, $cc, $address, $bugaddress, $testaddress, $thanksaddress,
     $filename, $messageid, $domain, $subject, $from, $verbose, $ed, $outfile,
     $fh, $me, $body, $andcc, %REP, $ok, $thanks, $progname,
-    $Is_MacOS, $Is_MSWin32, $Is_Linux, $Is_VMS, $Is_OpenBSD,
+    $Is_MSWin32, $Is_Linux, $Is_VMS, $Is_OpenBSD,
     $report_about_module, $category, $severity,
-
+    %opt, $have_attachment, $attachments, $has_patch, $mime_boundary
 );
 
+my $running_noninteractively = !-t STDIN;
+
 my $perl_version = $^V ? sprintf("%vd", $^V) : $];
 
 my $config_tag2 = "$perl_version - $Config{cf_time}";
 
 Init();
 
-if ($::opt_h) { Help(); exit; }
-if ($::opt_d) { Dump(*STDOUT); exit; }
-if (!-t STDIN && !($ok and not $::opt_n)) {
+if ($opt{h}) { Help(); exit; }
+if ($opt{d}) { Dump(*STDOUT); exit; }
+if ($running_noninteractively && !$opt{t} && !($ok and not $opt{n})) {
     paraprint <<"EOF";
 Please use $progname interactively. If you want to
 include a file, you can use the -f switch.
@@ -190,7 +114,7 @@ EOF
 }
 
 Query();
-Edit() unless $usefile || ($ok and not $::opt_n);
+Edit() unless $usefile || ($ok and not $opt{n});
 NowWhat();
 if ($outfile) {
     save_message_to_disk($outfile);
@@ -198,9 +122,22 @@ if ($outfile) {
     Send();
     if ($thanks) {
        print "\nThank you for taking the time to send a thank-you message!\n\n";
+
+       paraprint <<EOF
+Please note that mailing lists are moderated, your message may take a while to
+show up.
+EOF
     } else {
        print "\nThank you for taking the time to file a bug report!\n\n";
+
+       paraprint <<EOF
+Please note that mailing lists are moderated, your message may take a while to
+show up. If you do not receive an automated response acknowledging your message
+within a few hours (check your SPAM folder and outgoing mail) please consider
+sending an email directly from your mail client to perlbug\@perl.org.
+EOF
     }
+
 }
 
 exit;
@@ -254,13 +191,8 @@ sub Init {
     $Is_VMS = $^O eq 'VMS';
     $Is_Linux = lc($^O) eq 'linux';
     $Is_OpenBSD = lc($^O) eq 'openbsd';
-    $Is_MacOS = $^O eq 'MacOS';
 
-    @ARGV = split m/\s+/,
-        MacPerl::Ask('Provide command line args here (-h for help):')
-        if $Is_MacOS && $MacPerl::Version =~ /App/;
-
-    if (!getopts("Adhva:s:b:f:F:r:e:SCc:to:n:T")) { Help(); exit; };
+    if (!getopts("Adhva:s:b:f:F:r:e:SCc:to:n:T:p:", \%opt)) { Help(); exit; };
 
     # This comment is needed to notify metaconfig that we are
     # using the $perladmin, $cf_by, and $cf_time definitions.
@@ -278,51 +210,65 @@ sub Init {
 
     if (basename ($0) =~ /^perlthanks/i) {
        # invoked as perlthanks
-       $::opt_T = 1;
-       $::opt_C = 1; # don't send a copy to the local admin
+       $opt{T} = 1;
+       $opt{C} = 1; # don't send a copy to the local admin
     }
 
-    if ($::opt_T) {
+    if ($opt{T}) {
        $thanks = 'thanks';
     }
     
     $progname = $thanks ? 'perlthanks' : 'perlbug';
     # Target address
-    $address = $::opt_a || ($::opt_t ? $testaddress
+    $address = $opt{a} || ($opt{t} ? $testaddress
                            : $thanks ? $thanksaddress : $bugaddress);
 
     # Users address, used in message and in From and Reply-To headers
-    $from = $::opt_r || "";
+    $from = $opt{r} || "";
 
     # Include verbose configuration information
-    $verbose = $::opt_v || 0;
+    $verbose = $opt{v} || 0;
 
     # Subject of bug-report message
-    $subject = $::opt_s || "";
+    $subject = $opt{s} || "";
 
     # Send a file
-    $usefile = ($::opt_f || 0);
+    $usefile = ($opt{f} || 0);
 
     # File to send as report
-    $file = $::opt_f || "";
+    $file = $opt{f} || "";
+
+    # We have one or more attachments
+    $have_attachment = ($opt{p} || 0);
+    $mime_boundary = ('-' x 12) . "$Version.perlbug" if $have_attachment;
+
+    # Comma-separated list of attachments
+    $attachments = $opt{p} || "";
+    $has_patch = 0; # TBD based on file type
+
+    for my $attachment (split /\s*,\s*/, $attachments) {
+        unless (-f $attachment && -r $attachment) {
+            die "The attachment $attachment is not a readable file: $!\n";
+        }
+        $has_patch = 1 if $attachment =~ m/\.(patch|diff)$/;
+    }
 
     # File to output to
-    $outfile = $::opt_F || "";
+    $outfile = $opt{F} || "";
 
     # Body of report
-    $body = $::opt_b || "";
+    $body = $opt{b} || "";
        
     # Editor
-    $ed = $::opt_e || $ENV{VISUAL} || $ENV{EDITOR} || $ENV{EDIT}
+    $ed = $opt{e} || $ENV{VISUAL} || $ENV{EDITOR} || $ENV{EDIT}
        || ($Is_VMS && "edit/tpu")
        || ($Is_MSWin32 && "notepad")
-       || ($Is_MacOS && '')
        || "vi";
 
     # Not OK - provide build failure template by finessing OK report
-    if ($::opt_n) {
-       if (substr($::opt_n, 0, 2) eq 'ok' )    {
-           $::opt_o = substr($::opt_n, 1);
+    if ($opt{n}) {
+       if (substr($opt{n}, 0, 2) eq 'ok' )     {
+           $opt{o} = substr($opt{n}, 1);
        } else {
            Help();
            exit();
@@ -331,10 +277,10 @@ sub Init {
 
     # OK - send "OK" report for build on this system
     $ok = '';
-    if ($::opt_o) {
-       if ($::opt_o eq 'k' or $::opt_o eq 'kay') {
+    if ($opt{o}) {
+       if ($opt{o} eq 'k' or $opt{o} eq 'kay') {
            my $age = time - $patchlevel_date;
-           if ($::opt_o eq 'k' and $age > 60 * 24 * 60 * 60 ) {
+           if ($opt{o} eq 'k' and $age > 60 * 24 * 60 * 60 ) {
                my $date = localtime $patchlevel_date;
                print <<"EOF";
 "perlbug -ok" and "perlbug -nok" do not report on Perl versions which
@@ -345,14 +291,14 @@ EOF
                exit();
            }
            # force these options
-           unless ($::opt_n) {
-               $::opt_S = 1; # don't prompt for send
-               $::opt_b = 1; # we have a body
+           unless ($opt{n}) {
+               $opt{S} = 1; # don't prompt for send
+               $opt{b} = 1; # we have a body
                $body = "Perl reported to build OK on this system.\n";
            }
-           $::opt_C = 1; # don't send a copy to the local admin
-           $::opt_s = 1; # we have a subject line
-           $subject = ($::opt_n ? 'Not ' : '')
+           $opt{C} = 1; # don't send a copy to the local admin
+           $opt{s} = 1; # we have a subject line
+           $subject = ($opt{n} ? 'Not ' : '')
                    . "OK: perl $perl_version ${patch_tags}on"
                    ." $::Config{'archname'} $::Config{'osvers'} $subject";
            $ok = 'ok';
@@ -367,9 +313,9 @@ EOF
     # we don't really want it. We'll just take it if we have to.)
     #
     # This has to be after the $ok stuff above because of the way
-    # that $::opt_C is forced.
-    $cc = $::opt_C ? "" : (
-       $::opt_c || $::Config{'perladmin'}
+    # that $opt{C} is forced.
+    $cc = $opt{C} ? "" : (
+       $opt{c} || $::Config{'perladmin'}
        || $::Config{'cf_email'} || $::Config{'cf_by'}
     );
 
@@ -388,7 +334,6 @@ EOF
     # My username
     $me = $Is_MSWin32 ? $ENV{'USERNAME'}
            : $^O eq 'os2' ? $ENV{'USER'} || $ENV{'LOGNAME'}
-           : $Is_MacOS ? $ENV{'USER'}
            : eval { getpwuid($<) };    # May be missing
 
     $from = $::Config{'cf_email'}
@@ -417,8 +362,8 @@ note to $thanksaddress instead of a bug report, please run 'perlthanks'.
 Please do not use $0 to send test messages, test whether perl
 works, or to report bugs in perl modules from CPAN.
 
-For help using perl, try posting to the Usenet newsgroup 
-comp.lang.perl.misc.
+Suggestions for how to find help using Perl can be found at
+http://perldoc.perl.org/perlcommunity.html
 EOF
        }
     }
@@ -454,20 +399,16 @@ EOF
            }
        } while (TrivialSubject($subject));
     }
+    $subject = '[PATCH] ' . $subject
+        if $has_patch && ($subject !~ m/^\[PATCH/i);
 
     # Prompt for return address, if needed
-    unless ($::opt_r) {
+    unless ($opt{r}) {
        # Try and guess return address
        my $guess;
 
        $guess = $ENV{'REPLY-TO'} || $ENV{'REPLYTO'} || $ENV{'EMAIL'}
            || $from || '';
-        if ($Is_MacOS) {
-            require Mac::InternetConfig;
-            $guess = $Mac::InternetConfig::InternetConfig{
-                Mac::InternetConfig::kICEmail()
-            };
-        }
 
        unless ($guess) {
                # move $domain to where we can use it elsewhere 
@@ -513,7 +454,7 @@ EOF
     }
 
     # Prompt for administrator address, unless an override was given
-    if( !$::opt_C and !$::opt_c ) {
+    if( !$opt{C} and !$opt{c} ) {
        my $description =  <<EOF;
 $0 can send a copy of this report to your local perl
 administrator.  If the address below is wrong, please correct it,
@@ -536,7 +477,7 @@ EOF
 
     # Prompt for editor, if no override is given
 editor:
-    unless ($::opt_e || $::opt_f || $::opt_b) {
+    unless ($opt{e} || $opt{f} || $opt{b}) {
 
     my $description;
 
@@ -662,9 +603,11 @@ EOF
     }
 
     # Generate report
-    open(REP,">$filename") or die "Unable to create report file '$filename': $!\n";
+    open(REP, '>:raw', $filename) or die "Unable to create report file '$filename': $!\n";
+    binmode(REP, ':raw :crlf') if $Is_MSWin32;
+
     my $reptype = !$ok ? ($thanks ? 'thank-you' : 'bug')
-       : $::opt_n ? "build failure" : "success";
+       : $opt{n} ? "build failure" : "success";
 
     print REP <<EOF;
 This is a $reptype report for perl from $from,
@@ -675,8 +618,9 @@ EOF
     if ($body) {
        print REP $body;
     } elsif ($usefile) {
-       open(F, "<$file")
+       open(F, '<:raw', $file)
                or die "Unable to read report file from '$file': $!\n";
+       binmode(F, ':raw :crlf') if $Is_MSWin32;
        while (<F>) {
            print REP $_
        }
@@ -728,12 +672,19 @@ Flags:
     severity=$severity
 EFF
 
+    if ($has_patch) {
+        print OUT <<EFF;
+    Type=Patch
+    PatchStatus=HasPatch
+EFF
+    }
+
     if ($report_about_module ) { 
         print OUT <<EFF;
     module=$report_about_module
 EFF
     }
-    if ($::opt_A) {
+    if ($opt{A}) {
        print OUT <<EFF;
     ack=no
 EFF
@@ -804,7 +755,7 @@ sub Edit {
        $ed = $entry unless $entry eq '';
     }
 
-    _edit_file($ed);
+    _edit_file($ed) unless $running_noninteractively;
 }
 
 sub _edit_file {
@@ -813,32 +764,26 @@ sub _edit_file {
     my $report_written = 0;
 
     while ( !$report_written ) {
-        if ($Is_MacOS) {
-            require ExtUtils::MakeMaker;
-            ExtUtils::MM_MacOS::launch_file($filename);
-            _prompt('', "Press Enter when done." );
-        } else {    # we're not on oldschool mac os
-            my $exit_status = system("$editor $filename");
-            if ($exit_status) {
-                my $desc = <<EOF;
+        my $exit_status = system("$editor $filename");
+        if ($exit_status) {
+            my $desc = <<EOF;
 The editor you chose ('$editor') could not be run!
 
 If you mistyped its name, please enter it now, otherwise just press Enter.
 EOF
-                my $entry = _prompt( $desc, 'Editor', $editor );
-                if ( $entry ne "" ) {
-                    $editor = $entry;
-                    next;
-                } else {
-                    paraprint <<EOF;
+            my $entry = _prompt( $desc, 'Editor', $editor );
+            if ( $entry ne "" ) {
+                $editor = $entry;
+                next;
+            } else {
+                paraprint <<EOF;
 You may want to save your report to a file, so you can edit and
 mail it later.
 EOF
-                    return;
-                }
+                return;
             }
         }
-        return if ( $ok and not $::opt_n ) || $body;
+        return if ( $ok and not $opt{n} ) || $body;
 
         # Check that we have a report that has some, eh, report in it.
 
@@ -869,7 +814,7 @@ sub Cancel {
 
 sub NowWhat {
     # Report is done, prompt for further action
-    if( !$::opt_S ) {
+    if( !$opt{S} ) {
        while(1) {
            my $menu = <<EOF;
 
@@ -887,15 +832,18 @@ a few options. You can:
 EOF
       retry:
         print $menu;
-           my $action =  _prompt('', "Action (Send/Display/Edit/Subject/Save to File)");;
+           my $action =  _prompt('', "Action (Send/Display/Edit/Subject/Save to File)",
+               $opt{t} ? 'q' : '');
         print "\n";
            if ($action =~ /^(f|sa)/i) { # <F>ile/<Sa>ve
             if ( SaveMessage() ) { exit }
            } elsif ($action =~ /^(d|l|sh)/i ) { # <D>isplay, <L>ist, <Sh>ow
                # Display the message
-               open(REP, "<$filename") or die "Couldn't open file '$filename': $!\n";
-               while (<REP>) { print $_ }
-               close(REP) or die "Error closing report file '$filename': $!";
+               print _read_report($filename);
+               if ($have_attachment) {
+                   print "\n\n---\nAttachment(s):\n";
+                   for my $att (split /\s*,\s*/, $attachments) { print "    $att\n"; }
+               }
            } elsif ($action =~ /^su/i) { # <Su>bject
                my $reply = _prompt( "Subject: $subject", "If the above subject is fine, press Enter. Otherwise, type a replacement now\nSubject");
                if ($reply ne '') {
@@ -933,7 +881,7 @@ sub TrivialSubject {
     if ($subject =~
        /^(y(es)?|no?|help|perl( (bug|problem))?|bug|problem)$/i ||
        length($subject) < 4 ||
-       $subject !~ /\s/) {
+       ($subject !~ /\s/ && ! $opt{t})) { # non-whitespace is accepted in test mode
        print "\nThe subject you entered wasn't very descriptive. Please try again.\n\n";
         return 1;
     } else {
@@ -995,6 +943,7 @@ Advanced usage:
 
 $0  [-v] [-a address] [-s subject] [-b body | -f inpufile ] [ -F outputfile ]
     [-r returnaddress] [-e editor] [-c adminaddress | -C] [-S] [-t] [-h]
+    [-p patchfile ]
 $0  [-v] [-r returnaddress] [-A] [-ok | -okay | -nok | -nokay]
 
 
@@ -1003,6 +952,8 @@ Options:
   -v    Include Verbose configuration data in the report
   -f    File containing the body of the report. Use this to
         quickly send a prepared message.
+  -p    File containing a patch or other text attachment. Separate
+        multiple files with commas.
   -F    File to output the resulting mail message to, instead of mailing.
   -S    Send without asking for confirmation.
   -a    Address to send the report to. Defaults to '$address'.
@@ -1065,6 +1016,7 @@ sub _prompt {
     }
     print $prompt. ($default ? " [$default]" :''). ": ";
        my $result = scalar(<>);
+    return $default if !defined $result; # got eof
     chomp($result);
        $result =~ s/^\s*(.*?)\s*$/$1/s;
     if ($default && $result eq '') {
@@ -1090,21 +1042,80 @@ sub _message_headers {
     $headers{'Message-Id'} = $messageid if ($messageid);
     $headers{'Reply-To'}   = $from      if ($from);
     $headers{'From'}       = $from      if ($from);
+    if ($have_attachment) {
+        $headers{'MIME-Version'} = '1.0';
+        $headers{'Content-Type'} = qq{multipart/mixed; boundary=\"$mime_boundary\"};
+    }
     return \%headers;
 }
 
+sub _add_body_start {
+    my $body_start = <<"BODY_START";
+This is a multi-part message in MIME format.
+--$mime_boundary
+Content-Type: text/plain; format=fixed
+Content-Transfer-Encoding: 8bit
+
+BODY_START
+    return $body_start;
+}
+
+sub _add_attachments {
+    my $attach = '';
+    for my $attachment (split /\s*,\s*/, $attachments) {
+        my $attach_file = basename($attachment);
+        $attach .= <<"ATTACHMENT";
+
+--$mime_boundary
+Content-Type: text/x-patch; name="$attach_file"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: attachment; filename="$attach_file"
+
+ATTACHMENT
+
+        open my $attach_fh, '<:raw', $attachment
+            or die "Couldn't open attachment '$attachment': $!\n";
+        while (<$attach_fh>) { $attach .= $_; }
+        close($attach_fh) or die "Error closing attachment '$attachment': $!";
+    }
+
+    $attach .= "\n--$mime_boundary--\n";
+    return $attach;
+}
+
+sub _read_report {
+    my $fname = shift;
+    my $content;
+    open( REP, "<:raw", $fname ) or die "Couldn't open file '$fname': $!\n";
+    binmode(REP, ':raw :crlf') if $Is_MSWin32;
+    # wrap long lines to make sure the report gets delivered
+    local $Text::Wrap::columns = 900;
+    local $Text::Wrap::huge = 'overflow';
+    while (<REP>) {
+        if ($::HaveWrap && /\S/) { # wrap() would remove empty lines
+            $content .= Text::Wrap::wrap(undef, undef, $_);
+        } else {
+            $content .= $_;
+        }
+    }
+    close(REP) or die "Error closing report file '$fname': $!";
+    return $content;
+}
+
 sub build_complete_message {
     my $content = _build_header(%{_message_headers()}) . "\n\n";
-    open( REP, "<$filename" ) or die "Couldn't open file '$filename': $!\n";
-    while (<REP>) { $content .= $_; }
-    close(REP) or die "Error closing report file '$filename': $!";
+    $content .= _add_body_start() if $have_attachment;
+    $content .= _read_report($filename);
+    $content .= _add_attachments() if $have_attachment;
     return $content;
 }
 
 sub save_message_to_disk {
     my $file = shift;
 
-           open OUTFILE, ">$file" or do { warn  "Couldn't open '$file': $!\n"; return undef};
+        open OUTFILE, '>:raw', $file or do { warn  "Couldn't open '$file': $!\n"; return undef};
+        binmode(OUTFILE, ':raw :crlf') if $Is_MSWin32;
+
         print OUTFILE build_complete_message();
         close(OUTFILE) or do { warn  "Error closing $file: $!"; return undef };
            print "\nMessage saved.\n";
@@ -1112,22 +1123,27 @@ sub save_message_to_disk {
 }
 
 sub _send_message_vms {
-    if (   ( $address =~ /@/ and $address !~ /^\w+%"/ )
-        or ( $cc =~ /@/ and $cc !~ /^\w+%"/ ) ) {
-        my $prefix;
-        foreach ( qw[ IN MX SMTP UCX PONY WINS ], '' ) {
-            $prefix = "$_%", last if $ENV{"MAIL\$PROTOCOL_$_"};
+
+    my $mail_from  = $from;
+    my $rcpt_to_to = $address;
+    my $rcpt_to_cc = $cc;
+
+    map { $_ =~ s/^[^<]*<//;
+          $_ =~ s/>[^>]*//; } ($mail_from, $rcpt_to_to, $rcpt_to_cc);
+
+    if ( open my $sff_fh, '|-:raw', 'MCR TCPIP$SYSTEM:TCPIP$SMTP_SFF.EXE SYS$INPUT:' ) {
+        print $sff_fh "MAIL FROM:<$mail_from>\n";
+        print $sff_fh "RCPT TO:<$rcpt_to_to>\n";
+        print $sff_fh "RCPT TO:<$rcpt_to_cc>\n" if $rcpt_to_cc;
+        print $sff_fh "DATA\n";
+        print $sff_fh build_complete_message();
+        my $success = close $sff_fh;
+        if ($success ) {
+            print "\nMessage sent\n";
+            return;
         }
-        $address = qq[${prefix}"$address"] unless $address =~ /^\w+%"/;
-        $cc = qq[${prefix}"$cc"] unless !$cc || $cc =~ /^\w+%"/;
-    }
-    $subject =~ s/"/""/g;
-    $address =~ s/"/""/g;
-    $cc      =~ s/"/""/g;
-    my $sts = system(qq[mail/Subject="$subject" $filename. "$address","$cc"]);
-    if ($sts) {
-        die "Can't spawn off mail (leaving bug report in $filename): $sts";
     }
+    die "Mail transport failed (leaving bug report in $filename): $^E\n";
 }
 
 sub _send_message_mailsend {
@@ -1138,9 +1154,10 @@ sub _send_message_mailsend {
     }
 
     $fh = $msg->open;
-    open(REP, "<$filename") or die "Couldn't open '$filename': $!\n";
-    while (<REP>) { print $fh $_ }
-    close(REP) or die "Error closing $filename: $!";
+    binmode($fh, ':raw');
+    print $fh _add_body_start() if $have_attachment;
+    print $fh _read_report($filename);
+    print $fh _add_attachments() if $have_attachment;
     $fh->close or die "Error sending mail: $!";
 
     print "\nMessage sent.\n";
@@ -1183,8 +1200,8 @@ send to '$address' with your normal mail client.
 EOF
     }
 
-    open( SENDMAIL, "|$sendmail -t -oi" )
-        || die "'|$sendmail -t -oi' failed: $!";
+    open( SENDMAIL, "|-:raw", $sendmail, "-t", "-oi", "-f", $from )
+        || die "'|$sendmail -t -oi -f $from' failed: $!";
     print SENDMAIL build_complete_message();
     if ( close(SENDMAIL) ) {
         print "\nMessage sent\n";
@@ -1205,7 +1222,8 @@ sub _fingerprint_lines_in_report {
     # we can track whether the user does any editing.
     # yes, *all* whitespace is ignored.
 
-    open(REP, "<$filename") or die "Unable to open report file '$filename': $!\n";
+    open(REP, '<:raw', $filename) or die "Unable to open report file '$filename': $!\n";
+    binmode(REP, ':raw :crlf') if $Is_MSWin32;
     while (my $line = <REP>) {
         $line =~ s/\s+//g;
         $new_lines++ if (!$REP{$line});
@@ -1360,11 +1378,11 @@ will help a great deal.  In other words, try to analyze the problem
 
 =item Can you fix the bug yourself?
 
-A bug report which I<includes a patch to fix it> will almost
-definitely be fixed.  When sending a patch, please use the C<diff>
-program with the C<-u> option to generate "unified" diff files.
-Bug reports with patches are likely to receive significantly more
-attention and interest than those without patches.
+If so, that's great news; bug reports with patches are likely to
+receive significantly more attention and interest than those without
+patches.  Please attach your patch to the report using the C<-p> option.
+When sending a patch, create it using C<git format-patch> if possible,
+though a unified diff created with C<diff -pu> will do nearly as well.
 
 Your patch may be returned with requests for changes, or requests for more
 detailed explanations about your fix.
@@ -1373,10 +1391,10 @@ Here are a few hints for creating high-quality patches:
 
 Make sure the patch is not reversed (the first argument to diff is
 typically the original file, the second argument your changed file).
-Make sure you test your patch by applying it with the C<patch>
-program before you send it on its way.  Try to follow the same style
-as the code you are trying to patch.  Make sure your patch really
-does work (C<make test>, if the thing you're patching is covered
+Make sure you test your patch by applying it with C<git am> or the
+C<patch> program before you send it on its way.  Try to follow the
+same style as the code you are trying to patch.  Make sure your patch
+really does work (C<make test>, if the thing you're patching is covered
 by Perl's test suite).
 
 =item Can you use C<perlbug> to submit the report?
@@ -1410,11 +1428,12 @@ a duplicate of an existing report, you may not receive a personal
 reply.
 
 If it is important to you that your bug be fixed, do monitor the
-perl5-porters@perl.org mailing list and the commit logs to development
+perl5-porters@perl.org mailing list (mailing lists are moderated, your
+message may take a while to show up) and the commit logs to development
 versions of Perl, and encourage the maintainers with kind words or
 offers of frosty beverages.  (Please do be kind to the maintainers.
-Harassing or flaming them is likely to have the opposite effect of
-the one you want.)
+Harassing or flaming them is likely to have the opposite effect of the
+one you want.)
 
 Feel free to update the ticket about your bug on http://rt.perl.org
 if a new version of Perl is released and your bug is still present.
@@ -1501,6 +1520,11 @@ days old.
 
 As B<-nok> except it will report on older systems.
 
+=item B<-p>
+
+The names of one or more patch files or other text attachments to be
+included with the report.  Multiple files must be separated with commas.
+
 =item B<-r>
 
 Your return address.  The program will ask you to confirm its default
@@ -1518,6 +1542,8 @@ supply one on the command line.
 =item B<-t>
 
 Test mode.  The target address defaults to B<perlbug-test@perl.org>.
+Also makes it possible to command perlbug from a pipe or file, for
+testing purposes.
 
 =item B<-T>
 
@@ -1535,12 +1561,12 @@ Kenneth Albanowski (E<lt>kjahds@kjahds.comE<gt>), subsequently
 I<doc>tored by Gurusamy Sarathy (E<lt>gsar@activestate.comE<gt>),
 Tom Christiansen (E<lt>tchrist@perl.comE<gt>), Nathan Torkington
 (E<lt>gnat@frii.comE<gt>), Charles F. Randall (E<lt>cfr@pobox.comE<gt>),
-Mike Guy (E<lt>mjtg@cam.a.ukE<gt>), Dominic Dunlop
-(E<lt>domo@computer.orgE<gt>), Hugo van der Sanden (E<lt>hv@crypt.org<gt>),
+Mike Guy (E<lt>mjtg@cam.ac.ukE<gt>), Dominic Dunlop
+(E<lt>domo@computer.orgE<gt>), Hugo van der Sanden (E<lt>hv@crypt.orgE<gt>),
 Jarkko Hietaniemi (E<lt>jhi@iki.fiE<gt>), Chris Nandor
 (E<lt>pudge@pobox.comE<gt>), Jon Orwant (E<lt>orwant@media.mit.eduE<gt>,
-Richard Foley (E<lt>richard.foley@rfi.netE<gt>), and Jesse Vincent
-(E<lt>jesse@bestpractical.com<gt>).
+Richard Foley (E<lt>richard.foley@rfi.netE<gt>), Jesse Vincent
+(E<lt>jesse@bestpractical.comE<gt>), and Craig A. Berry (E<lt>craigberry@mac.comE<gt>).
 
 =head1 SEE ALSO