summaryrefslogtreecommitdiff
path: root/conmux
diff options
context:
space:
mode:
authorapw <apw@592f7852-d20e-0410-864c-8624ca9c26a4>2006-12-07 21:01:14 +0000
committerapw <apw@592f7852-d20e-0410-864c-8624ca9c26a4>2006-12-07 21:01:14 +0000
commit4c6ec3807c2b038e0d762041b745124a831f9cfa (patch)
tree0cfbcb0ce9dfe3fecfde5cc3397faf5ae919d0d9 /conmux
parenta43eb8a274e0da9f48b6d4914c5cb72ca924d354 (diff)
conmux: initial import of the Console Multiplexor
Import the Console Multiplexor (CONMUX) subsystem, developed by the IBM Linux Technology Center. CONMUX is a console abstractor, presenting any console with a consistent location, naming and semantic. Access to the console and hardreset of the machine is the same regardless of the underlying access methodology. Through this abstraction we create a simple and consistent interface to disparate consoles simplifying programatic use of the console. This provides for easy integration of console handling into a test harness. This allow CONMUX to be used to capture Linux kernel messages only available on the external system console, as well as providing a framework for detecting, diagnosing and rescuing paniced and hung systems. Signed-off-by: Andy Whitcroft <andyw@uk.ibm.com> Acked-by: Dustin Kirkland <dustin.kirkland@us.ibm.com> git-svn-id: svn://test.kernel.org/autotest/trunk@425 592f7852-d20e-0410-864c-8624ca9c26a4
Diffstat (limited to 'conmux')
-rw-r--r--conmux/.version1
-rw-r--r--conmux/COPYING348
-rw-r--r--conmux/Conmux.pm209
-rwxr-xr-xconmux/INSTALL34
-rw-r--r--conmux/Makefile47
-rw-r--r--conmux/README61
-rwxr-xr-xconmux/conmux1289
-rwxr-xr-xconmux/conmux-attach88
-rwxr-xr-xconmux/conmux-registry282
-rw-r--r--conmux/conmux.html158
-rwxr-xr-xconmux/console192
-rwxr-xr-xconmux/drivers/blade234
-rwxr-xr-xconmux/drivers/hmc597
-rwxr-xr-xconmux/drivers/module.mk15
-rwxr-xr-xconmux/drivers/reboot-netfinity98
-rwxr-xr-xconmux/drivers/reboot-newisys49
-rwxr-xr-xconmux/drivers/reboot-numaq224
-rwxr-xr-xconmux/drivers/reboot-rsa101
-rwxr-xr-xconmux/drivers/reboot-rsa291
-rwxr-xr-xconmux/examples/README11
-rwxr-xr-xconmux/examples/command.cf4
-rwxr-xr-xconmux/examples/socket.cf4
-rwxr-xr-xconmux/helpers/autoboot-helper72
-rwxr-xr-xconmux/helpers/module.mk14
-rwxr-xr-xconmux/helpers/tickle-helper99
-rwxr-xr-xconmux/mkdeb9
-rwxr-xr-xconmux/start138
27 files changed, 4469 insertions, 0 deletions
diff --git a/conmux/.version b/conmux/.version
new file mode 100644
index 00000000..e906e569
--- /dev/null
+++ b/conmux/.version
@@ -0,0 +1 @@
+conmux 1 00 00 1000
diff --git a/conmux/COPYING b/conmux/COPYING
new file mode 100644
index 00000000..80e35181
--- /dev/null
+++ b/conmux/COPYING
@@ -0,0 +1,348 @@
+The Console Multiplexor
+
+(C) Copyright IBM Corp. 2004, 2005, 2006
+
+The Console Multiplexor is released under the GNU Public Licence V2
+a copy of which should be included below.
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/conmux/Conmux.pm b/conmux/Conmux.pm
new file mode 100644
index 00000000..47b8cd27
--- /dev/null
+++ b/conmux/Conmux.pm
@@ -0,0 +1,209 @@
+#
+# Conmux.pm -- core console multiplexor package
+#
+# Implements the core multiplexor functionality such as resolution of
+# names and connecting to the conmux server.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+package Conmux;
+use URI::Escape;
+
+our $Config;
+
+BEGIN {
+ my $cf = '/usr/local/conmux/etc/config';
+ if (-f $cf) {
+ open(CFG, "<$cf") || die "Conmux: $cf: open failed - $!\n";
+ while(<CFG>) {
+ chomp;
+ next if (/^#/ || /^\s*$/ || !/=/);
+
+ my ($name, $value) = split(/=/, $_, 2);
+ $value =~ s/^"//;
+ $value =~ s/"$//;
+
+ # Substitute variables.
+ while ($value =~ /\$([A-Za-z0-9_]+)/) {
+ my $v = $Config->{$1};
+ $value =~ s/\$$1/$v/;
+ }
+ $Config->{$name} = $value;
+ }
+ close(CFG);
+ }
+}
+
+sub encodeArgs {
+ my (%a) = @_;
+ my ($a, $n, $s);
+
+ ##print "0<$_[0]> ref<" . ref($_[0]) . ">\n";
+
+ # Handle being passed references to hashes too ...
+ $a = \%a;
+ $a = $_[0] if (ref($_[0]) eq "HASH");
+
+ for $n (sort keys %{$a}) {
+ $s .= uri_escape($n) . '=' . uri_escape($a->{$n}) .
+ ' ';
+ }
+ chop($s);
+ $s;
+}
+
+sub decodeArgs {
+ my ($s) = @_;
+ my (%a, $nv, $n, $v);
+
+ # Decode the standard argument stream.
+ for $nv (split(' ', $s)) {
+ ($n, $v) = split('=', $nv, 2);
+ $a{uri_unescape($n)} = uri_unescape($v);
+ }
+
+ %a;
+}
+
+sub sendCmd {
+ my ($fh, $c, $a) = @_;
+ my ($rs);
+
+ # Send the encoded command ...
+ print $fh $c . " " . encodeArgs($a) . "\n";
+
+ # Read the reply.
+ $rs = <$fh>;
+ chomp($rs);
+
+ decodeArgs($rs);
+}
+
+sub sendRequest {
+ my ($fh, $c, $a) = @_;
+ my %a = { 'result' => 'more' };
+
+ # Send the encoded command ...
+ print $fh $c . " " . encodeArgs($a) . "\n";
+
+ %a;
+}
+sub revcResult {
+ my ($fh) = @_;
+ my ($rs);
+
+ # Read the reply.
+ $rs = <$fh>;
+ chomp($rs);
+
+ decodeArgs($rs);
+}
+
+#
+# Configuration.
+#
+sub configRegistry {
+ my $reg = $Config->{'registry'};
+
+ $reg = "localhost" if (!$reg);
+ $reg;
+}
+
+# Connect to the host/port specified on the command line,
+# or localhost:23
+sub connect {
+ my ($to) = @_;
+ my ($reg, $sock);
+
+ # host:port
+ if ($to =~ /:/) {
+ # Already in the right form.
+
+ # registry/service
+ } elsif ($to =~ m@(.*)/(.*)@) {
+ my ($host, $service) = ($1, $2);
+
+ $to = Conmux::Registry::lookup($host, $service);
+
+ # service
+ } else {
+ $to = Conmux::Registry::lookup('-', $to);
+ }
+
+ $sock = new IO::Socket::INET(Proto => 'tcp', PeerAddr => $to)
+ or die "Conmux::connect $to: connect failed - $@\n";
+
+ # Turn on keep alives by default.
+ $sock->sockopt(SO_KEEPALIVE, 1);
+
+ $sock;
+}
+
+package Conmux::Registry;
+sub lookup {
+ my ($host, $service) = @_;
+
+ $host = Conmux::configRegistry() if ($host eq '-');
+
+ # Connect to the registry service and lookup the requested service.
+ my $reg = new IO::Socket::INET(Proto => 'tcp',
+ PeerAddr => "$host", PeerPort => 63000)
+ or die "Conmux::connect: registry not available - $@\n";
+
+ my %r = Conmux::sendCmd($reg, 'LOOKUP', { 'service' => $service });
+ die "Conmux::Registry::lookup: $service: error - $r{'status'}\n"
+ if ($r{status} ne "OK");
+
+ close($reg);
+
+ $r{'result'};
+}
+
+sub add {
+ my ($host, $service, $location) = @_;
+
+ $host = Conmux::configRegistry() if ($host eq '-');
+
+ # Connect to the registry service and lookup the requested service.
+ my $reg = new IO::Socket::INET(Proto => 'tcp',
+ PeerAddr => "$host", PeerPort => 63000)
+ or die "Conmux::connect: registry not available - $@\n";
+
+ my %r = Conmux::sendCmd($reg, 'ADD', { 'service' => $service,
+ 'location' => $location });
+ die "Conmux::Registry::add: $service: error - $r{'status'}\n"
+ if ($r{status} ne "OK");
+
+ close($reg);
+
+ 1;
+}
+
+sub list {
+ my ($host, $service, $location) = @_;
+ my (@results, %r);
+
+ $host = Conmux::configRegistry() if ($host eq '-');
+
+ # Connect to the registry service and ask for a list.
+ my $reg = new IO::Socket::INET(Proto => 'tcp',
+ PeerAddr => "$host", PeerPort => 63000)
+ or die "Conmux::connect: registry not available - $@\n";
+
+ %r = Conmux::sendCmd($reg, 'LIST', { });
+## while ($r{'status'} eq 'more') {
+## %r = receiveResult($reg);
+## push(@results, $r{'result'});
+## }
+ die "Conmux::Registry::list: error - $r{'status'}\n"
+ if ($r{'status'} ne "OK");
+
+ close($reg);
+
+ $r{'result'};
+}
+
+1;
diff --git a/conmux/INSTALL b/conmux/INSTALL
new file mode 100755
index 00000000..24f1de35
--- /dev/null
+++ b/conmux/INSTALL
@@ -0,0 +1,34 @@
+Prerequisites
+=============
+This package required the following perl modules to be installed:
+ o IO::Multiplex;
+
+Debian Packages:
+ o libio-multiplex-perl
+
+Fedora Packages:
+ o perl-IO-Multiplex
+
+
+Building
+========
+To make and install this package to the default location
+(/usr/local/conmux):
+
+ # make install
+
+To an alternative location:
+
+ # make PREFIX=/usr/alt/conmux install
+
+To build for a specified prefix, but installed into a temporary tree:
+
+ # make PREFIX=/usr/alt/conmux BUILD=build/location install
+
+
+Legal
+=====
+(C) Copyright IBM Corp. 2004, 2005, 2006
+Author: Andy Whitcroft <andyw@uk.ibm.com>
+
+The Console Multiplexor is released under the GNU Public License V2
diff --git a/conmux/Makefile b/conmux/Makefile
new file mode 100644
index 00000000..f78d205d
--- /dev/null
+++ b/conmux/Makefile
@@ -0,0 +1,47 @@
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+BUILD=
+PREFIX=/usr/local/conmux
+BASE=$(BUILD)$(PREFIX)
+BINS=console conmux-attach
+LIBS=Conmux.pm
+SBIN=conmux-registry conmux start
+
+MODULES=helpers drivers
+
+all::
+
+install::
+ @[ -d $(BASE) ] || mkdir -p $(BASE)
+ @[ -d $(BASE)/bin ] || mkdir $(BASE)/bin
+ @[ -d $(BASE)/lib ] || mkdir $(BASE)/lib
+ @[ -d $(BASE)/sbin ] || mkdir $(BASE)/sbin
+ @[ -d $(BASE)/log ] || mkdir $(BASE)/log
+ @[ -d $(BASE)/etc ] || mkdir $(BASE)/etc
+ for f in $(BINS); do \
+ rm -f $(BASE)/bin/$$f; \
+ cp -p $$f $(BASE)/bin/$$f; \
+ chmod 755 $(BASE)/bin/$$f; \
+ done
+ for f in $(SBIN); do \
+ rm -f $(BASE)/sbin/$$f; \
+ cp -p $$f $(BASE)/sbin/$$f; \
+ chmod 755 $(BASE)/sbin/$$f; \
+ done
+ for f in $(LIBS); do \
+ rm -f $(BASE)/lib/$$f; \
+ cp -p $$f $(BASE)/lib/$$f; \
+ chmod 644 $(BASE)/lib/$$f; \
+ done
+
+release::
+ $(MAKE) BUILD=build install
+ (cd build; tar cf - *) | gzip >conmux.tgz
+ rm -rf build
+
+clean::
+ rm -f conmux.tgz
+
+include $(patsubst %, %/module.mk, $(MODULES))
diff --git a/conmux/README b/conmux/README
new file mode 100644
index 00000000..d9d34c4c
--- /dev/null
+++ b/conmux/README
@@ -0,0 +1,61 @@
+CONMUX -- Console Multiplexor
+=============================
+CONMUX is a console abstractor. Presenting any console with a
+consistent location, naming and semantic. Access to the console,
+and hardreset of the machine is the same regardless of the underlying
+access methodology.
+
+
+License
+-------
+See the COPYING file for details.
+
+
+Installation
+------------
+See the INSTALL file for details.
+
+
+Configuration
+-------------
+A console multiplexor is defined using a per instance configuration file.
+The standard startup scripts expect this to be in the ROOT/etc and have
+a .cf suffix.
+
+Below is an example configuration defining the console for a NUMA-Q:
+
+ $ cat kite.cf
+ # START:autoboot
+ listener kite
+ socket console 'kite console' console.server.here.com:6000
+ command 'hardreset' 'initated a hard reset' \
+ 'conmux-attach -o status localhost/kite reboot-numaq vcs \
+ vcsconsole.server.here.com kite 12346 administrator passwd'
+ $
+
+These are connected to using the console command, using the symbolic console
+name:
+
+ $ console kite
+
+
+Automatic Startup
+-----------------
+See the start script for automated startup of console daemons.
+This script is designed to be used at system boot time out of
+/etc/init.d.
+
+
+Documentation and Examples
+--------------------------
+More detailed information on the workings of conmux can be found in
+'conmux.html'. Example configuration files can be found in the
+'examples' directory.
+
+
+Legal
+-----
+(C) Copyright IBM Corp. 2004, 2005, 2006
+Author: Andy Whitcroft <andyw@uk.ibm.com>
+
+The Console Multiplexor is released under the GNU Public License V2
diff --git a/conmux/conmux b/conmux/conmux
new file mode 100755
index 00000000..6f628433
--- /dev/null
+++ b/conmux/conmux
@@ -0,0 +1,1289 @@
+#!/usr/bin/perl
+#
+# conmux -- the main console multiplexor daemon
+#
+# Main console multiplexor daemon. There is one of these daemons for
+# each open console supported in the system. Clients are directed to
+# this daemon via the conmux-registry deamon.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+use strict;
+
+use Symbol qw(gensym);
+
+use IO::Socket;
+use IO::Multiplex;
+use IPC::Open3;
+use URI::Escape;
+use Net::Domain;
+
+# Find our internal libraries.
+###my $lib = $0; $lib =~ s@/[^/]+$@@;
+###push(@INC, $lib);
+###require Conmux;
+##use lib ".";
+##use Conmux;
+use lib "/usr/local/conmux/lib";
+use Conmux;
+
+our $P = 'conmux';
+our $debug = 0;
+
+$SIG{'CHLD'} = "IGNORE";
+
+$| = 1;
+
+#
+# CALLBACK: this class is used to provide a timed callback. The multiplexor
+# libarary allows us to set a timeout on any open file we have registered.
+# So, we open a new file descriptor to /dev/null and set a timeout on that.
+#
+package Callback;
+
+sub new {
+ my ($class, $mux, $who, $time) = @_;
+ my $self = bless { 'who' => $who }, $class;
+
+ my ($fh);
+
+ print "Callback::new [$self] mux<$mux> who<$who> time<$time>\n"
+ if ($main::debug);
+
+ # Open a file handle to nothing, we need this to hang the timeout
+ # on in the multiplexor. It will fail with a mux_eof, which we ignore.
+ open($fh, "</dev/null") || die "$P: /dev/null: open failed - $!\n";
+
+ $mux->add($fh);
+ $mux->set_callback_object($self, $fh);
+
+ $mux->set_timeout($fh, $time);
+
+ $self;
+}
+sub mux_timeout {
+ my ($self, $mux, $fh) = @_;
+
+ print "Callback::mux_timeout [$self] mux<$mux> fh<$fh>\n"
+ if ($main::debug);
+
+ $self->{'who'}->callback_timeout();
+
+ $mux->close($fh);
+}
+sub DESTROY {
+ my ($self) = @_;
+ print "Callback::DESTROY [$self]\n" if ($main::debug);
+}
+
+#
+# LISTENER SOCKET: creates an intenet listener for new clients and
+# connects them to the junction provided.
+#
+package ListenerSocket;
+
+sub new {
+ my ($class, $mux, $port) = @_;
+ my $self = bless { 'mux' => $mux }, $class;
+
+ print "ListenerSocket::new [$self] mux<$mux> port<$port>\n"
+ if ($main::debug);
+
+ $self->initialise($port);
+
+ $self;
+}
+
+sub initialise {
+ my ($self, $port) = @_;
+ my ($sock);
+
+ print "ListenerSocket::initialise [$self] port<$port> "
+ if ($main::debug);
+
+ # Create a listening socket and add it to the multiplexor.
+ my $sock = new IO::Socket::INET(Proto => 'tcp',
+ LocalPort => $port,
+ Listen => 4,
+ ReuseAddr => 1)
+ or die "socket: $@";
+
+ print " adding $self $sock\n" if ($main::debug);
+ $self->mux->listen($sock);
+ $self->mux->set_callback_object($self, $sock);
+ $self->listener($sock);
+}
+
+# DATA accessors.
+sub mux {
+ my $self = shift;
+ if (@_) { $self->{'mux'} = shift }
+ return $self->{'mux'};
+}
+sub listener {
+ my $self = shift;
+ if (@_) { $self->{'listener'} = shift }
+ return $self->{'listener'};
+}
+
+sub address {
+ my ($self) = @_;
+ Net::Domain::hostfqdn() . ':' . $self->{'listener'}->sockport();
+}
+
+# JUNCTION: callbacks.
+##sub junctionInput {
+##}
+##sub junctionEOF {
+## my ($self) = @_;
+##
+## $self->{'junction'}->junctionRemove($self, 'console-client');
+## $self->{'mux'}->close($self->{'listener'});
+##}
+
+# Handle new connections by instantiating a new client class.
+sub mux_connection {
+ my ($self, $mux, $fh) = @_;
+ my ($client);
+
+ print "ListenerSocket::mux_connection [$self] mux<$mux> fh<$fh>\n"
+ if ($main::debug);
+
+ # Make a new client connection.
+ $client = ClientCmd->new($mux, $fh);
+ print " new connection $self $client\n" if ($main::debug);
+}
+
+sub DESTROY {
+ my ($self) = @_;
+
+ print "ListenerSocket::DESTROY [$self]\n" if ($main::debug);
+
+ close($self->listener);
+}
+
+#
+# JUNCTION: generic junction box object, connects names groups of objects
+# to other named groups.
+#
+# Expects the following callbacks to be defined on each object registered:
+# junctionInput($from, $data)
+# junctionEOF($from, $to)
+#
+package Junction;
+
+sub new {
+ my ($class) = @_;
+ my $self = bless { }, $class;
+
+ print "Junction::new [$self]\n" if ($main::debug);
+
+ $self;
+}
+
+sub junctionAdd {
+ my ($self, $client) = @_;
+
+ print "Junction::junctionAdd [$self] client<$client>\n"
+ if ($main::debug);
+
+ # Add ourselves to the list of recipients.
+ $self->{$client} = $client;
+}
+
+sub junctionInput {
+ my ($self, $client, $data) = @_;
+ my ($c);
+
+ print "Junction::junctionInput [$self] client<$client> " .
+ "data<$data>\n" if ($main::debug);
+
+ # Send this data on to the clients listed in the output list.
+ for $c (values %{$self}) {
+ print " sending to $c\n" if ($main::debug);
+ $c->junctionInput($client, $data);
+ }
+}
+sub junctionEOF {
+ my ($self, $client) = @_;
+ my ($c);
+
+ print "Junction::junctionEOF [$self] client<$client>\n"
+ if ($main::debug);
+
+ # Send this eof on to the clients listed in the output list.
+ for $c (values %{$self}) {
+ print " sending to $c\n" if ($main::debug);
+ $c->junctionEOF($client);
+ }
+}
+sub junctionRemove {
+ my ($self, $client) = @_;
+
+ print "Junction::junctionRemove [$self] client<$client>\n"
+ if ($main::debug);
+
+ # Drop this client from our lists.
+ delete $self->{$client};
+}
+
+#
+# PAYLOAD: generic payload object, connects itself to the requisite junction.
+#
+package Payload;
+
+my %payloads = ();
+my $payloads = 0;
+
+sub lookup {
+ my ($class, $name) = @_;
+
+ $payloads{$name};
+}
+
+sub found {
+ my ($class, $name, $self) = @_;
+
+ print "Payloads::found name<$name> self<$self>\n" if ($main::debug);
+
+ $payloads{$name} = $self;
+ $payloads++;
+}
+sub lost {
+ my ($class, $name, $self) = @_;
+
+ print "Payloads::lost name<$name> self<$self>\n" if ($main::debug);
+
+ undef $payloads{$name};
+ if (--$payloads == 0) {
+ exit(0);
+ }
+}
+
+sub new {
+ my ($class, $name, $title, $mux, @a) = @_;
+ my $self = bless { }, $class;
+
+ print "Payload::new [$self] name<$name> title<$title> mux<$mux>\n"
+ if ($main::debug);
+
+ Payload->found($name, $self);
+
+ $self->name($name);
+ $self->title($title);
+ $self->mux($mux);
+ $self->enabled(1);
+
+ $self->cin(Junction->new);
+ $self->cout(Junction->new);
+
+ $self->initialise(@a);
+
+ $self;
+}
+
+# Data accessors.
+sub name {
+ my $self = shift;
+ if (@_) { $self->{'name'} = shift }
+ return $self->{'name'};
+}
+sub title {
+ my $self = shift;
+ if (@_) { $self->{'title'} = shift }
+ return $self->{'title'};
+}
+sub mux {
+ my $self = shift;
+ if (@_) { $self->{'mux'} = shift }
+ return $self->{'mux'};
+}
+sub cin {
+ my $self = shift;
+ if (@_) { $self->{'cin'} = shift }
+ return $self->{'cin'};
+}
+sub cout {
+ my $self = shift;
+ if (@_) { $self->{'cout'} = shift }
+ return $self->{'cout'};
+}
+sub enabled {
+ my $self = shift;
+ if (@_) { $self->{'enabled'} = shift }
+ return $self->{'enabled'};
+}
+sub connected {
+ my $self = shift;
+ if (@_) { $self->{'connected'} = shift }
+ $self->transition();
+ return $self->{'connected'};
+}
+sub transition {
+ my $self = shift;
+ my $time = time;
+ if (($time - $self->{'trans_minor'}) > 30) {
+ $self->{'trans_major'} = $time;
+ }
+ $self->{'trans_minor'} = $time;
+}
+sub retry_timeout {
+ my $self = shift;
+ my $time = time - $self->{'trans_major'};
+
+ if ($time < 60) {
+ return 1;
+ } elsif ($time < 120) {
+ return 10;
+ } else {
+ return 30;
+ }
+}
+sub state {
+ my $self = shift;
+ my $ctime = $self->{'connected'};
+ my $ttime = $self->{'trans_major'};
+ my $time = time;
+
+ if ($ctime && ($time - $ctime) > 30) {
+ "connected";
+ } elsif ($ttime && ($time - $ttime) < 60) {
+ "transition";
+ } else {
+ "disconnected";
+ }
+}
+
+sub initialise {
+ my ($self) = @_;
+ my ($sock);
+
+ print "Payload::initialise [$self]\n" if ($main::debug);
+
+ # Ensure we recieve client input.
+ $self->cin->junctionAdd($self);
+
+ $self->connected(time);
+}
+
+# Telnet constants.
+my $TN_IAC = sprintf("%c", 255);
+my $TN_DONT = sprintf("%c", 254);
+my $TN_DO = sprintf("%c", 253);
+my $TN_WONT = sprintf("%c", 252);
+my $TN_WILL = sprintf("%c", 251);
+my $TN_SB = sprintf("%c", 250);
+my $TN_SE = sprintf("%c", 240);
+my $TN_BREAK = sprintf("%c", 243);
+
+my $TNOPT_ECHO = sprintf("%c", 1);
+my $TNOPT_SGA = sprintf("%c", 3);
+
+#
+# If we get here then we have accumulated a complete telnet
+# negotiation string.
+#
+# Telnet negotiation protocol - RFC#854:
+#
+# DO We are being asked to DO an option
+# DONT We are being asked to NOT DO an option
+# WILL We are being told they will DO an option
+# WONT We are being told they will NOT DO an option
+#
+# DO/DONT requests indicate we should {en,dis}able a mode.
+# We are expected to respond with WILL or WONT. To prevent
+# loops, we should not respond if the request matches our
+# current mode.
+#
+# WILL/WONT requests indicate the other end would like to
+# {en,dis}able a mode. We are expected to respond with
+# DO/DONT.
+#
+# If we want a particular mode {en,dis}abled then we may start
+# negotiation of that mode with a WILL/WONT.
+#
+# We want the other end to perform echo by default so we will
+# DO any request for ECHO and DONT all other requests.
+#
+
+sub mux_input {
+ my ($self, $mux, $fh, $input) = @_;
+ my ($client);
+
+ print "Payload::mux_input [$self] mux<$mux> fh<$fh> input<$$input>\n"
+ if ($main::debug);
+
+ while ($$input ne "") {
+ # Ordinary text.
+ if ($$input =~ s/^([^$TN_IAC]+)//) {
+ # Data coming in from the payload, this needs to go to
+ # all of the clients.
+ $self->cout->junctionInput($self, $1);
+ next;
+ }
+
+ # IAC,SB,...,SE
+ if ($$input =~ s/^$TN_IAC$TN_SB([^$TN_SE]+)$TN_SE//) {
+ print "SB\n" if ($main::debug);
+ next;
+ }
+ # IAC,[DO|DONT|WILL|WONT],<what>
+ if ($$input =~ s/^$TN_IAC$TN_DO(.)//) {
+ my $c = unpack("C", $1);
+ print "DO<$c:$1>\n" if ($main::debug);
+ # We are DONT on all options so WONT all requests.
+ $self->junctionInput($self, "$TN_IAC$TN_WONT$1");
+ next;
+ }
+ if ($$input =~ s/^$TN_IAC$TN_DONT(.)//) {
+ my $c = unpack("C", $1);
+ print "DONT<$c:$1>\n" if ($main::debug);
+ # We are already DONT on all options, no reply.
+ next;
+ }
+ if ($$input =~ s/^$TN_IAC$TN_WILL(.)//) {
+ my $c = unpack("C", $1);
+ print "WILL<$c:$1>\n" if ($main::debug);
+
+ my $reply = $TN_DONT;
+ if ($1 == $TNOPT_ECHO || $1 == $TNOPT_SGA) {
+ $reply = $TN_DO;
+ }
+ $self->junctionInput($self, "$TN_IAC$reply$1");
+ next;
+ }
+ if ($$input =~ s/^$TN_IAC$TN_WONT(.)//) {
+ my $c = unpack("C", $1);
+ print "WONT<$c:$1>\n" if ($main::debug);
+ $self->junctionInput($self, "$TN_IAC$TN_DONT$1");
+ next;
+ }
+ # IAC,<option>
+ if ($$input =~ s/^$TN_IAC([^$TN_SB$TN_DO$TN_DONT$TN_WILL$TN_WONT])//) {
+ print "OPTION<$1>\n" if ($main::debug);
+ next;
+ }
+
+ # Incomplete ...
+ if ($$input =~ /^$TN_IAC/) {
+ return;
+ }
+ }
+}
+sub junctionInput {
+ my ($self, $from, $data) = @_;
+ my ($fh);
+
+ print "Payload::junctionInput [$self] from<$from> data<$data>\n"
+ if ($main::debug);
+
+ ##$self->{'mux'}->write($self->{'wfh'}, $data);
+ # If we are connected ...
+ if ($self->{'wfh'}) {
+ $fh = $self->{'wfh'};
+ print $fh $data;
+ } else {
+ $from->junctionInput($self, "<<<NOT CONNECTED>>>\n");
+ }
+}
+
+sub mux_eof {
+ my ($self, $mux, $fh) = @_;
+ my ($client);
+
+ print "Payload::mux_eof [$self] mux<$mux> fh<$fh>\n" if ($main::debug);
+
+ # Check for a restartable connection.
+ if ($self->can("restart")) {
+ my ($timeout) = $self->retry_timeout();
+
+ $self->cout->junctionInput($self,
+ "<<<PAYLOAD LOST ... retrying in $timeout secs>>>\n");
+
+ # Schedule a timeout to trigger a reconnect.
+ Callback->new($mux, $self, $timeout);
+
+ } else {
+ $self->cout->junctionEOF($self);
+ $self->cin->junctionRemove($self);
+
+ Payload->lost($self->name, $self);
+ }
+
+ # Close down the payload ...
+ $mux->close($self->{'rfh'});
+ ##$mux->remove($self->{'wfh'});
+}
+
+sub mux_close {
+ my ($self, $mux, $fh) = @_;
+
+ $self->connected(0);
+
+ #close($self->{'rfh'});
+ close($self->{'wfh'});
+ undef $self->{'rfh'};
+ undef $self->{'wfh'};
+
+ if ($self->{'pid'}) {
+ # Kill the process group for this pid.
+ kill 1, 0 - $self->{'pid'};
+ undef $self->{'pid'};
+ }
+}
+
+sub callback_timeout {
+ my ($self) = @_;
+
+ print "Payload::callback_timeout [$self]\n" if ($main::debug);
+
+ if ($self->enabled) {
+ $self->cout->junctionInput($self, "<<<PAYLOAD RESTART>>>\n");
+ $self->openPayload();
+ } else {
+ $self->cout->junctionInput($self, "<<<PAYLOAD DISABLED>>>\n");
+ }
+}
+
+sub closePayload {
+ my ($self) = @_;
+
+ if ($self->connected) {
+ $self->cout->junctionInput($self, "<<<PAYLOAD CLOSED>>>\n");
+
+ # Close down the payload ...
+ $self->mux->close($self->{'rfh'});
+ }
+ if ($self->enabled) {
+ $self->enabled(0);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+sub openPayload {
+ my ($self) = @_;
+
+ $self->enabled(1);
+ if (!$self->connected) {
+ if ($self->can("restart")) {
+ $self->restart();
+
+ return 1;
+ }
+ }
+ return 0;
+}
+
+sub helpAdd {
+ my ($self, $cmd, $msg) = @_;
+
+ push(@{$self->{'help'}}, [ $cmd, $msg ]);
+}
+
+sub commandHelp {
+ my ($self) = @_;
+ my @entries = (
+ [ 'break', 'send a break sequence' ]
+ );
+
+ if (defined $self->{'help'}) {
+ ( @entries, @{$self->{'help'}} );
+
+ } else {
+ @entries;
+ }
+}
+sub commandAdd {
+ my ($self, $cmd, @a) = @_;
+
+ $self->{'cmd'}->{$cmd} = [ @a ];
+}
+sub commandExec {
+ my ($self, $client, $cmd, $a) = @_;
+ my ($exe);
+
+ print "Payload::commandExec [$self] client<$client> cmd<$cmd> a<$a>\n"
+ if ($main::debug);
+
+ $exe = $self->{'cmd'}->{$cmd};
+
+ if ($cmd eq "break") {
+ # Send a telnet break ...
+ $self->junctionInput($self, "$TN_IAC$TN_BREAK");
+ return;
+
+ } elsif ($cmd eq "close") {
+ if (!$self->enabled && !$self->connected) {
+ $client->junctionInput($self,
+ "console already closed\n");
+
+ } elsif ($self->closePayload()) {
+ $self->cout->junctionInput($self, "(" . $client->id .
+ ") triggered a console close\n");
+
+ } else {
+ $client->junctionInput($self, "ERROR: close failed\n");
+ }
+ return;
+
+ } elsif ($cmd eq "open") {
+ if ($self->connected) {
+ $client->junctionInput($self, "console already open\n");
+
+ } elsif ($self->openPayload()) {
+ $self->cout->junctionInput($self, "(" . $client->id .
+ ") triggered a console open\n");
+
+ } else {
+ $client->junctionInput($self, "open failed\n");
+ }
+ return;
+ }
+
+ # Ensure we error if we have no command.
+ if (!$exe) {
+ $client->junctionInput($self, "Command not recognised\n");
+ return;
+ }
+
+ my ($msg, $run) = @{$exe};
+ if ($msg ne '') {
+ $self->cout->junctionInput($self, "(" . $client->id .
+ ") $msg\n");
+ }
+
+ local(*IN, *OUT, *ERR);
+ my $pid = IPC::Open3::open3(*IN, *OUT, *ERR, $run);
+ close(*IN{IO});
+
+ # XXX: this should not be happening here.
+ $self->mux->add(*OUT{IO});
+ my $data = ClientData->new($self->mux, *OUT{IO});
+ $data->{'id'} = "cmd:$cmd stdout";
+
+ $data->payload($self);
+ $data->cout($self->cout);
+
+ # XXX: this should not be happening here.
+ $self->mux->add(*ERR{IO});
+ my $data = ClientData->new($self->mux, *ERR{IO});
+ $data->{'id'} = "cmd:$cmd stderr";
+
+ $data->payload($self);
+ $data->cout($client);
+}
+
+sub DESTROY {
+ my ($self) = @_;
+
+ print "Payload::DESTROY [$self]\n" if ($main::debug);
+}
+
+#
+# PAYLOAD APPLICATION: handles forking off a command as a payload.
+#
+package PayloadApplication;
+use base 'Payload';
+
+sub initialise {
+ my ($self, $cmd) = @_;
+ my ($pid, $wfh, $rfh);
+
+ print "PayloadApplication::initialise [$self] cmd<$cmd>"
+ if ($main::debug);
+
+ $self->SUPER::initialise();
+
+ # XXX: we cannot use the write buffering offered by the mux package
+ # without suffering a read error from the PWR file handle, there
+ # is no a way to add a write-only channel.
+
+ $self->{'args'} = $cmd;
+
+ # Start the payload ...
+ $pid = IPC::Open3::open3($wfh, $rfh, 0, "setsid " . $cmd);
+
+ $self->{'rfh'} = $rfh;
+ $self->{'wfh'} = $wfh;
+ $self->{'pid'} = $pid;
+
+ $self->mux->add($rfh);
+ ##$mux->add($wfh);
+
+ $self->mux->set_callback_object($self, $rfh);
+ ##$mux->set_callback_object($self, $wfh);
+
+ print "SHARE PAYLOAD: $self $wfh/$rfh (to $cmd) [fd=" .
+ fileno($wfh) . "/" . fileno($rfh) . "]\n" if ($main::debug);
+ print "payload '$cmd' on fd=" . fileno($wfh) . "/" .
+ fileno($rfh) . "\n";
+
+ $self;
+}
+
+sub restart {
+ my ($self) = @_;
+
+ $self->initialise($self->{'args'});
+}
+
+#
+# PAYLOAD SOCKET: handles a network socket as payload.
+#
+package PayloadSocket;
+use base 'Payload';
+
+sub initialise {
+ my ($self, $addr) = @_;
+ my ($payload);
+
+ print "PayloadSocket::initialise [$self] addr<$addr>\n"
+ if ($main::debug);
+
+ $self->SUPER::initialise();
+
+ $self->{'args'} = $addr;
+
+ # Create a listening socket and add it to the multiplexor.
+ my $payload = new IO::Socket::INET(PeerAddr => $addr);
+ if (!$payload) {
+ $self->connected(0);
+ if ($self->can("restart")) {
+ my ($timeout) = $self->retry_timeout();
+
+ $self->cout->junctionInput($self,
+ "<<<PAYLOAD ERROR ($!) ... retrying in $timeout secs>>>\n");
+ # Schedule a timeout to trigger a reconnect.
+ Callback->new($self->mux, $self, $timeout);
+ } else {
+ $self->cout->junctionEOF($self);
+ $self->cin->junctionRemove($self);
+
+ Payload->lost($self->name, $self);
+ }
+
+ } else {
+ $self->{'rfh'} = $payload;
+ $self->{'wfh'} = $payload;
+
+ print "SHARE PAYLOAD: $self $payload (to $addr) [fd=" .
+ fileno($payload) . "]\n" if ($main::debug);
+ print "payload '$addr' on fd=" . fileno($payload) . "\n";
+ $self->mux->add($payload);
+
+ $self->mux->set_callback_object($self, $payload);
+ }
+
+ print "SHARE PAYLOAD: $self $payload ... done\n" if ($main::debug);
+
+ $self;
+}
+
+sub restart {
+ my ($self) = @_;
+
+ $self->initialise($self->{'args'});
+}
+
+#
+# CLIENT: general client object, represents a remote client channel
+#
+package Client;
+
+sub new {
+ my ($class, $mux, $fh) = @_;
+ my $self = bless { 'mux' => $mux,
+ 'fh' => $fh }, $class;
+
+ print "Client::new [$self] mux<$mux> fh<$fh>\n"
+ if ($main::debug);
+
+ $self->initialise();
+
+ $self;
+}
+
+sub clone {
+ my ($class, $from) = @_;
+
+ my $self = bless { %{$from} }, $class;
+
+ print "Client::clone [$self] from<$from>\n" if ($main::debug);
+
+ $self->initialise();
+
+ $self;
+}
+
+# Data accessors.
+sub mux {
+ my $self = shift;
+ if (@_) { $self->{'mux'} = shift }
+ return $self->{'mux'};
+}
+sub payload {
+ my $self = shift;
+ if (@_) { $self->{'payload'} = shift }
+ return $self->{'payload'};
+}
+sub fh {
+ my $self = shift;
+ if (@_) { $self->{'fh'} = shift }
+ return $self->{'fh'};
+}
+sub id {
+ my $self = shift;
+ if (@_) { $self->{'id'} = shift }
+ return $self->{'id'};
+}
+sub announce {
+ my $self = shift;
+ if (@_) { $self->{'announce'} = shift }
+ return $self->{'announce'};
+}
+sub cout {
+ my $self = shift;
+ if (@_) { $self->{'cout'} = shift }
+ return $self->{'cout'};
+}
+sub cin {
+ my $self = shift;
+ if (@_) {
+ $self->{'cin'}->junctionRemove($self) if ($self->{'cin'});
+ $self->{'cin'} = shift;
+ $self->{'cin'}->junctionAdd($self) if ($self->{'cin'} != undef);
+ }
+ return $self->{'cin'};
+}
+
+sub initialise {
+ my ($self) = @_;
+
+ print "Client::initialise [$self]\n" if ($main::debug);
+
+ $self->mux->set_callback_object($self, $self->fh);
+}
+
+sub junctionInput {
+ my ($self, $from, $data) = @_;
+
+ print "Client::junctionInput [$self] data<$data>\n" if ($main::debug);
+
+ $self->mux->write($self->fh, $data);
+}
+sub junctionEOF {
+ my ($self, $from, $data) = @_;
+
+ print "Client::junctionEOF [$self] data<$data>\n" if ($main::debug);
+
+ $self->shutdown();
+}
+
+sub mux_eof {
+ my ($self, $mux, $fh, $input) = @_;
+
+ print "Client::mux_eof [$self] mux<$mux> fh<$fh> input<$input>\n"
+ if ($main::debug);
+
+ # Handle any pending input, then remove myself from the clients list.
+ $self->mux_input($mux, $fh, $input);
+ $self->cin(undef);
+ $self->cout(undef);
+
+ # Tell the multiplexor we no longer are using this channel.
+ $mux->shutdown($fh, 1);
+}
+sub mux_close {
+ my ($self, $mux, $fn) = @_;
+
+ print "Client::close [$self]\n" if ($main::debug);
+
+ if ($self->announce) {
+ $self->announce->junctionInput($self, "(" . $self->id .
+ ") disconnected\n");
+ }
+ print "$self->{'id'} disconnected\n";
+}
+
+sub shutdown {
+ my ($self) = @_;
+
+ print "Client::shutdown [$self]\n" if ($main::debug);
+
+ # Close myself down and tell the payload.
+ $self->mux->shutdown($self->fh, 2);
+}
+sub DESTROY {
+ my ($self) = @_;
+
+ print "Client::DESTROY [$self]\n" if ($main::debug);
+}
+
+#
+# CLIENT CMD: represents a client whilst in command mode, when we have commited
+# to connecting this will pass the client connection off to a ClientData
+# object.
+#
+package ClientCmd;
+use base 'Client';
+
+sub mux_input {
+ my ($self, $mux, $fh, $input) = @_;
+
+ print "Client::shutdown [$self] mux<$mux> fh<$fh> input<$$input>\n"
+ if ($main::debug);
+
+ while ($$input =~ s/^(.*?)\n//) {
+ my ($cmd, $args) = split(' ', $1, 2);
+ my (%args) = Conmux::decodeArgs($args);
+
+ my $reply = {
+ 'status' => 'ENOSYS unknown command',
+ };
+
+ # XXX: check authentication if required and reject the
+ # command out of hand - leak _nothing_.
+ if (!defined $args{'id'}) {
+ $reply->{'status'} = 'EACCES identifier required';
+ goto reply;
+ }
+ # They are who they say they are, record who that is.
+ $self->{'id'} = $args{'id'};
+
+ if ($cmd eq "CONNECT") {
+ # Switch over to data mode, hand this connection off
+ # to a data client instance, I am done.
+ my ($data, $to, $in, $out);
+ $data = ClientData->clone($self);
+
+ $to = $args{'to'};
+ if (!$to) {
+ $reply->{'status'} = "EINVAL CONNECT " .
+ " requires 'to' specifier";
+ goto reply;
+ }
+ my $payload = Payload->lookup($to);
+ if (!defined $payload) {
+ $reply->{'status'} = "EINVAL '$to' not a " .
+ " valid destination specifier";
+ goto reply;
+ }
+
+ $reply->{'status'} = 'OK';
+
+ # Get the payload title and pass that back.
+ $reply->{'title'} = $payload->title . ' [channel ' .
+ $payload->state() . ']';
+ $reply->{'state'} = $payload->state();
+
+ $data->payload($payload);
+ $args{'type'} = 'client' if (!$args{'type'});
+ if ($args{'type'} eq 'status') {
+ $data->cout($payload->cout);
+ } elsif ($args{'type'} eq 'client') {
+ if (!$args{'hide'}) {
+ $data->announce($payload->cout);
+ $payload->cout->junctionInput(
+ $self, "(" . $self->id .
+ ") connected\n");
+ }
+ $data->cin($payload->cout);
+ $data->cout($payload->cin);
+ } else {
+ $reply->{'status'} = "EINVAL '$args{'type'}' " .
+ "not a valid destination type";
+ goto reply;
+ }
+
+ print "$self->{'id'} connected to $to/$args{'type'}\n";
+
+ $self->junctionInput($self,
+ Conmux::encodeArgs($reply) . "\n");
+
+ # Don't handle any more input - its not going to be
+ # for us.
+ last;
+ }
+
+ reply:
+ # We're done, send back our response to this.
+ $self->junctionInput($self, Conmux::encodeArgs($reply) . "\n");
+ }
+}
+
+#
+# CLIENT DATA: handles a client connection when in data mode, attaches
+# the client connection to the relevant junction.
+#
+package ClientData;
+use base 'Client';
+
+my @help = (
+ [ 'msg', 'send a message to all connected clients' ],
+ [ 'quit', 'disconnect from the console' ],
+);
+sub mux_input {
+ my ($self, $mux, $fh, $input) = @_;
+
+ print "ClientData::mux_input [$self] mux<$mux> fh<$fh> input<$$input>\n"
+ if ($main::debug);
+
+ while ($$input ne "") {
+ if ($self->{'cmd'} eq '') {
+ # Check for an incomplete escape ... wait for more.
+ if ($$input =~ /^~$/s) {
+ return;
+ }
+ if ($$input =~ s/^~\$//s) {
+ $self->{'cmd'} = '>';
+ $self->junctionInput($self, "\r\nCommand> ");
+ next;
+ }
+ # Its not an escape ... pass it on.
+ # Ship anything before that cannot be the escape.
+ if ($$input =~ s/^(.[^~]*)(~|$)/\2/s) {
+ # Data coming in from the client, send it to
+ # the payload.
+ $self->cout->junctionInput($self, $1);
+ }
+ } else {
+ # Consume characters upto a newline, echo them back
+ # to the client as we go.
+ while ($$input =~ s/^([^\r\n])//) {
+ my $c = $1;
+ if ($c eq "\b" || $c eq "\x7f") {
+ if (length($self->{'cmd'}) > 1) {
+ $c = "\b \b";
+ substr($self->{'cmd'},
+ -1, 1, '');
+ } else {
+ $c = '';
+ }
+ } else {
+ $self->{'cmd'} .= $c;
+ }
+ $self->junctionInput($self, $c);
+ }
+ # If we arn't at a newline, then wait for more input.
+ if ($$input !~ s/^[\r\n]+//) {
+ return;
+ }
+
+ $self->junctionInput($self, "\n");
+
+ my ($cmd, $a) = split(' ', substr($self->{'cmd'},
+ 1), 2);
+ $self->{'cmd'} = '';
+
+ if ($cmd eq '') {
+
+ } elsif ($cmd eq 'help') {
+ my @cmds = $self->payload->commandHelp();
+
+ my $ent;
+ my $help = "Conmux commands:\n";
+ for $ent (@cmds, @help) {
+ $help .= sprintf(" %-20s %s\n",
+ $ent->[0], $ent->[1]);
+ }
+ $self->junctionInput($self, $help);
+
+ } elsif ($cmd eq 'quit') {
+ $self->shutdown();
+
+ } elsif ($cmd eq 'msg') {
+ $self->cin->junctionInput($self,
+ "($self->{'id'}) $a\n");
+
+ # Not a client command ... pass it to the payload.
+ } else {
+ $self->payload->commandExec($self, $cmd, $a);
+ }
+ }
+ }
+}
+
+#
+# LIBRARY: split a string honouring quoting.
+#
+package main;
+sub parse($) {
+ my ($str) = @_;
+
+ my ($pos, @args, $argc, $quote, $real, $c, $inc);
+
+ $inc = 0;
+ @args = ();
+ $argc = 0;
+ $quote = 0;
+ $real = 0;
+
+
+ $pos = 0;
+ while (substr($str, $pos, 1) eq " ") {
+ $pos++;
+ }
+ for (; $pos < length($str); $pos++) {
+ $c = substr($str, $pos, 1);
+ if ($quote != 2 && $c eq '\\') {
+ $real = 1;
+ $pos++;
+ $c = substr($str, $pos, 1);
+ } else {
+ $real = 0;
+ }
+
+ if ($quote != 2 && $c eq '"' && !$real) {
+ $quote ^= 1;
+ } elsif ($quote != 1 && $c eq "'" && !$real) {
+ $quote ^= 2;
+ } elsif ($c eq " " && $quote == 0 && !$real) {
+ while (substr($str, $pos, 1) eq " ") {
+ $pos++;
+ }
+ $pos--;
+ $argc++;
+ } else {
+ if ($inc) {
+ $inc = 0;
+ $argc++;
+ }
+ $args[$argc] .= $c;
+ }
+ }
+
+ @args;
+}
+
+#
+# MAIN: makes the IO multiplexor, junction, listener and payload and stitches
+# them all together.
+#
+package main;
+
+# Usage checks.
+if ($#ARGV != 0 && $#ARGV != 3) {
+ print STDERR "Usage: $P <config file>\n";
+ print STDERR " $P <local port> <title> socket <host>:<port>\n";
+ print STDERR " $P <local port> <title> cmd <cmd>\n";
+ exit 1
+}
+my @conf;
+if ($#ARGV == 3) {
+ my ($lport, $title, $what, $arg) = @ARGV;
+ @conf = (
+ "listener '$lport'",
+ "'$what' console '$title' '$arg'"
+ );
+} else {
+ my ($cf) = @ARGV;
+ open(CONF, '<', $cf) || die "$P: $cf: open failed - $!\n";
+ @conf = <CONF>;
+ close(CONF);
+}
+
+# Make a new multiplexer.
+my $mux = new IO::Multiplex;
+
+my ($line, $seg, $listener, $payload);
+$line = '';
+for $seg (@conf) {
+ # Handle comments, blank lines and line continuation.
+ chomp($seg); $seg =~ s/^\s+//;
+ next if ($seg =~ /^#/);
+ $line .= $seg;
+ if ($line =~ m/\\$/) {
+ chop($line);
+ next;
+ }
+ next if (/^\s+$/);
+
+ my ($cmd, @a) = parse($line);
+ $line = '';
+
+ if ($cmd eq "listener") {
+ if ($#a != 0) {
+ warn "$P: Usage: listener <port>\n" .
+ "$P: $line\n";
+ next;
+ }
+
+ my ($lport) = @a;
+ my ($rhost, $rname);
+
+ # port
+ if ($lport =~ m@^\d+$@) {
+ # Already in the right format.
+
+ # registry/service
+ } elsif ($lport =~ m@(.*)/(.*)@) {
+ ($rhost, $rname, $lport) = ($1, $2, 0);
+
+ # service
+ } else {
+ ($rhost, $rname, $lport) = ('-', $lport, 0);
+ }
+
+ # Create the client listener socket.
+ $listener = ListenerSocket->new($mux, $lport);
+
+ # Register us with the registry.
+ if ($rhost) {
+ Conmux::Registry::add($rhost, $rname, $listener->address);
+ }
+
+ } elsif ($cmd eq 'socket') {
+ if ($#a != 2) {
+ warn "$P: Usage: socket <name> <title> <host:port>\n" .
+ "$P: $line\n";
+ next;
+ }
+ my ($name, $title, $sock) = @a;
+
+ # Create the payload.
+ $payload = PayloadSocket->new($name, $title, $mux, $sock);
+
+ } elsif ($cmd eq 'application') {
+ if ($#a != 2) {
+ warn "$P: Usage: application <name> <title> <host:port>\n" .
+ "$P: $line\n";
+ next;
+ }
+ my ($name, $title, $app) = @a;
+
+ $payload = PayloadApplication->new($name, $title, $mux, $app);
+
+ } elsif ($cmd eq 'command') {
+ if ($#a != 2) {
+ warn "$P: Usage: command <name> <msg> <cmd>\n" .
+ "$P: $line\n";
+ next;
+ }
+ my ($name, $msg, $cmd) = @a;
+
+ $payload->commandAdd($name, $msg, $cmd);
+
+ } elsif ($cmd eq 'help') {
+ if ($#a != 1) {
+ warn "$P: Usage: $cmd <name> <msg>\n" .
+ "$P: $line\n";
+ next;
+ }
+ my ($name, $msg) = @a;
+
+ $payload->helpAdd($name, $msg);
+ } else {
+ warn "$P: $cmd: unknown configuration command\n";
+ }
+}
+
+# Hand over to the multiplexor.
+do {
+ eval { $mux->loop; };
+ warn "$@";
+} while ($@ =~ /^Use of freed value in iteration/);
+die "ERROR: $@\n";
diff --git a/conmux/conmux-attach b/conmux/conmux-attach
new file mode 100755
index 00000000..38ead0e8
--- /dev/null
+++ b/conmux/conmux-attach
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+#
+# conmux-attach -- attach commands to the console
+#
+# Attach stand alone commands to a console stream. You can specify
+# which of the commands file descriptors are connected to which of the
+# 'steams' in the multiplexor; the console input/output and the user
+# output.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+use IO::Socket;
+use Getopt::Long qw(:config no_auto_abbrev);
+
+# Find our internal libraries.
+###my $lib = $0; $lib =~ s@/[^/]+$@@;
+###push(@INC, $lib);
+###require Conmux;
+##use lib ".";
+##use Conmux;
+use lib "/usr/local/conmux/lib";
+use Conmux;
+
+my $P = 'conmux-attach';
+my ($in, $out, $err);
+
+# Usage.
+GetOptions(
+ 'i|stdout=s' => \$in,
+ 'o|stdin=s' => \$out,
+ 'e|stderr=s' => \$err,
+ 'b|bot=s' => \$bot,
+) or exit;
+
+if ($in eq '' && $out eq '' && $err eq '') {
+ $in = 'client';
+ $out = 'client';
+}
+
+if ($#ARGV < 1) {
+ print STDERR "$P: <host:port> <program> ...";
+ exit 1;
+}
+my ($mux, $app) = @ARGV;
+shift;
+$app =~ s@.*/@@; $app =~ s@\s.*$@@;
+$app = $bot if ($bot);
+
+#
+# Connect to the client channel.
+#
+sub conmux_connect {
+ my ($mux, $type) = @_;
+
+ if (!$cache{$mux, $type}) {
+ my $con = Conmux::connect($mux);
+ my %r = Conmux::sendCmd($con, 'CONNECT', {
+ 'id' => 'bot:' . $app,
+ 'to' => 'console',
+ 'type' => $type } );
+ die "$P: $mux: connect failed - $r{'status'}\n"
+ if ($r{'status'} ne "OK");
+
+ $cache{$mux, $type} = $con;
+ }
+
+ $cache{$mux, $type};
+}
+
+if ($in) {
+ my $to = conmux_connect($mux, $in);
+ open(STDIN, "<&", $to) ||
+ die "$P: unable to hand off socket to stdin - $!\n";
+}
+if ($out) {
+ my $to = conmux_connect($mux, $out);
+ open(STDOUT, ">&", $to) ||
+ die "$P: unable to hand off socket to stdout - $!\n";
+}
+if ($err) {
+ my $to = conmux_connect($mux, $err);
+ open(STDERR, ">&", $to) ||
+ die "$P: unable to hand off socket to stderr - $!\n";
+}
+exec @ARGV;
diff --git a/conmux/conmux-registry b/conmux/conmux-registry
new file mode 100755
index 00000000..7a4e0bfc
--- /dev/null
+++ b/conmux/conmux-registry
@@ -0,0 +1,282 @@
+#!/usr/bin/perl
+#
+# conmux-registry -- console name registry server
+#
+# Main registry server. This server holds host/port assignments for
+# conmux daemons registering with it. This allows users to specify
+# human names for their consoles and find the relevant conmux daemon.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+use strict;
+
+use Symbol qw(gensym);
+
+use IO::Socket;
+use IO::Multiplex;
+use URI::Escape;
+
+# Find our internal libraries.
+###my $lib = $0; $lib =~ s@/[^/]+$@@;
+###push(@INC, $lib);
+###require Conmux;
+##use lib ".";
+##use Conmux;
+use lib "/usr/local/conmux/lib";
+use Conmux;
+
+our $P = 'conmux-registry';
+our $debug = 0;
+
+#
+# LISTENER SOCKET: creates an intenet listener for new clients and
+# connects them to the junction provided.
+#
+package ListenerSocket;
+
+sub new {
+ my ($class, $mux, $port, $registry) = @_;
+ my $self = bless { 'mux' => $mux, 'registry' => $registry }, $class;
+
+ print "ListenerSocket::new [$self] mux<$mux> port<$port> " .
+ "registry<$registry>\n" if ($main::debug);
+
+ $self->initialise($mux, $port, $registry);
+
+ $self;
+}
+
+sub initialise {
+ my ($self, $mux, $port, $registry) = @_;
+ my ($sock);
+
+ print "ListenerSocket::initialise [$self] mux<$mux> port<$port> " .
+ "registry<$registry>\n" if ($main::debug);
+
+ # Create a listening socket and add it to the multiplexor.
+ my $sock = new IO::Socket::INET(Proto => 'tcp',
+ LocalPort => $port,
+ Listen => 4,
+ ReuseAddr => 1)
+ or die "socket: $@";
+
+ print " adding $self $sock\n" if ($main::debug);
+ $mux->listen($sock);
+ $mux->set_callback_object($self, $sock);
+ $self->{'listener'} = $sock;
+}
+
+# Handle new connections by instantiating a new client class.
+sub mux_connection {
+ my ($self, $mux, $fh) = @_;
+ my ($client);
+
+ print "ListenerSocket::mux_connection [$self] mux<$mux> fh<$fh>\n"
+ if ($main::debug);
+
+ # Make a new client connection.
+ $client = Client->new($mux, $fh, $self->{'registry'});
+ print " new connection $self $client\n" if ($main::debug);
+}
+
+sub DESTROY {
+ my ($self) = @_;
+
+ print "ListenerSocket::DESTROY [$self]\n" if ($main::debug);
+
+ close($self->{'listener'});
+}
+
+#
+# CLIENT: general client object, represents a remote client channel
+#
+package Client;
+
+sub new {
+ my ($class, $mux, $fh, $registry) = @_;
+ my $self = bless { 'mux' => $mux,
+ 'fh' => $fh,
+ 'registry' => $registry }, $class;
+
+ print "Client::new [$self] mux<$mux> fh<$fh> registry<$registry>\n"
+ if ($main::debug);
+
+ $self->initialise($mux, $fh, $registry);
+
+ $self;
+}
+
+sub initialise {
+ my ($self, $mux, $fh, $registry) = @_;
+
+ print "Client::initialise [$self] mux<$mux> fh<$fh> " .
+ "registry<$registry>\n" if ($main::debug);
+
+ $mux->set_callback_object($self, $fh);
+}
+
+sub mux_input {
+ my ($self, $mux, $fh, $input) = @_;
+
+ print "Client::mux_input [$self] mux<$mux> fh<$fh> input<$$input>\n"
+ if ($main::debug);
+
+ while ($$input =~ s/^(.*?)\n//) {
+ my ($cmd, $args) = split(' ', $1, 2);
+ my (%args) = Conmux::decodeArgs($args);
+
+ my $reply = {
+ 'status' => 'ENOSYS',
+ };
+
+ # Fill in the common results.
+ $reply->{'title'} = 'registry';
+
+ # Handle this command.
+ if ($cmd eq "LOOKUP") {
+ my $r = $self->{'registry'}->lookup($args{'service'});
+
+ if (defined $r) {
+ $reply->{'result'} = $r;
+ $reply->{'status'} = 'OK';
+
+ } else {
+ $reply->{'status'} = 'ENOENT entry not found';
+ }
+
+ } elsif ($cmd eq "ADD") {
+ $self->{'registry'}->add($args{'service'},
+ $args{'location'});
+ $reply->{'status'} = 'OK';
+
+ } elsif ($cmd eq "LIST") {
+ $reply->{'result'} = $self->{'registry'}->list();
+ $reply->{'status'} = 'OK';
+ }
+
+ $fh->write(Conmux::encodeArgs($reply) . "\n");
+ }
+}
+sub mux_eof {
+ my ($self, $mux, $fh, $input) = @_;
+
+ print "Client::mux_eof [$self] mux<$mux> fh<$fh> input<$input>\n"
+ if ($main::debug);
+
+ # Handle any pending input, then remove myself.
+ $self->mux_input($mux, $fh, $input);
+
+ # Tell the multiplexor we no longer are using this channel.
+ $mux->shutdown($fh, 1);
+}
+sub mux_close {
+ my ($self, $mux, $fn) = @_;
+
+ print "Client::close [$self]\n" if ($main::debug);
+}
+
+sub DESTROY {
+ my ($self) = @_;
+
+ print "Client::DESTROY [$self]\n" if ($main::debug);
+}
+
+#
+# REGISTRY: registry elements.
+#
+package Registry;
+
+sub new {
+ my ($class, $store) = @_;
+ my $self = bless { 'store' => $store }, $class;
+
+ my ($key, $val);
+
+ print "Registry::new [$self] store<$store>\n" if ($main::debug);
+
+ # Open the store and populate the keys.
+ open(S, '<', $store) || die "Registry::new: $store: open failed - $!\n";
+ while (<S>) {
+ chomp;
+
+ ($key, $val) = split(' ', $_);
+
+ $self->{'key'}->{$key} = $val;
+ }
+ close(S);
+
+ $self;
+}
+
+sub add {
+ my ($self, $what, $where) = @_;
+
+ my ($key);
+
+ print "Registry::add [$self] what<$what> where<$where>\n"
+ if ($main::debug);
+
+ $self->{'key'}->{$what} = $where;
+
+ print "$what at $where\n";
+
+ if (open(S, '>', $self->{'store'} . '.new')) {
+ foreach $key (sort keys %{$self->{'key'}}) {
+ print S "$key $self->{'key'}->{$key}\n";
+ }
+ close(S);
+ rename $self->{'store'} . '.new', $self->{'store'};
+
+ } else {
+ warn "$P: $self->{'store'}.new: open failed - $!";
+ }
+}
+
+sub lookup {
+ my ($self, $what) = @_;
+
+ print "Registry::lookup [$self] what<$what>\n" if ($main::debug);
+
+ $self->{'key'}->{$what};
+}
+
+sub list {
+ my ($self) = @_;
+ my ($r, $key);
+
+ print "Registry::list [$self]\n" if ($main::debug);
+
+ foreach $key (sort keys %{$self->{'key'}}) {
+ $r .= "$key $self->{'key'}->{$key}\n";
+ }
+
+ $r;
+}
+
+#
+# MAIN: makes the IO multiplexor, listener and registry and stitches
+# them all together.
+#
+package main;
+
+# Usage checks.
+if ($#ARGV != 1) {
+ print STDERR "Usage: $P <local port> <store>\n";
+ exit 1
+}
+my ($lport, $store) = @ARGV;
+
+# Make a new multiplexer.
+my $mux = new IO::Multiplex;
+
+# Make the registry object.
+my $registry = Registry->new($store);
+
+# Create the client listener socket.
+my $listener = ListenerSocket->new($mux, $lport, $registry);
+
+# Hand over to the multiplexor.
+$mux->loop;
diff --git a/conmux/conmux.html b/conmux/conmux.html
new file mode 100644
index 00000000..9f9cbeaf
--- /dev/null
+++ b/conmux/conmux.html
@@ -0,0 +1,158 @@
+<html>
+<!--
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+-->
+<head>
+<title>conmux - the console multiplexor</title>
+<style type="text/css">
+.example { white-space: pre; margin-left: 2em; margin-right: 2em;
+ font-family: monospace }
+table.quote { margin-left: 2em; margin-right: 2em }
+</style>
+</head>
+
+<body>
+<center>
+<h1>conmux - the console multiplexor</h1>
+</center>
+
+<p><em>conmux</em>, the console multiplexor is a system designed to abstract
+the concept of a console. That is to provide a virtualised machine interface,
+including access to the console and the 'switches' on the front panel; the
+/dev/console stream and the reset button. It creates the concept of a virtual
+console server for multiple consoles and provides access to and sharing of
+consoles connected to it.
+
+<p>There are two main motivations for wanting to do this. Firstly, we have
+many different machine types with vastly differing access methodologies for
+their consoles and for control functions (VCS, HMC, Annex) and we neither want
+to know what they are nor how they function. Secondly, most console sources
+are single access only and we would like to be able to share the console data
+between many consumers including users.
+
+<h2>Basic Usage</h2>
+
+<p>The main interface to the consoles is via the <code>console</code>
+program. This connects us to the console server for the machine and allows us
+to interact with it, including issuing <i>out-of-band</i> commands to control
+the machine.
+
+<div class="example">$ console &lt;host&gt;/&lt;console&gt;
+</div>
+
+<p>In the example below we indicate that the console we require is located on
+the virtual console server <code>consoles.here.com</code> and the
+specific console is <code>elm3b70</code>.
+
+<div class="example">$ console consoles.here.com/elm3b70
+Connected to elm3b70 console (~$quit to exit)
+
+Debian GNU/Linux 3.1 elm3b70 ttyS0
+
+elm3b70 login:
+</div>
+
+<p>Once connected we can interact normally with the console stream. To
+perform front pannel operation such as peforming an <em>hard reset</em> we
+switch to command mode. This is achieved using the escape sequence
+<code>~$</code>. Nore the prompt <code>Command&gt;</code>
+
+<div class="example">elm3b70 login: <em>~$</em>
+Command&gt; quit
+Connection closed
+$
+</div>
+
+<h2>Command Summary</h2>
+
+<p>The following commands are generally available:
+
+<p><table class="quote" rules="all" frame="box">
+
+<tr><th>Command<th>Description</tr>
+
+<tr><td>quit<td>quit this console session, note that this disconnects us from
+the session it does not affect the integity of the session itself.</tr>
+
+<tr><td>hardreset<td>force a hard reset on the machine, this may be a simple
+reset or a power off/on sequence whatever is required by this system.</tr>
+
+</table>
+
+<h2>Architecture</h2>
+
+<p>The conmux provides a virtual console multiplexor system reminicent of
+an Annex terminal server. You refer to the conmux <em>server</em> and
+<em>lines</em>, unlike an Annex lines are referred to by mnemonic names.
+Above we referred to the console for <code>elm3b70</code> 'connected to' the
+server <code>consoles.here.com</cond>. A virtual console server consists
+of a number of server processes. One <code>conmux-registry</code> server,
+several <code>conmux</code> servers and optionally several <em>helper</em>
+processes.
+
+<p><code>conmux-registry</code>: a server is defined by the server registry.
+This maintains the mnemonic name to current server location relation. When
+a client wishes to attach to a console on a server, the registry is first
+queried to locate the server currently handling that console.
+
+<p><code>conmux</code>: for each connected console there is a corresponding
+console multiplexor. This process is responsible for maintaining the
+connection to the console and for redistributing the output to the various
+connected clients. It is also responsible for handling "panel" commands
+from the client channels.
+
+<p><code>autoboot-helper</code>: an example helper which aids systems which
+are not capable of an automatic reboot. It connects to a console and watches
+for tell-tale reboot activity, preforming a "panel" <em>hardreset</em> when
+required. This provides the impression of seamless reboot for systems
+which this does not work.
+
+<h2>Configuration</h2>
+
+<h3>conmux-registry</h3>
+
+<p>Configuration of this service is very simple. Supplying the default
+registry port (normally 63000) and the location for the persistant registry
+database.
+
+<h3>conmux</h3>
+
+<p>Configuration of each conmux is complex. Each has a listener, payload
+and optionally one or more panel commands. Configuration is provided via
+a per console configuration file. This file consists of lines defining each
+element:
+
+<p><code>listener &lt;server&gt;/&lt;name&gt;</code>:
+defines the name of this console port as it appears in the registry.
+
+<p><code>socket &lt;name&gt; &lt;title&gt; &lt;host&gt;:&lt;port&gt;</code>:
+defines a console payload connected to a tcp socket on the network.
+<code>name</code> defines this payload within the multiplexor,
+<code>title</code> is announced to the connecting clients.
+
+<p><code>application &lt;name&gt; &lt;title&gt; &lt;cmd&gt;</code>:
+defines a console payload which is accessed by running a specific command.
+<code>name</code> defines this payload within the multiplexor,
+<code>title</code> is announced to the connecting clients.
+
+<p><code>command &lt;panel&gt; &lt;message&gt; &lt;cmd&gt;</code>:
+defines a panel command for the preceeding payload, triggerd when
+<code>panel</code> is typed at the command prompt. <code>message</code>
+is announced to the user community. <code>cmd</cmd> will be actually
+executed.
+
+<p>For example here is the configuration for a NUMA-Q system which is rebooted
+using a remote VCS console and for which the real console channel is
+on an Annex terminal server:
+
+<div class="example">listener localhost/elm3b130
+socket console 'elm3b130 console' console.server.here.com:2040
+ command 'hardreset' 'initated a hard reset' \
+ './reboot-numaq vcs 1.2.3.4 elm3b130 12346 Administrator password'
+</div>
+
+</body>
+</html>
diff --git a/conmux/console b/conmux/console
new file mode 100755
index 00000000..a7ad2ab6
--- /dev/null
+++ b/conmux/console
@@ -0,0 +1,192 @@
+#!/usr/bin/perl
+#
+# console <host>/<machine> -- interactive client interface
+#
+# The main interactive client interace to conmux. Allows direct
+# interaction with the payload, as well as allowing break out
+# to the conmux menu to envoke defined commands; for example
+# hardreset.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+$| = 1;
+
+our $P = 'console';
+
+use IO::Socket;
+use IO::Multiplex;
+use Getopt::Long qw(:config no_auto_abbrev);
+
+my $CONMUX = '/usr/local/conmux';
+my $CONMUX = $ENV{'CONMUX_ROOT'} if ($ENV{'CONMUX_ROOT'});
+
+# Find our internal libraries.
+###my $lib = $0; $lib =~ s@/[^/]+$@@;
+###push(@INC, $lib);
+###require Conmux;
+##use lib ".";
+##use Conmux;
+use lib "/usr/local/conmux/lib";
+use Conmux;
+
+# Basic terminal handling.
+sub termRaw {
+ $termSettings = `stty -g`;
+ system "stty raw -echo opost onlret";
+}
+sub termRestore {
+ system "stty $termSettings";
+}
+
+my $bot;
+my $list;
+my $status;
+GetOptions(
+ 'b|bot=s' => \$bot,
+ 'l|list' => \$list,
+ 's|status' => \$status,
+);
+sub usage {
+ warn "Usage: $P <service>\n";
+ warn " $P <registry>/<service>\n";
+ warn " $P <host>:<port>\n";
+ warn " $P --status <service>\n";
+ die " $P --list [<registry>]\n";
+}
+
+my $id;
+if ($bot) {
+ $id = 'bot:' . $bot;
+} else {
+ $id = 'user:' . $ENV{'LOGNAME'};
+}
+
+#
+# MODE: registry list.
+#
+if ($list) {
+ if ($#ARGV == -1) {
+ print Conmux::Registry::list('-');
+
+ } elsif ($#ARGV == 0) {
+ print Conmux::Registry::list($ARGV[0]);
+
+ } else {
+ usage();
+ }
+ exit 0
+}
+
+#
+# COMMAND: payload status command
+#
+if ($status) {
+ usage() if ($#ARGV != 0);
+
+ my $sock;
+ eval {
+ $sock = Conmux::connect($ARGV[0]);
+ };
+ if ($@) {
+ print "unavailable\n";
+ exit 0
+ }
+ my %r = Conmux::sendCmd($sock, 'CONNECT', { 'id' => $id,
+ 'to' => 'console', 'hide' => 1 });
+ if ($r{'status'} ne 'OK') {
+ print "unavailable\n";
+
+ } elsif ($r{'state'}) {
+ print "$r{'state'}\n";
+
+ } else {
+ print "unknown\n";
+ }
+ exit 0;
+}
+
+
+#
+# COMMAND: general payload connect.
+#
+if ($#ARGV != 0) {
+ usage();
+}
+
+# Create a multiplex object
+my $mux = new IO::Multiplex;
+
+# Connect to the host/port specified on the command line,
+# or localhost:23
+my $sock = Conmux::connect($ARGV[0]);
+
+my %r = Conmux::sendCmd($sock, 'CONNECT', { 'id' => $id, 'to' => 'console' });
+die "$P: $ARGV[0]: connect failed - $r{'status'}\n" if ($r{'status'} ne 'OK');
+
+print "Connected to $r{'title'} (~\$quit to exit)\n";
+
+# OK, we are now connected ... add the relevant file handles to the mux
+$mux->add($sock);
+$mux->add(\*STDIN);
+# We want to buffer output to the terminal. This prevents the program
+# from blocking if the user hits CTRL-S for example.
+$mux->add(\*STDOUT);
+
+# We're not object oriented, so just request callbacks to the
+# current package
+$mux->set_callback_object(__PACKAGE__);
+
+termRaw();
+
+# Enter the main mux loop.
+$mux->loop;
+
+# mux_input is called when input is available on one of
+# the descriptors.
+sub mux_input {
+ my $package = shift;
+ my $mux = shift;
+ my $fh = shift;
+ my $input = shift;
+
+ # Figure out whence the input came, and send it on to the
+ # other place.
+ if ($fh == $sock) {
+ print STDOUT $$input;
+
+ # Remove the input from the input buffer.
+ $$input = '';
+ } else {
+ print $sock $$input;
+
+ # Remove the input from the input buffer.
+ $$input = '';
+## while ($$input ne "") {
+## # Check for an incomplete escape ... wait for more.
+## if ($$input =~ /^~$/s) {
+## return;
+## }
+## if ($$input =~ s/^~\$//s) {
+## $mux->shutdown($sock, 1);
+## }
+## # Its not an escape ... pass it on.
+## # Ship anything before that cannot be the escape.
+## if ($$input =~ s/^(.[^~]*)(~|$)/\2/s) {
+## print $sock $1;
+## }
+## }
+ }
+}
+
+# This gets called if the other end closes the connection.
+sub mux_eof {
+ print STDERR "Connection Closed\n";
+ termRestore();
+ exit;
+}
+sub mux_close {
+ print "CLOSE\n";
+}
diff --git a/conmux/drivers/blade b/conmux/drivers/blade
new file mode 100755
index 00000000..76a839af
--- /dev/null
+++ b/conmux/drivers/blade
@@ -0,0 +1,234 @@
+#!/usr/bin/expect --
+#
+# blade -- console and management control for IBM blade servers.
+#
+# Obtain consoles for and hard reset support for blades in
+# IBM blade centres. Blades are identified by their blade centre ids.
+#
+# usage:
+# blade reboot|console <blade id> <user> <password> <connect command> ...
+#
+# example:
+# blade reboot blade[4] FOO BAR telnet 1.2.3.4
+# blade console blade[4] FOO BAR telnet 1.2.3.4
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "blade"
+
+if { [llength $argv] < 5 } {
+ puts stderr "Usage: $P <command> <blade> <login> <password> <connect command ...>"
+ exit 1
+}
+
+log_user 0
+
+set cmd [lindex $argv 0]
+set system [lindex $argv 1]
+set username [lindex $argv 2]
+set password [lindex $argv 3]
+set command [lrange $argv 4 end]
+
+# Validate the command.
+switch -- $cmd {
+ console {}
+ reboot {}
+ default {
+ puts stderr "$P: $cmd: unrecognised command";
+ exit 1
+ }
+}
+# Ensure the blade name is fully qualified. This ensures that
+# the console prompt detection as reliable as possible. Add the system
+# prefix if it was not specified.
+switch -re -- "$system" {
+ system:.* { }
+ .* { set system "system:$system" }
+}
+
+#log_file -a "$logfile"
+
+set elapsed_time 0
+set timeout 30
+
+proc note {m} {
+ global P
+ puts "$P: $m"
+}
+proc warn {m} {
+ global P
+ puts "$P: WARNING: $m"
+}
+proc winge {m} {
+ global P
+ puts "$P: ERROR: $m"
+}
+
+note "Logging into blade centre with command \"$command\" to restart it";
+
+# CONNECT: connect to the remote console.
+eval spawn $command
+expect {
+ default {
+ winge "login prompt not issued"
+ exit 2
+ }
+ "Connection closed by foreign host." {
+ winge "Telnet connection closed."
+ exit 1
+ }
+ "Unable to connect to remote host:" {
+ winge "Connection to remote console failed"
+ exit 2
+ }
+ "username:" {
+ #note "saw login prompt"
+ }
+}
+
+# AUTHENTICATE: send username and password at the relevant prompts
+note "sending login ..."
+send -- "$username\r"
+expect {
+ default {
+ winge "password prompt not issued"
+ exit 2
+ }
+ "password:" {
+ #note "password prompt found"
+ }
+}
+
+note "sending password ..."
+send -- "$password\r"
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ "Invalid login!" {
+ winge "login/password incorrect ... aborting"
+ exit 1
+ }
+ -- "system>" {
+ #note "command prompt found"
+ }
+}
+
+# SYSTEM: first validate the system specifier, if it does not
+# exist get a listing and print that out whilst we are connected.
+note "selecting system '$system' ..."
+send "env -T $system\r"
+set found 0
+set prompt "$system>"
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ OK {
+ set found 1
+ exp_continue
+ }
+ -- "system>" {
+ #note "command prompt found"
+ }
+ -ex $prompt {
+ #note "command prompt found"
+ }
+}
+
+# The system the user specified was not found, give them a hand by
+# getting a list of systems.
+if {$found == 0} {
+ note "Defined systems:"
+ send "list -l 2\r"
+ expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -- "system>" {
+ #note "command prompt found"
+ }
+ "\n$" {
+ puts -nonewline "$expect_out(buffer)"
+ exp_continue
+ }
+ }
+ note "complete ... exiting"
+ send "exit\r"
+ exit 1
+}
+
+# CONSOLE: open a console channel
+if {[string compare $cmd "console"] == 0} {
+ note "connecting to console ..."
+ send "console -o\r"
+ interact {
+ -o -ex $prompt {
+ return
+ }
+ }
+ winge "console lost"
+ exit 0
+}
+
+# Function to wait until the issued power command has taken effect.
+proc powerWait {s} {
+ global prompt
+ set ok 0
+ for {set retry 1} {$ok == 0 && $retry < 30} {incr retry} {
+ note "checking for power state $s ..."
+ after 2000
+ send "power -state\r"
+ expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -ex $s {
+ set ok 1
+ exp_continue
+ }
+ -ex "$prompt" {
+ #note "command prompt found"
+ }
+ }
+ }
+ if {$ok == 0} {
+ winge "system did not enter power $s state ... aborting"
+ exit 2
+ }
+}
+
+# DOWN: shut the system down ... hard. Expect to OK before the
+# prompt in the case of success.
+note "powering cycling the system ..."
+send "power -cycle\r"
+set fail 1
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ "OK" {
+ set fail 0
+ exp_continue
+ }
+ -ex "$prompt" {
+ #note "command prompt found"
+ }
+}
+if {$fail == 1} {
+ winge "power cycle failed ... system not rebooted"
+ exit 2
+}
+powerWait On
+
+note "complete ... exiting"
+send "exit"
+exit 0
diff --git a/conmux/drivers/hmc b/conmux/drivers/hmc
new file mode 100755
index 00000000..13894f43
--- /dev/null
+++ b/conmux/drivers/hmc
@@ -0,0 +1,597 @@
+#!/usr/bin/expect --
+#
+# hmc -- handle consoles and rebooting of HMC based systems
+#
+# Allow connecting to the console of and rebooting of HMC connected
+# machines. Machines are identified by the HMC IP address, connected
+# system name, and partition name.
+#
+# usage:
+# hmc open-term|reboot -h <hmc IP> -m <system> -p <partition> \
+# -U <user> -P <password> -V <hmc version>
+#
+# example:
+# hmc open-term -m elm3b70 -p FullSystemPartition -U hscroot -P passwd \
+# -V 2.6 -h 1.2.3.4
+# hmc reboot -m elm3b70 -p FullSystemPartition -U hscroot -P passwd \
+# -V 2.6 -h 1.2.3.4
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "hmc"
+
+# See interactions.
+log_user 0
+#exp_internal 1
+
+proc note {m} {
+ global P
+ puts "$P: $m"
+}
+proc warn {m} {
+ global P
+ puts "$P: WARNING: $m"
+}
+proc winge {m} {
+ global P
+ puts "$P: MACHINE ERROR: $m"
+}
+
+#
+# OPTIONS: options parser.
+#
+proc shift {_list} {
+ upvar $_list list
+
+ set res [lindex $list 0]
+ set list [lreplace $list 0 0]
+
+ return $res
+}
+proc arg {_list arg} {
+ upvar $_list list
+
+ if {[llength $list] < 1} {
+ winge "$arg: required argument missing"
+ exit 1
+ }
+
+ return [shift list]
+}
+
+set user {hscroot}
+set host {}
+set system {}
+set lpar {}
+set profile {default}
+set mode {norm}
+set passwd {}
+set version {}
+
+set cmd [lindex $argv 0]
+shift argv
+while {[llength $argv] > 0} {
+ switch -- [shift argv] {
+ -h { set host [arg argv h]}
+ hmc { set host [arg argv h]}
+ -m { set system [arg argv m]}
+ -l { set lpar [arg argv l]}
+ -p { set lpar [arg argv p]}
+ -f { set profile [arg argv f]}
+ -b { set mode [arg argv b]}
+ -U { set user [arg argv P]}
+ -P { set passwd [arg argv P]}
+ -V { set version [arg argv P]}
+ }
+}
+
+if {[llength $argv] > 0} {
+ puts stderr "Usage: $P <cmd> -h <hmc> -m <system> -p <lpar> -f <profile> -b <mode>"
+ exit 1
+}
+if {[string compare $host ""] == 0 ||
+ [string compare $system ""] == 0 ||
+ [string compare $lpar ""] == 0} \
+{
+ winge "hmc (-h), machine (-m) and lpar (-p) required"
+ exit 1
+}
+
+set prompt {___EOF___HMC___EOF___}
+set hscpath {/opt/hsc/bin}
+
+#log_file -a "$logfile"
+
+set elapsed_time 0
+set timeout 30
+
+# Ensure we have a terminal so we don't get prompted for one.
+if {![info exists env(TERM)]} {
+ set env(TERM) {vt100}
+}
+
+# If we have a password don't use any ssh-keys we may have been offered.
+if {[string compare $passwd ""] == 0} {
+ set command "ssh $env(SSH_OPTIONS) $user@$host"
+} else {
+ set command "ssh $user@$host"
+}
+
+# CONNECT: connect to the remote console. Set the prompt up so we
+# know when a command we execute has completed.
+note "Logging into HMC console with command \"$command\" ..."
+eval spawn $command
+send "PS1=$prompt\r"
+expect {
+ "password:" {
+ if {[string compare $passwd ""] == 0} {
+ winge "$host: requesting password - not hacked"
+ exit 2
+ }
+ send "$passwd\r"
+ after 500
+ send "PS1=$prompt\r"
+ exp_continue
+ }
+ "Connection timed out" {
+ winge "$host: cannot connect to console"
+ exit 2
+ }
+ "Name or service not known" {
+ winge "$host: HMC name invalid"
+ exit 1
+ }
+ "Permission denied" {
+ winge "$host: authentication failure, permission denied"
+ exit 2
+ }
+ timeout {
+ winge "$host: HMC did not prompt us, timed out"
+ exit 2
+ }
+ eof {
+ winge "$host: HMC disconnected without prompting"
+ exit 2
+ }
+
+ "Terminal type?" {
+ send "vt100\r"
+ send "PS1=$prompt\r"
+ exp_continue
+ }
+
+ "PS1=$prompt" {
+ # Ignore us typing the change to the prompt.
+ exp_continue
+ }
+ "$prompt" {
+ # Prompt see, we are in and working, drop out.
+ ## note "prompt seen"
+ }
+
+ -re "^\[^\n]*\n" {
+ # We need to absorb any login information, motd etc.
+ ## puts "NOISE<$expect_out(buffer)>"
+ exp_continue
+ }
+}
+
+proc runit {cmd _out} {
+ upvar $_out out
+
+ global prompt
+
+ send "$cmd"
+
+ set text ""
+ set line 0
+
+ set timeout 120
+ expect {
+
+ -re "^\[^\n]*\n" {
+ ##note "LINE: $expect_out(buffer)"
+ if { $line > 0 } {
+ append text $expect_out(buffer)
+ }
+ incr line
+ exp_continue
+ }
+
+ -re "$prompt" {
+ ##note "prompt seen"
+ }
+
+ timeout {
+ set out "hmc: command timeout"
+ return 255
+ }
+ }
+ set out [string trimright $text]
+
+ send "echo \$?\r"
+ set text ""
+ set line 0
+ set timeout 30
+ expect {
+ -re "^\[^\n]*\n" {
+ ##note "LINE: $expect_out(buffer)"
+ if { $line > 0 } {
+ append text $expect_out(buffer)
+ }
+ incr line
+ exp_continue
+ }
+
+ -re "$prompt" {
+ ##note "prompt seen"
+ }
+
+ timeout {
+ set out "hmc: command status timeout"
+ return 255
+ }
+ }
+ ##note "EXIT: $text"
+
+ return [string trimright $text]
+}
+
+proc extract_names {out} {
+ set res {}
+ foreach sys [split $out] {
+ if {[string compare $sys ""] != 0} {
+ lappend res $sys
+ }
+ }
+
+ return $res
+}
+
+# Find out the current version of the HMC software.
+if {[string compare $version ""] == 0} {
+ runit "rpm -q IBMhsc.coreserver --qf '%{VERSION}'; echo ''\r" version
+}
+if {[string compare $version ""] == 0} {
+ winge "unable to obtain HMC code version"
+ exit 1
+}
+note "HMC revision v$version"
+
+# Based on the version of the HMC software select payload.
+if {$version < 4.0} {
+ #
+ # VERSION 2.6/3.0 specific support.
+ #
+ proc system_list {} {
+ global hscpath
+
+ # Ask for a list of systems.
+ if {[runit "lssyscfg -r sys --all -F name\r" out] != 0} {
+ winge "unable to obtain list of systems"
+ exit 1
+ }
+
+ return [extract_names $out]
+ }
+ proc system_state {system} {
+ global hscpath
+
+ # Ask for the system status.
+ if {[runit "lssyscfg -r sys -n $system -F state\r" out] != 0} {
+ winge "$system: failed to get status"
+ exit 1
+ }
+
+ return $out
+ }
+ proc lpar_list {system} {
+ global hscpath
+
+ # Ask for a list of lpars for this system.
+ if {[runit "lssyscfg -r lpar -m $system --all -F name\r" out] != 0} {
+ winge "unable to obtain list of lpars"
+ exit 1
+ }
+
+ return [extract_names $out]
+ }
+ proc state {system lpar} {
+ global hscpath
+
+ # Ask for the lpar status.
+ if {[runit "lssyscfg -r lpar -m $system -n $lpar -F state\r" out] != 0} {
+ winge "$system/$lpar: failed to get lpar status"
+ exit 1
+ }
+
+ return $out
+ }
+ proc reboot_metal {system lpar profile mode} {
+ global hscpath
+
+ # If we have no connection wait for the machine to appear.
+ # XXX: timeout?
+ set was {}
+ set when [clock seconds]
+ if {[string compare [system_state $system] "Ready"] != 0} {
+ while {([clock seconds] - $when) < 60} {
+ sleep 15
+ set state [system_state $system]
+
+ note "waiting for stable connected state ($state)"
+ if {$state != $was || [string compare $state "No Connection"] == 0} {
+ set was $state
+ set when [clock seconds]
+ }
+ }
+ }
+
+ # See if the system is is in Error, if so attempt to
+ # power it on.
+ if {[string compare [system_state $system] "Error"] == 0} {
+ note "starting full system (from Error)"
+ if {[runit "chsysstate -r sys -m $system -n $system -b $mode -o on -c full\r" out] == 0} {
+ note "started"
+ return
+ }
+ note "start failed - attempting shutdown"
+ }
+
+ # See if the system is up, if so shut it down.
+ if {[string compare [system_state $system] "No Power"] != 0} {
+ note "shutting down full system"
+ if {[runit "chsysstate -r sys -m $system -n $system -o off -c full\r" out] != 0} {
+ winge "$system: power off failed\n$out"
+ exit 2
+ }
+ }
+
+ while {[string compare [system_state $system] "No Power"] != 0} {
+ note "waiting for shutdown"
+ sleep 15
+ }
+
+ note "starting full system"
+ if {[runit "chsysstate -r sys -m $system -n $system -b $mode -o on -c full\r" out] != 0} {
+ winge "$system: power on failed"
+ exit 2
+ }
+ note "started"
+ }
+ proc reboot {system lpar profile mode} {
+ global hscpath
+
+ # Handle the bare metal separatly.
+ if {[string compare $lpar "FullSystemPartition"] == 0} {
+ return [reboot_metal $system $lpar $profile $mode]
+ }
+
+ # Partitions in Error state are tricky, you _may_ either
+ # have to shut them down and then start them, or you may
+ # just have to start them. So, if we are in Error start
+ # the partition, if it fails we can just drop through
+ # to the regular stop/start cycle.
+ if {[string compare [state $system $lpar] "Error"] == 0} {
+ note "starting lpar (from Error)"
+ if {[runit "chsysstate -r lpar -m $system -n $lpar -b $mode -o on\r" out] == 0} {
+ note "started"
+ return
+ }
+ note "start failed - attempting shutdown"
+ }
+
+ # See if the lpar is up, if so shut it down.
+ if {[string compare [state $system $lpar] "Ready"] != 0} {
+ note "shutting down lpar"
+ if {[runit "chsysstate -r lpar -m $system -n $lpar -o off\r" out] != 0} {
+ winge "$system/$lpar: power off failed\n$out"
+ exit 2
+ }
+ }
+
+ while {[string compare [state $system $lpar] "Ready"] != 0} {
+ note "waiting for shutdown"
+ sleep 15
+ }
+
+ note "starting lpar"
+ if {[runit "chsysstate -r lpar -m $system -n $lpar -b $mode -o on\r" out] != 0} {
+ winge "$system/$lpar: power on failed\n$out"
+ exit 2
+ }
+ note "started"
+ }
+
+}
+
+if {$version >= 4.0} {
+ #
+ # VERSION 4.x specific support.
+ #
+ proc system_list {} {
+ global hscpath
+
+ # Ask for a list of systems.
+ if {[runit "lssyscfg -r sys -F name\r" out] != 0} {
+ winge "unable to obtain list of systems"
+ exit 1
+ }
+
+ return [extract_names $out]
+ }
+ proc system_state {system} {
+ global hscpath
+
+ # Ask for the system status.
+ if {[runit "lssyscfg -r sys -m $system -F state\r" out] != 0} {
+ winge "$system: failed to get system status"
+ exit 1
+ }
+
+ return $out
+ }
+ proc lpar_list {system} {
+ global hscpath
+
+ # Ask for a list of lpars for this system.
+ if {[runit "lssyscfg -r lpar -m $system -F name\r" out] != 0} {
+ winge "unable to obtain list of lpars"
+ exit 1
+ }
+
+ return [extract_names $out]
+ }
+ proc state {system lpar} {
+ global hscpath
+
+ # Ask for the lpar status.
+ if {[runit "lssyscfg -r lpar -m $system --filter lpar_names=$lpar -F state\r" out] != 0} {
+ winge "$system/$lpar: failed to get lpar status"
+ exit 1
+ }
+
+ return $out
+ }
+
+ proc reboot {system lpar profile mode} {
+ global hscpath
+
+ # Partitions in Error state are tricky, you _may_ either
+ # have to shut them down and then start them, or you may
+ # just have to start them. So, if we are in Error start
+ # the partition, if it fails we can just drop through
+ # to the regular stop/start cycle.
+ if {[string compare [state $system $lpar] "Error"] == 0} {
+ note "starting lpar (from Error)"
+ if {[runit "chsysstate -r lpar -m $system -n $lpar -f $profile -b $mode -o on\r" out] == 0} {
+ note "started"
+ return
+ }
+ note "start failed - attempting shutdown"
+ }
+
+ # See if the lpar is up, if so shut it down.
+ if {[string compare [state $system $lpar] "Not Activated"] != 0} {
+ note "shutting down lpar"
+ if {[runit "chsysstate -r lpar -m $system -n $lpar -o shutdown --immed\r" out] != 0} {
+ winge "$system/$lpar: power off failed\n$out"
+ exit 2
+ }
+ }
+
+ while {[string compare [state $system $lpar] "Not Activated"] != 0} {
+ note "waiting for shutdown"
+ sleep 15
+ }
+
+ note "starting lpar"
+ if {[runit "chsysstate -r lpar -m $system -n $lpar -f $profile -b $mode -o on\r" out] != 0} {
+ winge "$system/$lpar: power on failed\n$out"
+ exit 2
+ }
+ note "started"
+ }
+}
+
+#
+# VERSION: common
+#
+proc open-term {system lpar} {
+ global prompt
+ global hscpath
+ global spawn_id
+
+ note "opening console ..."
+ send "ulimit -t 3600; \[ -f cleandown ] && sudo /home/hscroot/cleandown $system $lpar; mkvterm -m $system -p $lpar\r"
+ expect {
+ "NVTS" { }
+ "Open in progress.." { }
+ "$prompt" {
+ winge "$system/$lpar: open console failed"
+ exit 2
+ }
+ }
+
+ note "console open"
+ interact {
+ -i $spawn_id
+ " Connection has closed" {
+ winge "$system/$lpar: console connection closed"
+ exit 2
+ }
+ {Error in communication path to the partition} {
+ winge "$system/$lpar: lost contact with lpar"
+ exit 2
+ }
+ "$prompt" {
+ winge "$system/$lpar: open console failed"
+ exit 2
+ }
+ eof {
+ winge "$system/$lpar: payload lost"
+ exit 2
+ }
+ \000 {
+ }
+
+ }
+ note "console closed"
+}
+
+proc close-term {system lpar} {
+ global hscpath
+
+ note "closing console ..."
+ if {[runit "rmvterm -m $system -p $lpar\r" out] != 0} {
+ winge "$system/$lpar: close console failed"
+ exit 2
+ }
+}
+
+# Look and see if this system exists.
+set systems [system_list]
+if {[lsearch -exact $systems $system] < 0} {
+ winge "$system: system not known; console knows: $systems"
+ exit 1
+}
+set lpars [lpar_list $system]
+if {[lsearch -exact $lpars $lpar] < 0} {
+ winge "$system/$lpar: lpar not known; console knows: $lpars"
+ exit 1
+}
+
+# Check for system in Operating/Ready.
+set state [system_state $system]
+if {[string compare $lpar "FullSystemPartition"] != 0 &&
+ [string compare $state "Operating"] != 0 &&
+ [string compare $state "Ready"] != 0} \
+{
+ winge "$system: unusable - $state"
+ exit 1
+}
+
+# Ask for the lpar status, to see if it exists.
+set lstate [state $system $lpar]
+note "$system/$lpar: found ($state/$lstate)"
+
+#
+# COMMANDS: command.
+#
+switch -- $cmd {
+ {open-term} {
+ close-term $system $lpar
+ open-term $system $lpar
+ }
+ {close-term} {
+ close-term $system $lpar
+ }
+ {reboot} {
+ reboot $system $lpar $profile $mode
+ }
+}
+
+exit 0
diff --git a/conmux/drivers/module.mk b/conmux/drivers/module.mk
new file mode 100755
index 00000000..1c28816d
--- /dev/null
+++ b/conmux/drivers/module.mk
@@ -0,0 +1,15 @@
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+
+DRIVERS:=blade hmc reboot-netfinity reboot-newisys reboot-numaq \
+ reboot-rsa reboot-rsa2
+
+install::
+ @[ -d $(BASE)/lib/drivers ] || mkdir $(BASE)/lib/drivers
+ for f in $(DRIVERS); do \
+ rm -f $(BASE)/lib/drivers/$$f; \
+ cp -p drivers/$$f $(BASE)/lib/drivers/$$f; \
+ chmod 755 $(BASE)/lib/drivers/$$f; \
+ done
diff --git a/conmux/drivers/reboot-netfinity b/conmux/drivers/reboot-netfinity
new file mode 100755
index 00000000..e7f986c1
--- /dev/null
+++ b/conmux/drivers/reboot-netfinity
@@ -0,0 +1,98 @@
+#!/usr/bin/expect -f
+#
+# reboot-netfinity -- reboot netfinity machines via their management port
+#
+# This expect script reboots a machine via a managemant port.
+# It only works with the Netfinity-style Service Processors.
+# They have a number-driven interface
+#
+# example machines: Netfinity 6000R, 8500R
+# xSeries x350, x370
+#
+# usage:
+# reboot-netfinity <user> <password> <connect command> ...
+#
+# examples:
+# reboot-netfinity USERID PASSW0RD telnet 1.2.3.4 7033
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Dave Hansen <haveblue@us.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "reboot-netfinity"
+
+if {[llength $argv] < 2} {
+ puts stderr "Usage: $P <userid> <password> <cmd> ..."
+ exit 1
+}
+set argc [llength $argv]
+set userid [lindex $argv 0]
+set password [lindex $argv 1]
+set timeout 30
+set send_slow {1 .1}
+
+set command [lrange $argv 2 end]
+
+puts "$P: Logging into service processor with command \"$command\" to restart it";
+
+eval spawn [lrange $argv 2 end]
+
+send -s -- "\033\033\033\033\033"
+expect *;
+send "\033"
+
+expect {
+ #Log in
+ "USER ID:" {
+ send -s -- "$userid\r"
+ expect -exact "PASSWORD:"
+ send -s -- "$password\r"
+ exp_continue;
+ }
+ #Some machines use different login prompts
+ "User_id:" {
+ send -s -- "$userid\r"
+ expect -exact "Password:"
+ send -s -- "$password\r"
+ exp_continue;
+ }
+ #already logged in
+ "Z - Start Remote Video" {
+ send "6"
+ expect -exact "4 - Power On"
+ send "3"
+ expect -exact "2 - Power Off Immediately"
+ send "2"
+ expect -exact "0 - Write"
+ send "0"
+ expect -exact "6.3.2: Write"
+
+ send -s -- "\033\033\033\033\033"
+ expect -exact "Z - Start Remote Video"
+ send "6"
+ expect -exact "4 - Power On"
+ send "4"
+ expect -exact "2 - Power On Release CPU's Reset"
+ send "2"
+ expect -exact "0 - Write"
+ send "0"
+ expect -exact "6.4.2: Write"
+
+ # On kernels with ACPI support, the above may send an ACPI
+ # event to the OS to perform a clean shutdown. If that happens
+ # we may not ever come back up. Add the reboot command below
+ # to finish the job.
+ send -s -- "\033\033\033\033\033"
+ expect -exact "Z - Start Remote Video"
+ send "7"
+ expect -exact "4 - Restart SP"
+ send "2"
+ expect -exact "0 - Write"
+ send "0"
+ expect -exact "7.2: Write"
+ }
+
+}
+
+puts "$P: reboot initiated"
diff --git a/conmux/drivers/reboot-newisys b/conmux/drivers/reboot-newisys
new file mode 100755
index 00000000..a4080f59
--- /dev/null
+++ b/conmux/drivers/reboot-newisys
@@ -0,0 +1,49 @@
+#!/usr/bin/expect -f
+#
+# reboot-newisys -- reboot a newisys system via its management interface
+#
+# This expect script reboots a newisys machine using its command
+# interface. This is typically accessed over ssh.
+#
+# usage:
+# reboot-newisys <user> <password> <connect command> ...
+#
+# examples:
+# reboot-newisys USERID PASSW0RD ssh 1.2.3.4
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Daniel Sauble <djsauble@us.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "reboot-newisys"
+
+if {[llength $argv] != 4} {
+ puts stderr "Usage: $P <userid> <password> <type> <machine>"
+ exit 1
+}
+set userid [lindex $argv 0]
+set password [lindex $argv 1]
+set machine [lindex $argv 3]
+set timeout 30
+
+puts "$P: connecting to $machine to initiate reboot ..."
+
+# Log in
+eval spawn "ssh -o StrictHostKeyChecking=no $userid\@$machine"
+expect "password: "
+send -- "$password\r"
+expect "localhost \$ "
+send "platform get power state\r"
+
+# Check to see if the machine is on or off
+# then restart it
+expect {
+ "On" {send "platform set power state cycle -f\r"}
+ "Off" {send "platform set power state on -f\r"}
+}
+expect "localhost \$ "
+expect "localhost \$ "
+send "exit\r"
+
+puts "$P: reboot initated"
diff --git a/conmux/drivers/reboot-numaq b/conmux/drivers/reboot-numaq
new file mode 100755
index 00000000..acabf1ec
--- /dev/null
+++ b/conmux/drivers/reboot-numaq
@@ -0,0 +1,224 @@
+#!/usr/bin/expect --
+#
+# reboot-numaq -- reboot Numa-Q systems connected to a VCS console
+#
+# Use the remote telnet managment interface on the VCS console to
+# hardreset systems. Systems are identified by their system names
+# within VCS.
+#
+# usage:
+# reboot-numaq <type> <console IP> <system> <port> <userid> <password>
+#
+# examples:
+# reboot-numaq vcs 1.2.3.4 hummer 12346 FOO BAR
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "reboot-numaq"
+
+if { [llength $argv] != 6 } {
+ puts stderr "Usage: reboot-numaq vcs <console addr> <system> <console port> <userid> <password>"
+ exit 1
+}
+
+log_user 0
+#stty echo
+set argc [llength $argv]
+set console_ip [lindex $argv 1]
+set system [lindex $argv 2]
+set console_port [lindex $argv 3]
+set username [lindex $argv 4]
+set password [lindex $argv 5]
+
+#log_file -a "$logfile"
+
+set elapsed_time 0
+set timeout 30
+
+set command "telnet $console_ip $console_port"
+
+proc note {m} {
+ global P
+ puts "$P: $m"
+}
+proc warn {m} {
+ global P
+ puts "$P: WARNING: $m"
+}
+proc winge {m} {
+ global P
+ puts "$P: MACHINE ERROR: reboot failed - $m"
+}
+
+# CONNECT: connect to the remote console.
+note "Logging into VCS console with command \"$command\" to restart it"
+eval spawn $command
+expect {
+ default {
+ winge "login prompt not issued"
+ exit 2
+ }
+ "Connection closed by foreign host." {
+ winge "Telnet connection closed."
+ exit 1
+ }
+ "Unable to connect to remote host:" {
+ winge "Connection to remote console failed";
+ exit 2
+ }
+ "login:" {
+ note "saw login prompt"
+ }
+}
+
+# AUTHENTICATE: send username and password at the relevant prompts
+note "sending login ..."
+send -- "$username\r"
+expect {
+ default {
+ winge "password prompt not issued"
+ exit 2
+ }
+ "password:" {
+ note "password prompt found"
+ }
+}
+
+note "sending password ..."
+send -- "$password\r"
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ "Authentication Failed" {
+ winge "login/password incorrect ... aborting"
+ exit 1
+ }
+ -- "->" {
+ #note "command prompt found"
+ }
+}
+
+# SYSTEM: try and select the system, if the specified system does not
+# exist get a listing and print that out whilst we are connected.
+note "selecting system '$system' ..."
+send "cd $system\r"
+set found 1
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -re "cd: path '.*' not found" {
+ winge "system not defined"
+ set found 0
+ exp_continue
+ }
+ -- "->" {
+ #note "command prompt found"
+ }
+}
+
+# The system the user specified was not found, give them a hand by
+# getting a list of systems.
+if {$found == 0} {
+ note "Defined systems:"
+ send "sysdef -l\r"
+ expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -re "sysdef -l\r\n$" {
+ exp_continue
+ }
+ -- "->" {
+ #note "command prompt found"
+ }
+ "\n$" {
+ puts -nonewline "$expect_out(buffer)"
+ exp_continue
+ }
+ }
+ note "complete ... exiting"
+ send "exit\r"
+ exit 1
+}
+
+# DOWN: shut the system down ... hard. Expect to see nothing before the
+# prompt in the case of success.
+set timeout 65
+set fail 1
+for {set retry 1} {$fail == 1 && $retry <= 3} {incr retry} {
+ note "powering off the system ..."
+ send "power -f -t 60000 off\r"
+ set fail 0
+ expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -- "->" {
+ #note "command prompt found"
+ }
+ "*E*" {
+ warn "power off failed ... attempt $retry"
+ puts -nonewline "$expect_out(buffer)"
+ set fail 1
+ exp_continue
+ }
+ }
+}
+if {$fail == 1} {
+ winge "power off failed ... system not rebooted"
+ exit 2
+}
+set timeout 30
+
+# UP: power the system on ... and then ask it to boot.
+note "powering on the system ..."
+send "power on\r"
+set fail 0
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -- "->" {
+ #note "command prompt found"
+ }
+ "*E*" {
+ warn "power on failed ..."
+ set fail 1
+ exp_continue
+ }
+}
+if {$fail == 1} {
+ winge "power off failed ... system not rebooted"
+ exit 2
+}
+
+note "booting the system ..."
+send "boot\r"
+expect {
+ default {
+ winge "command prompt not issued"
+ exit 2
+ }
+ -- "->" {
+ #note "command prompt found"
+ }
+ "*E*" {
+ warn "boot failed ..."
+ set fail 1
+ exp_continue
+ }
+}
+
+note "complete ... exiting"
+send "exit"
+exit 0
diff --git a/conmux/drivers/reboot-rsa b/conmux/drivers/reboot-rsa
new file mode 100755
index 00000000..9b5082db
--- /dev/null
+++ b/conmux/drivers/reboot-rsa
@@ -0,0 +1,101 @@
+#!/usr/bin/expect
+#
+# reboot-rsa -- reboot systems with RSA management cards
+#
+# Reboot systems via their RSA (I) managment card interface.
+#
+# usage:
+# reboot-rsa <userid> <password> <connect commands> ...
+#
+# example
+# reboot-rsa FOO BAR telnet 1.2.3.4
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Dave Hansen <haveblue@us.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "reboot-rsa"
+
+if {[llength $argv] < 3} {
+ puts stderr "Usage: $P <username> <password> <cmd> ..."
+ exit 1
+}
+set username [lindex $argv 0]
+set password [lindex $argv 1]
+
+log_user 0
+#stty echo
+#log_file -a "$logfile"
+
+proc note {m} {
+ global P
+ puts "$P: $m"
+}
+proc warn {m} {
+ global P
+ puts "$P: WARNING: $m"
+}
+proc winge {m} {
+ global P
+ puts "$P: ERROR: $m"
+}
+
+set elapsed_time 0
+set timeout 10
+
+set command [lrange $argv 2 end]
+eval spawn [lrange $argv 2 end]
+
+note "Logging into service processor with command \"$command\" to restart it";
+
+expect {
+ "Connection closed by foreign host." {
+ winge "Telnet connection closed."
+ exit 1;
+ }
+ "Unable to connect to remote host:" {
+ winge "Someone may already have the service processor";
+ exit 2;
+ }
+ "Login ID:" {
+ send "$username\t$password\r"
+ }
+ timeout {
+ winge "never saw opening screen with \"Login ID:\""
+ exit 2;
+ }
+}
+
+expect {
+ "Log Off" {
+ send "\t\t\t\t\r"
+ note "Saw opening screen, selecting \"Server Power/Restart\"";
+ }
+ timeout {
+ }
+}
+
+expect {
+ "Restart Server Immediately" {
+ note "Saw \"Server Power/Restart\" screen. Selecting \"Restart\"";
+ send "\t\t\t\t\t\r";
+ }
+}
+
+expect {
+ "Restarting the system immediately" {
+ note "Saw restart confirmation prompt, pressing enter";
+ send "\r"
+ }
+}
+
+expect {
+ "The system is performing a Restart Server Immediately" {
+ note "the system is restarting";
+ exit 0;
+ }
+}
+
+winge "an error occurred while restarting the server"
+exit 1;
diff --git a/conmux/drivers/reboot-rsa2 b/conmux/drivers/reboot-rsa2
new file mode 100755
index 00000000..b3d97b03
--- /dev/null
+++ b/conmux/drivers/reboot-rsa2
@@ -0,0 +1,91 @@
+#!/usr/bin/expect
+#
+# reboot-rsa2 -- reboot systems via their RSA-II management cards
+#
+# Use a systems RSA (II) managment card to hard reset that system.
+#
+# usage:
+# reboot-rsa2 <userid> <password> <connect command> ...
+#
+# examples:
+# reboot-rsa2 FOO BAR telnet 1.2.3.4 23
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Aileen Sheedy <asheedy@us.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "reboot-rsa2"
+
+if {[llength $argv] < 3} {
+ puts stderr "Usage: $P <username> <password> <cmd> ..."
+ exit 1
+}
+set username [lindex $argv 0]
+set password [lindex $argv 1]
+
+log_user 0
+#stty echo
+#log_file -a "$logfile"
+
+proc note {m} {
+ global P
+ puts "$P: $m"
+}
+proc warn {m} {
+ global P
+ puts "$P: WARNING: $m"
+}
+proc winge {m} {
+ global P
+ puts "$P: ERROR: $m"
+}
+
+set elapsed_time 0
+set timeout 10
+
+set command [lrange $argv 2 end]
+eval spawn [lrange $argv 2 end]
+
+note "Logging into service processor with command \"$command\" to restart it"
+
+expect {
+ "Connection closed by foreign host." {
+ winge "Telnet connection closed."
+ exit 1
+ }
+ "Unable to connect to remote host:" {
+ winge "Someone may already have the service processor"
+ exit 2
+ }
+ "username:" {
+ send "$username\r$password\r"
+ }
+ timeout {
+ winge "Never saw opening screen with \"username:\""
+ exit 2
+ }
+}
+
+expect {
+ "Invalid login!" {
+ winge "Invalid username or password"
+ exit 2
+ }
+ ">" {
+ send "reset\r"
+ }
+}
+
+expect {
+ "ok" {
+ note "System restarting"
+ send "exit\r"
+ exit 0
+ }
+ timeout {
+ }
+}
+
+winge "an error occurred while restarting the server"
+exit 1
diff --git a/conmux/examples/README b/conmux/examples/README
new file mode 100755
index 00000000..7b12b429
--- /dev/null
+++ b/conmux/examples/README
@@ -0,0 +1,11 @@
+EXAMPLES
+========
+This directory contains some sample configuration files for conmux.
+
+socket.cf -- where the console is exposed directly on a tcp port on
+ the local network.
+
+command.cf -- where the console is obtained through a driver.
+
+Details on the command line options for each driver are in the
+comments at the top of the driver.
diff --git a/conmux/examples/command.cf b/conmux/examples/command.cf
new file mode 100755
index 00000000..1d6b9990
--- /dev/null
+++ b/conmux/examples/command.cf
@@ -0,0 +1,4 @@
+listener ppc64
+application console 'ppc64 console' 'hmc open-term -m bigppc -p bigppc-lpar1 -U user -P passwd -V 2.6 hmc 1.2.3.4'
+ command 'hardreset' 'initated a hard reset' 'hmc reboot -m bigppc -p bigppc-lpar1 -U user -P passwd -V 2.6 hmc 1.2.3.4'
+ help 'hardreset' 'initiate a system hard reset for this machine'
diff --git a/conmux/examples/socket.cf b/conmux/examples/socket.cf
new file mode 100755
index 00000000..513622d3
--- /dev/null
+++ b/conmux/examples/socket.cf
@@ -0,0 +1,4 @@
+listener hummer
+socket console 'hummer console' '1.2.3.4:2088'
+ command 'hardreset' 'initated a hard reset' 'reboot-numaq vcs 1.2.3.5 hummer 12346 user passwd'
+ help 'hardreset' 'initiate a system hard reset for this machine'
diff --git a/conmux/helpers/autoboot-helper b/conmux/helpers/autoboot-helper
new file mode 100755
index 00000000..e147fc82
--- /dev/null
+++ b/conmux/helpers/autoboot-helper
@@ -0,0 +1,72 @@
+#! /usr/bin/expect
+#
+# autoboot-helper -- automatic boot helper
+#
+# Some machines have real issues rebooting. This helper watches their
+# console output for the telltale signs of a reboot in progress. When
+# spotted this triggers an automated 'manual' hardreset. For use when
+# machines fail to reboot at the BIOS level.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "autoboot-numaq"
+log_user 0
+
+if {$argc != 0} {
+ puts stderr "Usage: $P"
+ exit 1
+}
+
+proc note {msg} {
+ global P
+ puts stderr "$P: $msg"
+}
+proc warn {msg} {
+ global P
+ puts stderr "$P: $msg"
+ puts "~\$msg $msg"
+}
+
+set timeout -1
+set likely 0
+expect_user {
+ {TEST;} {
+ warn "test trigger detected"
+ exp_continue
+ }
+ {Unmounting file systems} {
+ note "controlled reboot in progress ..."
+ set likely [clock seconds]
+ exp_continue
+ }
+ {Unmounting local filesystems...} {
+ note "controlled reboot in progress ..."
+ set likely [clock seconds]
+ exp_continue
+ }
+ -ex {***** REBOOT LINUX *****} {
+ note "fsck failure occured ..."
+ set likely [clock seconds]
+ exp_continue
+ }
+ {Restarting system.} {
+ if {$likely != 0} {
+ warn "shutdown complete, restart indicated"
+ puts "~\$hardreset"
+ set likely 0
+ } else {
+ warn "likely false positive"
+ }
+ exp_continue
+ }
+ "*\n" {
+ if {$likely > 0 && ([clock seconds] - $likely) > 60} {
+ warn "trigger timeout"
+ set likely 0
+ }
+ exp_continue
+ }
+}
diff --git a/conmux/helpers/module.mk b/conmux/helpers/module.mk
new file mode 100755
index 00000000..649f2ef3
--- /dev/null
+++ b/conmux/helpers/module.mk
@@ -0,0 +1,14 @@
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+
+HELPERS:=autoboot-helper tickle-helper
+
+install::
+ @[ -d $(BASE)/lib/helpers ] || mkdir $(BASE)/lib/helpers
+ for f in $(HELPERS); do \
+ rm -f $(BASE)/lib/helpers/$$f; \
+ cp -p helpers/$$f $(BASE)/lib/helpers/$$f; \
+ chmod 755 $(BASE)/lib/helpers/$$f; \
+ done
diff --git a/conmux/helpers/tickle-helper b/conmux/helpers/tickle-helper
new file mode 100755
index 00000000..2623c529
--- /dev/null
+++ b/conmux/helpers/tickle-helper
@@ -0,0 +1,99 @@
+#! /usr/bin/expect
+#
+# tickle-helper -- watch for reboots and 'tickle' the console during them
+#
+# Some consoles get broken when the machine reboots. There are normally
+# fixed by trying to use them at or arround the reboot. Watch for reboots
+# and initiate use of the console to trigger a drop/reconnect cycle.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+set P "tickle-helper"
+log_user 0
+
+if {$argc != 0} {
+ puts stderr "Usage: $P"
+ exit 1
+}
+
+proc note {msg} {
+ global P
+ puts stderr "$P: $msg"
+}
+proc warn {msg} {
+ global P
+ puts stderr "$P: $msg"
+ puts "~\$msg $msg"
+}
+
+proc tickle {} {
+ set timeout 5
+ warn "tickling console ..."
+ puts ""
+ set now [clock seconds]
+ expect_user {
+ {blade: ERROR: console lost} {
+ }
+ {Elapsed time since release of system processors:} {
+ }
+ "*\n" {
+ if {([clock seconds] - $now) > 5} {
+ set now [clock seconds]
+ warn "tickling console ..."
+ puts ""
+ }
+ exp_continue
+ }
+ timeout {
+ set now [clock seconds]
+ warn "tickling console ..."
+ puts ""
+ exp_continue
+ }
+ }
+ set timeout -1
+ warn "tickle complete ..."
+}
+
+set timeout -1
+set likely 0
+expect_user {
+ {TEST;} {
+ warn "test trigger detected"
+ exp_continue
+ }
+ -re {Unmounting file systems|Unmounting local filesystems...} {
+ note "controlled reboot in progress ..."
+ set likely [clock seconds]
+ exp_continue
+ }
+ -ex {***** REBOOT LINUX *****} {
+ note "fsck failure occured ..."
+ set likely [clock seconds]
+ exp_continue
+ }
+ -re {HARDBOOT INITIATED|initated a hard reset} {
+ tickle
+ exp_continue
+ }
+ -re {Please stand by while rebooting the system|Restarting system} {
+ if {$likely != 0} {
+ warn "shutdown complete, restart indicated"
+ tickle
+ set likely 0
+ } else {
+ warn "likely false positive"
+ }
+ exp_continue
+ }
+ "*\n" {
+ if {$likely > 0 && ([clock seconds] - $likely) > 60} {
+ warn "trigger timeout"
+ set likely 0
+ }
+ exp_continue
+ }
+}
diff --git a/conmux/mkdeb b/conmux/mkdeb
new file mode 100755
index 00000000..14c43ef0
--- /dev/null
+++ b/conmux/mkdeb
@@ -0,0 +1,9 @@
+#!/bin/sh
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+read name major minor patch build <.version
+alien --version=$major.$minor.$patch --description="console multiplexor" conmux.tgz
diff --git a/conmux/start b/conmux/start
new file mode 100755
index 00000000..92402af6
--- /dev/null
+++ b/conmux/start
@@ -0,0 +1,138 @@
+#! /bin/sh
+#
+# start -- start up configured conmux servers on this host.
+#
+# (C) Copyright IBM Corp. 2004, 2005, 2006
+# Author: Andy Whitcroft <andyw@uk.ibm.com>
+#
+# The Console Multiplexor is released under the GNU Public License V2
+#
+if [ -f ~/.gmm.conf ]; then
+ . ~/.gmm.conf
+fi
+CONMUX=${CONMUX:-/usr/local/conmux}
+
+cmd="start"
+if [ "$1" != "" ]; then
+ cmd="$1"
+fi
+
+PATH=$CONMUX/bin:$CONMUX/sbin:$CONMUX/lib/drivers:$CONMUX/lib/helpers:$PATH
+
+function start() {
+ typeset name="$1"
+ typeset pf="$CONMUX/log/$name.pid"
+
+ shift
+
+ # Determine whether it is already running ... if so leave it be.
+ if [ -f "$pf" ]; then
+ if kill -0 `cat "$pf"` 2>/dev/null; then
+ return 1
+ fi
+ fi
+
+ echo "starting $name ..."
+ "$@" >"$CONMUX/log/$name.log" 2>&1 &
+ echo "$!" >"$pf"
+
+ return 0
+}
+function stop() {
+ typeset name="$1"
+ typeset pf="$CONMUX/log/$name.pid"
+
+ echo "stopping $name ..."
+ # Kill it and clear up
+ kill -HUP `cat "$pf"` 2>/dev/null
+ rm -f "$pf"
+}
+
+existing=""
+for i in $CONMUX/log/*.pid
+do
+ n=${i%.pid}
+ n=${n#$CONMUX/log/}
+
+ if [ "$n" != "*" ]; then
+ existing="$existing $n"
+ fi
+done
+
+if [ "$cmd" = "start" ]; then
+ autoboot=""
+ [ -f $CONMUX/etc/registry ] || touch $CONMUX/etc/registry
+ start registry $CONMUX/sbin/conmux-registry 63000 $CONMUX/etc/registry
+ if [ "$?" -eq 0 ]; then
+ sleep 1
+ fi
+ started="registry"
+ pause=0
+ for i in $CONMUX/etc/*.cf
+ do
+ n=${i%.cf}
+ n=${n#$CONMUX/etc/}
+
+ if [ "$n" != "*" ]; then
+ if [ -f "$CONMUX/log/$n.cf" ]; then
+ if ! cmp -s "$i" "$CONMUX/log/$n.cf"; then
+ stop $n
+ fi
+ fi
+ start $n $CONMUX/sbin/conmux $i
+ if [ "$?" -eq 0 ]; then
+ pause=1
+ fi
+ started="$started $n"
+
+ # Preserve the orginal configuration file.
+ cp "$i" "$CONMUX/log/$n.cf"
+
+ if grep -q TYPE:numaq "$i"; then
+ autoboot="$autoboot $n"
+ fi
+ for i in `grep FLAGS: "$i"`; do
+ case "$i" in
+ \#|FLAGS:) ;;
+ *) autoboot="$autoboot $n/$i" ;;
+ esac
+ done
+ fi
+ done
+ if [ "$pause" -eq 1 ]; then
+ sleep 1
+ fi
+ for nh in $autoboot
+ do
+ name="${nh%/*}"
+ helper="${nh#*/}"
+
+ mn="${name#abat-}"
+ start $name-$helper-helper $CONMUX/bin/conmux-attach $mn $helper-helper
+ started="$started $name-$helper-helper"
+ done
+fi
+
+if [ "$cmd" = "start" -o "$cmd" = "stop" ]; then
+ for i in $existing
+ do
+ case " $started " in
+ *\ $i\ *) ;;
+ *) stop "$i" ;;
+ esac
+ done
+fi
+
+if [ "$cmd" = "status" ]; then
+ for n in $existing
+ do
+ mn="${n#abat-}"
+ case "$n" in
+ registry|*-helper)
+ ;;
+ *)
+ status=`console -s $mn`
+ echo "$mn $status"
+ esac
+ done
+fi