source: trunk/locker/sbin/commit-email.pl @ 1400

Last change on this file since 1400 was 1400, checked in by andersk, 14 years ago
Update commit-email.pl and commit-zephyr from upstream.
  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 25.8 KB
RevLine 
[375]1#!/usr/bin/env perl
2
3# ====================================================================
[1400]4# This script is deprecated.  The Subversion developers recommend
5# using mailer.py for post-commit and post-revprop change
6# notifications.  If you wish to improve or add features to a
7# post-commit notification script, please do that work on mailer.py.
8# See http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/mailer .
9# ====================================================================
10
11# ====================================================================
[719]12# commit-email.pl: send a notification email describing either a
13# commit or a revprop-change action on a Subversion repository.
[375]14#
15# For usage, see the usage subroutine or run the script with no
16# command line arguments.
17#
[719]18# This script requires Subversion 1.2.0 or later.
19#
[1400]20# $HeadURL: http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/commit-email.pl.in $
21# $LastChangedDate: 2009-05-12 13:25:35 -0400 (Tue, 12 May 2009) $
22# $LastChangedBy: blair $
23# $LastChangedRevision: 37715 $
[719]24#
[375]25# ====================================================================
[719]26# Copyright (c) 2000-2006 CollabNet.  All rights reserved.
[375]27#
28# This software is licensed as described in the file COPYING, which
29# you should have received as part of this distribution.  The terms
30# are also available at http://subversion.tigris.org/license-1.html.
31# If newer versions of this license are posted there, you may use a
32# newer version instead, at your option.
33#
34# This software consists of voluntary contributions made by many
35# individuals.  For exact contribution history, see the revision
36# history and logs, available at http://subversion.tigris.org/.
37# ====================================================================
38
39# Turn on warnings the best way depending on the Perl version.
[719]40BEGIN {
41  if ( $] >= 5.006_000)
42    { require warnings; import warnings; }
43  else
44    { $^W = 1; }
45}
46
[375]47use strict;
48use Carp;
[719]49use POSIX qw(strftime);
50my ($sendmail, $smtp_server);
[375]51
52######################################################################
53# Configuration section.
54
[719]55# Sendmail path, or SMTP server address.
56# You should define exactly one of these two configuration variables,
57# leaving the other commented out, to select which method of sending
58# email should be used.
59# Using --stdout on the command line overrides both.
60$sendmail = "/usr/sbin/sendmail";
61#$smtp_server = "127.0.0.1";
[375]62
63# Svnlook path.
64my $svnlook = "/usr/bin/svnlook";
65
66# By default, when a file is deleted from the repository, svnlook diff
67# prints the entire contents of the file.  If you want to save space
68# in the log and email messages by not printing the file, then set
69# $no_diff_deleted to 1.
70my $no_diff_deleted = 0;
[719]71# By default, when a file is added to the repository, svnlook diff
72# prints the entire contents of the file.  If you want to save space
73# in the log and email messages by not printing the file, then set
74# $no_diff_added to 1.
75my $no_diff_added = 0;
[375]76
[719]77# End of Configuration section.
78######################################################################
79
80# Check that the required programs exist, and the email sending method
81# configuration is sane, to ensure that the administrator has set up
82# the script properly.
[375]83{
84  my $ok = 1;
85  foreach my $program ($sendmail, $svnlook)
86    {
[719]87      next if not defined $program;
[375]88      if (-e $program)
89        {
90          unless (-x $program)
91            {
92              warn "$0: required program `$program' is not executable, ",
93                   "edit $0.\n";
94              $ok = 0;
95            }
96        }
97      else
98        {
99          warn "$0: required program `$program' does not exist, edit $0.\n";
100          $ok = 0;
101        }
102    }
[719]103  if (not (defined $sendmail xor defined $smtp_server))
104    {
105      warn "$0: exactly one of \$sendmail or \$smtp_server must be ",
106           "set, edit $0.\n";
107      $ok = 0;
108    }
[375]109  exit 1 unless $ok;
110}
111
[719]112require Net::SMTP if defined $smtp_server;
[375]113
114######################################################################
115# Initial setup/command-line handling.
116
117# Each value in this array holds a hash reference which contains the
118# associated email information for one project.  Start with an
119# implicit rule that matches all paths.
120my @project_settings_list = (&new_project);
121
[719]122# Process the command line arguments till there are none left.
123# In commit mode: The first two arguments that are not used by a command line
124# option are the repository path and the revision number.
125# In revprop-change mode: The first four arguments that are not used by a
126# command line option are the repository path, the revision number, the
127# author, and the property name. This script has no support for the fifth
128# argument (action) added to the post-revprop-change hook in Subversion
129# 1.2.0 yet - patches welcome!
[375]130my $repos;
131my $rev;
[719]132my $author;
133my $propname;
[375]134
[719]135my $mode = 'commit';
136my $date;
137my $diff_file;
138
[375]139# Use the reference to the first project to populate.
140my $current_project = $project_settings_list[0];
141
142# This hash matches the command line option to the hash key in the
143# project.  If a key exists but has a false value (''), then the
144# command line option is allowed but requires special handling.
145my %opt_to_hash_key = ('--from' => 'from_address',
[719]146                       '--revprop-change' => '',
147                       '-d'     => '',
[375]148                       '-h'     => 'hostname',
149                       '-l'     => 'log_file',
150                       '-m'     => '',
151                       '-r'     => 'reply_to',
[719]152                       '-s'     => 'subject_prefix',
153                       '--summary' => '',
154                       '--diff' => '',
155                       '--stdout' => '');
[375]156
157while (@ARGV)
158  {
159    my $arg = shift @ARGV;
160    if ($arg =~ /^-/)
161      {
162        my $hash_key = $opt_to_hash_key{$arg};
163        unless (defined $hash_key)
164          {
165            die "$0: command line option `$arg' is not recognized.\n";
166          }
167
[719]168        my $value;
169        if ($arg ne '--revprop-change' and $arg ne '--stdout' and $arg ne '--summary')
[375]170          {
[719]171            unless (@ARGV)
172              {
173                die "$0: command line option `$arg' is missing a value.\n";
174              }
175            $value = shift @ARGV;
[375]176          }
177
178        if ($hash_key)
179          {
180            $current_project->{$hash_key} = $value;
181          }
182        else
183          {
[719]184            if ($arg eq '-m')
[375]185              {
[719]186                $current_project                = &new_project;
187                $current_project->{match_regex} = $value;
188                push(@project_settings_list, $current_project);
[375]189              }
[719]190            elsif ($arg eq '-d')
191              {
192                if ($mode ne 'revprop-change')
193                  {
194                    die "$0: `-d' is valid only when used after"
195                      . " `--revprop-change'.\n";
196                  }
197                if ($diff_file)
198                  {
199                    die "$0: command line option `$arg'"
200                      . " can only be used once.\n";
201                  }
202                $diff_file = $value;
203              }
204            elsif ($arg eq '--revprop-change')
205              {
206                if (defined $repos)
207                  {
208                    die "$0: `--revprop-change' must be specified before"
209                      . " the first non-option argument.\n";
210                  }
211                $mode = 'revprop-change';
212              }
213            elsif ($arg eq '--diff')
214              {
215                $current_project->{show_diff} = parse_boolean($value);
216              }
217            elsif ($arg eq '--stdout')
218              {
219                $current_project->{stdout} = 1;
220              }
221            elsif ($arg eq '--summary')
222              {
223                $current_project->{summary} = 1;
224              }
225            else
226              {
227                die "$0: internal error:"
228                  . " should not be handling `$arg' here.\n";
229              }
[375]230          }
231      }
232    else
233      {
234        if (! defined $repos)
235          {
236            $repos = $arg;
237          }
238        elsif (! defined $rev)
239          {
240            $rev = $arg;
241          }
[719]242        elsif (! defined $author && $mode eq 'revprop-change')
243          {
244            $author = $arg;
245          }
246        elsif (! defined $propname && $mode eq 'revprop-change')
247          {
248            $propname = $arg;
249          }
[375]250        else
251          {
252            push(@{$current_project->{email_addresses}}, $arg);
253          }
254      }
255  }
256
[719]257if ($mode eq 'commit')
258  {
259    &usage("$0: too few arguments.") unless defined $rev;
260  }
261elsif ($mode eq 'revprop-change')
262  {
263    &usage("$0: too few arguments.") unless defined $propname;
264  }
[375]265
266# Check the validity of the command line arguments.  Check that the
267# revision is an integer greater than 0 and that the repository
268# directory exists.
269unless ($rev =~ /^\d+/ and $rev > 0)
270  {
271    &usage("$0: revision number `$rev' must be an integer > 0.");
272  }
273unless (-e $repos)
274  {
275    &usage("$0: repos directory `$repos' does not exist.");
276  }
277unless (-d _)
278  {
279    &usage("$0: repos directory `$repos' is not a directory.");
280  }
281
282# Check that all of the regular expressions can be compiled and
283# compile them.
284{
285  my $ok = 1;
286  for (my $i=0; $i<@project_settings_list; ++$i)
287    {
288      my $match_regex = $project_settings_list[$i]->{match_regex};
289
290      # To help users that automatically write regular expressions
291      # that match the root directory using ^/, remove the / character
292      # because subversion paths, while they start at the root level,
293      # do not begin with a /.
294      $match_regex =~ s#^\^/#^#;
295
296      my $match_re;
297      eval { $match_re = qr/$match_regex/ };
298      if ($@)
299        {
300          warn "$0: -m regex #$i `$match_regex' does not compile:\n$@\n";
301          $ok = 0;
302          next;
303        }
304      $project_settings_list[$i]->{match_re} = $match_re;
305    }
306  exit 1 unless $ok;
307}
308
[719]309# Harvest common data needed for both commit or revprop-change.
[375]310
311# Figure out what directories have changed using svnlook.
[719]312my @dirschanged = &read_from_process($svnlook, 'dirs-changed', $repos,
[375]313                                     '-r', $rev);
314
315# Lose the trailing slash in the directory names if one exists, except
316# in the case of '/'.
317my $rootchanged = 0;
318for (my $i=0; $i<@dirschanged; ++$i)
319  {
320    if ($dirschanged[$i] eq '/')
321      {
322        $rootchanged = 1;
323      }
324    else
325      {
326        $dirschanged[$i] =~ s#^(.+)[/\\]$#$1#;
327      }
328  }
329
330# Figure out what files have changed using svnlook.
[719]331my @svnlooklines = &read_from_process($svnlook, 'changed', $repos, '-r', $rev);
[375]332
333# Parse the changed nodes.
334my @adds;
335my @dels;
336my @mods;
337foreach my $line (@svnlooklines)
338  {
339    my $path = '';
340    my $code = '';
341
342    # Split the line up into the modification code and path, ignoring
343    # property modifications.
344    if ($line =~ /^(.).  (.*)$/)
345      {
346        $code = $1;
347        $path = $2;
348      }
349
350    if ($code eq 'A')
351      {
352        push(@adds, $path);
353      }
354    elsif ($code eq 'D')
355      {
356        push(@dels, $path);
357      }
358    else
359      {
360        push(@mods, $path);
361      }
362  }
363
[719]364# Declare variables which carry information out of the inner scope of
365# the conditional blocks below.
366my $subject_base;
367my $subject_logbase;
368my @body;
369# $author - declared above for use as a command line parameter in
370#   revprop-change mode.  In commit mode, gets filled in below.
[375]371
[719]372if ($mode eq 'commit')
373  {
374    ######################################################################
375    # Harvest data using svnlook.
[375]376
[719]377    # Get the author, date, and log from svnlook.
378    my @infolines = &read_from_process($svnlook, 'info', $repos, '-r', $rev);
379    $author = shift @infolines;
380    $date = shift @infolines;
381    shift @infolines;
382    my @log = map { "$_\n" } @infolines;
383
384    ######################################################################
385    # Modified directory name collapsing.
386
387    # Collapse the list of changed directories only if the root directory
388    # was not modified, because otherwise everything is under root and
389    # there's no point in collapsing the directories, and only if more
390    # than one directory was modified.
391    my $commondir = '';
392    my @edited_dirschanged = @dirschanged;
393    if (!$rootchanged and @edited_dirschanged > 1)
[375]394      {
[719]395        my $firstline    = shift @edited_dirschanged;
396        my @commonpieces = split('/', $firstline);
397        foreach my $line (@edited_dirschanged)
[375]398          {
[719]399            my @pieces = split('/', $line);
400            my $i = 0;
401            while ($i < @pieces and $i < @commonpieces)
[375]402              {
[719]403                if ($pieces[$i] ne $commonpieces[$i])
404                  {
405                    splice(@commonpieces, $i, @commonpieces - $i);
406                    last;
407                  }
408                $i++;
[375]409              }
410          }
[719]411        unshift(@edited_dirschanged, $firstline);
[375]412
[719]413        if (@commonpieces)
[375]414          {
[719]415            $commondir = join('/', @commonpieces);
416            my @new_dirschanged;
417            foreach my $dir (@edited_dirschanged)
[375]418              {
[719]419                if ($dir eq $commondir)
420                  {
421                    $dir = '.';
422                  }
423                else
424                  {
425                    $dir =~ s#^\Q$commondir/\E##;
426                  }
427                push(@new_dirschanged, $dir);
[375]428              }
[719]429            @edited_dirschanged = @new_dirschanged;
[375]430          }
431      }
[719]432    my $dirlist = join(' ', @edited_dirschanged);
[375]433
[719]434    ######################################################################
435    # Assembly of log message.
[375]436
[719]437    if ($commondir ne '')
438      {
439        $subject_base = "r$rev - in $commondir: $dirlist";
440      }
441    else
442      {
443        $subject_base = "r$rev - $dirlist";
444      }
445    my $summary = @log ? $log[0] : '';
446    chomp($summary);
447    $subject_logbase = "r$rev - $summary";
448
449    # Put together the body of the log message.
450    push(@body, "Author: $author\n");
451    push(@body, "Date: $date\n");
452    push(@body, "New Revision: $rev\n");
453    push(@body, "\n");
454    if (@adds)
455      {
456        @adds = sort @adds;
457        push(@body, "Added:\n");
458        push(@body, map { "   $_\n" } @adds);
459      }
460    if (@dels)
461      {
462        @dels = sort @dels;
463        push(@body, "Removed:\n");
464        push(@body, map { "   $_\n" } @dels);
465      }
466    if (@mods)
467      {
468        @mods = sort @mods;
469        push(@body, "Modified:\n");
470        push(@body, map { "   $_\n" } @mods);
471      }
472    push(@body, "Log:\n");
473    push(@body, @log);
474    push(@body, "\n");
[375]475  }
[719]476elsif ($mode eq 'revprop-change')
[375]477  {
[719]478    ######################################################################
479    # Harvest data.
480
481    my @svnlines;
482    # Get the diff file if it was provided, otherwise the property value.
483    if ($diff_file)
484      {
485        open(DIFF_FILE, $diff_file) or die "$0: cannot read `$diff_file': $!\n";
486        @svnlines = <DIFF_FILE>;
487        close DIFF_FILE;
488      }
489    else
490      {
491        @svnlines = &read_from_process($svnlook, 'propget', '--revprop', '-r',
492                                       $rev, $repos, $propname);
493      }
494
495    ######################################################################
496    # Assembly of log message.
497
498    $subject_base = "propchange - r$rev $propname";
499
500    # Put together the body of the log message.
501    push(@body, "Author: $author\n");
502    push(@body, "Revision: $rev\n");
503    push(@body, "Property Name: $propname\n");
504    push(@body, "\n");
505    unless ($diff_file)
506      {
507        push(@body, "New Property Value:\n");
508      }
509    push(@body, map { /[\r\n]+$/ ? $_ : "$_\n" } @svnlines);
510    push(@body, "\n");
[375]511  }
512
[719]513# Cached information - calculated when first needed.
514my @difflines;
515
[375]516# Go through each project and see if there are any matches for this
517# project.  If so, send the log out.
518foreach my $project (@project_settings_list)
519  {
520    my $match_re = $project->{match_re};
521    my $match    = 0;
522    foreach my $path (@dirschanged, @adds, @dels, @mods)
523      {
524        if ($path =~ $match_re)
525          {
526            $match = 1;
527            last;
528          }
529      }
530
531    next unless $match;
532
533    my @email_addresses = @{$project->{email_addresses}};
534    my $userlist        = join(' ', @email_addresses);
535    my $to              = join(', ', @email_addresses);
536    my $from_address    = $project->{from_address};
537    my $hostname        = $project->{hostname};
538    my $log_file        = $project->{log_file};
539    my $reply_to        = $project->{reply_to};
540    my $subject_prefix  = $project->{subject_prefix};
[719]541    my $summary         = $project->{summary};
542    my $diff_wanted     = ($project->{show_diff} and $mode eq 'commit');
543    my $stdout          = $project->{stdout};
[375]544
[719]545    my $subject         = $summary ? $subject_logbase : $subject_base;
[375]546    if ($subject_prefix =~ /\w/)
547      {
548        $subject = "$subject_prefix $subject";
549      }
550    my $mail_from = $author;
551
552    if ($from_address =~ /\w/)
553      {
554        $mail_from = $from_address;
555      }
556    elsif ($hostname =~ /\w/)
557      {
558        $mail_from = "$mail_from\@$hostname";
559      }
[719]560    elsif (defined $smtp_server and ! $stdout)
561      {
562        die "$0: use of either `-h' or `--from' is mandatory when ",
563            "sending email using direct SMTP.\n";
564      }
[375]565
566    my @head;
[719]567    my $formatted_date;
[1400]568    if ($stdout)
[719]569      {
570        $formatted_date = strftime('%a %b %e %X %Y', localtime());
571        push(@head, "From $mail_from $formatted_date\n");
572      }
573    $formatted_date = strftime('%a, %e %b %Y %X %z', localtime());
574    push(@head, "Date: $formatted_date\n");
[375]575    push(@head, "To: $to\n");
576    push(@head, "From: $mail_from\n");
577    push(@head, "Subject: $subject\n");
578    push(@head, "Reply-to: $reply_to\n") if $reply_to;
579
580    ### Below, we set the content-type etc, but see these comments
581    ### from Greg Stein on why this is not a full solution.
582    #
583    # From: Greg Stein <gstein@lyra.org>
584    # Subject: Re: svn commit: rev 2599 - trunk/tools/cgi
585    # To: dev@subversion.tigris.org
586    # Date: Fri, 19 Jul 2002 23:42:32 -0700
[719]587    #
[375]588    # Well... that isn't strictly true. The contents of the files
589    # might not be UTF-8, so the "diff" portion will be hosed.
[719]590    #
[375]591    # If you want a truly "proper" commit message, then you'd use
592    # multipart MIME messages, with each file going into its own part,
593    # and labeled with an appropriate MIME type and charset. Of
594    # course, we haven't defined a charset property yet, but no biggy.
[719]595    #
[375]596    # Going with multipart will surely throw out the notion of "cut
597    # out the patch from the email and apply." But then again: the
598    # commit emailer could see that all portions are in the same
[719]599    # charset and skip the multipart thang.
600    #
[375]601    # etc etc
[719]602    #
[375]603    # Basically: adding/tweaking the content-type is nice, but don't
604    # think that is the proper solution.
605    push(@head, "Content-Type: text/plain; charset=UTF-8\n");
606    push(@head, "Content-Transfer-Encoding: 8bit\n");
607
608    push(@head, "\n");
609
[719]610    if ($diff_wanted and not @difflines)
[375]611      {
[719]612        # Get the diff from svnlook.
613        my @no_diff_deleted = $no_diff_deleted ? ('--no-diff-deleted') : ();
614        my @no_diff_added = $no_diff_added ? ('--no-diff-added') : ();
615        @difflines = &read_from_process($svnlook, 'diff', $repos,
616                                        '-r', $rev, @no_diff_deleted,
617                                        @no_diff_added);
618        @difflines = map { /[\r\n]+$/ ? $_ : "$_\n" } @difflines;
619      }
620
621    if ($stdout)
622      {
623        print @head, @body;
624        print @difflines if $diff_wanted;
625      }
626    elsif (defined $sendmail and @email_addresses)
627      {
[375]628        # Open a pipe to sendmail.
[719]629        my $command = "$sendmail -f'$mail_from' $userlist";
[375]630        if (open(SENDMAIL, "| $command"))
631          {
632            print SENDMAIL @head, @body;
[719]633            print SENDMAIL @difflines if $diff_wanted;
[375]634            close SENDMAIL
635              or warn "$0: error in closing `$command' for writing: $!\n";
636          }
637        else
638          {
639            warn "$0: cannot open `| $command' for writing: $!\n";
640          }
641      }
[719]642    elsif (defined $smtp_server and @email_addresses)
643      {
644        my $smtp = Net::SMTP->new($smtp_server)
645          or die "$0: error opening SMTP session to `$smtp_server': $!\n";
646        handle_smtp_error($smtp, $smtp->mail($mail_from));
647        handle_smtp_error($smtp, $smtp->recipient(@email_addresses));
648        handle_smtp_error($smtp, $smtp->data());
649        handle_smtp_error($smtp, $smtp->datasend(@head, @body));
650        if ($diff_wanted)
651          {
652            handle_smtp_error($smtp, $smtp->datasend(@difflines));
653          }
654        handle_smtp_error($smtp, $smtp->dataend());
655        handle_smtp_error($smtp, $smtp->quit());
656      }
[375]657
658    # Dump the output to logfile (if its name is not empty).
659    if ($log_file =~ /\w/)
660      {
661        if (open(LOGFILE, ">> $log_file"))
662          {
663            print LOGFILE @head, @body;
[719]664            print LOGFILE @difflines if $diff_wanted;
[375]665            close LOGFILE
666              or warn "$0: error in closing `$log_file' for appending: $!\n";
667          }
668        else
669          {
670            warn "$0: cannot open `$log_file' for appending: $!\n";
671          }
672      }
673  }
674
675exit 0;
676
[719]677sub handle_smtp_error
678{
679  my ($smtp, $retval) = @_;
680  if (not $retval)
681    {
682      die "$0: SMTP Error: " . $smtp->message() . "\n";
683    }
684}
685
[375]686sub usage
687{
688  warn "@_\n" if @_;
[719]689  die "usage (commit mode):\n",
690      "  $0 REPOS REVNUM [[-m regex] [options] [email_addr ...]] ...\n",
691      "usage: (revprop-change mode):\n",
692      "  $0 --revprop-change REPOS REVNUM USER PROPNAME [-d diff_file] \\\n",
693      "    [[-m regex] [options] [email_addr ...]] ...\n",
694      "options are:\n",
695      "  -m regex              Regular expression to match committed path\n",
[375]696      "  --from email_address  Email address for 'From:' (overrides -h)\n",
697      "  -h hostname           Hostname to append to author for 'From:'\n",
698      "  -l logfile            Append mail contents to this log file\n",
699      "  -r email_address      Email address for 'Reply-To:'\n",
700      "  -s subject_prefix     Subject line prefix\n",
[719]701      "  --summary             Use first line of commit log in subject\n",
702      "  --diff y|n            Include diff in message (default: y)\n",
703      "                        (applies to commit mode only)\n",
704      "  --stdout              Spit the message in mbox format to stdout.\n",
[375]705      "\n",
706      "This script supports a single repository with multiple projects,\n",
[719]707      "where each project receives email only for actions that affect that\n",
708      "project.  A project is identified by using the -m command line\n".
709      "option with a regular expression argument.  If the given revision\n",
710      "contains modifications to a path that matches the regular\n",
711      "expression, then the action applies to the project.\n",
[375]712      "\n",
[719]713      "Any of the following email addresses and command line options\n",
714      "(other than -d) are associated with this project, until the next -m,\n",
715      "which resets the options and the list of email addresses.\n",
716      "\n",
[375]717      "To support a single project conveniently, the script initializes\n",
718      "itself with an implicit -m . rule that matches any modifications\n",
[719]719      "to the repository.  Therefore, to use the script for a single-\n",
720      "project repository, just use the other command line options and\n",
[375]721      "a list of email addresses on the command line.  If you do not want\n",
[719]722      "a rule that matches the entire repository, then use -m with a\n",
[375]723      "regular expression before any other command line options or email\n",
[719]724      "addresses.\n",
725      "\n",
726      "'revprop-change' mode:\n",
727      "The message will contain a copy of the diff_file if it is provided,\n",
728      "otherwise a copy of the (assumed to be new) property value.\n",
729      "\n";
[375]730}
731
732# Return a new hash data structure for a new empty project that
733# matches any modifications to the repository.
734sub new_project
735{
736  return {email_addresses => [],
737          from_address    => '',
738          hostname        => '',
739          log_file        => '',
740          match_regex     => '.',
741          reply_to        => '',
[719]742          subject_prefix  => '',
743          show_diff       => 1,
744          stdout          => 0};
[375]745}
746
[719]747sub parse_boolean
748{
749  if ($_[0] eq 'y') { return 1; };
750  if ($_[0] eq 'n') { return 0; };
751
752  die "$0: valid boolean options are 'y' or 'n', not '$_[0]'\n";
753}
754
[375]755# Start a child process safely without using /bin/sh.
756sub safe_read_from_pipe
757{
758  unless (@_)
759    {
760      croak "$0: safe_read_from_pipe passed no arguments.\n";
761    }
762
[1400]763  my $openfork_available = $^O ne "MSWin32";
[719]764  if ($openfork_available) # We can fork on this system.
[375]765    {
[719]766      my $pid = open(SAFE_READ, '-|');
767      unless (defined $pid)
768        {
769          die "$0: cannot fork: $!\n";
770        }
771      unless ($pid)
772        {
773          open(STDERR, ">&STDOUT")
774            or die "$0: cannot dup STDOUT: $!\n";
775          exec(@_)
776            or die "$0: cannot exec `@_': $!\n";
777        }
[375]778    }
[1400]779  else  # Running on Windows.  No fork.
[375]780    {
[719]781      my @commandline = ();
782      my $arg;
[1400]783
[719]784      while ($arg = shift)
785        {
786          $arg =~ s/\"/\\\"/g;
787          if ($arg eq "" or $arg =~ /\s/) { $arg = "\"$arg\""; }
788          push(@commandline, $arg);
789        }
[1400]790
[719]791      # Now do the pipe.
792      open(SAFE_READ, "@commandline |")
793        or die "$0: cannot pipe to command: $!\n";
[375]794    }
795  my @output;
796  while (<SAFE_READ>)
797    {
798      s/[\r\n]+$//;
799      push(@output, $_);
800    }
801  close(SAFE_READ);
802  my $result = $?;
803  my $exit   = $result >> 8;
804  my $signal = $result & 127;
805  my $cd     = $result & 128 ? "with core dump" : "";
806  if ($signal or $cd)
807    {
808      warn "$0: pipe from `@_' failed $cd: exit=$exit signal=$signal\n";
809    }
810  if (wantarray)
811    {
812      return ($result, @output);
813    }
814  else
815    {
816      return $result;
817    }
818}
819
820# Use safe_read_from_pipe to start a child process safely and return
821# the output if it succeeded or an error message followed by the output
822# if it failed.
823sub read_from_process
824{
825  unless (@_)
826    {
827      croak "$0: read_from_process passed no arguments.\n";
828    }
829  my ($status, @output) = &safe_read_from_pipe(@_);
830  if ($status)
831    {
832      return ("$0: `@_' failed with this output:", @output);
833    }
834  else
835    {
836      return @output;
837    }
838}
Note: See TracBrowser for help on using the repository browser.