version 1.2, 2003/08/12 09:58:49
|
version 1.27, 2003/12/22 12:02:19
|
Line 33
|
Line 33
|
# it is a command line tool. The following command line syntax (usage) |
# it is a command line tool. The following command line syntax (usage) |
# is supported: |
# is supported: |
# |
# |
# lonManage -push <tablename> newfile host |
# lonManage -push <tablename> newfile [host] |
# Push <tablename> to the lonTabs directory. Note that |
# Push <tablename> to the lonTabs directory. Note that |
# <tablename> must be one of: |
# <tablename> must be one of: |
# hosts (hosts.tab) |
# host (hosts.tab) |
# domain (domain.tab) |
# domain (domain.tab) |
# |
# |
# lonManage -reinit lonc host |
# lonManage -reinit lonc [host] |
# Sends a HUP signal to the remote systems's lond. |
# Sends a HUP signal to the remote systems's lond. |
# |
# |
# lonmanage -reinit lond host |
# lonmanage -reinit lond [host] |
# Requests the remote system's lond perform the same action as if |
# Requests the remote system's lond perform the same action as if |
# it had received a HUP signal. |
# it had received a HUP signal. |
# |
# |
# In the above syntax, the host above is the hosts.tab name of a host, |
# In the above syntax, the host above is the hosts.tab name of a host, |
# not the IP address of the host. |
# not the IP address of the host |
# |
# |
# $Log$ |
# If [host] is not supplied, every host in the client's hosts.tab |
# Revision 1.2 2003/08/12 09:58:49 foxr |
# table is iterated through and procesed.. |
# Add usage and skeleton documentation. |
|
# |
# |
# |
# |
|
|
sub Usage {} { |
|
|
|
|
# Modules required: |
|
|
|
use lib "."; |
|
|
|
use strict; # Because it's good practice. |
|
use English; # Cause I like meaningful names. |
|
use Getopt::Long; |
|
use LondConnection; |
|
use IO::Poll qw(POLLRDNORM POLLWRNORM POLLIN POLLHUP POLLOUT); |
|
|
|
# File scoped variables: |
|
|
|
my %perlvar; # Perl variable defs from apache config. |
|
my %hostshash; # Host table as a host indexed hash. |
|
|
|
my $MyHost=""; # Host name to use as me. |
|
my $ForeignHostTab=""; # Name of foreign hosts table. |
|
|
|
my $DefaultServerPort = 5663; # Default server port if standalone. |
|
my $ServerPort; # Port used to connect to lond. |
|
|
|
my $TransitionTimeout = 5; # Poll timeout in seconds. |
|
|
|
|
|
# LondConnection::SetDebug(10); |
|
|
|
|
|
# |
|
# prints out utility's command usage info. |
|
# |
|
sub Usage { |
print "Usage:"; |
print "Usage:"; |
print <<USAGE; |
print <<USAGE; |
lonManage -push <tablename> newfile host |
lonManage [--myname=host --hosts=table] --push=<tablename> newfile [host] |
Push <tablename> to the lonTabs directory. Note that |
Push <tablename> to the lonTabs directory. Note that |
<tablename> must be one of: |
<tablename> must be one of: |
hosts (hosts.tab) |
host (hosts.tab) |
domain (domain.tab) |
domain (domain.tab) |
|
|
lonManage -reinit lonc host |
lonManage [--myname=host --hosts=table] --reinit=lonc [host] |
Sends a HUP signal to the remote systems's lond. |
Causes lonc in the remote system to reread hosts.tab and |
|
adjust the set of clients that are being maintained to match |
lonmanage -reinit lond host |
the new file. |
Requests the remote system's lond perform the same action as if |
|
it had received a HUP signal. |
|
|
lonManage [--myname=host --hosts=table] --reinit=lond [host] |
|
Causes lond in the remote system to reread the hosts.tab file |
|
and adjust the set of servers to match changes in that file. |
|
|
In the above syntax, the host above is the hosts.tab name of a host, |
In the above syntax, the host above is the hosts.tab name of a host, |
not the IP address of the host. |
not the IP address of the host. |
|
|
|
If [host] is omitted, all hosts in the hosts.tab file are iterated |
|
over. |
|
|
|
lonManage [--myname=host --hosts=table] --edit=<tablename> editscript [host] |
|
Requests lond edit the hosts or domain table (selected by |
|
tablename) with the editing command in editscript. If |
|
host is supplied the individual host is operated on, |
|
otherwise, the entire cluster is operated on. |
|
The edit file has edit request, one per line of the form: |
|
append|newline |
|
replace|key|newline |
|
delete|key |
|
The key is a loncapa hostname if editing the host file |
|
or a domain name if editing the domain table file. |
|
|
|
For all of the above syntaxes if --myname=host and --hosts=table are |
|
supplied (both must be present), the utility runs in standalone mode |
|
presenting itself to the world as 'host' and using the hosts.tab file |
|
specified in the --hosts switch. |
USAGE |
USAGE |
|
|
|
|
} |
} |
|
|
# |
# |
# If command parsing failed, then print usage: |
# Make a direct connection to the lond in 'host'. The port is |
|
# gotten from the global variable: ServerPort. |
|
# Returns: |
|
# The connection or undef if one could not be formed. |
|
# |
|
sub MakeLondConnection { |
|
my $host = shift; |
|
|
|
my $Connection = LondConnection->new($host, $ServerPort); |
|
return return $Connection; |
|
} |
|
# |
|
# Process the connection state machine until the connection |
|
# becomes idle. This is used both to negotiate the initial |
|
# connection, during which the LondConnection sequences a rather |
|
# complex state machine and during the transaction itself |
|
# for a simpler set of transitions. |
|
# All we really need to be concerned with is whether or not |
|
# we're readable or writable and the final state: |
|
# |
|
# Parameter: |
|
# connection - Represents the LondConnection to be sequenced. |
|
# timeout - Maximum time to wait for readable/writable sockets. |
|
# in seconds. < 0 waits forever. |
|
# Return: |
|
# 'ok' - We got to idle ok. |
|
# 'error:msg' - An error occured. msg describes the error. |
|
# |
|
sub SequenceStateMachine { |
|
my $connection = shift; |
|
my $timeout = shift; |
|
|
|
my $Socket = $connection->GetSocket; |
|
my $returnstatus = "ok"; # optimist!!! |
|
my $error = 0; # Used to force early loop termination |
|
# damned perl has no break!!. |
|
my $state = $connection->GetState; |
|
|
|
while(($connection->GetState ne "Idle") && (!$error)) { |
|
# |
|
# Figure out what the connection wants. read/write and wait for it |
|
# or for the timeout. |
|
# |
|
my $wantread = $connection->WantReadable; |
|
my $poll = new IO::Poll; |
|
$poll->mask($Socket, => $wantread ? POLLIN : POLLOUT); |
|
my $handlecount = $poll->poll($timeout); |
|
if($handlecount == 0) { # no handles ready... timeout!! |
|
$returnstatus = "error:"; |
|
$returnstatus .= "Timeout in state $state\n"; |
|
$error = 1; |
|
} else { |
|
my $done = $poll->handles(); |
|
my $status; |
|
$status = $wantread ? $connection->Readable : |
|
$connection->Writable; |
|
if($status != 0) { |
|
$returnstatus = "error:"; |
|
$returnstatus .= " I/O failed in state $state\n"; |
|
$error = 1; |
|
} |
|
} |
|
$state = $connection->GetState; |
|
} |
|
return $returnstatus; |
|
} |
|
|
|
# |
|
# This function runs through the section of the connection |
|
# state machine that has to do with negotiating the startup |
|
# sequence with lond. The general strategy is to loop |
|
# until the connection state becomes idle or disconnected. |
|
# Disconnected indicates an error or rejection of the |
|
# connection at some point in the negotiation. |
|
# idle indicates a connection ready for a request. |
|
# The main loop consults the object to determine if it |
|
# wants to be writeable or readable, waits for that |
|
# condition on the socket (with timeout) and then issues |
|
# the appropriate LondConnection call. Note that |
|
# LondConnection is capable of doing everything necessary |
|
# to get to the initial idle state. |
|
# |
|
# |
|
# Parameters: |
|
# connection - A connection that has been created with |
|
# the remote lond. This connection should |
|
# be in the Connected state ready to send |
|
# the init sequence. |
|
# |
|
sub NegotiateStartup { |
|
my $connection = shift; |
|
my $returnstatus = "ok"; # Optimistic!!. |
|
|
|
my $state = $connection->GetState; |
|
if($state ne "Connected") { |
|
print "Error: Initial lond connection state: $state should be Connected\n"; |
|
return "error"; |
|
} |
|
|
|
return SequenceStateMachine($connection, $TransitionTimeout); |
|
} |
|
# |
|
# Perform a transaction with the remote lond. |
|
# Paramters: |
|
# connection - the connection object that represents |
|
# a LondConnection to the remote lond. |
|
# command - The request to send to the remote system. |
|
# Returns: |
|
# The 'reaction' of the lond to this command. |
|
# However if the connection to lond is lost during the transaction |
|
# or some other error occurs, the text "error:con_lost" is returned. |
|
# |
|
sub PerformTransaction { |
|
my $connection = shift; |
|
my $command = shift; |
|
my $retval; # What we'll returnl. |
|
|
|
|
|
# Set up the connection to do the transaction then |
|
# do the I/O until idle or error. |
|
# |
|
$connection->InitiateTransaction($command); |
|
|
|
my $status = SequenceStateMachine($connection, $TransitionTimeout); |
|
if($status eq "ok") { |
|
$retval = $connection->GetReply; |
|
} else { |
|
$retval = $status; |
|
} |
|
|
|
return $retval; |
|
} |
|
# |
|
# Performs a transaction direct to a remote lond. |
|
# Parameter: |
|
# cmd - The text of the request. |
|
# host - The host to which the request ultimately goes. |
|
# Returns: |
|
# The text of the reply from the lond or con_lost if not able to contact |
|
# lond/lonc etc. |
|
# |
|
sub subreply { |
|
my $cmd = shift; |
|
my $host = shift; |
|
|
|
|
|
my $connection = MakeLondConnection($host); |
|
if ($connection eq undef) { |
|
return "Connect Failed"; |
|
} |
|
my $reply = NegotiateStartup($connection); |
|
if($reply ne "ok") { |
|
return "connection negotiation failed"; |
|
} |
|
my $reply = PerformTransaction($connection, $cmd); |
|
return $reply; |
|
|
|
|
|
} |
|
# |
|
# Use Getopt::Long to parse the parameters of the program. |
|
# |
|
# Return value is a list consisting of: |
|
# A 'command' which is one of: |
|
# push - table push requested. |
|
# reinit - reinit requested. |
|
# Additional parameters as follows: |
|
# for push: Tablename, hostname |
|
# for reinit: Appname hostname |
|
# |
|
# This function does not validation of the parameters of push and |
|
# reinit. |
|
# |
|
# returns a list. The first element of the list is the operation name |
|
# (e.g. reinit or push). The second element is the switch parameter. |
|
# for push, this is the table name, for reinit, this is the process name. |
|
# Additional elements of the list are the command argument. The count of |
|
# command arguments is validated, but not their semantics. |
|
# |
|
# returns an empty list if the parse fails. |
|
# |
|
|
|
|
|
sub ParseArgs { |
|
my $pushing = ''; |
|
my $reinitting = ''; |
|
my $editing = ''; |
|
|
|
if(!GetOptions('push=s' => \$pushing, |
|
'reinit=s' => \$reinitting, |
|
'edit=s' => \$editing, |
|
'myname=s' => \$MyHost, |
|
'hosts=s' => \$ForeignHostTab)) { |
|
return (); |
|
} |
|
# The --myname and --hosts switch must have values and |
|
# most both appear if either appears: |
|
|
|
if(($MyHost ne "") && ($ForeignHostTab eq "")) { |
|
return (); |
|
} |
|
if(($ForeignHostTab ne "") && ($MyHost eq "")) { |
|
return (); |
|
} |
|
|
|
# Require exactly one of --push, --reinit, or --edit |
|
|
|
my $command = ''; |
|
my $commandarg = ''; |
|
my $paramcount = @ARGV; # Number of additional arguments. |
|
|
|
my $commands = 0; # Number of commands seen. |
|
|
|
if($pushing ne '') { |
|
|
|
# --push takes in addition a table, and an optional host: |
|
# |
|
|
|
if(($paramcount != 2) && ($paramcount != 1)) { |
|
return (); # Invalid parameter count. |
|
} |
|
|
|
$commands++; # Count a command seen. |
|
$command = 'push'; |
|
$commandarg = $pushing; |
|
} |
|
|
|
if ($reinitting ne '') { |
|
|
|
# --reinit takes in addition just an optional host name |
|
|
|
if($paramcount > 1) { |
|
return (); |
|
} |
|
$commands++; # Count a command seen. |
|
$command = 'reinit'; |
|
$commandarg = $reinitting; |
|
} |
|
|
|
# --edit takes a script file and optional host name. |
|
# |
|
if ($editing ne "") { |
|
if(($paramcount != 2) && ($paramcount != 1)) { |
|
return (); # Invalid parameter count. |
|
} |
|
|
|
$commands++; # Count a command seen. |
|
$command = 'edit'; |
|
$commandarg = $editing; |
|
} |
|
|
|
# At this point, $commands must be 1 or else we've seen |
|
# The wrong number of command switches: |
|
|
|
if($commands != 1) { |
|
return (); |
|
} |
|
|
|
# Build the result list: |
|
|
|
my @result = ($command, $commandarg); |
|
my $i; |
|
for($i = 0; $i < $paramcount; $i++) { |
|
push(@result, $ARGV[$i]); |
|
} |
|
|
|
return @result; |
|
} |
|
# |
|
# Build the editor script. This function: |
|
# - Opens the edit script file. |
|
# - Reads each line of the edit script file |
|
# - Replaces the ending \n with a / |
|
# - Appends it to the EditScript variable. |
|
# - Returns the contents of the EditScript variable. |
|
# Parameters: |
|
# tabletype - The type of table being built: |
|
# hosts or domain |
|
# scriptname - The script input file. |
|
# |
|
sub BuildEditScript { |
|
my $TableType = shift; |
|
my $ScriptName = shift; |
|
|
|
#Stub |
|
|
|
my @EditScript = ( |
|
"$TableType\:append|". |
|
"nscll2|nscl\:library\:lonkashy.nscl.msu.edu\:35.8.32.89\n" |
|
."delete|nscll2" |
|
); |
|
return \@EditScript; |
|
} |
|
# Read the loncapa configuration stuff. If ForeignHostTab is empty, |
|
# assume we are part of a loncapa cluster and read the hosts.tab |
|
# file from the config directory. Otherwise, ForeignHossTab |
|
# is the name of an alternate configuration file to read in |
|
# standalone mode. |
|
# |
|
sub ReadConfig { |
|
|
|
|
|
|
|
if($ForeignHostTab eq "") { |
|
my $perlvarref = LondConnection::read_conf('loncapa.conf'); |
|
%perlvar = %{$perlvarref}; |
|
my $hoststab = LondConnection::read_hosts( |
|
"$perlvar{lonTabDir}/hosts.tab"); |
|
%hostshash = %{$hoststab}; |
|
$MyHost = $perlvar{lonHostID}; # Set hostname from vars. |
|
$ServerPort = $perlvar{londPort}; |
|
} else { |
|
|
|
LondConnection::ReadForeignConfig($MyHost, |
|
$ForeignHostTab); |
|
my $hoststab = LondConnection::read_hosts($ForeignHostTab); # we need to know too. |
|
%hostshash = %{$hoststab}; |
|
$ServerPort = $DefaultServerPort; |
|
} |
|
} |
|
# |
|
# Determine if the target host is valid. |
|
# This is done by reading the current hosts.tab file. |
|
# For the host to be valid, it must be inthe file. |
|
# |
|
# Parameters: |
|
# host - Name of host to check on. |
|
# Returns: |
|
# true if host is valid. |
|
# false if host is invalid. |
|
# |
|
sub ValidHost { |
|
my $host = shift; |
|
|
|
|
|
return defined $hostshash{$host}; |
|
|
|
} |
|
|
|
|
|
|
|
# |
|
# Performs a transaction with lonc. |
|
# By the time this is called, the transaction has already been |
|
# validated by the caller. |
|
# |
|
# Parameters: |
|
# |
|
# host - hosts.tab name of the host whose lonc we'll be talking to. |
|
# command - The base command we'll be asking lond to execute. |
|
# body - [optional] If supplied, this is a command body that is a ref. |
|
# to an array of lines that will be appended to the |
|
# command. |
|
# |
|
# NOTE: |
|
# The command will be done as an encrypted operation. |
|
# |
|
sub Transact { |
|
my $host = shift; |
|
my $command = shift; |
|
my $haveBody= 0; |
|
my $body; |
|
my $i; |
|
|
|
if(scalar @ARG) { |
|
$body = shift; |
|
$haveBody = 1; |
|
} |
|
# Construct the command to send to the server: |
|
|
|
my $request = "encrypt\:"; # All requests are encrypted. |
|
$request .= $command; |
|
if($haveBody) { |
|
$request .= "\:"; |
|
my $bodylines = scalar @$body; |
|
for($i = 0; $i < $bodylines; $i++) { |
|
$request .= $$body[$i]; |
|
} |
|
} else { |
|
$request .= "\n"; |
|
} |
|
# Body is now built... transact with lond.. |
|
|
|
my $answer = subreply($request, $host); |
|
|
|
print "$answer\n"; |
|
|
|
} |
|
# |
|
# Called to push a file to the remote system. |
|
# The only legal files to push are hosts.tab and domain.tab. |
|
# Security is somewhat improved by |
|
# |
|
# - Requiring the user run as root. |
|
# - Connecting with lonc rather than lond directly ensuring this is a loncapa |
|
# host |
|
# - We must appear in the remote host's hosts.tab file. |
|
# - The host must appear in our hosts.tab file. |
|
# |
|
# Parameters: |
|
# tablename - must be one of hosts or domain. |
|
# tablefile - name of the file containing the table to push. |
|
# host - name of the host to push this file to. |
|
# |
|
# |
|
# |
|
sub PushFile { |
|
my $tablename = shift; |
|
my $tablefile = shift; |
|
my $host = shift; |
|
|
|
# Open the table file: |
|
|
|
if(!open(TABLEFILE, "<$tablefile")) { |
|
die "ENOENT - No such file or directory $tablefile"; |
|
} |
|
|
|
# Require that the host be valid: |
|
|
|
if(!ValidHost($host)) { |
|
die "EHOSTINVAL - Invalid host $host"; # Ok so I invented this 'errno'. |
|
} |
|
# Read in the file. If the table name is valid, push it. |
|
|
|
my @table = <TABLEFILE>; # These files are pretty small. |
|
close TABLEFILE; |
|
|
|
if( ($tablename eq "host") || |
|
($tablename eq "domain")) { |
|
print("Pushing $tablename to $host\n"); |
|
Transact($host, "pushfile:$tablename",\@table); |
|
} else { |
|
die "EINVAL - Invalid parameter. tablename: $tablename must be host or domain"; |
|
} |
|
} |
|
# |
|
# This function forms and executes an edit file with a |
|
# remote lond server. We build the full transaction string |
|
# and use Transact to perform the transaction. |
|
# Paramters: |
|
# host - loncapa name of host to operate on. |
|
# body - Body of the command. We send: |
|
# edit:$body as the command request. |
|
# |
|
sub EditFile { |
|
my $host = shift; |
|
my $body = shift; |
|
|
|
if(!ValidHost($host)) { |
|
die "EHOSTINVAL - Invalid host $host"; |
|
} |
|
Transact($host, "edit", $body); |
|
} |
|
|
|
# |
|
# This function is called to reinitialize a server in a remote host. |
|
# The servers that can be reinitialized are: |
|
# - lonc - The lonc client process. |
|
# - lond - The lond daemon. |
|
# NOTE: |
|
# Reinitialization in this case means re-scanning the hosts table, |
|
# starting new lond/lonc's as approprate and stopping existing lonc/lond's. |
|
# |
|
# Parameters: |
|
# process - The name of the process to reinit (lonc or lond). |
|
# host - The host in which this reinit will happen. |
|
# |
|
# >>>BUGBUG<<<< This belongs in lonnet.pm |
|
# |
|
sub ReinitProcess { |
|
my $process = shift; |
|
my $host = shift; |
|
|
|
# Ensure the host is valid: |
|
|
|
if(!ValidHost($host)) { |
|
die "EHOSTINVAL - Invalid host $host"; |
|
} |
|
# Ensure target process selector is valid: |
|
|
|
if(($process eq "lonc") || |
|
($process eq "lond")) { |
|
print("Reinitializing $process in $host\n"); |
|
Transact($host, "reinit:$process"); |
|
} else { |
|
die "EINVAL -Invalid parameter. Process $process must be lonc or lond"; |
|
} |
|
} |
|
#--------------------------- Entry point: -------------------------- |
|
|
|
|
|
|
if(0) { |
# Parse the parameters |
|
# If command parsing failed, then print usage: |
|
|
|
my @params = ParseArgs; |
|
my $nparam = @params; |
|
|
|
if($nparam == 0) { |
Usage; |
Usage; |
|
exit -1; |
|
} |
|
# |
|
# Next, ensure we are running as EID root. |
|
# |
|
if ($EUID != 0) { |
|
die "ENOPRIV - No privilege for requested operation" |
|
} |
|
|
|
# |
|
# Read the configuration file. |
|
# |
|
|
|
ReadConfig; # Read the configuration info (incl.hosts). |
|
|
|
# Based on the operation requested invoke the appropriate function: |
|
|
|
my $operation = shift @params; |
|
|
|
if($operation eq "push") { # push tablename filename host |
|
my $tablename = shift @params; |
|
my $tablefile = shift @params; |
|
my $host = shift @params; |
|
if($host) { |
|
PushFile($tablename, $tablefile, $host); |
|
} else { # Push to whole cluster. |
|
foreach my $host (keys %hostshash) { |
|
PushFile($tablename, $tablefile, $host); |
|
} |
|
} |
|
|
|
} elsif($operation eq "reinit") { # reinit processname host. |
|
my $process = shift @params; |
|
my $host = shift @params; |
|
if ($host) { |
|
ReinitProcess($process, $host); |
|
} else { # Reinit whole cluster. |
|
foreach my $host (keys %hostshash) { |
|
ReinitProcess($process,$host); |
|
} |
|
} |
|
} elsif($operation eq "edit") { # Edit a table. |
|
my $tablename = shift @params; |
|
my $scriptfile = shift @params; |
|
my $host = shift @params; |
|
my $CommandBody = BuildEditScript($tablename, $scriptfile); |
|
if ($host) { |
|
EditFile($host, $CommandBody); |
|
} else { |
|
foreach my $ClusterMember (keys %hostshash) { |
|
EditFile($ClusterMember, $CommandBody); |
|
} |
|
} |
} |
} |
|
else { |
|
Usage; |
|
} |
|
exit 0; |
|
|
=head1 NAME |
=head1 NAME |
lonManage - Command line utility for remote management of lonCAPA |
lonManage - Command line utility for remote management of lonCAPA |
Line 92 if(0) {
|
Line 699 if(0) {
|
=head1 SYNOPSIS |
=head1 SYNOPSIS |
|
|
Usage: |
Usage: |
B<lonManage -push <tablename> newfile host> |
B<lonManage --push=<tablename> newfile host> |
Push <tablename> to the lonTabs directory. Note that |
Push <tablename> to the lonTabs directory. Note that |
<tablename> must be one of: |
<tablename> must be one of: |
hosts (hosts.tab) |
hosts (hosts.tab) |
domain (domain.tab) |
domain (domain.tab) |
|
|
B<lonManage -reinit lonc host> |
B<lonManage --reinit=lonc host> |
Sends a HUP signal to the remote systems's lond. |
Sends a HUP signal to the remote systems's lond. |
|
|
B<lonmanage -reinit lond host> |
B<lonManage --reinit=lond host> |
Requests the remote system's lond perform the same action as if |
Requests the remote system's lond perform the same action as if |
it had received a HUP signal. |
it had received a HUP signal. |
|
|
In the above syntax, the host above is the hosts.tab name of a host, |
B<lonManage --edit=<tablename> editscript host> |
not the IP address of the host. |
Requests the remote system's lond perform an edit |
|
on <tablename> editscript supplies a set of |
|
editing commands. Each edit command is one of : |
|
|
|
append|key|newline |
|
delete|key| |
|
replace|key|newline |
|
|
|
The key above is the value of the loncapa host name |
|
in the file. |
|
|
|
In the above syntax, the host above is the |
|
hosts.tab name of a host, |
|
not the IP address of the host. |
|
|
|
|
=head1 DESCRIPTION |
=head1 DESCRIPTION |
|
|
=head1 PREREQUISITES |
=head1 PREREQUISITES |
|
|
|
=item strict |
|
=item Getopt::Long |
|
=item English |
|
=item IO::Socket::UNIX |
|
=item LONCAPA::LondConnection |
|
|
|
=head1 KEY Subroutines. |
|
|
=head1 CATEGORIES |
=head1 CATEGORIES |
Command line utility |
Command line utility |
|
|