R
RU
Hi Folks,
I'm currently working on some cluster scripts which, among other things,
need to start and stop oracle. The typical (/bin/sh) nasty cluster
script does something like:
#!/bin/sh
su -m oracle -c "sqlplus / as sysdba"<<_EOF_
startup
_EOF_
-snip-
I'm trying to provide the same (or better) functionality using more-or-
less pure perl. I have come up with a subroutine ("run_process()") which
I will include at the bottom. I'm using select to determine if there is
output on STDOUT or STDERR, and if I can write to the process on STDIN
(if I have input I'd like to send to the process). The problem is, I'm
catching SIGCHLD, and when a SIGCHLD is caught, I stop looking for output
from STDOUT and STDERR. I've tested the subroutine and it seems to work,
but I'm slightly worried that it might be possible to lose output because
of the interruption caused by SIGCHLD. You might wonder why I'm not just
using Expect. I'm trying to put something minimal together that solves
this problem for many cases, and Expect has too much administrative
overhead for me. Anyhow, is there a better/more reliable way of ensuring
I get all the output from the child process?
Also, you may notice that I keep a count of open output filedescriptors
STDOUT and STDERR, and leave the while-loop when the count reaches zero.
Unfortunately this does not work, as STDERR doesn't get closed
(apparently) when the child process terminates, or at least the method I
used doesn't notice it. Any comments?
thanks,
Rob Urban
----------------------------snip-----------------------------------------
package ScriptLib;
use FileHandle;
use IPC::Open3;
sub run_command
{
my ($cmdref, $su_user, $input_ref);
# parse args
while($_ = shift) {
if (/^-cmd/) {
$cmdref = shift;
} elsif (/^-su/) {
$su_user = shift;
} elsif (/^-input/) {
$input_ref = shift;
}
}
my $debug = 1;
my @cmd = @{$cmdref};
if ($su_user) {
unshift(@cmd, 'su', '-m', $su_user, '-c');
}
my @input = defined($input_ref) ? @{$input_ref} : ();
my ($read_fh, $write_fh, $err_fh);
$debug && print "run_command()\n";
$debug && print "CMD: [", join(' ', @cmd), "]\n";
$SIG{PIPE} = 'IGNORE';
$SIG{CHLD} = \&handle_sigchild;
my $child_exited = 0;
sub handle_sigchild
{
$debug && print "got SIGCHLD!!!\n";
$child_exited = 1;
}
$err_fh = FileHandle->new; # only reader and writer are auto-gen'd
my $pid = open3($write_fh, $read_fh, $err_fh, @cmd);
# read output until EOF
my ($rout, $rin, $wout, $win, $eout, $ein);
$rin = $ein = '';
my $nclosed = 0;
my ($buf, $ret, $out, $err);
my ($out_open, $err_open) = (1, 1);
my ($fileno_write, $fileno_err, $fileno_read);
my $have_input = 0;
if (@input) {
$win = '';
$fileno_write = fileno($write_fh);
vec($win, $fileno_write, 1) = 1;
$have_input = 1;
} else {
close($write_fh);
}
my $want_closed = 0;
if (defined($read_fh)) {
$fileno_read = fileno($read_fh);
vec($rin, $fileno_read, 1) = 1;
$want_closed++;
}
if (defined($err_fh)) {
$fileno_err = fileno($err_fh);
vec($ein, $fileno_err, 1) = 1;
$want_closed++;
}
my $input_line;
$debug && print " going into read loop...\n";
while (!$child_exited && ($nclosed < $want_closed)) {
$debug && print "\n**top of while**,nclosed=[$nclosed]\n";
if ($have_input && !@input) {
$debug && print "input exhausted. setting
win=undef\n";
$win = undef;
}
$debug && print "going into select...\n";
my $nfound = select($rout=$rin, $wout=$win, $eout=$ein,
undef);
$debug && print "after select, nfound=[$nfound]\n";
if ($nfound) {
#---------------------------------------------
# STDOUT
#---------------------------------------------
if (vec($rout, $fileno_read, 1)) {
$debug && print "stdout has something...
\n";
$ret = sysread($read_fh, $buf, 512);
$debug && print "read [$ret] bytes\n";
if ($ret == 0) {
$nclosed++;
$debug && print "incrementing
nclosed\n";
$out_open = 0;
$rin = undef;
}
$debug && print " STDOUT: [$buf]\n";
$out .= $buf;
}
#---------------------------------------------
# STDERR
#---------------------------------------------
if (vec($eout, $fileno_err, 1)) {
$debug && print "stderr has something...
\n";
$ret = sysread($err_fh, $buf, 512);
$debug && print "read [$ret] bytes\n";
if ($ret == 0) {
$nclosed++;
$debug && print "incrementing
nclosed\n";
$err_open = 0;
}
$debug && print " STDERR: [$buf]\n";
$err .= $buf;
}
#---------------------------------------------
# STDIN
#---------------------------------------------
if (vec($wout, $fileno_write, 1)) {
$debug && print "stdin is ready for
input...\n";
$input_line = shift(@input)."\n";
$debug && print "INPUT: [$input_line]\n";
$ret = syswrite($write_fh, $input_line);
defined($ret) || die "write failed: $!\n";
$debug && print "wrote [$ret] bytes\n";
}
}
}
defined($input_ref) && close($write_fh);
defined($read_fh) && close($read_fh);
defined($err_fh) && close($err_fh);
waitpid($pid, 0);
my $status = $?;
$debug && print "waitpid returned status [$status]\n";
return ($status/256, $out, $err);
}
1;
I'm currently working on some cluster scripts which, among other things,
need to start and stop oracle. The typical (/bin/sh) nasty cluster
script does something like:
#!/bin/sh
su -m oracle -c "sqlplus / as sysdba"<<_EOF_
startup
_EOF_
-snip-
I'm trying to provide the same (or better) functionality using more-or-
less pure perl. I have come up with a subroutine ("run_process()") which
I will include at the bottom. I'm using select to determine if there is
output on STDOUT or STDERR, and if I can write to the process on STDIN
(if I have input I'd like to send to the process). The problem is, I'm
catching SIGCHLD, and when a SIGCHLD is caught, I stop looking for output
from STDOUT and STDERR. I've tested the subroutine and it seems to work,
but I'm slightly worried that it might be possible to lose output because
of the interruption caused by SIGCHLD. You might wonder why I'm not just
using Expect. I'm trying to put something minimal together that solves
this problem for many cases, and Expect has too much administrative
overhead for me. Anyhow, is there a better/more reliable way of ensuring
I get all the output from the child process?
Also, you may notice that I keep a count of open output filedescriptors
STDOUT and STDERR, and leave the while-loop when the count reaches zero.
Unfortunately this does not work, as STDERR doesn't get closed
(apparently) when the child process terminates, or at least the method I
used doesn't notice it. Any comments?
thanks,
Rob Urban
----------------------------snip-----------------------------------------
package ScriptLib;
use FileHandle;
use IPC::Open3;
sub run_command
{
my ($cmdref, $su_user, $input_ref);
# parse args
while($_ = shift) {
if (/^-cmd/) {
$cmdref = shift;
} elsif (/^-su/) {
$su_user = shift;
} elsif (/^-input/) {
$input_ref = shift;
}
}
my $debug = 1;
my @cmd = @{$cmdref};
if ($su_user) {
unshift(@cmd, 'su', '-m', $su_user, '-c');
}
my @input = defined($input_ref) ? @{$input_ref} : ();
my ($read_fh, $write_fh, $err_fh);
$debug && print "run_command()\n";
$debug && print "CMD: [", join(' ', @cmd), "]\n";
$SIG{PIPE} = 'IGNORE';
$SIG{CHLD} = \&handle_sigchild;
my $child_exited = 0;
sub handle_sigchild
{
$debug && print "got SIGCHLD!!!\n";
$child_exited = 1;
}
$err_fh = FileHandle->new; # only reader and writer are auto-gen'd
my $pid = open3($write_fh, $read_fh, $err_fh, @cmd);
# read output until EOF
my ($rout, $rin, $wout, $win, $eout, $ein);
$rin = $ein = '';
my $nclosed = 0;
my ($buf, $ret, $out, $err);
my ($out_open, $err_open) = (1, 1);
my ($fileno_write, $fileno_err, $fileno_read);
my $have_input = 0;
if (@input) {
$win = '';
$fileno_write = fileno($write_fh);
vec($win, $fileno_write, 1) = 1;
$have_input = 1;
} else {
close($write_fh);
}
my $want_closed = 0;
if (defined($read_fh)) {
$fileno_read = fileno($read_fh);
vec($rin, $fileno_read, 1) = 1;
$want_closed++;
}
if (defined($err_fh)) {
$fileno_err = fileno($err_fh);
vec($ein, $fileno_err, 1) = 1;
$want_closed++;
}
my $input_line;
$debug && print " going into read loop...\n";
while (!$child_exited && ($nclosed < $want_closed)) {
$debug && print "\n**top of while**,nclosed=[$nclosed]\n";
if ($have_input && !@input) {
$debug && print "input exhausted. setting
win=undef\n";
$win = undef;
}
$debug && print "going into select...\n";
my $nfound = select($rout=$rin, $wout=$win, $eout=$ein,
undef);
$debug && print "after select, nfound=[$nfound]\n";
if ($nfound) {
#---------------------------------------------
# STDOUT
#---------------------------------------------
if (vec($rout, $fileno_read, 1)) {
$debug && print "stdout has something...
\n";
$ret = sysread($read_fh, $buf, 512);
$debug && print "read [$ret] bytes\n";
if ($ret == 0) {
$nclosed++;
$debug && print "incrementing
nclosed\n";
$out_open = 0;
$rin = undef;
}
$debug && print " STDOUT: [$buf]\n";
$out .= $buf;
}
#---------------------------------------------
# STDERR
#---------------------------------------------
if (vec($eout, $fileno_err, 1)) {
$debug && print "stderr has something...
\n";
$ret = sysread($err_fh, $buf, 512);
$debug && print "read [$ret] bytes\n";
if ($ret == 0) {
$nclosed++;
$debug && print "incrementing
nclosed\n";
$err_open = 0;
}
$debug && print " STDERR: [$buf]\n";
$err .= $buf;
}
#---------------------------------------------
# STDIN
#---------------------------------------------
if (vec($wout, $fileno_write, 1)) {
$debug && print "stdin is ready for
input...\n";
$input_line = shift(@input)."\n";
$debug && print "INPUT: [$input_line]\n";
$ret = syswrite($write_fh, $input_line);
defined($ret) || die "write failed: $!\n";
$debug && print "wrote [$ret] bytes\n";
}
}
}
defined($input_ref) && close($write_fh);
defined($read_fh) && close($read_fh);
defined($err_fh) && close($err_fh);
waitpid($pid, 0);
my $status = $?;
$debug && print "waitpid returned status [$status]\n";
return ($status/256, $out, $err);
}
1;