#!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } use lib "$::XCATROOT/lib/perl"; use strict; use File::Basename; use Cwd; use Socket; #use Data::Dumper; use Getopt::Long; require xCAT::MsgUtils; require xCAT::DSHCLI; use xCAT::Utils; use xCAT::TableUtils; use xCAT::RemoteShellExp; require xCAT::Client; my $bname = basename($0); #----------------------------------------------------------------------------- =head1 xdsh/xdcp This program is the client interface for xdsh/xdcp. xdsh/xdcp command This is the interface to for xdsh/xdsp The command can run in client/server mode (default) or in bypass mode where it does not use the xcat daemon xcatd. Bypass mode is useful, when executing the command on the Management Server and in particular if you want to run as a non-root id. Call parse_args to verify mode (client/server or bypass) and whether to use Env Variables Build hash and submit request See man page for options =cut #----------------------------------------------------------------------------- # Main # report unsupported dsh exports &check_invalid_exports; my $cmdref; my $arg; my @SaveARGV = @ARGV; $cmdref->{cwd}->[0] = cwd(); $cmdref->{command}->[0] = $bname; # save my command name my $arg = @SaveARGV[0]; if ($arg =~ /^-/) # no noderange { # check for help, bypass, other client flags if ($bname eq "xdsh") { &parse_args_xdsh; } else { # xdcp &parse_args_xdcp; } if ($::ROOTIMG != 1) { # if not running against rootimg then noderange required xCAT::MsgUtils->message("I", "Node range not specified, see $bname man page for syntax.\n"); exit 1; } } else { $cmdref->{noderange}->[0] = $arg; # save noderange my $tmp = shift(@SaveARGV); # take if off the argument list if (!($cmdref->{noderange}->[0])) { xCAT::MsgUtils->message("I", "Node range not specified, see man page for syntax.\n"); exit 1; } @ARGV = @SaveARGV; # noderange removed for parsing if ($bname eq "xdsh") { &parse_args_xdsh($cmdref->{noderange}->[0]); } else { # xdcp &parse_args_xdcp; } } foreach my $sarg (@SaveARGV) { push(@{$cmdref->{arg}}, $sarg); } # add environment variables, if they have not already been assigned with # command line flags if (!($::NODE_RSH)) { if ($ENV{'DSH_NODE_RSH'}) { push(@{$cmdref->{env}}, "DSH_NODE_RSH=$ENV{'DSH_NODE_RSH'}"); } } if (!($::NODE_RCP)) { if ($ENV{'DSH_NODE_RCP'}) { push(@{$cmdref->{env}}, "DSH_NODE_RCP=$ENV{'DSH_NODE_RCP'}"); } } if (!($::NODE_OPTS)) { if ($ENV{'DSH_NODE_OPTS'}) { push(@{$cmdref->{env}}, "DSH_NODE_OPTS=$ENV{'DSH_NODE_OPTS'}"); } } if (!($::FANOUT)) { if ($ENV{'DSH_FANOUT'}) { push(@{$cmdref->{env}}, "DSH_FANOUT=$ENV{'DSH_FANOUT'}"); } } if (!($::TIMEOUT)) { if ($ENV{'DSH_TIMEOUT'}) { push(@{$cmdref->{env}}, "DSH_TIMEOUT=$ENV{'DSH_TIMEOUT'}"); } } if ($ENV{'DSH_REMOTE_PASSWORD'}) { push(@{$cmdref->{env}}, "DSH_REMOTE_PASSWORD=$ENV{'DSH_REMOTE_PASSWORD'}"); } if ($ENV{'DSH_FROM_USERID'}) { push(@{$cmdref->{env}}, "DSH_FROM_USERID=$ENV{'DSH_FROM_USERID'}"); } if ($ENV{'DSH_TO_USERID'}) { push(@{$cmdref->{env}}, "DSH_TO_USERID=$ENV{'DSH_TO_USERID'}"); } if ($ENV{'DEVICETYPE'}) { push(@{$cmdref->{env}}, "DEVICETYPE=$ENV{'DEVICETYPE'}"); } if ($ENV{'DSH_RSYNC_FILE'}) { push(@{$cmdref->{env}}, "DSH_RSYNC_FILE=$ENV{'DSH_RSYNC_FILE'}"); } if ($ENV{'RSYNCSN'}) { push(@{$cmdref->{env}}, "RSYNCSN=$ENV{'RSYNCSN'}"); } if ($ENV{'RSYNCSNONLY'}) { push(@{$cmdref->{env}}, "RSYNCSNONLY=$ENV{'RSYNCSNONLY'}"); } if ($ENV{'DCP_PULL'}) { push(@{$cmdref->{env}}, "DCP_PULL=$ENV{'DCP_PULL'}"); } if ($ENV{'DSHEXECUTE'}) { push(@{$cmdref->{env}}, "DSHEXECUTE=$ENV{'DSHEXECUTE'}"); } if ($ENV{'DSH_ENVIRONMENT'}) { push(@{$cmdref->{env}}, "DSH_ENVIRONMENT=$ENV{'DSH_ENVIRONMENT'}"); } xCAT::Client::submit_request($cmdref, \&xCAT::Client::handle_response); exit $xCAT::Client::EXITCODE; #----------------------------------------------------------------------------- =head3 parse_args_xdsh Parses for dsh input Check if the command ask for help and display usage Need to check only for the -X flag Need to check -B flag to determine mode =cut #----------------------------------------------------------------------------- sub parse_args_xdsh { my $snodes = shift; Getopt::Long::Configure("posix_default"); Getopt::Long::Configure("no_gnu_compat"); Getopt::Long::Configure("bundling"); my %options = (); if ( !GetOptions( 'e|execute' => \$options{'execute'}, 'f|fanout=i' => \$options{'fanout'}, 'h|help' => \$options{'help'}, 'l|user=s' => \$options{'user'}, 'm|monitor' => \$options{'monitor'}, 'o|node-options=s' => \$options{'node-options'}, 'q|show-config' => \$options{'show-config'}, 'r|node-rsh=s' => \$options{'node-rsh'}, 'i|rootimg=s' => \$options{'rootimg'}, 's|stream' => \$options{'streaming'}, 't|timeout=i' => \$options{'timeout'}, 'v|verify' => \$options{'verify'}, 'z|exit-status' => \$options{'exit-status'}, 'B|bypass' => \$options{'bypass'}, 'c|cleanup' => \$options{'cleanup'}, 'E|environment=s' => \$options{'environment'}, 'I|ignore-sig|ignoresig=s' => \$options{'ignore-signal'}, 'K|keysetup' => \$options{'ssh-setup'}, 'L|no-locale' => \$options{'no-locale'}, 'Q|silent' => \$options{'silent'}, 'S|syntax=s' => \$options{'syntax'}, 'T|trace' => \$options{'trace'}, 'V|version' => \$options{'version'}, 'devicetype=s' => \$options{'devicetype'}, 'nodestatus|nodestatus' => \$options{'nodestatus'}, 'command-name|commandName=s' => \$options{'command-name'}, 'command-description|commandDescription=s' => \$options{'command-description'}, 'X:s' => \$options{'ignore_env'} ) ) { xCAT::DSHCLI->usage_dsh; exit 1; } if ($options{'help'}) { xCAT::DSHCLI->usage_dsh; exit 0; } # cleanup the service node temp directory and exit # if default to not prompt, if user specified prompt if (($options{'cleanup'}) && (!$snodes)) { my $msg = "Service node range must be supplied on the -c command."; xCAT::MsgUtils->message("E", $msg); exit 1; } if ($options{'cleanup'}) { # get the directories on the servicenode to put the files in my $defaultsyndir = "/var/xcat/syncfiles"; my @syndir = xCAT::TableUtils->get_site_attribute("SNsyncfiledir"); my $synfiledir; if ($syndir[0]) { $synfiledir = $syndir[0]; } else { $synfiledir = "/var/xcat/syncfiles"; # default } # for append function my $defaultnodesyndir = "/var/xcat/node/syncfiles"; my @syndir2 = xCAT::TableUtils->get_site_attribute("nodesyncfiledir"); my $synfiledir2; if ($syndir2[0]) { $synfiledir2 = $syndir2[0]; } else { $synfiledir2 = "/var/xcat/node/syncfiles"; # default } if ($defaultsyndir ne $synfiledir) { my $answer; my $msg = "Do you wish to erase $synfiledir,$synfiledir2 and all subdirectories?\n Enter Y or N."; xCAT::MsgUtils->message('I', "$msg"); `stty -echo`; chop($answer = ); `stty echo`; $answer =~ tr/a-z/A-Z/; # convert to upper if ($answer ne "Y") { exit 0; } } my $cmd = "xdsh $snodes -v rm -rf $synfiledir/*;xdsh $snodes -v rm -rf $synfiledir2"; xCAT::Utils->runcmd($cmd, 0); if ($::RUNCMD_RC != 0) { # error my $msg = "Error from $cmd: to cleanup servicenodes"; xCAT::MsgUtils->message("E", $msg); exit 1; } exit 0; } if ($options{'bypass'}) { $ENV{XCATBYPASS} = "yes"; # bypass xcatd } if ($options{'show-config'}) { xCAT::DSHCLI->show_dsh_config; exit 0; } my $executescript; #check for full path to file if ($options{execute}) { # this can be a file + parameters $executescript = join ' ', @ARGV; if ($executescript !~ /^\//) { #relative path $executescript = xCAT::Utils->full_path($executescript); } $ENV{'DSHEXECUTE'} = $executescript; # execute script } # find out who is the current user running xdsh #my $current_userid = getlogin(); # does not work for su my $current_userid = getpwuid($>); $ENV{DSH_FROM_USERID} = $current_userid; # find out who we are going to log on to the node as my $to_userid; if ($options{'user'}) # if -l option { $to_userid = $options{'user'}; } else { $to_userid = $current_userid; } $ENV{DSH_TO_USERID} = $to_userid; # if devicetype=Mellanox, xdsh does not setup ssh, rspconfig does my $devicepath = $options{'devicetype'}; $devicepath =~ s/::/\//g; if (($options{'ssh-setup'}) && ($devicepath =~ /Mellanox/i)) { my $msg = "You do not use xdsh -K to setup the Mellanox switch ssh keys. Use rspconfig. See man page for rspconfig option sshcfg={enable|disable}."; xCAT::MsgUtils->message("E", $msg); exit 2; } # only allow -K with -l if --devicetype defined if ( (($options{'user'}) && ($options{'ssh-setup'})) && (!($options{'devicetype'}))) { my $msg = "The -K and -l flag may only be used if --devicetype is specified."; xCAT::MsgUtils->message("E", $msg); exit 2; } if ($options{'ssh-setup'}) # if going to setup ssh keys { my $msg; if (!($ENV{'DSH_REMOTE_PASSWORD'})) { # if not already set # prompt for the password for the userid on the node that will be setup my $userpw; $msg = "Enter the password for the userid: $to_userid on the node where the ssh keys \nwill be updated:\n"; xCAT::MsgUtils->message("I", $msg); system("stty -echo"); # turn off keyboard chop($userpw = ); system("stty echo"); # turn on keyboard if ($userpw eq "") { # did not enter a password $msg = "Did not enter a password must abort the key exchange"; xCAT::MsgUtils->message("E", $msg); exit 2; } else { # password entered pass to the server $ENV{DSH_REMOTE_PASSWORD} = $userpw; } } # Get the home directory my $home = xCAT::Utils->getHomeDir($current_userid); $ENV{'DSH_FROM_USERID_HOME'} = $home; # # create /.ssh dir if needed # my $sshdir = "$home/.ssh"; if (!-d $sshdir) { my $cmd = "/bin/mkdir -m 700 -p $sshdir"; my $outref = xCAT::Utils->runcmd("$cmd", 0); if ($::RUNCMD_RC != 0) { xCAT::MsgUtils->message('E', "Could not create $sshdir directory."); exit 1; } } # add config file with strict host checking no my $cmd = "echo \"StrictHostKeyChecking no\" >> $home/.ssh/config"; xCAT::Utils->runcmd($cmd, 0); if ($::RUNCMD_RC != 0) { # error $msg = "Error from $cmd\n"; xCAT::MsgUtils->message("E", $msg); } my $cmd = "chmod 0600 $home/.ssh/config"; xCAT::Utils->runcmd($cmd, 0); if ($::RUNCMD_RC != 0) { # error $msg = "Error from $cmd\n"; xCAT::MsgUtils->message("E", $msg); } # if current_userid is not "root", we need to generate the keys # here before becoming root while running under xcatd # if ($current_userid ne "root") { $::REMOTE_SHELL = "/usr/bin/ssh"; my $callback = \&xCAT::Client::handle_response; # generates new keys for non-root id, if they do not already exist my $rc= xCAT::RemoteShellExp->remoteshellexp("k",$callback,$::REMOTE_SHELL); if ($rc != 0) { $msg = "remoteshellexp failed generating keys."; xCAT::MsgUtils->message("E", $msg); } } } # end setup of ssh if ($options{'version'}) { my $version = xCAT::Utils->Version(); $version .= "\n"; xCAT::MsgUtils->message("N", $version); exit 0; } if ($options{'rootimg'}) { $::ROOTIMG = 1; } if ($options{'node-rsh'}) # if set on command line, use it { $::NODE_RSH = 1; } if ($options{'node-opts'}) # if set on command line, use it { $::NODE_OPTS = 1; } if ($options{'fanout'}) # if set on command line, use it { $::FANOUT = 1; } if ($options{'timeout'}) # if set on command line, use it { $::TIMEOUT = 1; } if (defined $options{'ignore_env'}) { xCAT::DSHCLI->ignoreEnv($options{'ignore_env'}); } } #----------------------------------------------------------------------------- =head3 parse_args_xdcp Parses for dcp input Check if the command ask for help and display usage Need to check -X flag to determine how to set Environment Variables Need to check -B flag to determine mode =cut #----------------------------------------------------------------------------- sub parse_args_xdcp { my %options = (); Getopt::Long::Configure("posix_default"); Getopt::Long::Configure("no_gnu_compat"); Getopt::Long::Configure("bundling"); if ( !GetOptions( 'f|fanout=i' => \$options{'fanout'}, 'F|File=s' => \$options{'File'}, 'h|help' => \$options{'help'}, 'i|rootimg=s' => \$options{'rootimg'}, 'l|user=s' => \$options{'user'}, 'm|monitor' => \$options{'monitor'}, 'o|node-options=s' => \$options{'node-options'}, 'q|show-config' => \$options{'show-config'}, 'p|preserve' => \$options{'preserve'}, 'r|c|node-rcp=s' => \$options{'node-rcp'}, 's' => \$options{'rsyncSN'}, 't|timeout=i' => \$options{'timeout'}, 'v|verify' => \$options{'verify'}, 'B|bypass' => \$options{'bypass'}, 'Q|silent' => \$options{'silent'}, 'P|pull' => \$options{'pull'}, 'R|recursive' => \$options{'recursive'}, 'T|trace' => \$options{'trace'}, 'V|version' => \$options{'version'}, 'nodestatus|nodestatus' => \$options{'nodestatus'}, 'X:s' => \$options{'ignore_env'} ) ) { xCAT::DSHCLI->usage_dcp; exit 1; } if ($options{'help'}) { xCAT::DSHCLI->usage_dcp; exit 0; } if ($options{'show-config'}) { xCAT::DSHCLI->show_dsh_config; exit 0; } if (defined($options{File}) && ($options{File} !~ /^\//)) { #relative path $options{File} = xCAT::Utils->full_path($options{File}); } if (defined($options{rootimg}) && ($options{rootimg} !~ /^\//)) { #relative path $options{rootimg} = xCAT::Utils->full_path($options{rootimg}); } # rsync only to the service node if ($options{'rsyncSN'}) { $ENV{'RSYNCSNONLY'} = "yes"; # rsync file to SN } # if Pull function hierarchy must be handled special in plugin if ($options{'pull'}) { $ENV{'DCP_PULL'} = "yes"; } # if rsyncing to a node, not the root image # then must sync to service node first, if using hierarchy # These env variable are used in xdsh.pm preprocessing if ((!($options{'rootimg'})) && ($options{'File'})) { $ENV{'RSYNCSN'} = "yes"; #rsync file to SN, if exist } if ($options{'File'}) { $ENV{'DSH_RSYNC_FILE'} = $options{'File'}; # rsync file } if ($options{'version'}) { my $version = xCAT::Utils->Version(); $version .= "\n"; xCAT::MsgUtils->message("I", $version); exit 0; } if ($options{'rootimg'}) { $::ROOTIMG = 1; } if (($options{'rootimg'}) && (!($options{'File'}))) { xCAT::MsgUtils->message("E", "To use -i flag you must supply the -F flag\n."); exit 1; } if ($options{'node-rcp'}) # if set on command line, use it { $::NODE_RCP = 1; } if ($options{'bypass'}) { $ENV{XCATBYPASS} = "yes"; # bypass xcatd } } #----------------------------------------------------------------------------- =head3 check_invalid_exports Check for unsupported dsh exports and warns =cut #----------------------------------------------------------------------------- sub check_invalid_exports { ## # Check for unsupported Environment Variables # DSH_DEVICE_LIST, DSH_DEVICE_OPTS, DSH_DEVICE_RCP,DSH_DEVICE_RSH, # DSH_NODEGROUP_PATH # For support Env Variables tell them to use the command line flag ## if ($ENV{'DSH_CONTEXT'}) { xCAT::MsgUtils->message("I", "DSH_CONTEXT is set but is not supported. It will be ignored.\n"); } if ($ENV{'DSH_LIST'}) # if file of nodes input { xCAT::MsgUtils->message("I", "DSH_LIST is set but is not supported. It will be ignored.\n"); } if ($ENV{'DSH_NODE_LIST'}) { xCAT::MsgUtils->message( "I", "DSH_NODE_LIST is set but is not supported. It will be ignored.\n" ); } if ($ENV{'DSH_DEVICE_LIST'}) { xCAT::MsgUtils->message( "I", "DSH_DEVICE_LIST is set but is not supported. It will be ignored.\n" ); } if ($ENV{'DSH_DEVICE_OPTS'}) { xCAT::MsgUtils->message( "I", "DSH_DEVICE_OPTS is set but is not supported. It will be ignored.\n" ); } if ($ENV{'DSH_DEVICE_RCP'}) { xCAT::MsgUtils->message( "I", "DSH_DEVICE_RCP is set but is not supported. It will be ignored.\n" ); } if ($ENV{'DSH_DEVICE_RSH'}) { xCAT::MsgUtils->message( "I", "DSH_DEVICE_RSH is set but is not supported. It will be ignored.\n" ); } if ($ENV{'DSH_NODEGROUP_PATH'}) { xCAT::MsgUtils->message( "I", "DSH_NODEGROUP_PATH is set but is not supported. It will be ignored.\n" ); } if ($ENV{'RSYNC_RSH'}) { xCAT::MsgUtils->message("I", " RSYNC_RSH is set but is not supported. It will be ignored.\n"); } if ($ENV{'DSH_REPORT'}) { xCAT::MsgUtils->message("I", " DSH_REPORT is set but is not supported. It will be ignored.\n"); } }