diff --git a/perl-xCAT-2.0/xCAT/Client.pm b/perl-xCAT-2.0/xCAT/Client.pm index 10dcc6db2..d803027c8 100644 --- a/perl-xCAT-2.0/xCAT/Client.pm +++ b/perl-xCAT-2.0/xCAT/Client.pm @@ -27,19 +27,20 @@ use Storable qw(dclone); my $xcathost='localhost:3001'; my $plugins_dir; my %resps; +my $EXITCODE; # save the bitmask of all exit codes returned by calls to handle_response() 1; ################################# # submit_request will take an xCAT command and pass it to the xCAT # server for execution. -# +# # If the XCATBYPASS env var is set, the connection to the server/daemon # will be bypassed and the plugin will be called directly. If it is -# set to one or more directories (separated by ":"), all perl modules -# in those directories will be loaded in as plugins (for duplicate -# commands, last one in wins). If it is set to any other value -# (e.g. "yes", "default", whatever string you want) the default plugin +# set to one or more directories (separated by ":"), all perl modules +# in those directories will be loaded in as plugins (for duplicate +# commands, last one in wins). If it is set to any other value +# (e.g. "yes", "default", whatever string you want) the default plugin # directory /opt/xcat/lib/perl/xCAT_plugin will be used. # # Input: @@ -54,12 +55,12 @@ my %resps; # } # Callback - A subroutine ref that will be called to process the output # from the plugin. -# +# # NOTE: The request hash will get converted to XML when passed to the # xcatd daemon, and will get converted back to a hash before being # passed to the plugin. The XMLin ForceArray option is used to # force all XML constructs to be arrays so that the plugin code -# and callback routines can access the data consistently. +# and callback routines can access the data consistently. # The input request and the response hash created by the plugin should # always create hashes with array values. ################################# @@ -72,6 +73,7 @@ sub submit_request { unless ($keyfile) { $keyfile = $ENV{HOME}."/.xcat/client-key.pem"; } unless ($certfile) { $certfile = $ENV{HOME}."/.xcat/client-cert.pem"; } unless ($cafile) { $cafile = $ENV{HOME}."/.xcat/ca.pem"; } + $xCAT::Client::EXITCODE = 0; # clear out exit code before invoking the plugin # If XCATBYPASS is set, invoke the plugin process_request method directly @@ -128,12 +130,6 @@ sub submit_request { if ($response =~ m/<\/xcatresponse>/) { $rsp = XMLin($response,SuppressEmpty=>undef,ForceArray=>1); $response=''; - if ($rsp->{warning}) { - printf ("Warning: ".$rsp->{warning}->[0]."\n"); - } - if ($rsp->{error}) { - printf "Error: ". $rsp->{error}->[0]."\n"; - } $callback->($rsp); if ($rsp->{serverdone}) { last; @@ -143,10 +139,10 @@ sub submit_request { ################################### # scan_plugins -# will load all plugin perl modules and build a list of supported +# will load all plugin perl modules and build a list of supported # commands # -# NOTE: This is copied from xcatd (last merge 10/3/07). +# NOTE: This is copied from xcatd (last merge 10/3/07). # Will eventually move to using common source.... ################################### sub scan_plugins { @@ -184,9 +180,9 @@ sub scan_plugins { ################################### # plugin_command -# will invoke the correct plugin +# will invoke the correct plugin # -# NOTE: This is copied from xcatd (last merge 10/3/07). +# NOTE: This is copied from xcatd (last merge 10/3/07). # Will eventually move to using common source.... ################################### sub plugin_command { @@ -333,7 +329,7 @@ sub plugin_command { # do_request # called from a plugin to execute another xCAT plugin command internally # -# NOTE: This is copied from xcatd (last merge 10/3/07). +# NOTE: This is copied from xcatd (last merge 10/3/07). # Will eventually move to using common source.... ################################### sub do_request { @@ -402,7 +398,160 @@ sub build_response { -} +} # end of submit_request() + + + +########################################## +# handle_response is a default callback that can be passed into submit_response() +# It is invoked repeatedly by submit_response() to print out the data returned by +# the plugin. +# +# The normal flow is: +# -> client cmd (e.g. nodels, which is just a link to xcatclient) +# -> xcatclient +# -> submit_request() +# -> send xml request to xcatd +# -> xcatd +# -> process_request() of the plugin +# <- plugin callback +# <- xcatd +# <- xcatd sends xml response to client +# <- submit_request() read response +# <- handle_response() prints responses and saves exit codes +# <- xcatclient gets exit code and exits +# +# But in XCATBYPASS mode, the flow is: +# -> client cmd (e.g. nodels, which is just a link to xcatclient) +# -> xcatclient +# -> submit_request() +# -> process_request() of the plugin +# <- handle_response() prints responses and saves exit codes +# <- xcatclient gets exit code and exits +# +# Format of the response hash: +# {data => [ 'data str1', 'data str2', '...' ] } +# +# Results are printed as: +# data str1 +# data str2 +# +# or: +# {data => [ {desc => [ 'desc1' ], +# contents => [ 'contents1' ] }, +# {desc => [ 'desc2 ], +# contents => [ 'contents2' ] } +# : +# ] } +# NOTE: In this format, only the data array can have more than one +# element. All other arrays are assumed to be a single element. +# Results are printed as: +# desc1: contents1 +# desc2: contents2 +# +# or: +# {node => [ {name => ['node1'], +# data => [ {desc => [ 'node1 desc' ], +# contents => [ 'node1 contents' ] } ] }, +# {name => ['node2'], +# data => [ {desc => [ 'node2 desc' ], +# contents => [ 'node2 contents' ] } ] }, +# : +# ] } +# NOTE: Only the node array can have more than one element. +# All other arrays are assumed to be a single element. +# +# This was generated from the corresponding XML: +# +# +# node1 +# +# node1 desc +# node1 contents +# +# +# +# node2 +# +# node2 desc +# node2 contents +# +# +# +# +# Results are printed as: +# node_name: desc: contents +########################################## +sub handle_response { + my $rsp = shift; + # Handle errors + if ($rsp->{errorcode}) { + if (ref($rsp->{errorcode}) eq 'ARRAY') { foreach my $ecode (@{$rsp->{errorcode}}) { $xCAT::Client::EXITCODE |= $ecode; } } + else { $xCAT::Client::EXITCODE |= $rsp->{errorcode}; } # assume it is a non-reference scalar + } + if ($rsp->{warning}) { + if (ref($rsp->{warning}) eq 'ARRAY') { print ("Warning: " . $rsp->{warning}->[0] . "\n"); } + else { print ("Warning: ".$rsp->{warning}."\n"); } + } + if ($rsp->{error}) { + if (ref($rsp->{error}) eq 'ARRAY') { print ("Error: " . $rsp->{error}->[0] . "\n"); } + else { print ("Error: ".$rsp->{error}."\n"); } + } + + # Handle {node} structure + if ($rsp->{node}) { + my $nodes=($rsp->{node}); + my $node; + foreach $node (@$nodes) { + my $desc=$node->{name}->[0]; + if ($node->{errorcode}) { + if (ref($node->{errorcode}) eq 'ARRAY') { foreach my $ecode (@{$node->{errorcode}}) { $xCAT::Client::EXITCODE |= $ecode; } } + else { $xCAT::Client::EXITCODE |= $node->{errorcode}; } # assume it is a non-reference scalar + } + if ($node->{data}) { + if (ref(\($node->{data}->[0])) eq 'SCALAR') { + $desc=$desc.": ".$node->{data}->[0]; + } else { + if ($node->{data}->[0]->{desc}) { + $desc=$desc.": ".$node->{data}->[0]->{desc}->[0]; + } + if ($node->{data}->[0]->{contents}) { + $desc="$desc: ".$node->{data}->[0]->{contents}->[0]; + } + } + } + if ($desc) { + print "$desc\n"; + } + } + } + + # Handle {data} structure with no nodes + if ($rsp->{data}) { + my $data=($rsp->{data}); + my $data_entry; + foreach $data_entry (@$data) { + my $desc; + if (ref(\($data_entry)) eq 'SCALAR') { + $desc=$data_entry; + } else { + if ($data_entry->{desc}) { + $desc=$data_entry->{desc}->[0]; + } + if ($data_entry->{contents}) { + if ($desc) { + $desc="$desc: ".$data_entry->{contents}->[0]; + } else { + $desc=$data_entry->{contents}->[0]; + } + } + } + if ($desc) { + print "$desc\n"; + } + } + } +} # end of handle_response diff --git a/xCAT-client-2.0/bin/xcatclient b/xCAT-client-2.0/bin/xcatclient index e7867dc54..932ebad63 100755 --- a/xCAT-client-2.0/bin/xcatclient +++ b/xCAT-client-2.0/bin/xcatclient @@ -1,175 +1,44 @@ #!/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'; -} + +# Used as a standard client cmd that can be used for many of the xcat cmds. +# It grabs the arguments, noderange, and stdin and then submits the request to +# xcatd and waits for responses. Most of the client/server communication is +# contained in Client.pm. + +# To use this, sym link your cmd name to this script. + +BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } use lib "$::XCATROOT/lib/perl"; use Cwd; -use IO::Socket::SSL; -use IO::Socket::INET; +#use IO::Socket::SSL; +#use IO::Socket::INET; use File::Basename; -use Data::Dumper; -use xCAT::Client submit_request; +#use Data::Dumper; +use xCAT::Client; + my $bname = basename($0); - -######################################### -# Main -# Build hash and submit request -######################################### - -my $exitcode = 0; #var to store exit code that results from responses my $cmdref; -if($bname =~ /xcatclient/) { - $cmdref->{command}->[0]=shift @ARGV; -} elsif ($bname =~/^(.*$)/) { - $cmdref->{command}->[0] = $1; -} else { - printf("Bad usage\n"); - exit(1); -} +if ($bname =~ /xcatclient/) { $cmdref->{command}->[0]=shift @ARGV; } # xcatclient was invoked directly and the 1st arg is cmd name that is used to locate the plugin +else { $cmdref->{command}->[0] = $bname; } # the cmd was sym linked to xcatclient $cmdref->{cwd}->[0] = cwd(); if (-p STDIN) { my $data; - while ( ) { - $data.=$_; - } + while ( ) { $data.=$_; } $cmdref->{stdin}->[0]=$data; } +# Consider the 1st non-hyphen arg to be the noderange. All others (before and after) go on the arg list. my $arg=shift(@ARGV); while ($arg =~ /^-/) { push (@{$cmdref->{arg}}, $arg); $arg=shift(@ARGV); } -if ($arg ne "NO_NODE_RANGE") { +if ($arg ne "NO_NODE_RANGE") { $cmdref->{noderange}->[0]=$arg; } -foreach (@ARGV) { push (@{$cmdref->{arg}}, $_ ); } -#$cmdref->{arg}=\@ARGV; -xCAT::Client::submit_request($cmdref,\&handle_response); -exit $exitcode; - - -########################################## -# handle_response is the callback that is -# invoked to print out the data returned by -# the plugin. -# -# Format of the response hash: -# {data => [ 'data str1', 'data str2', '...' ] } -# -# Results are printed as: -# data str1 -# data str2 -# -# or: -# {data => [ {desc => [ 'desc1' ], -# contents => [ 'contents1' ] }, -# {desc => [ 'desc2 ], -# contents => [ 'contents2' ] } -# : -# ] } -# NOTE: In this format, only the data array can have more than one -# element. All other arrays are assumed to be a single element. -# Results are printed as: -# desc1: contents1 -# desc2: contents2 -# -# or: -# {node => [ {name => ['node1'], -# data => [ {desc => [ 'node1 desc' ], -# contents => [ 'node1 contents' ] } ] }, -# {name => ['node2'], -# data => [ {desc => [ 'node2 desc' ], -# contents => [ 'node2 contents' ] } ] }, -# : -# ] } -# NOTE: Only the node array can have more than one element. -# All other arrays are assumed to be a single element. -# -# This was generated from the corresponding HTML: -# -# -# node1 -# -# node1 desc -# node1 contents -# -# -# -# node2 -# -# node2 desc -# node2 contents -# -# -# -# -# Results are printed as: -# node_name: desc: contents -########################################## -sub handle_response { - my $rsp = shift; - # Handle {node} structure - if ($rsp->{errorcode}) { - foreach my $ecode (@{$rsp->{errorcode}}) { - $exitcode |= $ecode; - } - } - if ($rsp->{node}) { - my $nodes=($rsp->{node}); - my $node; - foreach $node (@$nodes) { - my $desc=$node->{name}->[0]; - if ($node->{errorcode}) { - foreach my $ecode (@{$node->{errorcode}}) { - $exitcode |= $ecode; - } - } - if ($node->{data}) { - if (ref(\($node->{data}->[0])) eq 'SCALAR') { - $desc=$desc.": ".$node->{data}->[0]; - } else { - if ($node->{data}->[0]->{desc}) { - $desc=$desc.": ".$node->{data}->[0]->{desc}->[0]; - } - if ($node->{data}->[0]->{contents}) { - $desc="$desc: ".$node->{data}->[0]->{contents}->[0]; - } - } - } - if ($desc) { - print "$desc\n"; - } - } - } - - # Handle {data} structure with no nodes - if ($rsp->{data}) { - my $data=($rsp->{data}); - my $data_entry; - foreach $data_entry (@$data) { - my $desc; - if (ref(\($data_entry)) eq 'SCALAR') { - $desc=$data_entry; - } else { - if ($data_entry->{desc}) { - $desc=$data_entry->{desc}->[0]; - } - if ($data_entry->{contents}) { - if ($desc) { - $desc="$desc: ".$data_entry->{contents}->[0]; - } else { - $desc=$data_entry->{contents}->[0]; - } - } - } - if ($desc) { - print "$desc\n"; - } - } - } -} +push (@{$cmdref->{arg}}, @ARGV); +xCAT::Client::submit_request($cmdref,\&xCAT::Client::handle_response); +exit $xCAT::Client::EXITCODE; diff --git a/xCAT-client-2.0/bin/xcatclientnnr b/xCAT-client-2.0/bin/xcatclientnnr index 3b1f60448..3a41c0763 100755 --- a/xCAT-client-2.0/bin/xcatclientnnr +++ b/xCAT-client-2.0/bin/xcatclientnnr @@ -1,25 +1,32 @@ #!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html + +# Used as a standard client cmd that can be used for xcat cmds that do not have +# noderange as an argument. See xcatclient for additional documentation. + +# To use this, sym link your cmd name to this script. + +BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } +use lib "$::XCATROOT/lib/perl"; +use Cwd; use File::Basename; +use xCAT::Client; -####################################### -# It handles the commands without noderanges. -####################################### my $bname = basename($0); -my $cmd; -if($bname =~ /xcatclientnnr/) { - $cmd = shift @ARGV; -} elsif ($bname =~/^(.*$)/) { - $cmd = $1; -} else { - printf("Bad usage\n"); - exit(1); +my $cmdref; +if ($bname =~ /xcatclientnnr/) { $cmdref->{command}->[0]=shift @ARGV; } # xcatclientnnr was invoked directly and the 1st arg is cmd name that is used to locate the plugin +else { $cmdref->{command}->[0] = $bname; } # the cmd was sym linked to xcatclientnnr +$cmdref->{cwd}->[0] = cwd(); + +if (-p STDIN) { + my $data; + while ( ) { $data.=$_; } + $cmdref->{stdin}->[0]=$data; } -exec("xcatclient $cmd NO_NODE_RANGE @ARGV 2>&1"); -#if (system("/usr/bin/xcatclient $cmd NO_NODE_RANGE @ARGV 2>&1") != 0) { -# print "$cmd failed: $?\n"; -#} -#exit; +push (@{$cmdref->{arg}}, @ARGV); + +xCAT::Client::submit_request($cmdref,\&xCAT::Client::handle_response); +exit $xCAT::Client::EXITCODE; diff --git a/xCAT-client-2.0/pods/man1/tabdump.1.pod b/xCAT-client-2.0/pods/man1/tabdump.1.pod new file mode 100644 index 000000000..85e32e602 --- /dev/null +++ b/xCAT-client-2.0/pods/man1/tabdump.1.pod @@ -0,0 +1,45 @@ +=head1 NAME + +B - display a database table in csv format. + +=head1 SYNOPSIS + +I + +I + +=head1 DESCRIPTION + +The tabdump command displays the header and all the rows of the specified table in csv format. +Only one table can be specified. If no table is specified, the list of existing +tables will be displayed. + +=head1 OPTIONS + +B<-?|-h|--help> Display usage message. + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + +1. To display the contents of the site table: + +I + +2. To see what tables exist in the xCAT database: + +I + +=head1 FILES + +/opt/xcat/sbin/tabdump + +=head1 NOTES + +This command is part of the xCAT software product. + + diff --git a/xCAT-client-2.0/pods/man1/tabrestore.1.pod b/xCAT-client-2.0/pods/man1/tabrestore.1.pod new file mode 100644 index 000000000..d729207bb --- /dev/null +++ b/xCAT-client-2.0/pods/man1/tabrestore.1.pod @@ -0,0 +1,50 @@ +=head1 NAME + +B - replaces the contents of an xCAT database table with the contents in a csv file. + +=head1 SYNOPSIS + +I + +I + +=head1 DESCRIPTION + +The tabrestore command reads the contents of the specified file and puts its data +in the corresponding table in the xCAT database. Any existing rows in that table +are replaced. The file must be in csv format. It could be created by tabdump. +Only one table can be specified. + +This command can be used to copy the example table entries in /opt/xcat/share/xcat/templates/e1350 +into the xCAT database. + +=head1 OPTIONS + +B<-?|-h|--help> Display usage message. + +=head1 RETURN VALUE + +0 The command completed successfully. + +1 An error has occurred. + +=head1 EXAMPLES + +1. To put rows into the mp table: + +I + +The file mp.csv could contain something like: + +#node,mpa,id,comments,disable +"blade","|\D+(\d+)|amm(($1-1)/14+1)|","|\D+(\d+)|(($1-1)%14+1)|",, + +=head1 FILES + +/opt/xcat/sbin/tabrestore + +=head1 NOTES + +This command is part of the xCAT software product. + + diff --git a/xCAT-client-2.0/sbin/tabrestore b/xCAT-client-2.0/sbin/tabrestore index 44d08eeff..1c1ff1f2b 100755 --- a/xCAT-client-2.0/sbin/tabrestore +++ b/xCAT-client-2.0/sbin/tabrestore @@ -1,166 +1,46 @@ #!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html -#Just like xcatclient, but needs to read a file in and pass it as $request->data -BEGIN -{ - $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; -} + +# Just like xcatclient, but needs to read a file in and pass it as $request->data + +BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; } use lib "$::XCATROOT/lib/perl"; -use IO::Socket::SSL; -use IO::Socket::INET; use File::Basename; -use Data::Dumper; -use xCAT::Client submit_request; -my $bname = basename($0); +use xCAT::Client; +use Getopt::Long; -######################################### -# Main -# Build hash and submit request -######################################### +sub usage { + print "Usage: tabrestore .csv\n"; + print " tabrestore [-?|-h|--help]\n"; + exit $_[0]; +} +#my $bname = basename($0); my $cmdref; -my $exitcode=0; $cmdref->{command}->[0] = "tabrestore"; +# Get the options +my $HELP; +if (!GetOptions('h|?|help' => \$HELP)) { usage(1); } my $arg=shift(@ARGV); while ($arg =~ /^-/) { push (@{$cmdref->{arg}}, $arg); $arg=shift(@ARGV); } -unless ($arg) { - printf("Usage: tabrestore [tablename].csv\n"); - exit(1); -} +unless ($arg) { usage(2); } # no filename specified + +# Open the specified table file and put its contents in the data key my $filename = $arg; -unless (-r $filename) { - printf("Unable to open $arg for reading.\n"); - exit(1); -} my $tabname = basename($filename); $tabname =~ s/\..*//; $cmdref->{table}->[0] = $tabname; my $fh; -open($fh,$filename); +unless (open($fh,$filename)) { print "Error: Unable to open $arg for reading.\n"; exit 3; } while (<$fh>) { push @{$cmdref->{data}},$_; } -foreach (@ARGV) { push (@{$cmdref->{arg}}, $_ ); } -#$cmdref->{arg}=\@ARGV; -xCAT::Client::submit_request($cmdref,\&handle_response); -exit $exitcode; +push (@{$cmdref->{arg}}, @ARGV); # get the rest of the arguments - -########################################## -# handle_response is the callback that is -# invoked to print out the data returned by -# the plugin. -# -# Format of the response hash: -# {data => [ 'data str1', 'data str2', '...' ] } -# -# Results are printed as: -# data str1 -# data str2 -# -# or: -# {data => [ {desc => [ 'desc1' ], -# contents => [ 'contents1' ] }, -# {desc => [ 'desc2 ], -# contents => [ 'contents2' ] } -# : -# ] } -# NOTE: In this format, only the data array can have more than one -# element. All other arrays are assumed to be a single element. -# Results are printed as: -# desc1: contents1 -# desc2: contents2 -# -# or: -# {node => [ {name => ['node1'], -# data => [ {desc => [ 'node1 desc' ], -# contents => [ 'node1 contents' ] } ] }, -# {name => ['node2'], -# data => [ {desc => [ 'node2 desc' ], -# contents => [ 'node2 contents' ] } ] }, -# : -# ] } -# NOTE: Only the node array can have more than one element. -# All other arrays are assumed to be a single element. -# -# This was generated from the corresponding HTML: -# -# -# node1 -# -# node1 desc -# node1 contents -# -# -# -# node2 -# -# node2 desc -# node2 contents -# -# -# -# -# Results are printed as: -# node_name: desc: contents -########################################## -sub handle_response { - my $rsp = shift; - # Handle {node} structure - if ($rsp->{node}) { - my $nodes=($rsp->{node}); - my $node; - foreach $node (@$nodes) { - my $desc=$node->{name}->[0]; - if ($node->{data}) { - if (ref(\($node->{data}->[0])) eq 'SCALAR') { - $desc=$desc.": ".$node->{data}->[0]; - } else { - if ($node->{data}->[0]->{desc}) { - $desc=$desc.": ".$node->{data}->[0]->{desc}->[0]; - } - if ($node->{data}->[0]->{contents}) { - $desc="$desc: ".$node->{data}->[0]->{contents}->[0]; - } - } - } - if ($desc) { - print "$desc\n"; - } - } - } - - # Handle {data} structure with no nodes - if ($rsp->{error}) { - $exitcode=1; - } - if ($rsp->{data}) { - my $data=($rsp->{data}); - my $data_entry; - foreach $data_entry (@$data) { - my $desc; - if (ref(\($data_entry)) eq 'SCALAR') { - $desc=$data_entry; - } else { - if ($data_entry->{desc}) { - $desc=$data_entry->{desc}->[0]; - } - if ($data_entry->{contents}) { - if ($desc) { - $desc="$desc: ".$data_entry->{contents}->[0]; - } else { - $desc=$data_entry->{contents}->[0]; - } - } - } - if ($desc) { - print "$desc\n"; - } - } - } -} +xCAT::Client::submit_request($cmdref,\&xCAT::Client::handle_response); +exit $xCAT::Client::EXITCODE; \ No newline at end of file diff --git a/xCAT-client-2.0/share/man/man1/tabdump.1 b/xCAT-client-2.0/share/man/man1/tabdump.1 new file mode 100644 index 000000000..b501b90c1 --- /dev/null +++ b/xCAT-client-2.0/share/man/man1/tabdump.1 @@ -0,0 +1,167 @@ +.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32 +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sh \" Subsection heading +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. | will give a +.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to +.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' +.\" expand to `' in nroff, nothing in troff, for use with C<>. +.tr \(*W-|\(bv\*(Tr +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.\" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.hy 0 +.if n .na +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "TABDUMP.1 1" +.TH TABDUMP.1 1 "2008-02-13" "perl v5.8.8" "User Contributed Perl Documentation" +.SH "NAME" +\&\fBtabdump\fR \- display a database table in csv format. +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fItabdump [table]\fR +.PP +\&\fItabdump [? | \-h | \-\-help]\fR +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +The tabdump command displays the header and all the rows of the specified table in csv format. +Only one table can be specified. If no table is specified, the list of existing +tables will be displayed. +.SH "OPTIONS" +.IX Header "OPTIONS" +\&\fB\-?|\-h|\-\-help\fR Display usage message. +.SH "RETURN VALUE" +.IX Header "RETURN VALUE" +0 The command completed successfully. +.PP +1 An error has occurred. +.SH "EXAMPLES" +.IX Header "EXAMPLES" +1. To display the contents of the site table: +.PP +\&\fItabdump site\fR +.PP +2. To see what tables exist in the xCAT database: +.PP +\&\fItabdump\fR +.SH "FILES" +.IX Header "FILES" +/opt/xcat/sbin/tabdump +.SH "NOTES" +.IX Header "NOTES" +This command is part of the xCAT software product. diff --git a/xCAT-client-2.0/share/man/man1/tabrestore.1 b/xCAT-client-2.0/share/man/man1/tabrestore.1 new file mode 100644 index 000000000..5f5041e9c --- /dev/null +++ b/xCAT-client-2.0/share/man/man1/tabrestore.1 @@ -0,0 +1,172 @@ +.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32 +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sh \" Subsection heading +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. | will give a +.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to +.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' +.\" expand to `' in nroff, nothing in troff, for use with C<>. +.tr \(*W-|\(bv\*(Tr +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.\" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.hy 0 +.if n .na +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "TABRESTORE.1 1" +.TH TABRESTORE.1 1 "2008-02-14" "perl v5.8.8" "User Contributed Perl Documentation" +.SH "NAME" +\&\fBtabrestore\fR \- replaces the contents of an xCAT database table with the contents in a csv file. +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fItabrestore table.csv\fR +.PP +\&\fItabrestore [? | \-h | \-\-help]\fR +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +The tabrestore command reads the contents of the specified file and puts its data +in the corresponding table in the xCAT database. Any existing rows in that table +are replaced. The file must be in csv format. It could be created by tabdump. +Only one table can be specified. +.PP +This command can be used to copy the example table entries in /opt/xcat/share/xcat/templates/e1350 +into the xCAT database. +.SH "OPTIONS" +.IX Header "OPTIONS" +\&\fB\-?|\-h|\-\-help\fR Display usage message. +.SH "RETURN VALUE" +.IX Header "RETURN VALUE" +0 The command completed successfully. +.PP +1 An error has occurred. +.SH "EXAMPLES" +.IX Header "EXAMPLES" +1. To put rows into the mp table: +.PP +\&\fItabrestore mp.csv\fR +.PP +The file mp.csv could contain something like: +.PP +#node,mpa,id,comments,disable +\&\*(L"blade\*(R",\*(L"|\eD+(\ed+)|amm(($1\-1)/14+1)|\*(R",\*(L"|\eD+(\ed+)|(($1\-1)%14+1)|\*(R",, +.SH "FILES" +.IX Header "FILES" +/opt/xcat/sbin/tabrestore +.SH "NOTES" +.IX Header "NOTES" +This command is part of the xCAT software product. diff --git a/xCAT-server-2.0/lib/xcat/plugins/tabutils.pm b/xCAT-server-2.0/lib/xcat/plugins/tabutils.pm index 02727044b..bcb18cccc 100644 --- a/xCAT-server-2.0/lib/xcat/plugins/tabutils.pm +++ b/xCAT-server-2.0/lib/xcat/plugins/tabutils.pm @@ -4,31 +4,13 @@ # xCAT plugin package to handle various commands that work with the # xCAT tables # -# Supported commands: -# nodeadd -# nodels -# nodech -# tabdump -# tabrestore -# noderm -# To be implemented: -# gettab -# chtab -# tabls -# getnodecfg (?? this doesn't seem much different from gettab) -# nr -# These were xCAT 1.2 commands. Are they still useful in xCAT 1.3? -# addattr -# delattr -# chtype -# ##################################################### package xCAT_plugin::tabutils; use xCAT::Table; use xCAT::Schema; use Data::Dumper; use xCAT::NodeRange; -use xCAT::Schema; #noderm will need to build list of tables.. +use xCAT::Schema; #use Getopt::Long qw(GetOptionsFromArray); @@ -51,17 +33,17 @@ sub handled_commands gettab => "tabutils", tabdump => "tabutils", tabrestore => "tabutils", - tabch => "tabutils", + tabch => "tabutils", # not implemented yet nodech => "tabutils", nodeadd => "tabutils", noderm => "tabutils", - tabls => "tabutils", + tabls => "tabutils", # not implemented yet nodels => "tabutils", - getnodecfg => "tabutils", - addattr => "tabutils", - delattr => "tabutils", - chtype => "tabutils", - nr => "tabutils", + getnodecfg => "tabutils", # not implemented yet (?? this doesn't seem much different from gettab) + addattr => "tabutils", # not implemented yet + delattr => "tabutils", # not implemented yet + chtype => "tabutils", # not implemented yet + nr => "tabutils", # not implemented yet tabgrep => "tabutils" }; } @@ -72,45 +54,15 @@ my %usage = ( nodeadd => "Usage: nodeadd [table.column=value] [table.column=value] ...", noderm => "Usage: noderm ", - tabdump => - "Usage: tabdump \n where is one of the following:\n " - . join("\n ", keys %xCAT::Schema::tabspec), - tabrestore => "Usage: tabrestore .csv", + # the usage for tabdump is in the tabdump function + #tabdump => "Usage: tabdump \n where is one of the following:\n " . join("\n ", keys %xCAT::Schema::tabspec), + # the usage for tabrestore is in the tabrestore client cmd + #tabrestore => "Usage: tabrestore .csv", ); ##################################################### # Process the command ##################################################### -sub gettab -{ - my $req = shift; - my $callback = shift; - my $keyspec = shift @{$req->{arg}}; - my @keypairs = split /,/, $keyspec; - my %keyhash; - foreach (@keypairs) - { - (my $key, my $value) = split /=/, $_; - $keyhash{$key} = $value; - } - my %tabhash; - foreach my $tabvalue (@{$req->{arg}}) - { - (my $table, my $column) = split /\./, $tabvalue; - $tabhash{$table}->{$column} = 1; - } - foreach my $tabn (keys %tabhash) - { - my $tab = xCAT::Table->new($tabn); - (my $ent) = $tab->getAttribs(\%keyhash, keys %{$tabhash{$tabn}}); - foreach my $coln (keys %{$tabhash{$tabn}}) - { - $callback->({data => ["$tabn.$coln:" . $ent->{$coln}]}); - } - $tab->close; - } -} - sub process_request { use Getopt::Long; @@ -141,11 +93,11 @@ sub process_request } elsif ($command eq "nodeadd" or $command eq "addnode") { - return chnode($nodes, $args, $callback, 1); + return nodech($nodes, $args, $callback, 1); } - elsif ($command eq "nodech" or $command eq "chnode") + elsif ($command eq "nodech" or $command eq "nodech") { - return chnode($nodes, $args, $callback, 0); + return nodech($nodes, $args, $callback, 0); } elsif ($command eq "tabrestore") { @@ -171,6 +123,36 @@ sub process_request } +sub gettab +{ + my $req = shift; + my $callback = shift; + my $keyspec = shift @{$req->{arg}}; + my @keypairs = split /,/, $keyspec; + my %keyhash; + foreach (@keypairs) + { + (my $key, my $value) = split /=/, $_; + $keyhash{$key} = $value; + } + my %tabhash; + foreach my $tabvalue (@{$req->{arg}}) + { + (my $table, my $column) = split /\./, $tabvalue; + $tabhash{$table}->{$column} = 1; + } + foreach my $tabn (keys %tabhash) + { + my $tab = xCAT::Table->new($tabn); + (my $ent) = $tab->getAttribs(\%keyhash, keys %{$tabhash{$tabn}}); + foreach my $coln (keys %{$tabhash{$tabn}}) + { + $callback->({data => ["$tabn.$coln:" . $ent->{$coln}]}); + } + $tab->close; + } +} + sub noderm { my $nodes = shift; @@ -219,11 +201,12 @@ sub noderm push @tablist, $_; } } - chnode($nodes, \@tablist, $cb, 0); + nodech($nodes, \@tablist, $cb, 0); } sub tabrestore { + # the usage for tabrestore is in the tabrestore client cmd #request->{data} is an array of CSV formatted lines my $request = shift; @@ -231,9 +214,8 @@ sub tabrestore my $table = $request->{table}->[0]; my $linenumber = 1; my $tab = xCAT::Table->new($table, -create => 1, -autocommit => 0); - unless ($tab) - { - $cb->({error => "Unable to open $table"}); + unless ($tab) { + $cb->({error => "Unable to open $table",errorcode=>4}); return; } $tab->delEntries(); #Yes, delete *all* entries @@ -269,8 +251,7 @@ sub tabrestore { error => "CSV missing opening \" for record with \" characters on line $linenumber, character " - . index($origline, $line) - . ": $origline" + . index($origline, $line) . ": $origline", errorcode=>4 } ); next LINE; @@ -291,8 +272,7 @@ sub tabrestore { error => "CSV unmatched \" in record on line $linenumber, character " - . index($origline, $line) - . ": $origline" + . index($origline, $line) . ": $origline", errorcode=>4 } ); next LINE; @@ -317,8 +297,7 @@ sub tabrestore { error => "CSV unescaped \" in record on line $linenumber, character " - . index($origline, $line) - . ": $origline" + . index($origline, $line) . ": $origline", errorcode=>4 } ); $rollback = 1; @@ -335,12 +314,7 @@ sub tabrestore if ($line) { $rollback = 1; - $cb->( - { - error => - "Too many fields on line $linenumber: $origline | $line" - } - ); + $cb->({error => "Too many fields on line $linenumber: $origline | $line", errorcode=>4}); next LINE; } @@ -349,14 +323,7 @@ sub tabrestore if (not defined($rc[0])) { $rollback = 1; - $cb->( - { - error => "DB error " - . $rc[1] - . " with line $linenumber: " - . $origline - } - ); + $cb->({error => "DB error " . $rc[1] . " with line $linenumber: " . $origline, errorcode=>4}); } } if ($rollback) @@ -374,69 +341,79 @@ sub tabrestore sub tabdump { - - #TODO: need to return header for not-yet existing, but schemad tabs - #TODO: schema defined column order. my $args = shift; my $cb = shift; my $table = ""; - foreach (@$args) - { - unless (/^-/) - { - if ($table) - { - return 1; #TODO: Error, usage - } - $table = $_; - } + my $HELP; + + sub tabdump_usage { + my $exitcode = shift @_; + my %rsp; + push @{$rsp{data}}, "Usage: tabdump [table]"; + push @{$rsp{data}}, " tabdump [-?|-h|--help]"; + if ($exitcode) { $rsp{errorcode} = $exitcode; } + $cb->(\%rsp); } - my $tabh = xCAT::Table->new($table); + + # Process arguments + @ARGV = @{$args}; + if (!GetOptions('h|?|help' => \$HELP)) { tabdump_usage(1); return; } + + if ($HELP) { tabdump_usage(0); return; } + if (scalar(@ARGV)>1) { tabdump_usage(1); return; } + my %rsp; + # If no arguments given, we display a list of the tables + if (!scalar(@ARGV)) { + push @{$rsp{data}}, keys %xCAT::Schema::tabspec; + $cb->(\%rsp); + return; + } + + $table = $ARGV[0]; + my $tabh = xCAT::Table->new($table); + + sub tabdump_header { + my $header = "#" . join(",", @_); + push @{$rsp{data}}, $header; + } + + # If the table does not exist yet (because its never been written to), + # at least show the header (the column names) unless ($tabh) { if (defined($xCAT::Schema::tabspec{$table})) { - my $header = join ",", @{$xCAT::Schema::tabspec{$table}->{cols}}; - $header = "#" . $header; - push @{$rsp{data}}, $header; - $cb->(\%rsp); + tabdump_header(@{$xCAT::Schema::tabspec{$table}->{cols}}); + $cb->(\%rsp); return; } - $cb->({error => "No such table: $table"}); + $cb->({error => "No such table: $table",errorcode=>1}); return 1; } + my $recs = $tabh->getAllEntries(); my $rec; - my $firstline = 1; - unless (@$recs) + unless (@$recs) # table exists, but is empty. Show header. { if (defined($xCAT::Schema::tabspec{$table})) { - my $header = join ",", @{$xCAT::Schema::tabspec{$table}->{cols}}; - $header = "#" . $header; - push @{$rsp{data}}, $header; - $cb->(\%rsp); + tabdump_header(@{$xCAT::Schema::tabspec{$table}->{cols}}); + $cb->(\%rsp); return; } } + # Display all the rows of the table in the order of the columns in the schema + tabdump_header(@{$tabh->{colnames}}); foreach $rec (@$recs) { my $line = ''; - if ($firstline) - { - $firstline = 0; - $line = join ",", @{$tabh->{colnames}}; - $line = "#" . $line; - push @{$rsp{data}}, $line; - $line = ''; - } foreach (@{$tabh->{colnames}}) { - $rec->{$_} =~ s/"/""/g; if (defined $rec->{$_}) { + $rec->{$_} =~ s/"/""/g; $line = $line . '"' . $rec->{$_} . '",'; } else @@ -444,14 +421,14 @@ sub tabdump $line .= ','; } } - $line =~ s/,$//; + $line =~ s/,$//; # remove the extra comma at the end $line = $line . $lineappend; push @{$rsp{data}}, $line; } $cb->(\%rsp); } -sub chnode +sub nodech { my $nodes = shift; my $args = shift;