File Coverage

blib/lib/ClearCase/ClearPrompt.pm
Criterion Covered Total %
statement 50 353 14.1
branch 21 270 7.7
condition 10 175 5.7
subroutine 7 19 36.8
pod 0 11 0.0
total 88 828 10.6


line stmt bran cond sub pod time code
1             package ClearCase::ClearPrompt;
2              
3             require 5.001;
4              
5             $VERSION = $VERSION = '1.31';
6             @EXPORT_OK = qw(clearprompt clearprompt_dir redirect tempname die
7             $CT $TriggerSeries
8             );
9              
10             %EXPORT_TAGS = ( 'all' => [ qw(
11             clearprompt
12             clearprompt_dir
13             redirect
14             tempname
15             ) ] );
16              
17             require Exporter;
18             @ISA = qw(Exporter);
19              
20             # Conceptually this is "use constant MSWIN ..." but ccperl can't do that.
21 2 50 33 2 0 18 sub MSWIN { ($^O || $ENV{OS}) =~ /MSWin32|Windows_NT/i ? 1 : 0 }
22              
23 1     1   931 use vars qw($TriggerSeries $StashFile);
  1         2  
  1         102  
24             $TriggerSeries = $ENV{CLEARCASE_CLEARPROMPT_TRIGGERSERIES};
25              
26             # Make $CT read-only but not a constant so it can be interpolated.
27             *CT = *CT = \ccpath('cleartool'); # double assignment suppresses warning
28              
29             if ($] > 5.004) {
30 1     1   4 use strict;
  1         1  
  1         911  
31 1     1   1058 eval "use subs 'die'"; # We override this and may also export it to caller
  1         40  
  1         4  
32             }
33              
34             my %Dialogs = ();
35             my %Mailings = ();
36             my %MailTo = (); # accumulates lists of users to mail various msgs to.
37              
38             (my $prog = $0) =~ s%.*[/\\]%%;
39              
40             sub rerun_in_debug_mode {
41             # Re-exec ourself with debugging turned on. If in GUI mode,
42             # rerun in a new window. This allows "perl -d" debugging of
43             # triggers in a GUI env.
44 0     0 0 0 delete $ENV{CLEARCASE_CLEARPROMPT_DEBUG}; # suppress recursion
45 0 0       0 return if $ENV{PERL_DL_NONLAZY}; # marker for 'make test'
46 0         0 my @cmd = ($^X, '-d', $0, @ARGV);
47 0 0       0 if (MSWIN()) {
48 0         0 for (@cmd) {
49 0 0       0 $_ = qq("$_") if m%\s%;
50             }
51 0 0       0 unshift(@cmd, qw(start /wait)) if $ENV{ATRIA_FORCE_GUI};
52             } else {
53 0 0       0 unshift(@cmd, qw(xterm -e)) if $ENV{ATRIA_FORCE_GUI};
54             }
55 0 0       0 if (MSWIN()) {
56             # This does not work with ccperl (5.001) if CC is installed to
57             # "C:\Program Files\...".
58 0         0 my $rc = system(@cmd);
59 0         0 exit($rc != 0);
60             } else {
61 0         0 exec(@cmd);
62             }
63             }
64              
65             sub dbg_shell {
66             # Fork an interactive shell and wait for it. Useful in triggers because
67             # it lets you explore the runtime environment of the trigger script.
68 0 0   0 0 0 return if $ENV{PERL_DL_NONLAZY}; # marker for 'make test'
69 0         0 my $cmd = $ENV{CLEARCASE_CLEARPROMPT_DEBUG_SHELL};
70 0 0 0     0 $cmd = MSWIN() ? $ENV{COMSPEC} : '/bin/sh' unless $cmd && -x $cmd;
    0          
71 0 0       0 if ($ENV{ATRIA_FORCE_GUI}) {
72 0 0       0 if (MSWIN()) {
73 0         0 $cmd = "start /wait $cmd";
74             } else {
75 0         0 $cmd = "xterm -e $cmd";
76             }
77             }
78 0 0       0 exit 1 if system $cmd;
79             }
80              
81             # Debugging aids. Documented in POD section. These can also be
82             # controlled via cmds at import time.
83             if ($ENV{CLEARCASE_CLEARPROMPT_DEBUG} ||
84             ($ENV{ATRIA_FORCE_GUI} && $ENV{PERL5OPT} && $ENV{PERL5OPT} =~ /-d/)) {
85             rerun_in_debug_mode();
86             } elsif ($ENV{CLEARCASE_CLEARPROMPT_DEBUG_SHELL}) {
87             dbg_shell();
88             }
89              
90             # Make an attempt to supply a full path to the specified program.
91             # Else fall back to relying on PATH.
92             sub ccpath {
93 1     1 0 3 my $name = shift;
94 1 50       2 if (MSWIN()) {
95 0         0 return $name; # no way to avoid relying on PATH in &^&@$! Windows
96             } else {
97 1   50     10 return join('/', $ENV{ATRIAHOME} || q(/usr/atria), 'bin', $name);
98             }
99             }
100              
101             # Generates a random-ish name for a temp file that doesn't yet exist.
102             # This function makes no pretense of being atomic; it's conceivable,
103             # though highly unlikely, that the generated filename could be
104             # taken between the time it's generated and the time it's used.
105             # The optional parameter becomes a filename extension. The optional
106             # 2nd parameter overrides the basename part of the generated path.
107             sub tempname {
108 0     0 0 0 my($custom, $tmpf) = @_;
109             # The preferred directory for temp files.
110 0 0 0     0 my $tmpd = MSWIN() ?
      0        
111             ($ENV{TEMP} || $ENV{TMP} || ( -d "$ENV{SYSTEMDRIVE}/temp" ?
112             "$ENV{SYSTEMDRIVE}/temp" : $ENV{SYSTEMDRIVE})) :
113             ($ENV{TMPDIR} || '/tmp');
114 0         0 $tmpd =~ s%\\%/%g;
115 0         0 my $ext = 'tmp';
116 0 0       0 return "$tmpd/$tmpf.$custom.$ext" if $tmpf;
117 0         0 (my $pkg = lc __PACKAGE__) =~ s/:+/-/g;
118 0         0 while (1) {
119 0         0 $tmpf = join('.', "$tmpd/$pkg", $$, int(rand 10000));
120 0 0       0 $tmpf .= $custom ? ".$custom.$ext" : ".$ext";
121 0 0       0 return $tmpf if ! -f $tmpf;
122             }
123             }
124              
125             # Run clearprompt with specified args and return what it returned. Uses the
126             # exact same syntax as the clearprompt executable ('ct man clearprompt')
127             # except for -outfile which is handled internally here.
128             sub clearprompt {
129 0     0 0 0 my $mode = shift;
130 0         0 my @args = @_;
131 0         0 my $data;
132              
133 0 0       0 return 0 if $ENV{ATRIA_WEB_GUI}; # must assume "" or 0 if ccweb interface
134              
135 0         0 local $!; # don't mess up errno in the caller's world.
136              
137             # Play back responses from the StashFile if it exists and other conditions
138             # are satisfied. It seems that CC sets the series id to all zeroes
139             # after an error condition (??) so we avoid that case explicitly.
140 0         0 my $lineno = (caller)[2];
141 0         0 my $subtext = "from $prog:$lineno";
142 0 0 0     0 if ($TriggerSeries && $ENV{CLEARCASE_SERIES_ID} &&
      0        
143             $ENV{CLEARCASE_SERIES_ID} !~ /^[0:.]+$/) {
144 0         0 (my $sid = $ENV{CLEARCASE_SERIES_ID}) =~ s%:+%-%g;
145 0         0 $StashFile = tempname($prog, "CLEARCASE_SERIES_ID=$sid");
146 0 0 0     0 if (!$ENV{CLEARCASE_BEGIN_SERIES} && -f $StashFile) {
147 0         0 do $StashFile;
148 0 0 0     0 if ($ENV{CLEARCASE_END_SERIES} &&
149             !$ENV{CLEARCASE_CLEARPROMPT_KEEP_CAPTURE}) {
150             # We delay the unlink due to weird Windows locking behavior
151 0         0 eval "END { unlink '$StashFile' }";
152             }
153 1     1   6 no strict 'vars';
  1         4  
  1         5161  
154 0         0 my $data = eval "\$stash$lineno";
155 0 0       0 _automail('PROMPT', "Replay $subtext", "REPLAY:\n",
156             defined($data) ? $data : 'undef');
157 0         0 return $data;
158             }
159             }
160              
161             # On Windows we must add an extra level of escaping to any args
162             # which might have special chars since all forms of system()
163             # appear to go through the %^%@# cmd shell (boo!). This is
164             # also handled by Perl 5.6.1, ActiveState build 630 but it will
165             # be a long time till we can count on that fix being present.
166 0 0       0 if (MSWIN()) {
167 0         0 for (0..$#args) {
168 0         0 my $i = $_;
169 0 0       0 if ($args[$i] =~ /^-(?:pro|ite|def|dfi|dir)/) {
170 0         0 $args[$i+1] =~ s/"/'/gs;
171 0         0 $args[$i+1] = qq("$args[$i+1]");
172             }
173             }
174             }
175              
176             # For clearprompt modes in which we get textual data back via a file,
177             # derive here a reasonable temp-file name and handle the details
178             # of reading the data out of it and unlinking it when done.
179             # For other modes, just fire off the cmd and return the status.
180             # In a void context, don't wait for the button to be pushed; just
181             # "fork" and proceed asynchonously since this is presumably just an
182             # informational message.
183             # If the cmd took a signal, return undef and leave the signal # in $?.
184 0 0       0 if ($mode =~ /text|file|list/) {
185 0         0 my $outf = tempname($mode);
186 0         0 my @cmd = (ccpath('clearprompt'), $mode, '-out', $outf, @args);
187 0 0       0 print STDERR "+ @cmd\n" if $ClearCase::ClearPrompt::Verbose;
188 0 0       0 if (!system(@cmd)) {
189 0 0       0 if (open(OUTFILE, $outf)) {
190 0         0 local $/ = undef;
191 0         0 $data = ;
192 0 0       0 $data = '' if !defined $data;
193 0         0 close(OUTFILE);
194             }
195             } else {
196             # If we took a signal, return undef with the signal # in $?. The
197             # clearprompt cmd apparently catches SIGINT and returns 0x400 for
198             # some reason; we fix it here so $? looks like a normal sig2.
199 0 0       0 $? = 2 if $? == 0x400;
200 0 0 0     0 $data = undef if $? && $? <= 0x80;
201             }
202 0 0       0 unlink $outf if -f $outf;
203 0 0       0 _automail('PROMPT', "Prompt $subtext", "PROMPT:\n", "@cmd\n",
204             "\nRESPONSE:\n", defined($data) ? $data : 'undef');
205             } else {
206 0         0 my @cmd = (ccpath('clearprompt'), $mode, @args);
207 0 0       0 print STDERR "+ @cmd\n" if $ClearCase::ClearPrompt::Verbose;
208 0 0       0 if (defined wantarray) {
209 0         0 system(@cmd);
210 0 0       0 $? = 2 if $? == 0x400; # see above
211 0 0 0     0 $data = ($? && $? <= 0x80) ? undef : $?>>8;
212 0 0       0 _automail('PROMPT', "Prompt $subtext", "PROMPT:\n",
213             "@cmd\n", "\nRESPONSE:\n", defined($data) ? $data : 'undef');
214             } else {
215 0         0 _automail('PROMPT', "Prompt $subtext", "PROMPT:\n", "@cmd\n");
216 0 0       0 if (MSWIN()) {
    0          
217             # Windows (always) GUI - fork new thread to run async
218 0         0 system(1, @cmd);
219 0         0 return;
220             } elsif (exists $ENV{DISPLAY}) {
221             # Unix GUI - must fork to run async
222 0 0       0 return if fork;
223 0         0 exec(@cmd);
224             } else {
225             # Unix cmd line - must close stdin to run async
226 0         0 open(SAVE_STDIN, ">&STDIN");
227 0         0 close(STDIN);
228 0         0 system(@cmd);
229 0         0 open(STDIN, ">&SAVE_STDIN");
230 0         0 close(SAVE_STDIN);
231             }
232             }
233             }
234              
235             # Record responses if $TriggerSeries is turned on.
236 0 0       0 if ($StashFile) {
237 0 0 0     0 if ($ENV{CLEARCASE_BEGIN_SERIES} && !$ENV{CLEARCASE_END_SERIES}) {
238 0         0 my $top = ! -f $StashFile;
239 0         0 eval { require Data::Dumper };
  0         0  
240 0 0 0     0 if ($@ || $] < 5.004) {
241 0         0 warn "$prog: Warning: TriggerSeries requires Data::Dumper\n";
242             } else {
243 0 0       0 open(STASH, ">>$StashFile") || die "$prog: $StashFile: $!";
244 0 0       0 print STASH "# This file contains data stashed for $prog\n"
245             if $top;
246 0         0 print STASH Data::Dumper->new([$data], ["stash$lineno"])->Dump;
247 0         0 close(STASH);
248 0 0       0 if (! $ENV{CLEARCASE_CLEARPROMPT_KEEP_CAPTURE}) {
249 0     0   0 $SIG{INT} = sub { unlink $StashFile };
  0         0  
250             }
251             }
252             }
253             }
254              
255 0         0 return $data;
256             }
257              
258             # Fake up a directory chooser using opendir/readdir/closedir and
259             # 'clearprompt list'.
260             sub clearprompt_dir {
261 0     0 0 0 require Cwd;
262 0         0 require File::Spec;
263 0         0 my($dir, $msg) = @_;
264 0         0 my(%subdirs, $items, @drives);
265 0         0 my $iwd = Cwd::abs_path('.');
266 0 0       0 $dir = $iwd if $dir eq '.';
267              
268 0 0       0 return 0 if $ENV{ATRIA_WEB_GUI}; # must assume "" or 0 if ccweb interface
269              
270 0         0 while (1) {
271 0 0       0 if (opendir(DIR, $dir)) {
272 0 0       0 %subdirs = map {$_ => 1} grep {-d "$dir/$_" || ! -e "$dir/$_"}
  0         0  
  0         0  
273             readdir(DIR);
274 0         0 chomp %subdirs;
275 0         0 closedir(DIR);
276             } else {
277 0         0 warn "$dir: $!\n";
278 0         0 $dir = File::Spec->rootdir;
279 0         0 next;
280             }
281 0 0 0     0 if (MSWIN() && $dir =~ m%^[A-Z]:[\\/]?$%i) {
282 0         0 delete $subdirs{'.'};
283 0         0 delete $subdirs{'..'};
284 0 0       0 @drives = grep {-e} map {"$_:"} 'C'..'Z' if !@drives;
  0         0  
  0         0  
285 0         0 $items = join(',', @drives, sort keys %subdirs);
286             } else {
287 0         0 $items = join(',', sort keys %subdirs);
288             }
289 0         0 my $resp = clearprompt(qw(list -items), $items,
290             '-pro', "$msg [ $dir ]");
291 0 0       0 if (!defined $resp) {
292 0         0 undef $dir;
293 0         0 last;
294             }
295 0         0 chomp $resp;
296 0 0 0     0 last if ! $resp || $resp eq '.';
297 0 0 0     0 if (MSWIN() && $resp =~ m%^[A-Z]:[\\/]?$%i) {
298 0         0 $dir = $resp;
299 0 0       0 chdir $dir || warn "$dir: $!\n";
300             } else {
301 0         0 $dir = Cwd::abs_path(File::Spec->catdir($dir, $resp));
302             }
303             }
304 0 0       0 chdir $iwd || warn "$iwd: $!\n";
305 0         0 return $dir;
306             }
307              
308             # Takes args in the form "redirect(STDERR => 'OFF', STDOUT => 'ON')" and
309             # enables or disables stdout/stderr as specified.
310             sub redirect {
311             # Stash these away at first use for potential future use, e.g. debugging.
312 0 0   0 0 0 open(SAVE_STDOUT, '>&STDOUT') if !defined fileno(SAVE_STDOUT);
313 0 0       0 open(SAVE_STDERR, '>&STDERR') if !defined fileno(SAVE_STDERR);
314              
315 0         0 while(@_) {
316 0         0 my $stream = uc shift;
317 0         0 my $state = shift;
318              
319 0 0 0     0 if ($stream ne 'STDOUT' && $stream ne 'STDERR') {
320 0         0 print SAVE_STDERR "unrecognized stream $stream\n";
321 0         0 next;
322             }
323              
324 0 0       0 if ($stream eq 'STDOUT') {
    0          
325 0 0       0 if ($state =~ /^OFF$/i) {
    0          
326 0 0       0 if (defined fileno(STDOUT)) {
327 0 0       0 open(HIDE_STDOUT, '>&STDOUT')
328             if !defined fileno(HIDE_STDOUT);
329 0         0 close(STDOUT);
330             }
331             } elsif ($state =~ /^ON$/i) {
332 0         0 open(STDOUT, '>&HIDE_STDOUT');
333             } else {
334 0 0       0 if (defined fileno(STDOUT)) {
335 0 0       0 open(HIDE_STDOUT, '>&STDOUT')
336             if !defined fileno(HIDE_STDOUT);
337 0 0       0 open(STDOUT, $state) || warn "$state: $!\n";
338             }
339             }
340             } elsif ($stream eq 'STDERR') {
341 0 0       0 if ($state =~ /^OFF$/i) {
    0          
342 0 0       0 if (defined fileno(STDERR)) {
343 0 0       0 open(HIDE_STDERR, '>&STDERR')
344             if !defined fileno(HIDE_STDERR);
345 0         0 close(STDERR);
346             }
347             } elsif ($state =~ /^ON$/i) {
348 0         0 open(STDERR, '>&HIDE_STDERR');
349             } else {
350 0 0       0 if (defined fileno(STDERR)) {
351 0 0       0 open(HIDE_STDERR, '>&STDERR')
352             if !defined fileno(HIDE_STDERR);
353 0 0       0 open(STDERR, $state) || warn "$state: $!\n";
354             }
355             }
356             }
357             }
358             }
359              
360             # Called like this "sendmsg([], $subject, @body_of_message)".
361             # I.e. a ref to a list of email addresses followed by a string
362             # scalar containing the subject. Remaining parameters are used
363             # as the body of the message. Returns true on successful delivery
364             # of msg to the MTA.
365             sub sendmsg {
366 0     0 0 0 my($r_to, $subj, @body) = @_;
367             # If no mailto list, no mail.
368 0 0       0 return 1 unless @$r_to;
369              
370             # Only drag Net::SMTP in at runtime since it's not core perl.
371 0         0 eval { require Net::SMTP };
  0         0  
372 0 0       0 if (! $@) {
373 0   0     0 my $name = $ENV{CLEARCASE_USER} || $ENV{USERNAME} || $ENV{LOGNAME};
374 0         0 my $smtp;
375 0         0 eval { $smtp = Net::SMTP->new };
  0         0  
376 0 0       0 if ($smtp) {
377 0         0 local $^W = 0; # hide a spurious warning from deep in Net::SMTP
378 0 0 0     0 $smtp->mail($name) &&
      0        
      0        
      0        
      0        
      0        
      0        
      0        
      0        
379             $smtp->to(@$r_to, {SkipBad => 1}) &&
380             $smtp->data() &&
381             $smtp->datasend("To: @$r_to\n") &&
382             $smtp->datasend("Subject: $subj\n") &&
383             $smtp->datasend(join(' ', 'X-Mailer:',__PACKAGE__,$VERSION)) &&
384             $smtp->datasend("\n") &&
385             $smtp->datasend(@body) &&
386             $smtp->dataend() &&
387             $smtp->quit &&
388             return 1; # succeeded, so return
389             }
390             }
391              
392             # If Net::SMTP isn't installed or didn't work, try notify.exe
393 0 0       0 my $nexe = MSWIN() ? 'notify' : '/usr/atria/bin/notify';
394 0         0 my $notify = qq($nexe -l triggers -s "$subj" ) .
395 0         0 join(' ', map {qq("$_")} @$r_to);
396 0 0       0 if (open(NOTIFY, "| $notify")) {
397 0         0 print NOTIFY @body;
398 0         0 return close(NOTIFY);
399             }
400 0         0 return 0; # failure
401             }
402              
403             # A private wrapper over sendmsg() to reformat the subj/msg
404             # appropriately for error message captures.
405             sub _automail {
406 0 0   0   0 return 0 if defined $ENV{CLEARCASE_CLEARPROMPT_NO_SENDMSG};
407 0         0 my $type = shift;
408 0 0 0     0 return unless exists $MailTo{$type} && $MailTo{$type};
409 0         0 my $addrs = $MailTo{$type};
410 0         0 my $subj = shift;
411             # We don't need Sys::Hostname except in this situation, so ...
412 0         0 eval { require Sys::Hostname; };
  0         0  
413 0 0       0 $subj .= ' on ' . Sys::Hostname::hostname() unless $@;
414 0         0 $subj .= ' via ClearCase::ClearPrompt';
415 0         0 sendmsg($addrs, $subj, @_);
416             }
417              
418             # Warning: significant hackery here. Basically, normal-looking symbol
419             # names are passed on to the Exporter import method as usual, whereas
420             # names of the form /WORD or +WORD or +WORD= are commands which
421             # cause special behavior within this routine. All commands start with
422             # '/', such as /TRIGGERSERIES and /ENV. Captures start with '+' and
423             # include +{CAPTURE,ERRORS,WARN,DIE,STDOUT,STDERR}. If the capture
424             # name has a list of users attached, eg "+STDERR=user1,user2,..",
425             # the captured messages are sent via email to the specified users.
426             # Use +CAPTURE= to email messages from all channels to .
427             ## Apologies to anyone trying to read this ... it's a real mess, due
428             ## mostly to my attempts to stay compatible with earler versions which
429             ## may not have involved the best design decisions.
430             my($tmpout, $tmperr); # these must be here for scoping reasons
431             sub import {
432             # First remember the entire parameter list.
433 1     1   9 my @p = @_;
434              
435             # Then separate it into "normal-looking" symbols to export into
436             # caller's namespace, "captures" which describe channels we need
437             # to arrange to capture here, and "commands" to deal with otherwise.
438             # Also, provide our own implementation of export tags for qw(:all).
439             # I'd prefer not to support that any more but do for back compat.
440 1         5 my %exports = map { $_ => 1 } grep !m%^[+/:]%, @p;
  2         5  
441 1         3 my %tags = map {substr($_, 1) => 1} grep m%^:%, @p;
  0         0  
442 1         4 my %caps = map {m%^.(\w+)=?(.*)%; $1 => $2} grep m%^\+%, @p;
  1         3  
  1         12  
443 1         3 my %cmds = map {m%^.(\w+)%; $1 => 1} grep m%^/%, @p;
  0         0  
  0         0  
444              
445             # Allow trigger series stashing to be turned on at import time,
446             # but let the EV override. We allow '+TRIGGERSERIES' for
447             # compatibility but '/TRIGGERSERIES' is preferred.
448 1 50 33     6 if (exists($cmds{TRIGGERSERIES}) || exists($caps{TRIGGERSERIES})) {
449 1   33     6 $cmds{TRIGGERSERIES} ||= $caps{TRIGGERSERIES};
450 1         1 delete $caps{TRIGGERSERIES};
451 1 50       4 $ClearCase::ClearPrompt::TriggerSeries = 1
452             if !exists($ENV{CLEARCASE_CLEARPROMPT_TRIGGERSERIES});
453             }
454              
455             # If requested to via '/ENV', modify all CLEARCASE_* EV's which
456             # use back (\) slashes such that they use forward (/) slashes
457             # instead, assuming that these will refer to pathnames or parts
458             # of pathnames, perhaps in MVFS space (e.g. CLEARCASE_VERSION_ID).
459 1 50 33     2 if (MSWIN() && exists($cmds{ENV})) {
460 0         0 for (keys %ENV) {
461 0 0       0 $ENV{$_} =~ s%\\%/%g if m%^CLEARCASE_%;
462             }
463             }
464              
465             # The user may request via /DEBUG that the script (typically a trigger)
466             # be rerun in debug mode. See POD.
467 1 50       3 rerun_in_debug_mode() if exists($cmds{DEBUG});
468              
469             # The user may request via /SHELL that the script (typically a trigger)
470             # fork an interactive shell so its runtime env can be explored.
471 1 50       4 dbg_shell() if exists($cmds{SHELL});
472              
473             # Allow this EV to override the capture list.
474 1 50       3 if ($ENV{CLEARCASE_CLEARPROMPT_CAPTURE_LIST}) {
475 0         0 @p = split /\s+/, $ENV{CLEARCASE_CLEARPROMPT_CAPTURE_LIST};
476 0         0 %caps = map {m%^.(\w+)=?(.*)%; $1 => $2} grep /^\+/, @p;
  0         0  
  0         0  
477 0         0 for (split /\s+/, @p) {
478 0         0 m%^.(\w+)=?(.*)%;
479 0         0 $caps{$1} = $2;
480             }
481             }
482              
483             # Now divide capture requests into those for dialog boxes and
484             # those for mailings.
485 1         4 %Dialogs = map {substr($_, 1) => 1} grep /^\+\w+$/, @p;
  1         3  
486 1         3 %Mailings = map {m%^.(\w+)=(.*)%; $1 => $2 } grep /^\+\w+=/, @p;
  0         0  
  0         0  
487              
488             # If :tags were requested, map them to their predefined export lists.
489 1         4 for (keys %tags) {
490 0         0 my $tag = $_;
491 0 0       0 next unless $EXPORT_TAGS{$tag};
492 0         0 for (@{$EXPORT_TAGS{$tag}}) {
  0         0  
493 0         0 $exports{$_} = 1;
494             }
495             }
496              
497             # Export the die func if its corresponding channel was requested.
498 1 50 33     8 $exports{'die'} = 1 if exists($caps{DIE}) ||
      33        
499             exists($caps{CAPTURE}) || exists($caps{ERRORS});
500              
501             # Set up the override hook for warn() if requested.
502 1 50 33     12 $SIG{__WARN__} = \&cpwarn if exists($caps{WARN}) ||
      33        
503             exists($caps{CAPTURE}) || exists($caps{ERRORS});
504              
505             # Export the non-cmd symbols, which may include die().
506 1         5 my @shares = grep {!/:/} keys %exports;
  2         7  
507 1 50       4 if ($] <= 5.001) {
508             # This weird hackery needed for ccperl (5.001) ...
509 0         0 my $caller = caller;
510 0 0       0 $caller = 'main' if $caller eq 'DB'; # hack for ccperl -d bug
511 0         0 for (@shares) {
512 0 0       0 if (s/^(\W)//) {
513 0         0 eval "*{$caller\::$_} = \\$1$_";
514             } else {
515 0         0 *{"$caller\::$_"} = \&$_;
  0         0  
516             }
517             }
518             } else {
519             # ... and this "normal" hackery is for modern perls.
520 1         134 __PACKAGE__->export_to_level(1, $p[0], @shares);
521             }
522              
523             # +CAPTURE grabs all forms of output while +ERRORS grabs only error
524             # forms (meaning everything but stdout). NOTE: we must be very careful
525             # about the fact that there may be keys which EXIST but whose
526             # values are UNDEFINED.
527 1 50       7 if (exists($Dialogs{CAPTURE})) {
    50          
528 0   0     0 $Dialogs{WARN} ||= $Dialogs{CAPTURE};
529 0   0     0 $Dialogs{DIE} ||= $Dialogs{CAPTURE};
530 0   0     0 $Dialogs{STDERR} ||= $Dialogs{CAPTURE};
531 0   0     0 $Dialogs{STDOUT} ||= $Dialogs{CAPTURE};
532 0         0 delete $Dialogs{CAPTURE};
533             } elsif (exists($Dialogs{ERRORS})) {
534 0   0     0 $Dialogs{WARN} ||= $Dialogs{ERRORS};
535 0   0     0 $Dialogs{DIE} ||= $Dialogs{ERRORS};
536 0   0     0 $Dialogs{STDERR} ||= $Dialogs{ERRORS};
537 0         0 delete $Dialogs{ERRORS};
538             }
539 1 50       7 if (exists($Mailings{CAPTURE})) {
    50          
540 0   0     0 $Mailings{WARN} ||= $Mailings{CAPTURE};
541 0   0     0 $Mailings{DIE} ||= $Mailings{CAPTURE};
542 0   0     0 $Mailings{STDERR} ||= $Mailings{CAPTURE};
543 0   0     0 $Mailings{STDOUT} ||= $Mailings{CAPTURE};
544 0         0 delete $Mailings{CAPTURE};
545             } elsif (exists($Mailings{ERRORS})) {
546 0   0     0 $Mailings{WARN} ||= $Mailings{ERRORS};
547 0   0     0 $Mailings{DIE} ||= $Mailings{ERRORS};
548 0   0     0 $Mailings{STDERR} ||= $Mailings{ERRORS};
549 0         0 delete $Mailings{ERRORS};
550             }
551              
552             # Set up the mailing lists for each channel as requested.
553 1 50       3 $MailTo{WARN} = [split /,/, $Mailings{WARN}] if $Mailings{WARN};
554 1 50       3 $MailTo{DIE} = [split /,/, $Mailings{DIE}] if $Mailings{DIE};
555 1 50       3 $MailTo{STDOUT} = [split /,/, $Mailings{STDOUT}] if $Mailings{STDOUT};
556 1 50       3 $MailTo{STDERR} = [split /,/, $Mailings{STDERR}] if $Mailings{STDERR};
557 1 50       3 $MailTo{PROMPT} = [split /,/, $Mailings{PROMPT}] if $Mailings{PROMPT};
558              
559             # Last, handle generic stdout and stderr unless the caller asks us not to.
560 1 50 33     2763 if (exists($caps{STDOUT}) || exists($caps{STDERR})) {
561 0           $tmpout = tempname('stdout');
562 0           $tmperr = tempname('stderr');
563              
564             # Connect stdout and stderr to temp files for later use in END {}.
565 0 0 0       if (exists($caps{STDOUT}) && ($ENV{ATRIA_FORCE_GUI} || $caps{STDOUT})) {
      0        
566 0           open(HOLDOUT, '>&STDOUT');
567 0 0         open(STDOUT, ">$tmpout") || warn "$tmpout: $!";
568             }
569 0 0 0       if (exists($caps{STDERR}) && ($ENV{ATRIA_FORCE_GUI} || $caps{STDERR})) {
      0        
570 0           open(HOLDERR, '>&STDERR');
571 0 0         open(STDERR, ">$tmperr") || warn "$tmperr: $!";
572             }
573              
574             # After program finishes, collect any stdout/stderr and display
575             # with clearprompt and/or mail it out.
576             sub endfunc {
577             # retain original exit code on stack
578 0     0 0   my $rc = $?;
579 0           local $?;
580              
581             # Restore stdout and stderr to their original fd's.
582 0 0         if (defined fileno HOLDOUT) {
583 0           open(STDOUT, '>&HOLDOUT');
584 0           close(HOLDOUT);
585             }
586 0 0         if (defined fileno HOLDERR) {
587 0           open(STDERR, '>&HOLDERR');
588 0           close(HOLDERR);
589             }
590              
591             # Then display any stdout we captured in a dialog box.
592 0 0 0       if (defined($tmpout) && -e $tmpout) {
593 0 0         open(OUT, $tmpout) || warn "$prog: $tmpout: $!";
594 0           my @msg = ;
595 0           close(OUT);
596 0 0         if (@msg) {
597 0           _automail('STDOUT', "Stdout from $prog", @msg);
598 0 0         if ($Dialogs{STDOUT}) {
599 0           my $t = "STDOUT\n\n @msg";
600 0           clearprompt(qw(proceed -type o -mask p -pref -pro), $t);
601             }
602             }
603 0 0         if (!$ENV{CLEARCASE_CLEARPROMPT_KEEP_CAPTURE}) {
604             # On Windows, we can't unlink this tempfile while
605             # any asynchronous dialog boxes are still on the
606             # screen due to threading/locking design, so we
607             # give the user some time to read & close them.
608 0 0         if (MSWIN()) {
609 0           system(1, qq($^X -e "sleep 30; unlink '$tmpout'"));
610             } else {
611 0 0         unlink($tmpout) || print "$prog: $tmpout: $!\n";
612             }
613             }
614             }
615             # Same as above but for stderr.
616 0 0 0       if (defined($tmperr) && -e $tmperr) {
617 0           my @msg;
618             {
619 0 0         open(ERR, $tmperr) || warn "$prog: $tmperr: $!";
  0            
620 0           local $^W = 0; # gives bogus error with AS build 623
621 0           @msg = ;
622 0           close(ERR);
623             }
624 0 0         if (@msg) {
625 0           _automail('STDERR', "Stderr from $prog", @msg);
626 0 0         if ($Dialogs{STDERR}) {
627 0           my $t = "STDERR\n\n @msg";
628 0           clearprompt(qw(proceed -type o -mask p -pref -pro), $t);
629             }
630             }
631 0 0         if (!$ENV{CLEARCASE_CLEARPROMPT_KEEP_CAPTURE}) {
632 0 0         if (MSWIN()) {
633 0           system(1, qq($^X -e "sleep 30; unlink '$tmperr'"));
634             } else {
635 0 0         unlink($tmperr) || print "$prog: $tmperr: $!\n";
636             }
637             }
638             }
639             };
640 0           eval "END { endfunc(); }";
641             }
642             }
643              
644             # This is a pseudo warn() func which is called via the $SIG{__WARN__} hook.
645             sub cpwarn {
646 0     0 0   my @msg = @_;
647             # always show line numbers if this dbg flag set
648 0 0         if ($ENV{CLEARCASE_CLEARPROMPT_SHOW_LINENO}) {
649 0           my($file, $line) = (caller)[1,2];
650 0           chomp $msg[-1];
651 0           push(@msg, " at $file line $line.\n");
652             }
653 0           _automail('WARN', "Warning from $prog", @msg);
654 0 0 0       if ($ENV{ATRIA_FORCE_GUI} && $Dialogs{WARN}) {
655 0           clearprompt(qw(proceed -type w -mask p -pref -pro), "WARNING\n\n@msg");
656 0           return undef; # to keep clearprompt() in void context
657             } else {
658 0           warn @msg;
659             }
660             }
661              
662             # A pseudo die() which can be made to override the caller's builtin.
663             sub die {
664 0     0     my @msg = @_;
665             # always show line numbers if this dbg flag set
666 0 0         if ($ENV{CLEARCASE_CLEARPROMPT_SHOW_LINENO}) {
667 0           my($file, $line) = (caller)[1,2];
668 0           chomp $msg[-1];
669 0           push(@msg, " at $file line $line.\n");
670             }
671 0           _automail('DIE', "Error from $prog", @msg);
672 0 0 0       if ($ENV{ATRIA_FORCE_GUI} && $Dialogs{DIE}) {
673 0           clearprompt(qw(proceed -type e -mask p -pref -pro), "ERROR\n\n@msg");
674 0 0 0       exit $! || $?>>8 || 255; # suppress the msg to stderr
675             } else {
676 0           require Carp;
677 0           CORE::die Carp::shortmess(@_);
678              
679             }
680             }
681              
682             1;
683              
684             __END__