#!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT::DSHCore; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } use locale; use strict; use Socket; use xCAT::MsgUtils; use xCAT::Utils; #--------------------------------------------------------------------------- =head3 fork_no_output Forks a process for the given command array and returns the process ID for the forked process. Since no I/O is needed for the pipes, no STDOUT/STDERR pipes are returned to the caller. Arguments: $fork_id - unique identifer to use for tracking the forked process @command - command and parameter array to execute in the forkec process Returns: $pid - process identifer for the forked process Globals: None Error: None Example: $pid = xCAT::DSHCore->fork_no_output('hostname1PID', @command_array); Comments: =cut #--------------------------------------------------------------------------- sub fork_no_output { my ($class, $fork_id, @command) = @_; my $pid; if ($pid = xCAT::Utils->xfork) { } elsif (defined $pid) { open(STDOUT, ">/dev/null"); open(STDERR, ">/dev/null"); select(STDOUT); $| = 1; select(STDERR); $| = 1; if (!(exec {$command[0]} @command)) { return (-4, undef); } } else { return (-3, undef); } return ($pid, undef, undef, undef, undef); } #--------------------------------------------------------------------------- =head3 fork_output Forks a process for the given command array and returns the process ID for the forked process and references to all I/O pipes for STDOUT and STDERR. Arguments: $fork_id - unique identifer to use for tracking the forked process @command - command and parameter array to execute in the forkec process Returns: $pid - process identifer for the forked process Globals: None Error: None Example: $pid = xCAT::DSHCore->fork_no_output('hostname1PID', @command_array); Comments: =cut #--------------------------------------------------------------------------- sub fork_output { my ($class, $fork_id, @command) = @_; no strict; my $pid; my %pipes = (); my $rout_fh = "rout_$fork_id"; my $rerr_fh = "rerr_$fork_id"; my $wout_fh = "wout_$fork_id"; my $werr_fh = "werr_$fork_id"; (pipe($rout_fh, $wout_fh) == -1) && return (-1, undef); (pipe($rerr_fh, $werr_fh) == -1) && return (-2, undef); if ($pid = fork) { close($wout_fh); close($werr_fh); } elsif (defined $pid) { close($rout_fh); close($rerr_fh); !(open(STDOUT, ">&$wout_fh")) && return (-5, undef); !(open(STDERR, ">&$werr_fh")) && return (-6, undef); select(STDOUT); $| = 1; select(STDERR); $| = 1; if (!(exec {$command[0]} @command)) { return (-4, undef); } } else { return (-3, undef); } return ($pid, *$rout_fh, *$rerr_fh, *$wout_fh, *$werr_fh); use strict; } #--------------------------------------------------------------------------- =head3 fork_output_for_commands Forks a process for the given command array and returns the process ID for the forked process and references to all I/O pipes for STDOUT and STDERR. In the child process, it will invoke the xCAT::DSHCore->fork_no_output() for the first command which is a no-output command and waitpid(). And then execute the left commands in the child process. Arguments: $fork_id - unique identifer to use for tracking the forked process @command - command and parameter array to execute in the forkec process Returns: $pid - process identifer for the forked process Globals: None Error: None Example: $pid = xCAT::DSHCore->fork_output_for_commands('hostname1PID', @command_array); Comments: =cut #--------------------------------------------------------------------------- sub fork_output_for_commands { my ($class, $fork_id, @commands) = @_; no strict; my $pid; my %pipes = (); my $rout_fh = "rout_$fork_id"; my $rerr_fh = "rerr_$fork_id"; my $wout_fh = "wout_$fork_id"; my $werr_fh = "werr_$fork_id"; (pipe($rout_fh, $wout_fh) == -1) && return (-1, undef); (pipe($rerr_fh, $werr_fh) == -1) && return (-2, undef); if ($pid = fork) { close($wout_fh); close($werr_fh); } elsif (defined $pid) { close($rout_fh); close($rerr_fh); !(open(STDOUT, ">&$wout_fh")) && return (-5, undef); !(open(STDERR, ">&$werr_fh")) && return (-6, undef); select(STDOUT); $| = 1; select(STDERR); $| = 1; if ( @commands > 1 ) { my $command0 = shift(@commands); my @exe_command0_process = xCAT::DSHCore->fork_no_output($fork_id, @$command0); waitpid($exe_command0_process[0], undef); } my $t_command = shift(@commands); my @command = @$t_command; if (!(exec {$command[0]} @command)) { return (-4, undef); } } else { return (-3, undef); } return ($pid, *$rout_fh, *$rerr_fh, *$wout_fh, *$werr_fh); use strict; } #--------------------------------------------------------------------------- =head3 pipe_handler Handles and processes dsh output from a given read pipe handle. The output is immediately written to each output file handle as it is available. Arguments: $options - options hash table describing dsh configuration options $target_properties - property information of the target related to the pipe handle $read_fh - reference to the read pipe handle $buffer_size - local buffer size to read data from the handle $label - prefix label to use for dsh output $write_buffer - buffer of data that is yet to be written (must wait until \n is read) @write_fhs - array of output file handles where output will be written Returns: 1 if the EOF reached on $read_fh undef otherwise Globals: None Error: None Example: Comments: =cut #--------------------------------------------------------------------------- # NOTE: global environment $::__DSH_LAST_LINE} only can be used in DSHCore::pipe_handler and DSHCore::pipe_handler_buffer $::__DSH_LAST_LINE = undef; sub pipe_handler { my ($class, $options, $target_properties, $read_fh, $buffer_size, $label, $write_buffer, @write_fhs) = @_; my $line; my $target_hostname; my $eof_reached = undef; my $cust_rc_deal =0; if ($::USER_POST_CMD) { # If user provide post-command to display return code, # the keyword 'DSH_RC' will be searched, # the return code is gotten in another way as shown like below: # ... # # # DSH_RC # # The last two lines are needed to be moved out from output $cust_rc_deal = 1; } while (sysread($read_fh, $line, $buffer_size) != 0 || ($eof_reached = 1)) { last if ($eof_reached && (!defined($::__DSH_LAST_LINE->{$label}))); if ($line =~ /^\n$/ && scalar(@$write_buffer) == 0) { # need to preserve blank lines in the output. $line = $label . $line; } my @lines = split "\n", $line; if (@$write_buffer) { my $buffered_line = shift @$write_buffer; my $next_line = shift @lines; $next_line = $buffered_line . $next_line; unshift @lines, $next_line; } if ($line !~ /\n$/) { push @$write_buffer, (pop @lines); } if (@lines) { if ($cust_rc_deal) { # Dump the last line at the beginning of current buffer if ($::__DSH_LAST_LINE->{$label}) { unshift @lines, "$::__DSH_LAST_LINE->{$label}" ; } # Pop current buffer to $::__DSH_LAST_LINE->{$label} if($line) { $::__DSH_LAST_LINE->{$label} = $lines[scalar @lines - 1]; pop @lines; # Skip this loop if array @lines is empty. if (scalar @lines == 0) { next; } } } $line = join "\n", @lines; $line .= "\n"; if ($line =~ /:DSH_TARGET_RC=/) { my $start_offset = index($line, ':DSH_TARGET_RC'); my $end_offset = index($line, ':', $start_offset + 1); my $target_rc_string = substr($line, $start_offset, $end_offset - $start_offset); my ($discard, $target_rc) = split '=', $target_rc_string; $line =~ s/:DSH_TARGET_RC=$target_rc:\n//g; $$target_properties{'target-rc'} = $target_rc; } if ( $::__DSH_LAST_LINE->{$label} =~ /DSH_RC/ && $cust_rc_deal) { my $target_rc = undef; # Get the number in the last line $line =~ /[\D]*([0-9]+)\s*$/ ; $target_rc = $1; $$target_properties{'target-rc'} = $target_rc; # Remove the last line $line =~ s/$target_rc\s*\n$//g; #$line = $line . "## ret=$target_rc"; # Clean up $::__DSH_LAST_LINE->{$label} undef $::__DSH_LAST_LINE->{$label} ; # when '-z' is specified, display return code $::DSH_EXIT_STATUS && ($line .="Remote_command_rc = $target_rc"); } if ($line ne '') { if ($line !~ /^$label\n$/) { $line = $label . $line; } $line =~ s/$/\n/ if $line !~ /\n$/; } $line =~ s/\n/\n$label/g; ($line =~ /\n$label$/) && ($line =~ s/\n$label$/\n/); chomp $line; my @output_files = (); my @output_file_nos = (); foreach my $write_fh (@write_fhs) { my $file_no = fileno($write_fh); if (grep /$file_no/, @output_file_nos) { $line =~ s/$label//g; } my $rsp={}; $rsp->{data}->[0] = $line; xCAT::MsgUtils->message("D", $rsp, $::CALLBACK); #print $write_fh $line; } if (@output_files) { foreach my $output_file (@output_files) { pop @write_fhs; close $output_file || print STDOUT "dsh> Error_file_closed $$target_properties{hostname} $output_file\n"; my $rsp={}; $rsp->{error}->[0] = "Error_file_closed $$target_properties{hostname $output_file}.\n"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); ($output_file == $$target_properties{'output-fh'}) && delete $$target_properties{'output-fh'}; ($output_file == $$target_properties{'output-fh'}) && delete $$target_properties{'error-fh'}; } } my $rin = ''; vec($rin, fileno($read_fh), 1) = 1; my $fh_count = select($rin, undef, undef, 0); last if ($fh_count == 0); } } return $eof_reached; } #--------------------------------------------------------------------------- =head3 pipe_handler_buffer Handles and processes dsh output from a given read pipe handle. The output is stored in a buffer supplied by the caller. Arguments: $target_properties - property information of the target related to the pipe handle $read_fh - reference to the read pipe handle $buffer_size - local buffer size to read data from the handle $label - prefix label to use for dsh output $write_buffer - buffer of data that is yet to be written (must wait until \n is read) $output_buffer - buffer where output will be written Returns: 1 if the EOF reached on $read_fh undef otherwise Globals: None Error: None Example: Comments: =cut #--------------------------------------------------------------------------- # NOTE: global environment $::__DSH_LAST_LINE only can be used in DSHCore::pipe_handler and DSHCore::pipe_handler_buffer sub pipe_handler_buffer { my ($class, $target_properties, $read_fh, $buffer_size, $label, $write_buffer, $output_buffer) = @_; my $line; my $eof_reached = undef; my $cust_rc_deal =0; if ($::USER_POST_CMD) { # If user provide post-command to display return code, # the keyword 'DSH_RC' will be searched, # the return code is gotten in another way as shown like below: # ... # # # DSH_RC # # The last two lines are needed to be moved out from output $cust_rc_deal = 1; } while ( (sysread($read_fh, $line, $buffer_size) != 0) || ($eof_reached = 1)) { last if ($eof_reached && (!defined($::__DSH_LAST_LINE->{$label}))); if ($line =~ /^\n$/ && scalar(@$write_buffer) == 0) { # need to preserve blank lines in the output. $line = $label . $line; } my @lines = split "\n", $line; if (@$write_buffer) { my $buffered_line = shift @$write_buffer; my $next_line = shift @lines; $next_line = $buffered_line . $next_line; unshift @lines, $next_line; } if ($line !~ /\n$/) { push @$write_buffer, (pop @lines); } if (@lines || $::__DSH_LAST_LINE->{$label}) { if ($cust_rc_deal) { # Dump the last line at the beginning of current buffer if ($::__DSH_LAST_LINE->{$label}) { unshift @lines, "$::__DSH_LAST_LINE->{$label}" ; undef $::__DSH_LAST_LINE->{$label} } if ($line) { # Pop current buffer to $::__DSH_LAST_LINE->{$label} $::__DSH_LAST_LINE->{$label} = $lines[scalar @lines - 1]; pop @lines; # Skip this loop if array @lines is empty. if (scalar @lines == 0) { next; } } } $line = join "\n", @lines; $line .= "\n"; if ($line =~ /:DSH_TARGET_RC=/) { my $start_offset = index($line, ':DSH_TARGET_RC'); my $end_offset = index($line, ':', $start_offset + 1); my $target_rc_string = substr($line, $start_offset, $end_offset - $start_offset); my ($discard, $target_rc) = split '=', $target_rc_string; $line =~ s/:DSH_TARGET_RC=$target_rc:\n//g; $$target_properties{'target-rc'} = $target_rc; } if ( $::__DSH_LAST_LINE->{$label} =~ /DSH_RC/ && $cust_rc_deal) { my $target_rc = undef; # Get the number in the last line $line =~ /[\D]*([0-9]+)\s*$/ ; $target_rc = $1; $$target_properties{'target-rc'} = $target_rc; # Remove the last line $line =~ s/$target_rc\s*\n$//g; #$line = $line . "## ret=$target_rc"; # Clean up $::__DSH_LAST_LINE->{$label} undef $::__DSH_LAST_LINE->{$label} ; # when '-z' is specified, display return code $::DSH_EXIT_STATUS && ($line .="Remote_command_rc = $target_rc"); } if ($line ne '') { if ($line !~ /^$label\n$/) { $line = $label . $line; } $line =~ s/$/\n/ if $line !~ /\n$/; } $line =~ s/\n/\n$label/g; ($line =~ /\n$label$/) && ($line =~ s/\n$label$/\n/); push @$output_buffer, $line; my $rin = ''; vec($rin, fileno($read_fh), 1) = 1; my $fh_count = select($rin, undef, undef, 0); last if ($fh_count == 0); } } return $eof_reached; } #--------------------------------------------------------------------------- =head3 ping_hostnames Executes ping on a given list of hostnames and returns a list of those hostnames that did not respond Arguments: @hostnames - list of hostnames to execute for fping Returns: @no_response - list of hostnames that did not respond undef if fping is not installed Globals: None Error: None Example: @bad_hosts = xCAT::DSHCore->ping_hostnames(@host_list); Comments: =cut #--------------------------------------------------------------------------- sub ping_hostnames { my ($class, @hostnames) = @_; my $ping = (($^O eq 'aix') && '/usr/sbin/ping') || (($^O eq 'linux') && '/bin/ping') || undef; !$ping && return undef; my @no_response = (); foreach my $hostname (@hostnames) { (system("$ping -c 1 -w 1 $hostname > /dev/null 2>&1") != 0) && (push @no_response, $hostname); } return @no_response; } #--------------------------------------------------------------------------- =head3 pping_hostnames Executes pping on a given list of hostnames and returns a list of those hostnames that did not respond Arguments: @hostnames - list of hostnames to execute for fping Returns: @no_response - list of hostnames that did not respond Globals: None Error: None Example: @bad_hosts = xCAT::DSHCore->pping_hostnames(@host_list); Comments: =cut #--------------------------------------------------------------------------- sub pping_hostnames { my ($class, @hostnames) = @_; my $hostname_list = join ",", @hostnames; # read site table, usefping attribute # if set then run pping -f to use fping # this fixes a broken nmap in Redhat 6.2 with ip alias (3512) my $cmd="$::XCATROOT/bin/pping $hostname_list"; # default my @usefping=xCAT::TableUtils->get_site_attribute("usefping"); if ((defined($usefping[0])) && ($usefping[0] eq "1")) { $cmd = "$::XCATROOT/bin/pping -f $hostname_list"; } #my $rsp={}; #$rsp->{data}->[0] = "running command $cmd"; #xCAT::MsgUtils->message("I", $rsp, $::CALLBACK); my @output = xCAT::Utils->runcmd($cmd, -1); if ($::RUNCMD_RC !=0) { my $rsp={}; $rsp->{error}->[0] = "Error from pping"; xCAT::MsgUtils->message("E", $rsp, $::CALLBACK); } $::RUNCMD_RC =0; # reset my @no_response = (); foreach my $line (@output) { my ($hostname, $result) = split ':', $line; my ($token, $status) = split ' ', $result; chomp($token); if ($token ne 'ping') { push @no_response, $hostname; } } return @no_response; } 1;