]> skyeroc.xyz Git - dgamelaunch/commitdiff
initial commit of new dgamelaunch to CVS
authorJoshua Kwan <joshk@triplehelix.org>
Tue, 30 Dec 2003 21:30:32 +0000 (21:30 +0000)
committerJoshua Kwan <joshk@triplehelix.org>
Tue, 30 Dec 2003 21:30:32 +0000 (21:30 +0000)
git-svn-id: svn://katsu.triplehelix.org/dgamelaunch/trunk@1 db0b04b0-f4d1-0310-9a6d-de3e77497b0e

17 files changed:
Bugs [new file with mode: 0644]
COPYING [new file with mode: 0644]
Changelog [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
dgamelaunch.c [new file with mode: 0644]
dgamelaunch.h [new file with mode: 0644]
dgl-default-rcfile [new file with mode: 0644]
io.c [new file with mode: 0644]
io.h [new file with mode: 0644]
last_char_is.c [new file with mode: 0644]
stripgfx.c [new file with mode: 0644]
stripgfx.h [new file with mode: 0644]
ttyplay.c [new file with mode: 0644]
ttyrec.c [new file with mode: 0644]
ttyrec.h [new file with mode: 0644]
virus.c [new file with mode: 0644]

diff --git a/Bugs b/Bugs
new file mode 100644 (file)
index 0000000..93256f9
--- /dev/null
+++ b/Bugs
@@ -0,0 +1,9 @@
+* Only the first 15 games in progress are shown on the games in progress
+screen.
+
+* Since virus kept calling alarm() on itself, I removed SIGALRM handling
+altogether, which breaks occasional refresh of the bottom status line.
+I don't consider this a huge bug, but if one day I, or someone else, 
+gets really bored, the full solution to this would be to bring back
+the virus signal handling, and just clear the alarm calls before exiting
+and going back to dgamelaunch code (or clear the alarm() handlers).
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..9ca61a0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 2001-2003 M. Drew Streib
+ 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.
+\f
+                   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.)
+\f
+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.
+\f
+  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.
+\f
+  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
+\f
+       Appendix: 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) 19yy  <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., 675 Mass Ave, Cambridge, MA 02139, 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) 19yy 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/Changelog b/Changelog
new file mode 100644 (file)
index 0000000..bccfaf3
--- /dev/null
+++ b/Changelog
@@ -0,0 +1,119 @@
+1.3.10.1 (2003/12/27)
+       * Merry Christmas, dtype...
+       * Major cleanups - remove most uses of strcpy() and strcat(), except
+         in virus.c, replace with snprintf.
+       * Add support for mailing users while they are playing.
+         [nh343-simple_mail.diff is needed]
+       * Rip out some crazy getopt code that was causing virus to go crazy
+         after a ":q!" Since dgamelaunch only passes up to argv[1] to virus
+         *ever*, just make it use argv[1] and panic if argc < 2, which should
+         never happen anyway.
+       * Add a confirmation to the change password screen so people like me
+         don't change their passwords by accident all the time.
+       * Removed strl{cat,cpy}.c, as they're not used by ttyrec even though
+         they are included.
+       * Remove many unused variables and make some functions void because
+         they don't return anything.
+       * Add some function prototypes in dgamelaunch.c to quell warnings.
+       * Make some vars unsigned to quell warnings.
+       * 'capital letter. experimental' section made irrelevant by use of
+         tolower(3) around getch()
+       * Change big conditional block to use a switch.
+       * Replace a global with a static variable within the function.
+
+1.3.10 (2003/10/22)
+       * Added a mode flag to the open call for inprogress lock files.
+         (jilles)
+
+1.3.9 (2003/09/06)
+       * Fixed the bug where editing options file wouldn't work on same
+         session as registration.
+       * Changed ttyplay timeout to 20 minutes with a better error msg.
+       * Made a zero length response on registration just dump you
+         back to the main menu.
+
+1.3.7 (2003/09/06)
+       * Fixed the infamous hlen bug! I was rewinding to the beginning
+         of payload, not header. 
+
+1.3.5 (2003/09/05)
+       * Returned SIGWINCH to defaults after exiting virus. This was
+         probably the screen resize crash bug. (maybe?)
+
+1.3.1 (2003/08/30)
+       * First shot at an engine to strip out graphics sets. It is a lot
+         harder than I would have thought, but doable at a 99% or so
+               success rate, I think, which should at least allow people to
+               view games in other graphics sets somewhat.
+
+1.2.22 (2003/08/29)
+       * Expanded README
+       * Hope to have caught the error in a partial read of the error.
+
+1.2.21 (2003/08/28)
+       * Added a README since I noticed people were downloading this.
+
+1.2.20 (2003/08/24)
+       * Added additional check on payload length. Just a sanity thing.
+
+1.2.19 (2003/08/24)
+       * Fixed (hopefully) that nasty bug that occurs when a partial read
+         after header read returned, and caused the next header read to
+               basically read random data (and thus a random length).
+
+1.2.18 (2003/08/14)
+       * After determining select() on files sucks ass, I've just reduced
+               the polling interval on the ttyplay engine to 100ms, and given
+               up on an otherwise proper select call. Humans shouldn't notice
+               the latency, but I always will... grrr...
+
+1.2.17 (2003/08/14)
+       * Put in the 50ms delay from header read until payload read during
+         preads in order to hopefully hack/wait around the race condition
+               99% of the time.
+       * Added a sleep(10) after receiving SIGHUP until sending one
+         to the child process in the mild hopes that this gets around
+               whatever condition is causing it not to receive and handle 
+               the SIGHUP now.
+
+1.2.16 (2003/08/14)
+       * All code is now run through indent(1) for cleanliness
+       * Game playback in progress now finds the last term reset and plays
+         forward from the last header before that reset.
+       * Some effort has been made to trap SIGHUP better and pass that 
+         along to child processes as well. This still needs work for
+               nethack itself, but dgamelaunch is clean so far.
+
+1.2.6 (2003/08/08)
+       * Viewing games in progress now works pretty well.
+
+1.2.0beta2 (2003/08/07)
+       * Added initial ttyrec integration, although everything is very
+         broken at this point. Once I learn more about tty libraries than
+               I ever want to know, maybe everything will work again.
+
+1.1.6 (2003/08/06)
+       * The last few revs included minor bugfixes found in production use,
+         changed some text in the program, and 1.1.6 is some minor packaging
+               changes for the purposes of a release to the public of the source
+               code.
+
+1.1.1 (2003/08/02)
+  * Introduced virus (vi editor) code, and provided a new option to edit
+         personal nethack rc files.
+
+1.0.0 (2003/08/01)
+       * Since 0.2.3 has been in production for over a year and a half, it
+         is hereby dubbed 1.0.0.
+
+0.2.2 (2001/11/11)
+  * added change password feature. also is the way to get password crypt()ed
+
+0.2.1 (2001/11/11)
+  * moved more of the crucial config into defines
+  * removed the echo of password on login
+
+0.2.0 (2001/11/11)
+  * added crypt() support for the password file. Now the program first
+    checks for the crypt() password, then the plaintext one for legacy
+    and for ease of admins changing passwords.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..4517ed7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+CC = gcc
+LDFLAGS = 
+CFLAGS = -g3 -O0 -Wall -W -Wno-unused-parameter
+SRCS = virus.c ttyrec.c dgamelaunch.c io.c ttyplay.c stripgfx.c
+OBJS = $(SRCS:.c=.o)
+LIBS = -lncurses -lcrypt
+
+all: dgamelaunch
+
+dgamelaunch: $(OBJS)
+       $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS)
+
+clean:
+       rm -f dgamelaunch
+       rm -f *.o
+install:
+       cp dgamelaunch /opt/nethack/nethack.dtype.org/
+indent:
+       indent -ts2 *.c *.h
+       rm *~
+release: clean indent
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..685a455
--- /dev/null
+++ b/README
@@ -0,0 +1,82 @@
+In short, there isn't an easy 'make install' option for this, as it
+runs in a chroot environment and is somewhat specific to a server.
+
+It should compile on most modern Linux distros, although there are
+a couple things which do hinder total portability.
+
+That said, it shouldn't be too hard to figure out most of the defines
+in dgamelaunch.(c|h), and I'm more than happy to help out with any
+questions.
+
+Me:            M. Drew Streib <dtype@dtype.org>
+Mailing list:  http://alt.org/mailman/listinfo/nethack/
+
+BASIC SETUP INSTRUCTIONS
+===========================
+
+1) Setup a chroot area, with a bin directory, a var directory, and a
+'dgldir' directory. You'll also need 'etc' and 'etc/terminfo' for
+curses.
+
+2) If using the #define's as an example, setup 'rcfiles', 'ttyrec',
+and 'inprogress' directories, in the dgldir. Also touch the files
+'dgl-login' and 'dgl-lock' in the main chroot directory.
+
+Your directories/files should now look like this:
+
+/chrootdir/bin/
+/chrootdir/var/
+/chrootdir/etc/
+/chrootdir/etc/terminfo/
+/chrootdir/dgldir/
+/chrootdir/dgldir/ttyrec/
+/chrootdir/dgldir/inprogress/
+/chrootdir/dgldir/rcfiles/
+/chrootdir/dgl-login (empty)
+/chrootdir/dgl-lock (empty)
+
+They should all be owned by the user you intend to run this as.
+
+2) In the #define's in dgamelaunch.c and dgamelaunch.h, change the
+values for those directories, sort of using my own directory setup
+as an example.
+
+3) Compile nethack, and basically tell it that /var is its playground,
+since it will be in the chroot environment. There should be no need
+for special nethack compilation instructions, but it will need to be 
+installed to the chroot environment. A static compile will ensure that
+it can run without any libraries, or you can of course place libraries
+in the environment.
+
+For security reasons, I'd avoid putting any shells, etc, in this environment
+though, of course. Just whatever needs to be exec'd, which is namely,
+nethack.
+
+4) Be sure to set the user id's in the #define's of dgamelaunch to
+the owner of the chroot directory, so nethack and dgamelaunch have
+access to their files. This user needs a lot of write access, but
+you can leave the executables owned by someone else.
+
+5) Either setup dgamelaunch as the shell for a single login, in which
+case it must be suid root (don't worry. It sheds privs right after the
+immediate chroot), or set it up as an inetd service. I use the following
+lines for my xinetd.conf.
+
+service telnet
+{
+  socket_type     = stream
+  protocol  = tcp
+  wait      = no
+  user      = root
+  server    = /usr/sbin/in.telnetd
+  server_args     = -L /opt/nethack/nethack.dtype.org/dgamelaunch
+  rlimit_cpu      = 3600
+  bind      = 64.71.163.206
+}
+
+6) Populate /chrootdir/etc/terminfo with terminfo files. I think that most
+modern ncurses will default to terminfo when it is available. Mine seemed
+to. This also makes it easy to add term types on the fly.
+
+7) You can test your compilation of dgamelaunch by simply running
+it as root, just as a shell (with suid) or inetd might do.
diff --git a/dgamelaunch.c b/dgamelaunch.c
new file mode 100644 (file)
index 0000000..e7c9654
--- /dev/null
@@ -0,0 +1,1097 @@
+/* nethacklaunch.c
+ *
+ * (c)2001-3 M. Drew Streib <dtype@dtype.org>
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * See this in program action at http://dtype.org/nethack/
+ *
+ * This is a little wrapper for nethack (and soon other programs) that
+ * will allow them to be run from a telnetd session, chroot, shed privs,
+ * make a simple login, then play the game.
+ *
+ * By default, this thing is also statically compiled, and can thus be
+ * run inside of a chroot jail itself if necessary.
+ *
+ * Yes, I know it is all global variables. Deal with it. The program
+ * is very small.
+ */
+
+/* a request from the author: please leave some remnance of
+ * 'based on dgamelaunch version xxx' in any derivative works, or
+ * even keep the line the same altogether. I'm probably happy 
+ * to make any changes you need. */
+
+#define VERLINES 7                                                     /* number of lines in this vanity text */
+#define VER1 "## dgamelaunch - network console game launcher\n"
+#define VER2 "## version 1.3.10\n"
+#define VER3 "## \n"
+#define VER4 "## (c)2001-3 M. Drew Streib. This program's source is released under the GPL.\n"
+#define VER5 "## Send mail to <dtype@dtype.org> for details or a copy of the source code.\n"
+#define VER6 "## ** Games on this server are recorded for in-progress viewing and playback!\n"
+#define VER7 "## Server info is at http://alt.org/nethack/"
+
+/* ************************************************************* */
+/* ************************************************************* */
+/* ************************************************************* */
+
+/* program stuff */
+
+#define _XOPEN_SOURCE                                          /* grantpt, etc. */
+#define _BSD_SOURCE                                                    /* setenv */
+
+#include "dgamelaunch.h"
+#include <stdlib.h>
+#include <curses.h>
+#include <crypt.h>
+#include <sys/types.h>
+#include <sys/file.h>                                          /* for flock() */
+#include <sys/ioctl.h>                                 /* ttyrec */
+#include <errno.h>
+#include <time.h>
+#include <dirent.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/signal.h>
+#include <assert.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <termios.h>
+
+extern int vi_main (int argc, char **argv);
+extern int ttyplay_main (char *ttyfile, int mode, int rstripgfx);
+extern int ttyrec_main (char *);
+extern int master;
+extern int slave;
+extern struct termios tt;
+extern struct winsize win;
+
+/* local functions */
+static void writefile (int);
+static void write_canned_rcfile (char*);
+static int userexist (char *);
+static int passwordgood (char *, char *);
+
+/* global variables */
+
+int caught_sighup = 0;
+int pid_game = 0;
+int loggedin = 0;
+char rcfilename[80];
+char ttyrec_filename[100];
+
+/* preallocate this mem. bad, but ohwell. is only for pointers */
+/* makes a max number of users compiled in */
+int f_num = 0;
+struct dg_user** users = NULL;
+struct dg_user* me;
+
+/* ************************************************************* */
+/* for ttyrec */
+
+void
+ttyrec_getmaster ()
+{
+       (void) tcgetattr (0, &tt);
+       (void) ioctl (0, TIOCGWINSZ, (char *) &win);
+       if ((master = open ("/dev/ptmx", O_RDWR)) < 0)
+               {
+                       exit (62);
+               }
+}
+
+/* ************************************************************* */
+
+void
+gen_ttyrec_filename ()
+{
+       time_t rawtime;
+       struct tm *ptm;
+
+       /* append time to filename */
+       time (&rawtime);
+       ptm = gmtime (&rawtime);
+       snprintf (ttyrec_filename, 100, "%04i-%02i-%02i.%02i:%02i:%02i.ttyrec",
+                                               ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
+                                               ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+}
+
+/* ************************************************************* */
+
+void
+gen_inprogress_lock ()
+{
+       char lockfile[130];
+       int fd;
+
+       snprintf (lockfile, 130, "%s%s:%s", LOC_INPROGRESSDIR,
+                                               me->username, ttyrec_filename);
+
+       fd = open (lockfile, O_WRONLY | O_CREAT, 0644);
+       if (flock (fd, LOCK_EX))
+               exit (68);
+}
+
+/* ************************************************************* */
+
+void
+catch_sighup ()
+{
+       caught_sighup = 1;
+       if (pid_game)
+               {
+                       sleep (10);
+                       kill (pid_game, SIGHUP);
+                       sleep (5);
+               }
+       exit (2);
+}
+
+/* ************************************************************* */
+
+void
+inprogressmenu ()
+{
+       int i;
+       DIR *pdir;
+       struct dirent *pdirent;
+       int fd;
+       struct stat pstat;
+       char fullname[130];
+       char ttyrecname[130];
+       char *replacestr;
+       char *games[15];
+       char m_name[26], m_date[11], m_time[9];
+       int m_namelen;
+       time_t ctime;
+       int menuchoice;
+
+
+       do
+               {
+                       clear ();
+                       mvaddstr (1, 1, VER1);
+                       mvaddstr (3, 1,
+                                                               "(Press 'q' during game playback to return to this menu.)");
+                       mvaddstr (4, 1,
+                                                               "(Use capital letter of selection to strip DEC graphics, VERY experimental!)");
+                       mvaddstr (5, 1, "The following games are in progress:");
+
+                       /* clean old games and list good ones */
+                       i = 0;
+                       pdir = opendir (LOC_INPROGRESSDIR);
+                       if (!pdir)
+                               exit (140);
+
+                       while ((pdirent = readdir (pdir)) && (i <= 14))
+                               {
+                                       snprintf (fullname, 130, "%s%s", LOC_INPROGRESSDIR,
+                                                                               pdirent->d_name);
+
+                                       fd = 0;
+                                       fd = open (fullname, O_RDONLY);
+                                       if ((fd > 0) && flock (fd, LOCK_EX | LOCK_NB))
+                                               {
+
+                                                       /* stat to check idle status */
+                                                       snprintf (ttyrecname, 130, "%s%s", LOC_TTYRECDIR,
+                                                                                               pdirent->d_name);
+                                                       replacestr = strstr (ttyrecname, ":");
+                                                       if (!replacestr)
+                                                               exit (145);
+                                                       replacestr[0] = '/';
+                                                       if (!stat (ttyrecname, &pstat))
+                                                               {
+                                                                       games[i] = pdirent->d_name;
+
+                                                                       memset (m_name, 0, 26);
+                                                                       memset (m_date, 0, 11);
+                                                                       memset (m_time, 0, 9);
+                                                                       m_namelen =
+                                                                               replacestr - ttyrecname - strlen (LOC_TTYRECDIR);
+                                                                       strncpy (m_name, pdirent->d_name, m_namelen);
+                                                                       strncpy (m_date, replacestr + 1, 10);
+                                                                       strncpy (m_time, replacestr + 12, 8);
+
+                                                                       i++;
+                                                                       
+                                                                       mvprintw (7 + i, 1, "%c) %-15s %s %s (%ldm %lds idle)",
+                                                                                                        i + 97, m_name, m_date, m_time,
+                                                                                                        (time (&ctime) - pstat.st_mtime) / 60,
+                                                                                                        (time (&ctime) - pstat.st_mtime) % 60);
+                                                               }
+                                               }
+                                       else
+                                               {
+                                                       unlink (fullname);
+                                               }
+                                       flock (fd, LOCK_UN | LOCK_NB);
+                                       close (fd);
+                               }
+
+                       mvaddstr (23, 1, "Watch which game? (r to refresh, q to quit) => ");
+                       refresh ();
+
+                       menuchoice = tolower (getch ());
+
+                       if ((menuchoice - 97) >= 0 && (menuchoice - 97) < i)
+                               {
+                                       /* valid choice has been made */
+                                       snprintf (ttyrecname, 130, "%s%s", LOC_TTYRECDIR,
+                                                                               games[menuchoice - 97]);
+                                       replacestr = strstr (ttyrecname, ":");
+                                       if (!replacestr)
+                                               exit (145);
+                                       replacestr[0] = '/';
+
+                                       clear ();
+                                       refresh ();
+                                       endwin ();
+                                       ttyplay_main (ttyrecname, 1, 0);
+                               }
+
+                       closedir (pdir);
+               }
+       while (menuchoice != 'q');
+}
+
+/* ************************************************************* */
+
+void
+changepw ()
+{
+       char buf[21];
+       int error = 2;
+
+       if (!loggedin)
+               return;
+
+       while (error)
+               {
+                       char repeatbuf[21];
+                       clear ();
+
+                       mvaddstr (1, 1, VER1);
+
+                       mvaddstr (5, 1,
+                                                               "Please enter a new password. Remember that this is sent over the net");
+                       mvaddstr (6, 1,
+                                                               "in plaintext, so make it something new and expect it to be relatively");
+                       mvaddstr (7, 1, "insecure.");
+                       mvaddstr (8, 1,
+                                                               "20 character max. No ':' characters. Blank line to abort.");
+                       mvaddstr (10, 1, "=> ");
+
+                       if (error == 1)
+                               {
+                                       mvaddstr (15, 1, "Sorry, the passwords don't match. Try again.");
+                                       move (10, 4);
+                               }
+
+                       refresh ();
+                       getnstr (buf, 20);
+
+                       if (buf && *buf == '\0')
+                               return;
+
+                       if (strstr(buf, ":") != NULL)
+                         exit(112);
+
+                       mvaddstr (12, 1, "And again:");
+                       mvaddstr (13, 1, "=> ");
+
+                       getnstr (repeatbuf, 20);
+
+                       if (!strcmp (buf, repeatbuf))
+                               error = 0;
+                       else
+                               error = 1;
+               }
+
+       me->password = strdup(crypt (buf, buf));
+       writefile (0);
+}
+
+/* ************************************************************* */
+
+void
+domailuser (char *username)
+{
+       unsigned int len, i;
+       char *spool_fn, message[80];
+       FILE *user_spool = NULL;
+       time_t now;
+       int mail_empty = 1;
+       struct flock fl = { F_WRLCK, SEEK_SET, 0, 0, getpid () };
+
+       assert (loggedin);
+
+       len =
+               (sizeof (LOC_SPOOLDIR) / sizeof (LOC_SPOOLDIR[0])) + strlen (username) +
+               1;
+       spool_fn = malloc (len + 1);
+       time (&now);
+       snprintf (spool_fn, len, "%s/%s", LOC_SPOOLDIR, username);
+
+       /* print the enter your message line */
+       clear ();
+       mvaddstr (1, 1, VER1);
+       mvaddstr (5, 1,
+                                               "Enter your message here. It is to be one line only and 80 characters or less.");
+       mvaddstr (7, 1, "=> ");
+
+       getnstr (message, 80);
+
+       for (i = 0; i < strlen (message); i++)
+               {
+                       if (message[i] != ' ' && message[i] != '\n' && message[i] != '\t')
+                               mail_empty = 0;
+               }
+
+       if (mail_empty)
+               {
+                       mvaddstr (9, 1, "This scroll appears to be blank.--More--");
+                       mvaddstr (10, 1, "(Aborting your message.)");
+                       getch ();
+                       return;
+               }
+
+       if ((user_spool = fopen (spool_fn, "a")) == NULL)
+               {
+                       mvaddstr (9, 1,
+                                                               "You fall into the water! You sink like a rock.--More--");
+                       mvprintw (10, 1,
+                                                               "(I couldn't open %s'%c spool file for some reason, so I'm giving up.)",
+                                                               username, (username[strlen (username) - 1] != 's') ? 's' : 0);
+                       getch ();
+                       return;
+               }
+
+       mvaddstr (9, 1, "Getting a lock on the mailspool...");
+       refresh ();
+
+       while (fcntl (fileno (user_spool), F_SETLK, &fl) == -1);
+
+       fprintf (user_spool, "%s:%s\n", me->username, message);
+
+       fl.l_type = F_UNLCK;
+
+       if (fcntl (fileno (user_spool), F_UNLCK, &fl) == -1)
+               mvaddstr (10, 1, "Couldn't unlock the file! Oh well.");
+
+       fclose (user_spool);
+
+       return;
+}
+
+void
+mailuser ()
+{
+       char buf[20], *user = NULL;
+       int error = 2;
+
+       clear ();
+       mvaddstr (1, 1, VER1);
+       mvaddstr (5, 1,
+                                               "To whom would you like to send a message? (Blank entry returns)");
+       mvaddstr (7, 1, "=> ");
+
+       while (error)
+               {
+                       if (error == 1)
+                               mvaddstr (9, 1, "There is no such user. Try again.  ");
+                       else if (error == 3)
+                               mvaddstr (9, 1, "That user is not playing right now.");
+
+                       mvaddstr (7, 1, "=>                     ");
+                       move (7, 4);
+                       getnstr (buf, 20);
+
+                       if (buf && *buf == '\0')
+                               return;
+
+                       if (userexist (buf) != -1)
+                               {
+                                       DIR *ip = opendir (LOC_INPROGRESSDIR);
+                                       struct dirent *dent;
+
+                                       error = 3;                                              /* unless set by loop below, assume not playing */
+
+                                       while ((dent = readdir (ip)) != NULL)
+                                               {
+                                                       char *trname = strdup (dent->d_name);
+                                                       int len =
+                                                               (sizeof (LOC_INPROGRESSDIR) / sizeof (LOC_INPROGRESSDIR[0])) +
+                                                               strlen (dent->d_name);
+                                                       char *fullpath = malloc (len + 1);
+                                                       int fd;
+
+                                                       snprintf (fullpath, len, "%s%s", LOC_INPROGRESSDIR,
+                                                                                               dent->d_name);
+
+                                                       fd = open (fullpath, O_RDONLY);
+                                                       user = strdup (strtok (trname, ":"));
+
+                                                       /* if it's locked, it's in session */
+                                                       if (!strcmp (buf, user))
+                                                               {
+                                                                       /* could lock? unlock and forget about it */
+                                                                       if (flock (fd, LOCK_EX | LOCK_NB) == 0)
+                                                                               {
+                                                                                       flock (fd, LOCK_UN | LOCK_NB);
+                                                                                       continue;
+                                                                               }
+                                                                       else                                    /* no lock, good deal */
+                                                                               {
+                                                                                       error = 0;
+                                                                                       break;
+                                                                               }
+                                                               }
+                                                       close (fd);
+
+                                                       free (user);
+                                                       free (trname);
+                                               }
+
+                                       closedir (ip);
+                               }
+                       else
+                               error = 1;
+               }
+
+       if (error == 0)
+               domailuser (user);
+}
+
+void
+drawmenu ()
+{
+       static int flood = 0;
+
+       clear ();
+
+       mvaddstr (1, 1, VER1);
+       mvaddstr (2, 1, VER2);
+       mvaddstr (3, 1, VER3);
+       mvaddstr (4, 1, VER4);
+       mvaddstr (5, 1, VER5);
+       mvaddstr (6, 1, VER6);
+       mvaddstr (7, 1, VER7);
+
+       if (loggedin)
+               {
+                       mvprintw (VERLINES + 2, 1, "Logged in as: %s", me->username);
+                       mvaddstr (VERLINES + 4, 1, "c) Change password");
+                       mvaddstr (VERLINES + 5, 1, "o) Edit option file (requires vi use)");
+                       mvaddstr (VERLINES + 6, 1, "w) Watch games in progress");
+                       mvaddstr (VERLINES + 7, 1, "m) Contact a current player");
+                       mvaddstr (VERLINES + 8, 1, "p) Play nethack!");
+                       mvaddstr (VERLINES + 9, 1, "q) Quit");
+                       mvaddstr (VERLINES + 11, 1, "=> ");
+               }
+       else
+               {
+                       mvaddstr (VERLINES + 2, 1, "Not logged in.");
+                       mvaddstr (VERLINES + 4, 1, "l) Login");
+                       mvaddstr (VERLINES + 5, 1, "r) Register new user");
+                       mvaddstr (VERLINES + 6, 1, "w) Watch games in progress");
+                       mvaddstr (VERLINES + 7, 1, "q) Quit");
+                       mvaddstr (VERLINES + 9, 1, "=> ");
+               }
+
+       refresh ();
+
+       /* for retarded clients */
+       flood++;
+       if (flood >= 20)
+               exit (119);
+}
+
+/* ************************************************************* */
+
+void
+freefile ()
+{
+       int i;
+
+       /* free existing mem, clear existing entries */
+       for (i = 0; i < f_num; i++)
+               {
+                       free (users[i]->password);
+                       free (users[i]->username);
+                       free (users[i]->email);
+                       free (users[i]->env);
+               }
+
+       f_num = 0;
+}
+
+/* ************************************************************* */
+
+void
+initncurses ()
+{
+       initscr ();
+       cbreak ();
+       echo ();
+       nonl ();
+       intrflush (stdscr, FALSE);
+       keypad (stdscr, TRUE);
+}
+
+/* ************************************************************* */
+
+void
+initvars ()
+{
+       me = malloc(sizeof(struct dg_user));
+}
+
+/* ************************************************************* */
+
+void
+login ()
+{
+       char user_buf[22], pw_buf[22];
+       int error = 2, me_index = -1;
+
+       loggedin = 0;
+
+       while (error)
+               {
+                       clear ();
+
+                       mvaddstr (1, 1, VER1);
+
+                       mvaddstr (5, 1,
+                                                               "Please enter your username. (blank entry returns to main menu)");
+                       mvaddstr (7, 1, "=> ");
+
+                       if (error == 1)
+                               {
+                                       mvaddstr (9, 1, "There was a problem with your last entry.");
+                                       move (7, 4);
+                               }
+
+                       refresh ();
+
+                       getnstr (user_buf, 20);
+
+                       if (user_buf && *user_buf == '\0')
+                               return;
+
+                       error = 1;
+
+                       if ((me_index = userexist (user_buf)) != -1)
+                       {
+                         me = users[me_index];
+                               error = 0;
+                       }
+               }
+
+       clear ();
+
+       mvaddstr (1, 1, VER1);
+
+       mvaddstr (5, 1, "Please enter your password.");
+       mvaddstr (7, 1, "=> ");
+
+       refresh ();
+
+       noecho ();
+       getnstr (pw_buf, 20);
+       echo ();
+
+       if (passwordgood (user_buf, pw_buf))
+               {
+                       loggedin = 1;
+                       snprintf (rcfilename, 80, "%s%s.nethackrc", LOC_DGLDIR, me->username);
+               }
+}
+
+/* ************************************************************* */
+
+void
+newuser ()
+{
+       char buf[1024];
+       int error = 2;
+       unsigned int i;
+
+       loggedin = 0;
+
+       while (error)
+               {
+                       clear ();
+
+                       mvaddstr (1, 1, VER1);
+
+                       mvaddstr (5, 1, "Welcome new user. Please enter a username.");
+                       mvaddstr (6, 1,
+                                                               "Only characters and numbers are allowed, with no spaces.");
+                       mvaddstr (7, 1, "20 character max.");
+                       mvaddstr (9, 1, "=> ");
+
+                       if (error == 1)
+                               {
+                                       mvaddstr (11, 1, "There was a problem with your last entry.");
+                                       move (9, 4);
+                               }
+
+                       refresh ();
+
+                       getnstr (buf, 20);
+                       if (userexist (buf) == -1)
+                               error = 0;
+
+                       for (i = 0; i < strlen (buf); i++)
+                               {
+                                       if (!
+                                                       (((buf[i] >= 'a') && (buf[i] <= 'z'))
+                                                        || ((buf[i] >= 'A') && (buf[i] <= 'Z')) || ((buf[i] >= '0')
+                                                                                                                                                                                                                                        && (buf[i] <=
+                                                                                                                                                                                                                                                        '9'))))
+                                               error = 1;
+                               }
+
+                       if (strlen (buf) < 2)
+                               error = 1;
+
+                       if (strlen (buf) == 0)
+                               return;
+               }
+
+       me->username = strdup(buf);
+
+       /* password step */
+
+       clear ();
+
+       mvaddstr (1, 1, VER1);
+
+       mvaddstr (5, 1, "Please enter a password.");
+       mvaddstr (6, 1,
+                                               "This is only trivially encoded at the server. Please use something");
+       mvaddstr (7, 1, "new and expect it to be relatively insecure.");
+       mvaddstr (8, 1, "20 character max. No ':' characters.");
+       mvaddstr (10, 1, "=> ");
+
+       refresh ();
+       getnstr (buf, 20);
+
+       /* we warned em */
+       if (strstr(buf, ":") != NULL)
+         exit(112);
+
+       me->password = strdup(crypt (buf, buf));
+
+       /* email step */
+
+       clear ();
+
+       mvaddstr (1, 1, VER1);
+
+       mvaddstr (5, 1, "Please enter your email address.");
+       mvaddstr (6, 1,
+                                               "This is sent _nowhere_ but will be used if you ask the sysadmin for lost");
+       mvaddstr (7, 1,
+                                               "password help. Please use a correct one. It only benefits you.");
+       mvaddstr (8, 1, "80 character max. No ':' characters.");
+       mvaddstr (10, 1, "=> ");
+
+       refresh ();
+       getnstr (buf, 80);
+
+       if (strstr(buf, ":") != NULL)
+         exit(113);
+
+       me->email = strdup(buf);
+       me->env = calloc(1, 1);
+
+       loggedin = 1;
+
+       snprintf (rcfilename, 80, "%s%s.nethackrc", LOC_DGLDIR, me->username);
+       write_canned_rcfile(rcfilename);
+
+       writefile (1);
+}
+
+/* ************************************************************* */
+
+int
+passwordgood (char *cname, char *cpw)
+{
+       if (!strncmp(crypt(cpw, cpw), me->password, 13))
+         return 1;
+       if (!strncmp(cpw, me->password, 20))
+         return 1;
+
+       return 0;
+}
+
+/* ************************************************************* */
+
+int
+readfile (int nolock)
+{
+       FILE *fp = NULL, *fpl = NULL;
+       char buf[1200];
+
+       memset (buf, 1024, 0);
+
+       /* read new stuff */
+
+       if (!nolock)
+               {
+                       fpl = fopen ("/dgl-lock", "r");
+                       if (!fpl)
+                               exit (106);
+                       if (flock (fileno (fpl), LOCK_SH))
+                               exit (114);
+               }
+
+       fp = fopen ("/dgl-login", "r");
+       if (!fp)
+               exit (106);
+
+       /* once per name in the file */
+       while (fgets (buf, 1200, fp))
+               {
+                       char *b = buf, *n = buf;
+
+                       users = realloc(users, sizeof(struct dg_user*) * (f_num + 1));
+                       users[f_num] = malloc(sizeof(struct dg_user));
+                       users[f_num]->username = (char *) calloc (22, sizeof (char));
+                       users[f_num]->email = (char *) calloc (82, sizeof (char));
+                       users[f_num]->password = (char *) calloc (22, sizeof (char));
+                       users[f_num]->env = (char *) calloc (1026, sizeof (char));
+
+                       /* name field, must be valid */
+                       while (*b != ':')
+                               {
+                                       if (!(((*b >= 'a') && (*b <= 'z')) || ((*b >= 'A') && (*b <= 'Z'))
+                                                               || ((*b >= '0') && (*b <= '9'))))
+                                               return 1;
+                                       users[f_num]->username[(b - n)] = *b;
+                                       b++;
+                                       if ((b - n) >= 21)
+                                               exit (100);
+                               }
+
+                       /* advance to next field */
+                       n = b + 1;
+                       b = n;
+
+                       /* email field */
+                       while (*b != ':')
+                               {
+                                       users[f_num]->email[(b - n)] = *b;
+                                       b++;
+                                       if ((b - n) > 80)
+                                               exit (101);
+                               }
+
+                       /* advance to next field */
+                       n = b + 1;
+                       b = n;
+
+                       /* pw field */
+                       while (*b != ':')
+                               {
+                                       users[f_num]->password[(b - n)] = *b;
+                                       b++;
+                                       if ((b - n) >= 20)
+                                               exit (102);
+                               }
+
+                       /* advance to next field */
+                       n = b + 1;
+                       b = n;
+
+                       /* env field */
+                       while ((*b != '\n') && (*b != 0) && (*b != EOF))
+                               {
+                                       users[f_num]->env[(b - n)] = *b;
+                                       b++;
+                                       if ((b - n) >= 1024)
+                                               exit (102);
+                               }
+
+                       f_num++;
+                       /* prevent a buffer overrun here */
+                       if (f_num >= MAXUSERS)
+                               exit (109);
+               }
+
+       if (!nolock)
+               {
+                       flock (fileno (fpl), LOCK_UN);
+                       fclose (fpl);
+               }
+       fclose (fp);
+       return 0;
+}
+
+/* ************************************************************* */
+
+int
+userexist (char *cname)
+{
+       int i;
+
+       for (i = 0; i < f_num; i++)
+               {
+                       if (!strncasecmp (cname, users[i]->username, 20))
+                               return i;
+               }
+
+       return -1;
+}
+
+/* ************************************************************* */
+
+void
+write_canned_rcfile (char* target)
+{
+       FILE *canned, *newfile;
+       char buf[1024];
+       size_t bytes;
+
+       if (!(newfile = fopen(target, "w")))
+       {
+bail:
+               mvaddstr(13,1,"You don't know how to write that! You write \"%s\" was here and the scroll disappears.");
+               mvaddstr(14,1,"(Sorry, but I couldn't open one of the nethackrc files. This is a bug.)");
+               return;
+       }
+                       
+       if (!(canned = fopen(LOC_CANNED, "r")))
+               goto bail;
+
+       while ((bytes = fread(buf, 1, 1024, canned)) > 0)
+       {
+               if (fwrite(buf, 1, bytes, newfile) != bytes)
+               {
+                       if (ferror(newfile))
+                       {
+                               mvaddstr(13,1,"Your hand slips while engraving.");
+                               mvaddstr(14,1,"(Encountered a problem writing the new file. This is a bug.)");
+                               fclose(canned);
+                               fclose(newfile);
+                               return;
+                       }
+               }
+       }
+       
+       fclose(canned);
+       fclose(newfile);
+}
+               
+
+void
+editoptions ()
+{
+       FILE *rcfile;
+       char *myargv[3];
+
+       rcfile = fopen (rcfilename, "r");
+       printf (" read");
+       if (!rcfile) /* should not really happen except for old users */
+               write_canned_rcfile(rcfilename);
+       
+       /* use virus to edit */
+
+       myargv[0] = "";
+       myargv[1] = rcfilename;
+       myargv[2] = 0;
+
+       endwin ();
+       vi_main (2, myargv);
+       refresh ();
+}
+
+/* ************************************************************* */
+
+void
+writefile (int requirenew)
+{
+       FILE *fp, *fpl;
+       int i = 0;
+       int my_done = 0;
+
+       fpl = fopen ("/dgl-lock", "r");
+       if (!fpl)
+               exit (115);
+       if (flock (fileno (fpl), LOCK_EX))
+               exit (107);
+
+       freefile ();
+       readfile (1);
+
+       fp = fopen ("/dgl-login", "w");
+       if (!fp)
+               exit (104);
+
+       for (i = 0; i < f_num; i++)
+               {
+                       if (loggedin && !strncasecmp (me->username, users[i]->username, 20))
+                               {
+                                       if (requirenew)
+                                               {
+                                                       /* this is if someone managed to register at the same time
+                                                        * as someone else. just die. */
+                                                       exit (111);
+                                               }
+                                       fprintf (fp, "%s:%s:%s:%s\n", me->username, me->email, me->password, me->env);
+                                       my_done = 1;
+                               }
+                       else
+                               {
+                                       fprintf (fp, "%s:%s:%s:%s\n", users[i]->username, users[i]->email, users[i]->password,
+                                                                        users[i]->env);
+                               }
+               }
+       if (loggedin && !my_done)
+               {                                                                                                               /* new entry */
+                       fprintf (fp, "%s:%s:%s:%s\n", me->username, me->email, me->password, me->env);
+               }
+
+       flock (fileno (fpl), LOCK_UN);
+       fclose (fp);
+       fclose (fpl);
+}
+
+/* ************************************************************* */
+/* ************************************************************* */
+/* ************************************************************* */
+
+int
+main (void)
+{
+       /* for chroot and program execution */
+       uid_t newuid = SHED_UID;
+       gid_t newgid = SHED_GID;
+       char atrcfilename[81], *spool;
+       unsigned int len;
+
+       int userchoice = 0;
+
+       /* signal handlers */
+       signal (SIGHUP, catch_sighup);
+
+       /* get master tty just before chroot (lives in /dev) */
+       ttyrec_getmaster ();
+       grantpt (master);
+       unlockpt (master);
+       if ((slave = open ((const char *) ptsname (master), O_RDWR)) < 0)
+               {
+                       exit (65);
+               }
+
+
+       /* chroot */
+       if (chroot (LOC_CHROOT))
+               {
+                       perror ("cannot change root directory");
+                       exit (1);
+               }
+
+       if (chdir ("/"))
+               {
+                       perror ("cannot chdir to root directory");
+                       exit (1);
+               }
+
+       /* shed privs. this is done immediately after chroot. */
+       setgid (newgid);
+       setuid (newuid);
+
+       initvars ();
+
+       /* simple login routine, uses ncurses */
+       if (readfile (0))
+               exit (110);
+
+       initncurses ();
+       while ((userchoice != 'p') | (!loggedin))
+               {
+                       drawmenu ();
+                       userchoice = getch ();
+                       switch (tolower (userchoice))
+                               {
+                               case 'c':
+                                       changepw ();
+                                       break;
+                               case 'w':
+                                       inprogressmenu ();
+                                       break;
+                               case 'o':
+                                       if (loggedin)
+                                               editoptions ();
+                                       break;
+                               case 'm':
+                                       if (loggedin)
+                                               mailuser ();
+                                       break;
+                               case 'q':
+                                       endwin ();
+                                       exit (1);
+                                       /* break; */
+                               case 'r':
+                                       if (!loggedin)                          /*not visible to loggedin */
+                                               newuser ();
+                                       break;
+                               case 'l':
+                                       if (!loggedin)                          /* not visible to loggedin */
+                                               login ();
+                                       break;
+                               }
+               }
+
+       assert(loggedin);
+       
+       endwin ();
+
+       /* environment */
+       snprintf (atrcfilename, 81, "@%s", rcfilename);
+
+       len = (sizeof (LOC_SPOOLDIR) / sizeof (LOC_SPOOLDIR[0])) + strlen (me->username) + 1;
+       spool = malloc (len + 1);
+       snprintf (spool, len, "%s/%s", LOC_SPOOLDIR, me->username);
+       
+       setenv ("NETHACKOPTIONS", atrcfilename, 1);
+       setenv ("MAIL", spool, 1);
+       setenv ("SIMPLEMAIL", "1", 1);
+
+       /* don't let the mail file grow */
+       if (access (spool, F_OK) == 0)
+               unlink (spool);
+
+       free (spool);
+
+       /* lock */
+       gen_ttyrec_filename ();
+       gen_inprogress_lock ();
+
+       /* launch program */
+       ttyrec_main (me->username);
+
+       /* NOW we can safely kill this */
+       freefile ();
+       
+       exit (1);
+       return 1;
+}
diff --git a/dgamelaunch.h b/dgamelaunch.h
new file mode 100644 (file)
index 0000000..1be1b9f
--- /dev/null
@@ -0,0 +1,27 @@
+/* IMPORTANT defines */
+
+#ifndef __DGAMELAUNCH_H
+#define __DGAMELAUNCH_H
+
+struct dg_user
+{
+       char* username;
+       char* email;
+       char* env;
+       char* password;
+       int flags;
+};
+
+#define SHED_UID 1031                                          /* the uid to shed privs to */
+#define SHED_GID 1031                                          /* the gid to shed privs to */
+#define MAXUSERS 64000                                 /* solves some preallocation issues. */
+
+#define LOC_CHROOT "/var/lib/dgamelaunch/"
+#define LOC_NETHACK "/bin/nethack"
+#define LOC_DGLDIR "/dgldir/rcfiles/"
+#define LOC_TTYRECDIR "/dgldir/ttyrec/"
+#define LOC_INPROGRESSDIR "/dgldir/inprogress/"
+#define LOC_SPOOLDIR "/var/mail"
+#define LOC_CANNED "/dgl-default-rcfile"
+
+#endif
diff --git a/dgl-default-rcfile b/dgl-default-rcfile
new file mode 100644 (file)
index 0000000..bb3f587
--- /dev/null
@@ -0,0 +1,20 @@
+# This is your personal nethackrc file. It is in the same format as default
+# nethack options files. Optionally uncomment the following lines as a
+# starter. This editor is vi-like. Type ESC a couple times, then ':q!'
+# (without quotes) to exit if you get stuck.
+#
+# See the following link for more options information:
+# http://www.nethack.org/v341/Guidebook.html#_TOCentry_40
+#
+# OPTIONS=showexp,showscore,time
+# OPTIONS=!autopickup,color
+# OPTIONS=catname:catfoo,dogname:dogfoo,horsename:horsefoo
+#
+# The following lines are necessary if you want to use the menucolors option.
+# OPTIONS=menucolors
+# MENUCOLOR=" blessed "=green
+# MENUCOLOR=" holy "=green
+# MENUCOLOR=" uncursed "=yellow
+# MENUCOLOR=" cursed "=red
+# MENUCOLOR=" unholy "=red
+# MENUCOLOR=" cursed .* (being worn)"=orange&underline
diff --git a/io.c b/io.c
new file mode 100644 (file)
index 0000000..15a81ae
--- /dev/null
+++ b/io.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2000 Satoru Takabayashi <satoru@namazu.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include "ttyrec.h"
+
+#define SWAP_ENDIAN(val) ((unsigned int) ( \
+    (((unsigned int) (val) & (unsigned int) 0x000000ffU) << 24) | \
+    (((unsigned int) (val) & (unsigned int) 0x0000ff00U) <<  8) | \
+    (((unsigned int) (val) & (unsigned int) 0x00ff0000U) >>  8) | \
+    (((unsigned int) (val) & (unsigned int) 0xff000000U) >> 24)))
+
+static int
+is_little_endian ()
+{
+       static int retval = -1;
+
+       if (retval == -1)
+               {
+                       int n = 1;
+                       char *p = (char *) &n;
+                       char x[] = { 1, 0, 0, 0 };
+
+                       assert (sizeof (int) == 4);
+
+                       if (memcmp (p, x, 4) == 0)
+                               {
+                                       retval = 1;
+                               }
+                       else
+                               {
+                                       retval = 0;
+                               }
+               }
+
+       return retval;
+}
+
+static int
+convert_to_little_endian (int x)
+{
+       if (is_little_endian ())
+               {
+                       return x;
+               }
+       else
+               {
+                       return SWAP_ENDIAN (x);
+               }
+}
+
+int
+read_header (FILE * fp, Header * h)
+{
+       int buf[3];
+       int numread;
+       long offset;
+
+       offset = ftell (fp);
+       numread = fread (buf, sizeof (int), 3, fp);
+
+       /* odd but possible race condition when full header isn't read because
+        * it isn't there yet. */
+       if (numread < 3)
+               {
+                       fseek (fp, offset, SEEK_SET);
+                       return 0;
+               }
+
+       h->tv.tv_sec = convert_to_little_endian (buf[0]);
+       h->tv.tv_usec = convert_to_little_endian (buf[1]);
+       h->len = convert_to_little_endian (buf[2]);
+
+       return 1;
+}
+
+int
+write_header (FILE * fp, Header * h)
+{
+       int buf[3];
+
+       buf[0] = convert_to_little_endian (h->tv.tv_sec);
+       buf[1] = convert_to_little_endian (h->tv.tv_usec);
+       buf[2] = convert_to_little_endian (h->len);
+
+       if (fwrite (buf, sizeof (int), 3, fp) == 0)
+               {
+                       return 0;
+               }
+
+       return 1;
+}
+
+static char *progname = "";
+void
+set_progname (const char *name)
+{
+       progname = strdup (name);
+}
+
+FILE *
+efopen (const char *path, const char *mode)
+{
+       FILE *fp = fopen (path, mode);
+       if (fp == NULL)
+               {
+                       fprintf (stderr, "%s: %s: %s\n", progname, path, strerror (errno));
+                       exit (EXIT_FAILURE);
+               }
+       return fp;
+}
diff --git a/io.h b/io.h
new file mode 100644 (file)
index 0000000..17b60ee
--- /dev/null
+++ b/io.h
@@ -0,0 +1,10 @@
+#ifndef __TTYREC_IO_H__
+#define __TTYREC_IO_H__
+
+#include "ttyrec.h"
+
+int read_header (FILE * fp, Header * h);
+int write_header (FILE * fp, Header * h);
+FILE *efopen (const char *path, const char *mode);
+
+#endif
diff --git a/last_char_is.c b/last_char_is.c
new file mode 100644 (file)
index 0000000..64e9a9d
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * busybox library eXtended function
+ *
+ * Copyright (C) 2001 Larry Doolittle, <ldoolitt@recycle.lbl.gov>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <string.h>
+
+/* Find out if the last character of a string matches the one given Don't
+ * underrun the buffer if the string length is 0.  Also avoids a possible
+ * space-hogging inline of strlen() per usage.
+ */
+char *
+last_char_is (const char *s, int c)
+{
+       char *sret;
+       if (!s)
+               return NULL;
+       sret = (char *) s + strlen (s) - 1;
+       if (sret >= s && *sret == c)
+               {
+                       return sret;
+               }
+       else
+               {
+                       return NULL;
+               }
+}
diff --git a/stripgfx.c b/stripgfx.c
new file mode 100644 (file)
index 0000000..614abb0
--- /dev/null
@@ -0,0 +1,394 @@
+/* Parts taken from drawing.c, nethack source */
+/* Copyright (c) NetHack Development Team 1992.                          */
+/* NetHack may be freely redistributed.  See license for details. */
+
+#include <string.h>
+#include "stripgfx.h"
+
+#define MAXPCHARS 92
+
+unsigned char gfx_map[256];
+unsigned int state = 0;
+
+static unsigned char no_graphics[MAXPCHARS] = {
+       ' ',                                                                                                    /* 0 */
+       /* stone */
+       '|',                                                                                                    /* vwall */
+       '-',                                                                                                    /* hwall */
+       '-',                                                                                                    /* tlcorn */
+       '-',                                                                                                    /* trcorn */
+       '-',                                                                                                    /* blcorn */
+       '-',                                                                                                    /* brcorn */
+       '-',                                                                                                    /* crwall */
+       '-',                                                                                                    /* tuwall */
+       '-',                                                                                                    /* tdwall */
+       '|',                                                                                                    /* 10 */
+       /* tlwall */
+       '|',                                                                                                    /* trwall */
+       '.',                                                                                                    /* ndoor */
+       '-',                                                                                                    /* vodoor */
+       '|',                                                                                                    /* hodoor */
+       '+',                                                                                                    /* vcdoor */
+       '+',                                                                                                    /* hcdoor */
+       '#',                                                                                                    /* bars */
+       '#',                                                                                                    /* tree */
+       '.',                                                                                                    /* room */
+       '#',                                                                                                    /* 20 */
+       /* dark corr */
+       '#',                                                                                                    /* lit corr */
+       '<',                                                                                                    /* upstair */
+       '>',                                                                                                    /* dnstair */
+       '<',                                                                                                    /* upladder */
+       '>',                                                                                                    /* dnladder */
+       '_',                                                                                                    /* altar */
+       '|',                                                                                                    /* grave */
+       '\\',                                                                                                   /* throne */
+       '#',                                                                                                    /* sink */
+       '{',                                                                                                    /* 30 */
+       /* fountain */
+       '}',                                                                                                    /* pool */
+       '.',                                                                                                    /* ice */
+       '}',                                                                                                    /* lava */
+       '.',                                                                                                    /* vodbridge */
+       '.',                                                                                                    /* hodbridge */
+       '#',                                                                                                    /* vcdbridge */
+       '#',                                                                                                    /* hcdbridge */
+       ' ',                                                                                                    /* open air */
+       '#',                                                                                                    /* [part of] a cloud */
+       '}',                                                                                                    /* 40 */
+       /* under water */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* 50 */
+       /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '"',                                                                                                    /* web */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* 60 */
+       /* trap */
+       '^',                                                                                                    /* trap */
+       '^',                                                                                                    /* trap */
+       '|',                                                                                                    /* vbeam */
+       '-',                                                                                                    /* hbeam */
+       '\\',                                                                                                   /* lslant */
+       '/',                                                                                                    /* rslant */
+       '*',                                                                                                    /* dig beam */
+       '!',                                                                                                    /* camera flash beam */
+       ')',                                                                                                    /* boomerang open left */
+       '(',                                                                                                    /* 70 */
+       /* boomerang open right */
+       '0',                                                                                                    /* 4 magic shield symbols */
+       '#',
+       '@',
+       '*',
+       '/',                                                                                                    /* swallow top left  */
+       '-',                                                                                                    /* swallow top center  */
+       '\\',                                                                                                   /* swallow top right  */
+       '|',                                                                                                    /* swallow middle left */
+       '|',                                                                                                    /* swallow middle right  */
+       '\\',                                                                                                   /* 80 */
+       /* swallow bottom left  */
+       '-',                                                                                                    /* swallow bottom center */
+       '/',                                                                                                    /* swallow bottom right  */
+       '/',                                                                                                    /* explosion top left     */
+       '-',                                                                                                    /* explosion top center   */
+       '\\',                                                                                                   /* explosion top right    */
+       '|',                                                                                                    /* explosion middle left  */
+       ' ',                                                                                                    /* explosion middle center */
+       '|',                                                                                                    /* explosion middle right */
+       '\\',                                                                                                   /* explosion bottom left  */
+       '-',                                                                                                    /* 90 */
+       /* explosion bottom center */
+       '/'                                                                                                             /* explosion bottom right */
+};
+
+static unsigned char ibm_graphics[MAXPCHARS] = {
+/* 0*/ 0x00,
+       0xb3,                                                                                                   /* : meta-3, vertical rule */
+       0xc4,                                                                                                   /* : meta-D, horizontal rule */
+       0xda,                                                                                                   /* :  meta-Z, top left corner */
+       0xbf,                                                                                                   /* :  meta-?, top right corner */
+       0xc0,                                                                                                   /* :  meta-@, bottom left */
+       0xd9,                                                                                                   /* :  meta-Y, bottom right */
+       0xc5,                                                                                                   /* :  meta-E, cross */
+       0xc1,                                                                                                   /* :  meta-A, T up */
+       0xc2,                                                                                                   /* :  meta-B, T down */
+       /*10 */ 0xb4,
+       /* :  meta-4, T left */
+       0xc3,                                                                                                   /* :  meta-C, T right */
+       0xfa,                                                                                                   /* : meta-z, centered dot */
+       0xfe,                                                                                                   /* :  meta-~, small centered square */
+       0xfe,                                                                                                   /* :  meta-~, small centered square */
+       0x00,
+       0x00,
+       240,                                                                                                    /* :  equivalence symbol */
+       241,                                                                                                    /* :  plus or minus symbol */
+       0xfa,                                                                                                   /* :  meta-z, centered dot */
+       /*20 */ 0xb0,
+       /* :  meta-0, light shading */
+       0xb1,                                                                                                   /* : meta-1, medium shading */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       /*30 */ 0xf4,
+       /* :  meta-t, integral top half */
+       0xf7,                                                                                                   /* :  meta-w, approx. equals */
+       0xfa,                                                                                                   /* : meta-z, centered dot */
+       0xf7,                                                                                                   /* :  meta-w, approx. equals */
+       0xfa,                                                                                                   /* : meta-z, centered dot */
+       0xfa,                                                                                                   /* : meta-z, centered dot */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       /*40 */ 0xf7,
+       /* : meta-w, approx. equals */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+/*50*/ 0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+/*60*/ 0x00,
+       0x00,
+       0x00,
+       0xb3,                                                                                                   /* : meta-3, vertical rule */
+       0xc4,                                                                                                   /* : meta-D, horizontal rule */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+/*70*/ 0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0xb3,                                                                                                   /* : meta-3, vertical rule */
+       0xb3,                                                                                                   /* : meta-3, vertical rule */
+/*80*/ 0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0xb3,                                                                                                   /* :  meta-3, vertical rule */
+       0x00,
+       0xb3,                                                                                                   /* :  meta-3, vertical rule */
+       0x00,
+/*90*/ 0x00,
+       0x00
+};
+
+static unsigned char dec_graphics[MAXPCHARS] = {
+/* 0*/ 0x00,
+       0xf8,                                                                                                   /* : meta-x, vertical rule */
+       0xf1,                                                                                                   /* : meta-q, horizontal rule */
+       0xec,                                                                                                   /* :  meta-l, top left corner */
+       0xeb,                                                                                                   /* :  meta-k, top right corner */
+       0xed,                                                                                                   /* :  meta-m, bottom left */
+       0xea,                                                                                                   /* :  meta-j, bottom right */
+       0xee,                                                                                                   /* :  meta-n, cross */
+       0xf6,                                                                                                   /* :  meta-v, T up */
+       0xf7,                                                                                                   /* :  meta-w, T down */
+       /*10 */ 0xf5,
+       /* :  meta-u, T left */
+       0xf4,                                                                                                   /* :  meta-t, T right */
+       0xfe,                                                                                                   /* : meta-~, centered dot */
+       0xe1,                                                                                                   /* :  meta-a, solid block */
+       0xe1,                                                                                                   /* :  meta-a, solid block */
+       0x00,
+       0x00,
+       0xfb,                                                                                                   /* :  meta-{, small pi */
+       0xe7,                                                                                                   /* :  meta-g, plus-or-minus */
+       0xfe,                                                                                                   /* :  meta-~, centered dot */
+/*20*/ 0x00,
+       0x00,
+       0x00,
+       0x00,
+       0xf9,                                                                                                   /* :  meta-y, greater-than-or-equals */
+       0xfa,                                                                                                   /* :  meta-z, less-than-or-equals */
+       0x00,                                                                                                   /* 0xc3, \E)3: meta-C, dagger */
+       0x00,
+       0x00,
+       0x00,
+       /*30 */ 0x00,
+       /* 0xdb, \E)3: meta-[, integral top half */
+       0xe0,                                                                                                   /* :  meta-\, diamond */
+       0xfe,                                                                                                   /* : meta-~, centered dot */
+       0xe0,                                                                                                   /* :  meta-\, diamond */
+       0xfe,                                                                                                   /* : meta-~, centered dot */
+       0xfe,                                                                                                   /* : meta-~, centered dot */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       /*40 */ 0xe0,
+       /* : meta-\, diamond */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+/*50*/ 0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,                                                                                                   /* 0xbd, \E)3: meta-=, int'l currency */
+       0x00,
+/*60*/ 0x00,
+       0x00,
+       0x00,
+       0xf8,                                                                                                   /* : meta-x, vertical rule */
+       0xf1,                                                                                                   /* : meta-q, horizontal rule */
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+/*70*/ 0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0x00,
+       0xef,                                                                                                   /* : meta-o, high horizontal line */
+       0x00,
+       0xf8,                                                                                                   /* : meta-x, vertical rule */
+       0xf8,                                                                                                   /* : meta-x, vertical rule */
+/*80*/ 0x00,
+       0xf3,                                                                                                   /* : meta-s, low horizontal line */
+       0x00,
+       0x00,
+       0xef,                                                                                                   /* :  meta-o, high horizontal line */
+       0x00,
+       0xf8,                                                                                                   /* :  meta-x, vertical rule */
+       0x00,
+       0xf8,                                                                                                   /* :  meta-x, vertical rule */
+       0x00,
+       /*90 */ 0xf3,
+       /* :  meta-s, low horizontal line */
+       0x00
+};
+
+static unsigned char IBM_r_oc_syms[18] = {     /* a la EPYX Rogue */
+/* 0*/ '\0',
+       0x00,
+       0x18,                                                                                                   /* weapon: up arrow */
+       /*  0x0a, */ 0x00,
+       /* armor:  Vert rect with o */
+       /*  0x09, */ 0x00,
+       /* ring:   circle with arrow */
+       /* 5 */ 0x0c,
+       /* amulet: "female" symbol */
+       0x00,
+       0x05,                                                                                                   /* food:   club (as in cards) */
+       0xad,                                                                                                   /* potion: upside down '!' */
+       0x0e,                                                                                                   /* scroll: musical note */
+/*10*/ 0x00,
+       0xe7,                                                                                                   /* wand:   greek tau */
+       0x0f,                                                                                                   /* gold:   yes it's the same as gems */
+       0x0f,                                                                                                   /* gems:   fancy '*' */
+       0x00,
+/*15*/ 0x00,
+       0x00,
+       0x00
+};
+
+void
+populate_gfx_array (int gfxset)
+{
+       int i;
+
+       memset (gfx_map, 0, 256);
+
+       if (gfxset == NO_GRAPHICS)
+               return;
+
+       for (i = 0; i < MAXPCHARS; i++)
+               {
+                       if ((gfxset == DEC_GRAPHICS) && (dec_graphics[i])
+                                       && !(gfx_map[dec_graphics[i]]))
+                               gfx_map[dec_graphics[i] - 128] = no_graphics[i];
+                       if ((gfxset == IBM_GRAPHICS) && (ibm_graphics[i]))
+                               gfx_map[ibm_graphics[i] - 128] = no_graphics[i];
+               }
+
+       /*
+          endwin();
+          for (i=0;i<256;i++) {
+          printf("%X:%X ",i,gfx_map[i]);
+          }
+          exit(1);  */
+}
+
+unsigned char
+strip_gfx (unsigned char inchar)
+{
+       if ((inchar == 0x0E) && (state == 0))
+               {
+                       state = 1;
+                       return 0x00;
+               }
+
+       if ((inchar == 0x0F) && (state == 1))
+               {
+                       state = 0;
+                       return inchar;
+               }
+
+       if ((inchar == 0x1B) && (state == 1))
+               {
+                       state = 0;
+                       return inchar;
+               }
+
+       if (gfx_map[inchar] && (state == 1))
+               {
+                       return gfx_map[inchar];
+               }
+
+       return inchar;
+}
diff --git a/stripgfx.h b/stripgfx.h
new file mode 100644 (file)
index 0000000..c96cad5
--- /dev/null
@@ -0,0 +1,7 @@
+
+#define NO_GRAPHICS 1
+#define DEC_GRAPHICS 2
+#define IBM_GRAPHICS 3
+
+void populate_gfx_array (int gfxset);
+unsigned char strip_gfx (unsigned char inchar);
diff --git a/ttyplay.c b/ttyplay.c
new file mode 100644 (file)
index 0000000..b544b75
--- /dev/null
+++ b/ttyplay.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2000 Satoru Takabayashi <satoru@namazu.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <curses.h>
+#include "ttyrec.h"
+#include "io.h"
+#include "stripgfx.h"
+
+extern int caught_sighup;
+off_t seek_offset_clrscr;
+int bstripgfx;
+
+typedef double (*WaitFunc) (struct timeval prev,
+                                                                                                               struct timeval cur, double speed);
+typedef int (*ReadFunc) (FILE * fp, Header * h, char **buf, int pread);
+typedef void (*WriteFunc) (char *buf, int len);
+typedef void (*ProcessFunc) (FILE * fp, double speed,
+                                                                                                                ReadFunc read_func, WaitFunc wait_func);
+
+struct timeval
+timeval_diff (struct timeval tv1, struct timeval tv2)
+{
+       struct timeval diff;
+
+       diff.tv_sec = tv2.tv_sec - tv1.tv_sec;
+       diff.tv_usec = tv2.tv_usec - tv1.tv_usec;
+       if (diff.tv_usec < 0)
+               {
+                       diff.tv_sec--;
+                       diff.tv_usec += 1000000;
+               }
+
+       return diff;
+}
+
+struct timeval
+timeval_div (struct timeval tv1, double n)
+{
+       double x = ((double) tv1.tv_sec + (double) tv1.tv_usec / 1000000.0) / n;
+       struct timeval div;
+
+       div.tv_sec = (int) x;
+       div.tv_usec = (x - (int) x) * 1000000;
+
+       return div;
+}
+
+double
+ttywait (struct timeval prev, struct timeval cur, double speed)
+{
+       struct timeval diff = timeval_diff (prev, cur);
+       fd_set readfs;
+
+       assert (speed != 0);
+       diff = timeval_div (diff, speed);
+
+       FD_SET (STDIN_FILENO, &readfs);
+       select (1, &readfs, NULL, NULL, &diff); /* skip if a user hits any key */
+       if (FD_ISSET (0, &readfs))
+               {                                                                                                               /* a user hits a character? */
+                       char c;
+                       read (STDIN_FILENO, &c, 1);     /* drain the character */
+                       switch (c)
+                               {
+                               case '+':
+                               case 'f':
+                                       speed *= 2;
+                                       break;
+                               case '-':
+                               case 's':
+                                       speed /= 2;
+                                       break;
+                               case '1':
+                                       speed = 1.0;
+                                       break;
+                               }
+               }
+       return speed;
+}
+
+double
+ttynowait (struct timeval prev, struct timeval cur, double speed)
+{
+       return 0;                                                                                       /* Speed isn't important. */
+}
+
+int
+ttyread (FILE * fp, Header * h, char **buf, int pread)
+{
+       long offset;
+
+       /* do this BEFORE header read, hlen bug */
+       offset = ftell (fp);
+
+       if (read_header (fp, h) == 0)
+               {
+                       return 0;
+               }
+
+       /* length should never be longer than one BUFSIZ */
+       if (h->len > BUFSIZ)
+               {
+                       perror ("hlen");
+                       exit (1);
+               }
+
+       *buf = malloc (h->len);
+       if (*buf == NULL)
+               {
+                       perror ("malloc");
+                       exit (1);
+               }
+
+       if (fread (*buf, 1, h->len, fp) != h->len)
+               {
+                       fseek (fp, offset, SEEK_SET);
+                       return 0;
+               }
+       return 1;
+}
+
+int
+ttypread (FILE * fp, Header * h, char **buf, int pread)
+{
+       int counter = 0;
+       fd_set readfs;
+       struct timeval zerotime;
+
+       zerotime.tv_sec = 0;
+       zerotime.tv_usec = 0;
+
+       /*
+        * Read persistently just like tail -f.
+        */
+       while (ttyread (fp, h, buf, 1) == 0)
+               {
+                       struct timeval w = { 0, 100000 };
+                       select (0, NULL, NULL, NULL, &w);
+                       clearerr (fp);
+                       if (counter++ > (20 * 60 * 10))
+                               {
+                                       endwin ();
+                                       printf ("Exiting due to 20 minutes of inactivity.\n");
+                                       exit (2);
+                                       return 0;
+                               }
+
+
+                       /* look for keypresses here. as good a place as any */
+                       FD_SET (STDIN_FILENO, &readfs);
+                       select (1, &readfs, NULL, NULL, &zerotime);
+                       if (FD_ISSET (0, &readfs))
+                               {                                                                                               /* a user hits a character? */
+                                       char c;
+                                       read (STDIN_FILENO, &c, 1);     /* drain the character */
+                                       switch (c)
+                                               {
+                                               case 'q':
+                                                       return 0;
+                                                       break;
+                                               }
+                               }
+               }
+       return 1;
+}
+
+void
+ttywrite (char *buf, int len)
+{
+       int i;
+
+       if (bstripgfx)
+               {
+                       for (i = 0; i < len; i++)
+                               {
+                                       buf[i] = strip_gfx (buf[i]);
+                               }
+               }
+
+       fwrite (buf, 1, len, stdout);
+}
+
+void
+ttynowrite (char *buf, int len)
+{
+       /* do nothing */
+}
+
+void
+ttyplay (FILE * fp, double speed, ReadFunc read_func,
+                                WriteFunc write_func, WaitFunc wait_func, off_t offset)
+{
+       int first_time = 1;
+       struct timeval prev;
+
+       setbuf (stdout, NULL);
+       setbuf (fp, NULL);
+
+       /* for dtype's attempt to get the last clrscr and playback from there */
+       if (offset)
+               {
+                       lseek (fileno (fp), offset, SEEK_SET);
+               }
+
+       while (1)
+               {
+                       char *buf;
+                       Header h;
+
+                       if (read_func (fp, &h, &buf, 0) == 0)
+                               {
+                                       break;
+                               }
+
+                       if (!first_time)
+                               {
+                                       speed = wait_func (prev, h.tv, speed);
+                               }
+                       first_time = 0;
+
+                       write_func (buf, h.len);
+                       prev = h.tv;
+                       free (buf);
+               }
+}
+
+void
+set_seek_offset_clrscr (FILE * fp)
+{
+       off_t raw_seek_offset = 0;
+       char *buf;
+       struct stat mystat;
+       int state = 0;
+       int i;
+       int bytesread;
+
+       lseek (fileno (fp), 0, SEEK_SET);
+       fstat (fileno (fp), &mystat);
+       buf = malloc (mystat.st_size);
+       bytesread = read (fileno (fp), buf, mystat.st_size);
+
+       /* one byte at at time sucks, but is a simple hack for the temp
+        * being to avoid looking for wraparounds */
+       for (i = 0; i < bytesread; i++)
+               {
+                       if (buf[i] == 0x1b)
+                               {
+                                       state = 1;
+                               }
+                       else if ((buf[i] == 0x5b) && (state == 1))
+                               {
+                                       state = 2;
+                               }
+                       else if ((buf[i] == 0x32) && (state == 2))
+                               {
+                                       state = 3;
+                               }
+                       else if ((buf[i] == 0x4a) && ((state == 2) || (state == 3)))
+                               {
+                                       state = 4;
+                               }
+                       else
+                               {
+                                       state = 0;
+                               }
+
+                       if (state == 4)
+                               {
+                                       raw_seek_offset = i - 2;
+                               }
+               }
+
+       free (buf);
+
+       /* now find last filepos that is less than seek offset */
+       lseek (fileno (fp), 0, SEEK_SET);
+       while (1)
+               {
+                       char *buf;
+                       Header h;
+
+                       if (ttyread (fp, &h, &buf, 0) == 0)
+                               {
+                                       break;
+                               }
+
+                       if (lseek (fileno (fp), 0, SEEK_CUR) < raw_seek_offset)
+                               {
+                                       seek_offset_clrscr = lseek (fileno (fp), 0, SEEK_CUR);
+                               }
+
+                       free (buf);
+               }
+
+}
+
+void
+ttyskipall (FILE * fp)
+{
+       /*
+        * Skip all records.
+        */
+       ttyplay (fp, 0, ttyread, ttynowrite, ttynowait, 0);
+}
+
+void
+ttyplayback (FILE * fp, double speed, ReadFunc read_func, WaitFunc wait_func)
+{
+       ttyplay (fp, speed, ttyread, ttywrite, wait_func, 0);
+}
+
+void
+ttypeek (FILE * fp, double speed, ReadFunc read_func, WaitFunc wait_func)
+{
+       ttyskipall (fp);
+       set_seek_offset_clrscr (fp);
+       if (seek_offset_clrscr)
+               {
+                       ttyplay (fp, 0, ttyread, ttywrite, ttynowait, seek_offset_clrscr);
+               }
+       ttyplay (fp, speed, ttypread, ttywrite, ttynowait, 0);
+}
+
+
+void
+usage (void)
+{
+       printf ("Usage: ttyplay [OPTION] [FILE]\n");
+       printf ("  -s SPEED Set speed to SPEED [1.0]\n");
+       printf ("  -n       No wait mode\n");
+       printf ("  -p       Peek another person's ttyrecord\n");
+       exit (EXIT_FAILURE);
+}
+
+int
+ttyplay_main (char *ttyfile, int mode, int rstripgfx)
+{
+       double speed = 1.0;
+       ReadFunc read_func = ttyread;
+       WaitFunc wait_func = ttywait;
+       ProcessFunc process = ttyplayback;
+       FILE *input = stdin;
+       struct termios old, new;
+
+       /* strip graphics mode flag */
+       bstripgfx = rstripgfx;
+       if (bstripgfx)
+               populate_gfx_array (DEC_GRAPHICS);
+
+       seek_offset_clrscr = 0;
+
+       if (mode == 1)
+               process = ttypeek;
+
+       input = efopen (ttyfile, "r");
+
+       tcgetattr (0, &old);                                    /* Get current terminal state */
+       new = old;                                                                              /* Make a copy */
+       new.c_lflag &= ~(ICANON | ECHO | ECHONL);       /* unbuffered, no echo */
+       tcsetattr (0, TCSANOW, &new);   /* Make it current */
+
+       process (input, speed, read_func, wait_func);
+       tcsetattr (0, TCSANOW, &old);   /* Return terminal state */
+
+       return 0;
+}
diff --git a/ttyrec.c b/ttyrec.c
new file mode 100644 (file)
index 0000000..32e03cb
--- /dev/null
+++ b/ttyrec.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 1980 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org>
+ * - added Native Language Support
+ */
+
+/* 2000-12-27 Satoru Takabayashi <satoru@namazu.org>
+ * - modify `script' to create `ttyrec'.
+ */
+
+/*
+ * script
+ */
+#include "dgamelaunch.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <sys/file.h>
+#include <sys/signal.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stropts.h>
+#include <stdlib.h>
+#include <fcntl.h>
+
+#include <sys/time.h>
+#include "ttyrec.h"
+#include "io.h"
+
+#define HAVE_inet_aton
+#define HAVE_scsi_h
+#define HAVE_kd_h
+
+#define _(FOO) FOO
+
+#ifdef HAVE_openpty
+#include <libutil.h>
+#endif
+
+#define CDEL _POSIX_VDISABLE
+
+extern char ttyrec_filename[100];
+extern int caught_sighup;
+extern int pid_game;
+
+void done (void);
+void fail (void);
+void fixtty (void);
+void getslave (void);
+void doinput (void);
+void dooutput (void);
+void doshell (char *);
+
+FILE *fscript;
+int master;
+int slave;
+int child;
+int subchild;
+
+struct termios tt;
+struct winsize win;
+int lb;
+int l;
+int aflg;
+int uflg;
+
+int
+ttyrec_main (char *username)
+{
+       void finish ();
+       char dirname[100];
+
+       /* create if it doesn't exist */
+       mkdir (dirname, 0755);
+
+       snprintf (dirname, 100, "/%s%s%s", LOC_TTYRECDIR, username,
+                                               ttyrec_filename);
+
+       if ((fscript = fopen (dirname, "w")) == NULL)
+               {
+                       perror (dirname);
+                       fail ();
+               }
+       setbuf (fscript, NULL);
+
+       fixtty ();
+
+       (void) signal (SIGCHLD, finish);
+       child = fork ();
+       if (child < 0)
+               {
+                       perror ("fork");
+                       fail ();
+               }
+       if (child == 0)
+               {
+                       subchild = child = fork ();
+                       if (child < 0)
+                               {
+                                       perror ("fork");
+                                       fail ();
+                               }
+                       if (child)
+                               {
+                                       close (slave);
+                                       pid_game = child;
+                                       dooutput ();
+                               }
+                       else
+                               doshell (username);
+               }
+       doinput ();
+
+       return 0;
+}
+
+void
+doinput ()
+{
+       register int cc;
+       char ibuf[BUFSIZ];
+
+       (void) fclose (fscript);
+       while ((cc = read (0, ibuf, BUFSIZ)) > 0)
+               (void) write (master, ibuf, cc);
+       done ();
+}
+
+#include <sys/wait.h>
+
+void
+finish ()
+{
+#if defined(SVR4)
+       int status;
+#else  /* !SVR4 */
+       union wait status;
+#endif /* !SVR4 */
+       register int pid;
+       register int die = 0;
+
+       while ((pid = wait3 ((int *) &status, WNOHANG, 0)) > 0)
+               if (pid == child)
+                       die = 1;
+
+       if (die)
+               done ();
+}
+
+struct linebuf
+{
+       char str[BUFSIZ + 1];                                   /* + 1 for an additional NULL character. */
+       int len;
+};
+
+
+void
+check_line (const char *line)
+{
+       static int uuencode_mode = 0;
+       static FILE *uudecode;
+
+       if (uuencode_mode == 1)
+               {
+                       fprintf (uudecode, "%s", line);
+                       if (strcmp (line, "end\n") == 0)
+                               {
+                                       pclose (uudecode);
+                                       uuencode_mode = 0;
+                               }
+               }
+       else
+               {
+                       int dummy;
+                       char dummy2[BUFSIZ];
+                       if (sscanf (line, "begin %o %s", &dummy, dummy2) == 2)
+                               {
+                                       /* 
+                                        * uuencode line found! 
+                                        */
+                                       uudecode = popen ("uudecode", "w");
+                                       fprintf (uudecode, "%s", line);
+                                       uuencode_mode = 1;
+                               }
+               }
+}
+
+void
+check_output (const char *str, int len)
+{
+       static struct linebuf lbuf = { "", 0 };
+       int i;
+
+       for (i = 0; i < len; i++)
+               {
+                       if (lbuf.len < BUFSIZ)
+                               {
+                                       lbuf.str[lbuf.len] = str[i];
+                                       if (lbuf.str[lbuf.len] == '\r')
+                                               {
+                                                       lbuf.str[lbuf.len] = '\n';
+                                               }
+                                       lbuf.len++;
+                                       if (lbuf.str[lbuf.len - 1] == '\n')
+                                               {
+                                                       if (lbuf.len > 1)
+                                                               {                                                               /* skip a blank line. */
+                                                                       lbuf.str[lbuf.len] = '\0';
+                                                                       check_line (lbuf.str);
+                                                               }
+                                                       lbuf.len = 0;
+                                               }
+                               }
+                       else
+                               {                                                                                               /* buffer overflow */
+                                       lbuf.len = 0;
+                               }
+               }
+}
+
+void
+dooutput ()
+{
+       int cc;
+       time_t tvec, time ();
+       char obuf[BUFSIZ], *ctime ();
+
+       setbuf (stdout, NULL);
+       (void) close (0);
+       tvec = time ((time_t *) NULL);
+       for (;;)
+               {
+                       Header h;
+
+                       cc = read (master, obuf, BUFSIZ);
+                       if (cc <= 0)
+                               break;
+                       if (uflg)
+                               check_output (obuf, cc);
+                       h.len = cc;
+                       gettimeofday (&h.tv, NULL);
+                       (void) write (1, obuf, cc);
+                       (void) write_header (fscript, &h);
+                       (void) fwrite (obuf, 1, cc, fscript);
+               }
+       done ();
+}
+
+void
+doshell (char *username)
+{
+       char *argv1 = LOC_NETHACK;
+       char *argv2 = "-u";
+       char *myargv[10];
+
+       getslave ();
+       (void) close (master);
+       (void) fclose (fscript);
+       (void) dup2 (slave, 0);
+       (void) dup2 (slave, 1);
+       (void) dup2 (slave, 2);
+       (void) close (slave);
+
+       myargv[0] = argv1;
+       myargv[1] = argv2;
+       myargv[2] = username;
+       myargv[3] = 0;
+
+       execvp (LOC_NETHACK, myargv);
+
+       fail ();
+}
+
+void
+fixtty ()
+{
+       struct termios rtt;
+
+       rtt = tt;
+       rtt.c_iflag = 0;
+       rtt.c_lflag &= ~(ISIG | ICANON | XCASE | ECHO | ECHOE | ECHOK | ECHONL);
+       rtt.c_oflag = OPOST;
+       rtt.c_cc[VINTR] = CDEL;
+       rtt.c_cc[VQUIT] = CDEL;
+       rtt.c_cc[VERASE] = CDEL;
+       rtt.c_cc[VKILL] = CDEL;
+       rtt.c_cc[VEOF] = 1;
+       rtt.c_cc[VEOL] = 0;
+       (void) tcsetattr (0, TCSAFLUSH, &rtt);
+}
+
+void
+fail ()
+{
+
+       (void) kill (0, SIGTERM);
+       done ();
+}
+
+void
+done ()
+{
+       time_t tvec, time ();
+       char *ctime ();
+
+       if (subchild)
+               {
+                       tvec = time ((time_t *) NULL);
+                       (void) fclose (fscript);
+                       (void) close (master);
+               }
+       else
+               {
+                       (void) tcsetattr (0, TCSAFLUSH, &tt);
+               }
+       exit (0);
+}
+
+void
+getslave ()
+{
+       (void) setsid ();
+       /* grantpt( master);
+          unlockpt(master);
+          if ((slave = open((const char *)ptsname(master), O_RDWR)) < 0) {
+          perror((const char *)ptsname(master));
+          fail();
+          perror("open(fd, O_RDWR)");
+          fail();
+          } */
+       if (isastream (slave))
+               {
+                       if (ioctl (slave, I_PUSH, "ptem") < 0)
+                               {
+                                       perror ("ioctl(fd, I_PUSH, ptem)");
+                                       fail ();
+                               }
+                       if (ioctl (slave, I_PUSH, "ldterm") < 0)
+                               {
+                                       perror ("ioctl(fd, I_PUSH, ldterm)");
+                                       fail ();
+                               }
+#ifndef _HPUX_SOURCE
+                       if (ioctl (slave, I_PUSH, "ttcompat") < 0)
+                               {
+                                       perror ("ioctl(fd, I_PUSH, ttcompat)");
+                                       fail ();
+                               }
+#endif
+                       (void) ioctl (0, TIOCGWINSZ, (char *) &win);
+               }
+}
diff --git a/ttyrec.h b/ttyrec.h
new file mode 100644 (file)
index 0000000..b728b7f
--- /dev/null
+++ b/ttyrec.h
@@ -0,0 +1,15 @@
+#ifndef __TTYREC_H__
+#define __TTYREC_H__
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+typedef struct header
+{
+       struct timeval tv;
+       size_t len;
+}
+Header;
+
+
+#endif
diff --git a/virus.c b/virus.c
new file mode 100644 (file)
index 0000000..d8f35f3
--- /dev/null
+++ b/virus.c
@@ -0,0 +1,4487 @@
+/* vi: set sw=8 ts=8: */
+/*
+ * virus - vi resembling utility skeleton - based on 
+ * tiny vi.c: A small 'vi' clone (from busybox 0.52)
+ *
+ * Copyright (C) 2001, 2002 Stefan Koerner <ripclaw@rocklinux.org>
+ * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
+ *
+ *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+char *vi_Version = "0.0.2+dgamelaunch 1.1.2";
+
+/*
+ * To compile:
+ *     gcc -Wall -Os -s -o vi virus.c
+ *     strip vi
+ */
+
+/*
+ * Things To Do:
+ *     EXINIT
+ *     $HOME/.exrc  and  ./.exrc
+ *     add magic to search     /foo.*bar
+ *     add :help command
+ *     :map macros
+ *     how about mode lines:   vi: set sw=8 ts=8:
+ *     if mark[] values were line numbers rather than pointers
+ *        it would be easier to change the mark when add/delete lines
+ *     More intelligence in refresh()
+ *     ":r !cmd"  and  "!cmd"  to filter text through an external command
+ *     A true "undo" facility
+ *     An "ex" line oriented mode- maybe using "cmdedit"
+ */
+
+//----  Feature --------------  Bytes to immplement
+#define vi_main                        vi_main
+#define BB_FEATURE_VI_COLON                    // 4288
+#define BB_FEATURE_VI_YANKMARK // 1408
+#define BB_FEATURE_VI_SEARCH           // 1088
+// #define BB_FEATURE_VI_USE_SIGNALS    // 1056
+#define BB_FEATURE_VI_DOT_CMD          //  576
+#define BB_FEATURE_VI_READONLY //  128
+#define BB_FEATURE_VI_SETOPTS          //  576
+#define BB_FEATURE_VI_SET                              //  224
+#define BB_FEATURE_VI_WIN_RESIZE       //  256  WIN_RESIZE
+// To test editor using CRASHME:
+//    vi -C filename
+// To stop testing, wait until all to text[] is deleted, or
+//    Ctrl-Z and kill -9 %1
+// while in the editor Ctrl-T will toggle the crashme function on and off.
+//#define BB_FEATURE_VI_CRASHME         // randomly pick commands to execute
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <regex.h>
+#include <ctype.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#include "last_char_is.c"
+
+#ifndef TRUE
+#define TRUE                   ((int)1)
+#define FALSE                  ((int)0)
+#endif /* TRUE */
+#define MAX_SCR_COLS           BUFSIZ
+
+// Misc. non-Ascii keys that report an escape sequence
+#define VI_K_UP                        128                                     // cursor key Up
+#define VI_K_DOWN              129                                     // cursor key Down
+#define VI_K_RIGHT             130                             // Cursor Key Right
+#define VI_K_LEFT              131                                     // cursor key Left
+#define VI_K_HOME              132                                     // Cursor Key Home
+#define VI_K_END               133                                     // Cursor Key End
+#define VI_K_INSERT            134                             // Cursor Key Insert
+#define VI_K_PAGEUP            135                             // Cursor Key Page Up
+#define VI_K_PAGEDOWN          136                     // Cursor Key Page Down
+#define VI_K_FUN1              137                                     // Function Key F1
+#define VI_K_FUN2              138                                     // Function Key F2
+#define VI_K_FUN3              139                                     // Function Key F3
+#define VI_K_FUN4              140                                     // Function Key F4
+#define VI_K_FUN5              141                                     // Function Key F5
+#define VI_K_FUN6              142                                     // Function Key F6
+#define VI_K_FUN7              143                                     // Function Key F7
+#define VI_K_FUN8              144                                     // Function Key F8
+#define VI_K_FUN9              145                                     // Function Key F9
+#define VI_K_FUN10             146                             // Function Key F10
+#define VI_K_FUN11             147                             // Function Key F11
+#define VI_K_FUN12             148                             // Function Key F12
+
+static const int YANKONLY = FALSE;
+static const int YANKDEL = TRUE;
+static const int FORWARD = 1;          // code depends on "1"  for array index
+static const int BACK = -1;                    // code depends on "-1" for array index
+static const int LIMITED = 0;          // how much of text[] in char_search
+static const int FULL = 1;                     // how much of text[] in char_search
+
+static const int S_BEFORE_WS = 1;      // used in skip_thing() for moving "dot"
+static const int S_TO_WS = 2;          // used in skip_thing() for moving "dot"
+static const int S_OVER_WS = 3;        // used in skip_thing() for moving "dot"
+static const int S_END_PUNCT = 4;      // used in skip_thing() for moving "dot"
+static const int S_END_ALNUM = 5;      // used in skip_thing() for moving "dot"
+
+typedef unsigned char Byte;
+
+
+static int editing;                                                    // >0 while we are editing a file
+static int cmd_mode;                                           // 0=command  1=insert
+static int file_modified;                              // buffer contents changed
+static int err_method;                                 // indicate error with beep or flash
+static int fn_start;                                           // index of first cmd line file name
+static int save_argc;                                          // how many file names on cmd line
+static int cmdcnt;                                                     // repetition count
+static fd_set rfds;                                                    // use select() for small sleeps
+static struct timeval tv;                              // use select() for small sleeps
+static char erase_char;                                        // the users erase character
+static int rows, columns;                              // the terminal screen is this size
+static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
+static char *SOs, *SOn;                                        // terminal standout start/normal ESC sequence
+static char *bell;                                                     // terminal bell sequence
+static char *Ceol, *Ceos;                              // Clear-end-of-line and Clear-end-of-screen ESC sequence
+static char *CMrc;                                                     // Cursor motion arbitrary destination ESC sequence
+static char *CMup, *CMdown;                    // Cursor motion up and down ESC sequence
+static Byte *status_buffer;                    // mesages to the user
+static Byte last_input_char;           // last char read from user
+static Byte last_forward_char; // last char searched for with 'f'
+static Byte *cfn;                                                              // previous, current, and next file name
+static Byte *text, *end, *textend;     // pointers to the user data in memory
+static Byte *screen;                                           // pointer to the virtual screen buffer
+static int screensize;                                 //            and its size
+static Byte *screenbegin;                              // index into text[], of top line on the screen
+static Byte *dot;                                                              // where all the action takes place
+static int tabstop;
+static struct termios term_orig, term_vi;      // remember what the cooked mode was
+
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+static int last_row;                                           // where the cursor was last moved to
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static jmp_buf restart;                                        // catch_sig()
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+static struct winsize winsize; // remember the window size
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+#ifdef BB_FEATURE_VI_DOT_CMD
+static int adding2q;                                           // are we currently adding user input to q
+static Byte *last_modifying_cmd;       // last modifying cmd for "."
+static Byte *ioq, *ioq_start;          // pointer to string for get_one_char to "read"
+#endif /* BB_FEATURE_VI_DOT_CMD */
+#if    defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
+static Byte *modifying_cmds;           // cmds that modify text[]
+#endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_READONLY
+static int vi_readonly, readonly;
+#endif /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SETOPTS
+static int autoindent;
+static int showmatch;
+static int ignorecase;
+#endif /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *reg[28];                                          // named register a-z, "D", and "U" 0-25,26,27
+static int YDreg, Ureg;                                        // default delete register and orig line for "U"
+static Byte *mark[28];                                 // user marks points somewhere in text[]-  a-z and previous context ''
+static Byte *context_start, *context_end;
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+static Byte *last_search_pattern;      // last pattern from a '/' or '?' search
+#endif /* BB_FEATURE_VI_SEARCH */
+
+
+static void edit_file (Byte *);        // edit one file
+static void do_cmd (Byte);                     // execute a command
+static void sync_cursor (Byte *, int *, int *);        // synchronize the screen cursor to dot
+static Byte *begin_line (Byte *);      // return pointer to cur line B-o-l
+static Byte *end_line (Byte *);        // return pointer to cur line E-o-l
+static Byte *dollar_line (Byte *);     // return pointer to just before NL
+static Byte *prev_line (Byte *);       // return pointer to prev line B-o-l
+static Byte *next_line (Byte *);       // return pointer to next line B-o-l
+static Byte *end_screen (void);        // get pointer to last char on screen
+static int count_lines (Byte *, Byte *);       // count line from start to stop
+static Byte *find_line (int);          // find begining of line #li
+static Byte *move_to_col (Byte *, int);        // move "p" to column l
+static int isblnk (Byte);                              // is the char a blank or tab
+static void dot_left (void);           // move dot left- dont leave line
+static void dot_right (void);          // move dot right- dont leave line
+static void dot_begin (void);          // move dot to B-o-l
+static void dot_end (void);                    // move dot to E-o-l
+static void dot_next (void);           // move dot to next line B-o-l
+static void dot_prev (void);           // move dot to prev line B-o-l
+static void dot_scroll (int, int);     // move the screen up or down
+static void dot_skip_over_ws (void);   // move dot pat WS
+static void dot_delete (void); // delete the char at 'dot'
+static Byte *bound_dot (Byte *);       // make sure  text[0] <= P < "end"
+static Byte *new_screen (int, int);    // malloc virtual screen memory
+static Byte *new_text (int);           // malloc memory for text[] buffer
+static Byte *char_insert (Byte *, Byte);       // insert the char c at 'p'
+static Byte *stupid_insert (Byte *, Byte);     // stupidly insert the char c at 'p'
+static Byte find_range (Byte **, Byte **, Byte);       // return pointers for an object
+static int st_test (Byte *, int, int, Byte *); // helper for skip_thing()
+static Byte *skip_thing (Byte *, int, int, int);       // skip some object
+static Byte *find_pair (Byte *, Byte); // find matching pair ()  []  {}
+static Byte *text_hole_delete (Byte *, Byte *);        // at "p", delete a 'size' byte hole
+static Byte *text_hole_make (Byte *, int);     // at "p", make a 'size' byte hole
+static Byte *yank_delete (Byte *, Byte *, int, int);   // yank text[] into register then delete
+static void show_help (void);          // display some help info
+static void print_literal (Byte *, Byte *);    // copy s to buf, convert unprintable
+static void rawmode (void);                    // set "raw" mode on tty
+static void cookmode (void);           // return to "cooked" mode on tty
+static int mysleep (int);                              // sleep for 'h' 1/100 seconds
+static Byte readit (void);                     // read (maybe cursor) key from stdin
+static Byte get_one_char (void);       // read 1 char from stdin
+static int file_size (Byte *); // what is the byte size of "fn"
+static int file_insert (Byte *, Byte *, int);
+static int file_write (Byte *, Byte *, Byte *);
+static void place_cursor (int, int, int);
+static void screen_erase ();
+static void clear_to_eol (void);
+static void clear_to_eos (void);
+static void standout_start (void);     // send "start reverse video" sequence
+static void standout_end (void);       // send "end reverse video" sequence
+static void flash (int);                               // flash the terminal screen
+static void beep (void);                               // beep the terminal
+static void indicate_error (char);     // use flash or beep to indicate error
+static void show_status_line (void);   // put a message on the bottom line
+static void psb (char *, ...); // Print Status Buf
+static void psbs (char *, ...);        // Print Status Buf in standout mode
+static void ni (Byte *);                               // display messages
+static void edit_status (void);        // show file status on status line
+static void redraw (int);                              // force a full screen refresh
+static void format_line (Byte *, Byte *, int);
+static void refresh (int);                     // update the terminal from screen[]
+
+#ifdef BB_FEATURE_VI_SEARCH
+static Byte *char_search (Byte *, Byte *, int, int);   // search for pattern starting at p
+static int mycmp (Byte *, Byte *, int);        // string cmp based in "ignorecase"
+#endif /* BB_FEATURE_VI_SEARCH */
+#ifdef BB_FEATURE_VI_COLON
+static void Hit_Return (void);
+static Byte *get_one_address (Byte *, int *);  // get colon addr, if present
+static Byte *get_address (Byte *, int *, int *);       // get two colon addrs, if present
+static void colon (Byte *);                    // execute the "colon" mode cmds
+#endif /* BB_FEATURE_VI_COLON */
+static Byte *get_input_line (Byte *);  // get input line- use "status line"
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static void winch_sig (int);           // catch window size changes
+static void suspend_sig (int); // catch ctrl-Z
+static void alarm_sig (int);           // catch alarm time-outs
+static void catch_sig (int);           // catch ctrl-C
+static void core_sig (int);                    // catch a core dump signal
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q (Byte);    // new queue for command
+static void end_cmd_q ();                              // stop saving input chars
+#else  /* BB_FEATURE_VI_DOT_CMD */
+#define end_cmd_q()
+#endif /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+static void window_size_get (int);     // find out what size the window is
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+#ifdef BB_FEATURE_VI_SETOPTS
+static void showmatching (Byte *);     // show the matching pair ()  []  {}
+#endif /* BB_FEATURE_VI_SETOPTS */
+#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
+static Byte *string_insert (Byte *, Byte *);   // insert the string at 'p'
+#endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *text_yank (Byte *, Byte *, int);  // save copy of "p" into a register
+static Byte what_reg (void);           // what is letter of current YDreg
+static void check_context (Byte);      // remember context for '' command
+static Byte *swap_context (Byte *);    // goto new context for '' command
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_CRASHME
+static void crash_dummy ();
+static void crash_test ();
+static int crashme = 0;
+#endif /* BB_FEATURE_VI_CRASHME */
+
+
+extern int
+vi_main (int argc, char **argv)
+{
+#ifdef BB_FEATURE_VI_YANKMARK
+       int i;
+#endif /* BB_FEATURE_VI_YANKMARK */
+
+       CMrc = "\033[%d;%dH";                                   // Terminal Crusor motion ESC sequence
+       CMup = "\033[A";                                                        // move cursor up one line, same col
+       CMdown = "\n";                                                          // move cursor down one line, same col
+       Ceol = "\033[0K";                                                       // Clear from cursor to end of line
+       Ceos = "\033[0J";                                                       // Clear from cursor to end of screen
+       SOs = "\033[7m";                                                        // Terminal standout mode on
+       SOn = "\033[0m";                                                        // Terminal standout mode off
+       bell = "\007";                                                          // Terminal bell sequence
+#ifdef BB_FEATURE_VI_CRASHME
+       (void) srand ((long) getpid ());
+#endif /* BB_FEATURE_VI_CRASHME */
+       status_buffer = (Byte *) malloc (200);  // hold messages to user
+#ifdef BB_FEATURE_VI_READONLY
+       vi_readonly = readonly = FALSE;
+       if (strncmp (argv[0], "view", 4) == 0)
+               {
+                       readonly = TRUE;
+                       vi_readonly = TRUE;
+               }
+#endif /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SETOPTS
+       autoindent = 1;
+       ignorecase = 1;
+       showmatch = 1;
+#endif /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_YANKMARK
+       for (i = 0; i < 28; i++)
+               {
+                       reg[i] = 0;
+               }                                                                                                               // init the yank regs
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_DOT_CMD
+       modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~";      // cmds modifying text[]
+#endif /* BB_FEATURE_VI_DOT_CMD */
+
+       if (argc >= 2)
+               {
+                       cfn = (Byte *) strdup (argv[1]);
+                       edit_file (cfn);
+               }
+       else
+               {
+                       fprintf (stderr, "%s: no file to edit, bailing out\n", argv[0]);
+                       exit (1);
+               }
+
+       //-----------------------------------------------------------
+
+
+       /* set these back to defaults. this was the infamous screen resize crash bug */
+       signal (SIGWINCH, SIG_DFL);
+       signal (SIGTSTP, SIG_DFL);
+
+       return (0);
+}
+
+static void
+edit_file (Byte * fn)
+{
+       char c;
+       int cnt, size, ch;
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       char *msg;
+       int sig;
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_YANKMARK
+       static Byte *cur_line;
+#endif /* BB_FEATURE_VI_YANKMARK */
+
+       rawmode ();
+       rows = 24;
+       columns = 80;
+       ch = -1;
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get (0);
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+       new_screen (rows, columns);             // get memory for virtual screen
+
+       cnt = file_size (fn);                                   // file size
+       size = 2 * cnt;                                                         // 200% of file size
+       new_text (size);                                                        // get a text[] buffer
+       screenbegin = dot = end = text;
+       if (fn != 0)
+               {
+                       ch = file_insert (fn, text, cnt);
+               }
+       if (ch < 1)
+               {
+                       (void) char_insert (text, '\n');        // start empty buf with dummy line
+               }
+       file_modified = FALSE;
+#ifdef BB_FEATURE_VI_YANKMARK
+       YDreg = 26;                                                                             // default Yank/Delete reg
+       Ureg = 27;                                                                              // hold orig line for "U" cmd
+       for (cnt = 0; cnt < 28; cnt++)
+               {
+                       mark[cnt] = 0;
+               }                                                                                                               // init the marks
+       mark[26] = mark[27] = text;             // init "previous context"
+#endif /* BB_FEATURE_VI_YANKMARK */
+
+       err_method = 1;                                                         // flash
+       last_forward_char = last_input_char = '\0';
+       crow = 0;
+       ccol = 0;
+       edit_status ();
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       signal (SIGHUP, catch_sig);
+       signal (SIGINT, catch_sig);
+       signal (SIGALRM, alarm_sig);
+       signal (SIGTERM, catch_sig);
+       signal (SIGQUIT, core_sig);
+       signal (SIGILL, core_sig);
+       signal (SIGTRAP, core_sig);
+       signal (SIGIOT, core_sig);
+       signal (SIGABRT, core_sig);
+       signal (SIGFPE, core_sig);
+       signal (SIGBUS, core_sig);
+       signal (SIGSEGV, core_sig);
+#ifdef SIGSYS
+       signal (SIGSYS, core_sig);
+#endif
+       signal (SIGWINCH, winch_sig);
+       signal (SIGTSTP, suspend_sig);
+       sig = setjmp (restart);
+       if (sig != 0)
+               {
+                       msg = "";
+                       if (sig == SIGWINCH)
+                               msg = "(window resize)";
+                       if (sig == SIGHUP)
+                               msg = "(hangup)";
+                       if (sig == SIGINT)
+                               msg = "(interrupt)";
+                       if (sig == SIGTERM)
+                               msg = "(terminate)";
+                       if (sig == SIGBUS)
+                               msg = "(bus error)";
+                       if (sig == SIGSEGV)
+                               msg = "(I tried to touch invalid memory)";
+                       if (sig == SIGALRM)
+                               msg = "(alarm)";
+
+                       psbs ("-- caught signal %d %s--", sig, msg);
+                       screenbegin = dot = text;
+               }
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+
+       editing = 1;
+       cmd_mode = 0;                                                                   // 0=command  1=insert  2='R'eplace
+       cmdcnt = 0;
+       tabstop = 8;
+       offset = 0;                                                                             // no horizontal offset
+       c = '\0';
+#ifdef BB_FEATURE_VI_DOT_CMD
+       if (last_modifying_cmd != 0)
+               free (last_modifying_cmd);
+       if (ioq_start != NULL)
+               free (ioq_start);
+       ioq = ioq_start = last_modifying_cmd = 0;
+       adding2q = 0;
+#endif /* BB_FEATURE_VI_DOT_CMD */
+       redraw (FALSE);                                                         // dont force every col re-draw
+       show_status_line ();
+
+       //------This is the main Vi cmd handling loop -----------------------
+       while (editing > 0)
+               {
+#ifdef BB_FEATURE_VI_CRASHME
+                       if (crashme > 0)
+                               {
+                                       if ((end - text) > 1)
+                                               {
+                                                       crash_dummy ();         // generate a random command
+                                               }
+                                       else
+                                               {
+                                                       crashme = 0;
+                                                       dot = string_insert (text, (Byte *) "\n\n#####  Ran out of text to work on.  #####\n\n");       // insert the string
+                                                       refresh (FALSE);
+                                               }
+                               }
+#endif /* BB_FEATURE_VI_CRASHME */
+                       last_input_char = c = get_one_char ();  // get a cmd from user
+#ifdef BB_FEATURE_VI_YANKMARK
+                       // save a copy of the current line- for the 'U" command
+                       if (begin_line (dot) != cur_line)
+                               {
+                                       cur_line = begin_line (dot);
+                                       text_yank (begin_line (dot), end_line (dot), Ureg);
+                               }
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_DOT_CMD
+                       // These are commands that change text[].
+                       // Remember the input for the "." command
+                       if (!adding2q && ioq_start == 0
+                                       && strchr ((char *) modifying_cmds, c) != NULL)
+                               {
+                                       start_new_cmd_q (c);
+                               }
+#endif /* BB_FEATURE_VI_DOT_CMD */
+                       do_cmd (c);                                                             // execute the user command
+                       //
+                       // poll to see if there is input already waiting. if we are
+                       // not able to display output fast enough to keep up, skip
+                       // the display update until we catch up with input.
+                       if (mysleep (0) == 0)
+                               {
+                                       // no input pending- so update output
+                                       refresh (FALSE);
+                                       show_status_line ();
+                               }
+#ifdef BB_FEATURE_VI_CRASHME
+                       if (crashme > 0)
+                               crash_test ();                                  // test editor variables
+#endif /* BB_FEATURE_VI_CRASHME */
+               }
+       //-------------------------------------------------------------------
+
+       place_cursor (rows, 0, FALSE);  // go to bottom of screen
+       clear_to_eol ();                                                        // Erase to end of line
+       cookmode ();
+}
+
+static Byte readbuffer[BUFSIZ];
+
+#ifdef BB_FEATURE_VI_CRASHME
+static int totalcmds = 0;
+static int Mp = 85;                                                    // Movement command Probability
+static int Np = 90;                                                    // Non-movement command Probability
+static int Dp = 96;                                                    // Delete command Probability
+static int Ip = 97;                                                    // Insert command Probability
+static int Yp = 98;                                                    // Yank command Probability
+static int Pp = 99;                                                    // Put command Probability
+static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
+char chars[20] = "\t012345 abcdABCD-=.$";
+char *words[20] = { "this", "is", "a", "test",
+       "broadcast", "the", "emergency", "of",
+       "system", "quick", "brown", "fox",
+       "jumped", "over", "lazy", "dogs",
+       "back", "January", "Febuary", "March"
+};
+char *lines[20] = {
+       "You should have received a copy of the GNU General Public License\n",
+       "char c, cm, *cmd, *cmd1;\n",
+       "generate a command by percentages\n",
+       "Numbers may be typed as a prefix to some commands.\n",
+       "Quit, discarding changes!\n",
+       "Forced write, if permission originally not valid.\n",
+       "In general, any ex or ed command (such as substitute or delete).\n",
+       "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+       "Please get w/ me and I will go over it with you.\n",
+       "The following is a list of scheduled, committed changes.\n",
+       "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+       "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+       "Any question about transactions please contact Sterling Huxley.\n",
+       "I will try to get back to you by Friday, December 31.\n",
+       "This Change will be implemented on Friday.\n",
+       "Let me know if you have problems accessing this;\n",
+       "Sterling Huxley recently added you to the access list.\n",
+       "Would you like to go to lunch?\n",
+       "The last command will be automatically run.\n",
+       "This is too much english for a computer geek.\n",
+};
+char *multilines[20] = {
+       "You should have received a copy of the GNU General Public License\n",
+       "char c, cm, *cmd, *cmd1;\n",
+       "generate a command by percentages\n",
+       "Numbers may be typed as a prefix to some commands.\n",
+       "Quit, discarding changes!\n",
+       "Forced write, if permission originally not valid.\n",
+       "In general, any ex or ed command (such as substitute or delete).\n",
+       "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+       "Please get w/ me and I will go over it with you.\n",
+       "The following is a list of scheduled, committed changes.\n",
+       "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+       "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+       "Any question about transactions please contact Sterling Huxley.\n",
+       "I will try to get back to you by Friday, December 31.\n",
+       "This Change will be implemented on Friday.\n",
+       "Let me know if you have problems accessing this;\n",
+       "Sterling Huxley recently added you to the access list.\n",
+       "Would you like to go to lunch?\n",
+       "The last command will be automatically run.\n",
+       "This is too much english for a computer geek.\n",
+};
+
+// create a random command to execute
+static void
+crash_dummy ()
+{
+       static int sleeptime;                                   // how long to pause between commands
+       char c, cm, *cmd, *cmd1;
+       int i, cnt, thing, rbi, startrbi, percent;
+
+       // "dot" movement commands
+       cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
+
+       // is there already a command running?
+       if (strlen ((char *) readbuffer) > 0)
+               goto cd1;
+cd0:
+       startrbi = rbi = 0;
+       sleeptime = 0;                                                          // how long to pause between commands
+       memset (readbuffer, '\0', BUFSIZ - 1);  // clear the read buffer
+       // generate a command by percentages
+       percent = (int) lrand48 () % 100;       // get a number from 0-99
+       if (percent < Mp)
+               {                                                                                                               //  Movement commands
+                       // available commands
+                       cmd = cmd1;
+                       M++;
+               }
+       else if (percent < Np)
+               {                                                                                                               //  non-movement commands
+                       cmd = "mz<>\'\"";                                       // available commands
+                       N++;
+               }
+       else if (percent < Dp)
+               {                                                                                                               //  Delete commands
+                       cmd = "dx";                                                             // available commands
+                       D++;
+               }
+       else if (percent < Ip)
+               {                                                                                                               //  Inset commands
+                       cmd = "iIaAsrJ";                                        // available commands
+                       I++;
+               }
+       else if (percent < Yp)
+               {                                                                                                               //  Yank commands
+                       cmd = "yY";                                                             // available commands
+                       Y++;
+               }
+       else if (percent < Pp)
+               {                                                                                                               //  Put commands
+                       cmd = "pP";                                                             // available commands
+                       P++;
+               }
+       else
+               {
+                       // We do not know how to handle this command, try again
+                       U++;
+                       goto cd0;
+               }
+       // randomly pick one of the available cmds from "cmd[]"
+       i = (int) lrand48 () % strlen (cmd);
+       cm = cmd[i];
+       if (strchr (":\024", cm))
+               goto cd0;                                                                               // dont allow colon or ctrl-T commands
+       readbuffer[rbi++] = cm;                         // put cmd into input buffer
+
+       // now we have the command-
+       // there are 1, 2, and multi char commands
+       // find out which and generate the rest of command as necessary
+       if (strchr ("dmryz<>\'\"", cm))
+               {                                                                                                               // 2-char commands
+                       cmd1 = " \n\r0$^-+wWeEbBhjklHL";
+                       if (cm == 'm' || cm == '\'' || cm == '\"')
+                               {                                                                                               // pick a reg[]
+                                       cmd1 = "abcdefghijklmnopqrstuvwxyz";
+                               }
+                       thing = (int) lrand48 () % strlen (cmd1);       // pick a movement command
+                       c = cmd1[thing];
+                       readbuffer[rbi++] = c;          // add movement to input buffer
+               }
+       if (strchr ("iIaAsc", cm))
+               {                                                                                                               // multi-char commands
+                       if (cm == 'c')
+                               {
+                                       // change some thing
+                                       thing = (int) lrand48 () % strlen (cmd1);       // pick a movement command
+                                       c = cmd1[thing];
+                                       readbuffer[rbi++] = c;  // add movement to input buffer
+                               }
+                       thing = (int) lrand48 () % 4;   // what thing to insert
+                       cnt = (int) lrand48 () % 10;    // how many to insert
+                       for (i = 0; i < cnt; i++)
+                               {
+                                       if (thing == 0)
+                                               {                                                                               // insert chars
+                                                       readbuffer[rbi++] = chars[((int) lrand48 () % strlen (chars))];
+                                               }
+                                       else if (thing == 1)
+                                               {                                                                               // insert words
+                                                       strcat ((char *) readbuffer, words[(int) lrand48 () % 20]);
+                                                       strcat ((char *) readbuffer, " ");
+                                                       sleeptime = 0;          // how fast to type
+                                               }
+                                       else if (thing == 2)
+                                               {                                                                               // insert lines
+                                                       strcat ((char *) readbuffer, lines[(int) lrand48 () % 20]);
+                                                       sleeptime = 0;          // how fast to type
+                                               }
+                                       else
+                                               {                                                                               // insert multi-lines
+                                                       strcat ((char *) readbuffer, multilines[(int) lrand48 () % 20]);
+                                                       sleeptime = 0;          // how fast to type
+                                               }
+                               }
+                       strcat ((char *) readbuffer, "\033");
+               }
+cd1:
+       totalcmds++;
+       if (sleeptime > 0)
+               (void) mysleep (sleeptime);     // sleep 1/100 sec
+}
+
+// test to see if there are any errors
+static void
+crash_test ()
+{
+       static time_t oldtim;
+       time_t tim;
+       char d[2], buf[BUFSIZ], msg[BUFSIZ];
+
+       msg[0] = '\0';
+       if (end < text)
+               {
+                       strcat ((char *) msg, "end<text ");
+               }
+       if (end > textend)
+               {
+                       strcat ((char *) msg, "end>textend ");
+               }
+       if (dot < text)
+               {
+                       strcat ((char *) msg, "dot<text ");
+               }
+       if (dot > end)
+               {
+                       strcat ((char *) msg, "dot>end ");
+               }
+       if (screenbegin < text)
+               {
+                       strcat ((char *) msg, "screenbegin<text ");
+               }
+       if (screenbegin > end - 1)
+               {
+                       strcat ((char *) msg, "screenbegin>end-1 ");
+               }
+
+       if (strlen (msg) > 0)
+               {
+                       // alarm(0);
+                       sprintf (buf, "\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
+                                                        totalcmds, last_input_char, msg, SOs, SOn);
+                       write (1, buf, strlen (buf));
+                       while (read (0, d, 1) > 0)
+                               {
+                                       if (d[0] == '\n' || d[0] == '\r')
+                                               break;
+                               }
+                       // alarm(3);
+               }
+       tim = (time_t) time ((time_t *) 0);
+       if (tim >= (oldtim + 3))
+               {
+                       sprintf ((char *) status_buffer,
+                                                        "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
+                                                        totalcmds, M, N, I, D, Y, P, U, end - text + 1);
+                       oldtim = tim;
+               }
+       return;
+}
+#endif /* BB_FEATURE_VI_CRASHME */
+
+//---------------------------------------------------------------------
+//----- the Ascii Chart -----------------------------------------------
+//
+//  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
+//  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
+//  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
+//  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
+//  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
+//  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
+//  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
+//  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
+//  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
+//  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
+//  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
+//  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
+//  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
+//  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
+//  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
+//  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
+//---------------------------------------------------------------------
+
+//----- Execute a Vi Command -----------------------------------
+static void
+do_cmd (Byte c)
+{
+       Byte c1, *p, *q, *msg, buf[9], *save_dot;
+       int cnt, i, j, dir, yf;
+
+       c1 = c;                                                                                         // quiet the compiler
+       cnt = yf = dir = 0;                                             // quiet the compiler
+       p = q = save_dot = msg = buf;   // quiet the compiler
+       memset (buf, '\0', 9);                          // clear buf
+       if (cmd_mode == 2)
+               {
+                       // we are 'R'eplacing the current *dot with new char
+                       if (*dot == '\n')
+                               {
+                                       // don't Replace past E-o-l
+                                       cmd_mode = 1;                                   // convert to insert
+                               }
+                       else
+                               {
+                                       if (1 <= c && c <= 127)
+                                               {                                                                               // only ASCII chars
+                                                       if (c != 27)
+                                                               dot = yank_delete (dot, dot, 0, YANKDEL);       // delete char
+                                                       dot = char_insert (dot, c);     // insert new char
+                                               }
+                                       goto dc1;
+                               }
+               }
+       if (cmd_mode == 1)
+               {
+                       //  hitting "Insert" twice means "R" replace mode
+                       if (c == VI_K_INSERT)
+                               goto dc5;
+                       // insert the char c at "dot"
+                       if (1 <= c && c <= 127)
+                               {
+                                       dot = char_insert (dot, c);     // only ASCII chars
+                               }
+                       goto dc1;
+               }
+
+       switch (c)
+               {
+                       //case 0x01:    // soh
+                       //case 0x09:    // ht
+                       //case 0x0b:    // vt
+                       //case 0x0e:    // so
+                       //case 0x0f:    // si
+                       //case 0x10:    // dle
+                       //case 0x11:    // dc1
+                       //case 0x13:    // dc3
+#ifdef BB_FEATURE_VI_CRASHME
+               case 0x14:                                                                      // dc4  ctrl-T
+                       crashme = (crashme == 0) ? 1 : 0;
+                       break;
+#endif /* BB_FEATURE_VI_CRASHME */
+                       //case 0x16:    // syn
+                       //case 0x17:    // etb
+                       //case 0x18:    // can
+                       //case 0x1c:    // fs
+                       //case 0x1d:    // gs
+                       //case 0x1e:    // rs
+                       //case 0x1f:    // us
+                       //case '!':     // !- 
+                       //case '#':     // #- 
+                       //case '&':     // &- 
+                       //case '(':     // (- 
+                       //case ')':     // )- 
+                       //case '*':     // *- 
+                       //case ',':     // ,- 
+                       //case '=':     // =- 
+                       //case '@':     // @- 
+                       //case 'F':     // F- 
+                       //case 'K':     // K- 
+                       //case 'Q':     // Q- 
+                       //case 'S':     // S- 
+                       //case 'T':     // T- 
+                       //case 'V':     // V- 
+                       //case '[':     // [- 
+                       //case '\\':    // \- 
+                       //case ']':     // ]- 
+                       //case '_':     // _- 
+                       //case '`':     // `- 
+                       //case 'g':     // g- 
+                       //case 'u':     // u- FIXME- there is no undo
+                       //case 'v':     // v- 
+               default:                                                                                // unrecognised command
+                       buf[0] = c;
+                       buf[1] = '\0';
+                       if (c <= ' ')
+                               {
+                                       buf[0] = '^';
+                                       buf[1] = c + '@';
+                                       buf[2] = '\0';
+                               }
+                       ni ((Byte *) buf);
+                       end_cmd_q ();                                                   // stop adding to q
+               case 0x00:                                                                      // nul- ignore
+                       break;
+               case 2:                                                                         // ctrl-B  scroll up   full screen
+               case VI_K_PAGEUP:                                       // Cursor Key Page Up
+                       dot_scroll (rows - 2, -1);
+                       break;
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+               case 0x03:                                                                      // ctrl-C   interrupt
+                       longjmp (restart, 1);
+                       break;
+               case 26:                                                                                // ctrl-Z suspend
+                       suspend_sig (SIGTSTP);
+                       break;
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+               case 4:                                                                         // ctrl-D  scroll down half screen
+                       dot_scroll ((rows - 2) / 2, 1);
+                       break;
+               case 5:                                                                         // ctrl-E  scroll down one line
+                       dot_scroll (1, 1);
+                       break;
+               case 6:                                                                         // ctrl-F  scroll down full screen
+               case VI_K_PAGEDOWN:                             // Cursor Key Page Down
+                       dot_scroll (rows - 2, 1);
+                       break;
+               case 7:                                                                         // ctrl-G  show current status
+                       edit_status ();
+                       break;
+               case 'h':                                                                       // h- move left
+               case VI_K_LEFT:                                         // cursor key Left
+               case 8:                                                                         // ctrl-H- move left    (This may be ERASE char)
+               case 127:                                                                       // DEL- move left   (This may be ERASE char)
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_left ();
+                       break;
+               case 10:                                                                                // Newline ^J
+               case 'j':                                                                       // j- goto next line, same col
+               case VI_K_DOWN:                                         // cursor key Down
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_next ();                                                    // go to next B-o-l
+                       dot = move_to_col (dot, ccol + offset); // try stay in same col
+                       break;
+               case 12:                                                                                // ctrl-L  force redraw whole screen
+               case 18:                                                                                // ctrl-R  force redraw
+                       place_cursor (0, 0, FALSE);     // put cursor in correct place
+                       clear_to_eos ();                                        // tel terminal to erase display
+                       (void) mysleep (10);
+                       screen_erase ();                                        // erase the internal screen buffer
+                       refresh (TRUE);                                         // this will redraw the entire display
+                       break;
+               case 13:                                                                                // Carriage Return ^M
+               case '+':                                                                       // +- goto next line
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_next ();
+                       dot_skip_over_ws ();
+                       break;
+               case 21:                                                                                // ctrl-U  scroll up   half screen
+                       dot_scroll ((rows - 2) / 2, -1);
+                       break;
+               case 25:                                                                                // ctrl-Y  scroll up one line
+                       dot_scroll (1, -1);
+                       break;
+               case 27:                                                                                // esc
+                       if (cmd_mode == 0)
+                               indicate_error (c);
+                       cmd_mode = 0;                                                   // stop insrting
+                       end_cmd_q ();
+                       *status_buffer = '\0';          // clear status buffer
+                       break;
+               case ' ':                                                                       // move right
+               case 'l':                                                                       // move right
+               case VI_K_RIGHT:                                                // Cursor Key Right
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_right ();
+                       break;
+#ifdef BB_FEATURE_VI_YANKMARK
+               case '"':                                                                       // "- name a register to use for Delete/Yank
+                       c1 = get_one_char ();
+                       c1 = tolower (c1);
+                       if (islower (c1))
+                               {
+                                       YDreg = c1 - 'a';
+                               }
+                       else
+                               {
+                                       indicate_error (c);
+                               }
+                       break;
+               case '\'':                                                                      // '- goto a specific mark
+                       c1 = get_one_char ();
+                       c1 = tolower (c1);
+                       if (islower (c1))
+                               {
+                                       c1 = c1 - 'a';
+                                       // get the b-o-l
+                                       q = mark[(int) c1];
+                                       if (text <= q && q < end)
+                                               {
+                                                       dot = q;
+                                                       dot_begin ();                   // go to B-o-l
+                                                       dot_skip_over_ws ();
+                                               }
+                               }
+                       else if (c1 == '\'')
+                               {                                                                                               // goto previous context
+                                       dot = swap_context (dot);       // swap current and previous context
+                                       dot_begin ();                                   // go to B-o-l
+                                       dot_skip_over_ws ();
+                               }
+                       else
+                               {
+                                       indicate_error (c);
+                               }
+                       break;
+               case 'm':                                                                       // m- Mark a line
+                       // this is really stupid.  If there are any inserts or deletes
+                       // between text[0] and dot then this mark will not point to the
+                       // correct location! It could be off by many lines!
+                       // Well..., at least its quick and dirty.
+                       c1 = get_one_char ();
+                       c1 = tolower (c1);
+                       if (islower (c1))
+                               {
+                                       c1 = c1 - 'a';
+                                       // remember the line
+                                       mark[(int) c1] = dot;
+                               }
+                       else
+                               {
+                                       indicate_error (c);
+                               }
+                       break;
+               case 'P':                                                                       // P- Put register before
+               case 'p':                                                                       // p- put register after
+                       p = reg[YDreg];
+                       if (p == 0)
+                               {
+                                       psbs ("Nothing in register %c", what_reg ());
+                                       break;
+                               }
+                       // are we putting whole lines or strings
+                       if (strchr ((char *) p, '\n') != NULL)
+                               {
+                                       if (c == 'P')
+                                               {
+                                                       dot_begin ();                   // putting lines- Put above
+                                               }
+                                       if (c == 'p')
+                                               {
+                                                       // are we putting after very last line?
+                                                       if (end_line (dot) == (end - 1))
+                                                               {
+                                                                       dot = end;              // force dot to end of text[]
+                                                               }
+                                                       else
+                                                               {
+                                                                       dot_next ();    // next line, then put before
+                                                               }
+                                               }
+                               }
+                       else
+                               {
+                                       if (c == 'p')
+                                               dot_right ();                           // move to right, can move to NL
+                               }
+                       dot = string_insert (dot, p);   // insert the string
+                       end_cmd_q ();                                                   // stop adding to q
+                       break;
+               case 'U':                                                                       // U- Undo; replace current line with original version
+                       if (reg[Ureg] != 0)
+                               {
+                                       p = begin_line (dot);
+                                       q = end_line (dot);
+                                       p = text_hole_delete (p, q);    // delete cur line
+                                       p = string_insert (p, reg[Ureg]);       // insert orig line
+                                       dot = p;
+                                       dot_skip_over_ws ();
+                               }
+                       break;
+#endif /* BB_FEATURE_VI_YANKMARK */
+               case '$':                                                                       // $- goto end of line
+               case VI_K_END:                                                  // Cursor Key End
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot = end_line (dot + 1);
+                       break;
+               case '%':                                                                       // %- find matching char of pair () [] {}
+                       for (q = dot; q < end && *q != '\n'; q++)
+                               {
+                                       if (strchr ("()[]{}", *q) != NULL)
+                                               {
+                                                       // we found half of a pair
+                                                       p = find_pair (q, *q);
+                                                       if (p == NULL)
+                                                               {
+                                                                       indicate_error (c);
+                                                               }
+                                                       else
+                                                               {
+                                                                       dot = p;
+                                                               }
+                                                       break;
+                                               }
+                               }
+                       if (*q == '\n')
+                               indicate_error (c);
+                       break;
+               case 'f':                                                                       // f- forward to a user specified char
+                       last_forward_char = get_one_char ();    // get the search char
+                       //
+                       // dont seperate these two commands. 'f' depends on ';'
+                       //
+                       //**** fall thru to ... 'i'
+               case ';':                                                                       // ;- look at rest of line for last forward char
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (';');
+                               }                                                                                               // repeat cnt
+                       if (last_forward_char == 0)
+                               break;
+                       q = dot + 1;
+                       while (q < end - 1 && *q != '\n' && *q != last_forward_char)
+                               {
+                                       q++;
+                               }
+                       if (*q == last_forward_char)
+                               dot = q;
+                       break;
+               case '-':                                                                       // -- goto prev line
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_prev ();
+                       dot_skip_over_ws ();
+                       break;
+#ifdef BB_FEATURE_VI_DOT_CMD
+               case '.':                                                                       // .- repeat the last modifying command
+                       // Stuff the last_modifying_cmd back into stdin
+                       // and let it be re-executed.
+                       if (last_modifying_cmd != 0)
+                               {
+                                       ioq = ioq_start = (Byte *) strdup ((char *) last_modifying_cmd);
+                               }
+                       break;
+#endif /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_SEARCH
+               case '?':                                                                       // /- search for a pattern
+               case '/':                                                                       // /- search for a pattern
+                       buf[0] = c;
+                       buf[1] = '\0';
+                       q = get_input_line (buf);       // get input line- use "status line"
+                       if (strlen ((char *) q) == 1)
+                               goto dc3;                                                               // if no pat re-use old pat
+                       if (strlen ((char *) q) > 1)
+                               {                                                                                               // new pat- save it and find
+                                       // there is a new pat
+                                       if (last_search_pattern != 0)
+                                               {
+                                                       free (last_search_pattern);
+                                               }
+                                       last_search_pattern = (Byte *) strdup ((char *) q);
+                                       goto dc3;                                                       // now find the pattern
+                               }
+                       // user changed mind and erased the "/"-  do nothing
+                       break;
+               case 'N':                                                                       // N- backward search for last pattern
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dir = BACK;                                                             // assume BACKWARD search
+                       p = dot - 1;
+                       if (last_search_pattern[0] == '?')
+                               {
+                                       dir = FORWARD;
+                                       p = dot + 1;
+                               }
+                       goto dc4;                                                                       // now search for pattern
+                       break;
+               case 'n':                                                                       // n- repeat search for last pattern
+                       // search rest of text[] starting at next char
+                       // if search fails return orignal "p" not the "p+1" address
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+               dc3:
+                       if (last_search_pattern == 0)
+                               {
+                                       msg = (Byte *) "No previous regular expression";
+                                       goto dc2;
+                               }
+                       if (last_search_pattern[0] == '/')
+                               {
+                                       dir = FORWARD;                          // assume FORWARD search
+                                       p = dot + 1;
+                               }
+                       if (last_search_pattern[0] == '?')
+                               {
+                                       dir = BACK;
+                                       p = dot - 1;
+                               }
+               dc4:
+                       q = char_search (p, last_search_pattern + 1, dir, FULL);
+                       if (q != NULL)
+                               {
+                                       dot = q;                                                        // good search, update "dot"
+                                       msg = (Byte *) "";
+                                       goto dc2;
+                               }
+                       // no pattern found between "dot" and "end"- continue at top
+                       p = text;
+                       if (dir == BACK)
+                               {
+                                       p = end - 1;
+                               }
+                       q = char_search (p, last_search_pattern + 1, dir, FULL);
+                       if (q != NULL)
+                               {                                                                                               // found something
+                                       dot = q;                                                        // found new pattern- goto it
+                                       msg = (Byte *) "search hit BOTTOM, continuing at TOP";
+                                       if (dir == BACK)
+                                               {
+                                                       msg = (Byte *) "search hit TOP, continuing at BOTTOM";
+                                               }
+                               }
+                       else
+                               {
+                                       msg = (Byte *) "Pattern not found";
+                               }
+               dc2:
+                       psbs ("%s", msg);
+                       break;
+               case '{':                                                                       // {- move backward paragraph
+                       q = char_search (dot, (Byte *) "\n\n", BACK, FULL);
+                       if (q != NULL)
+                               {                                                                                               // found blank line
+                                       dot = next_line (q);    // move to next blank line
+                               }
+                       break;
+               case '}':                                                                       // }- move forward paragraph
+                       q = char_search (dot, (Byte *) "\n\n", FORWARD, FULL);
+                       if (q != NULL)
+                               {                                                                                               // found blank line
+                                       dot = next_line (q);    // move to next blank line
+                               }
+                       break;
+#endif /* BB_FEATURE_VI_SEARCH */
+               case '0':                                                                       // 0- goto begining of line
+               case '1':                                                                       // 1- 
+               case '2':                                                                       // 2- 
+               case '3':                                                                       // 3- 
+               case '4':                                                                       // 4- 
+               case '5':                                                                       // 5- 
+               case '6':                                                                       // 6- 
+               case '7':                                                                       // 7- 
+               case '8':                                                                       // 8- 
+               case '9':                                                                       // 9- 
+                       if (c == '0' && cmdcnt < 1)
+                               {
+                                       dot_begin ();                                   // this was a standalone zero
+                               }
+                       else
+                               {
+                                       cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
+                               }
+                       break;
+               case ':':                                                                       // :- the colon mode commands
+                       p = get_input_line ((Byte *) ":");      // get input line- use "status line"
+#ifdef BB_FEATURE_VI_COLON
+                       colon (p);                                                              // execute the command
+#else  /* BB_FEATURE_VI_COLON */
+                       if (*p == ':')
+                               p++;                                                                            // move past the ':'
+                       cnt = strlen ((char *) p);
+                       if (cnt <= 0)
+                               break;
+                       if (strncasecmp ((char *) p, "quit", cnt) == 0 ||
+                                       strncasecmp ((char *) p, "q!", cnt) == 0)
+                               {                                                                                               // delete lines
+                                       if (file_modified == TRUE && p[1] != '!')
+                                               {
+                                                       psbs ("No write since last change (:quit! overrides)");
+                                               }
+                                       else
+                                               {
+                                                       editing = 0;
+                                               }
+                               }
+                       else if (strncasecmp ((char *) p, "write", cnt) == 0 ||
+                                                        strncasecmp ((char *) p, "wq", cnt) == 0)
+                               {
+                                       cnt = file_write (cfn, text, end - 1);
+                                       file_modified = FALSE;
+                                       psb ("\"%s\" %dL, %dC", cfn, count_lines (text, end - 1), cnt);
+                                       if (p[1] == 'q')
+                                               {
+                                                       editing = 0;
+                                               }
+                               }
+                       else if (strncasecmp ((char *) p, "file", cnt) == 0)
+                               {
+                                       edit_status ();                         // show current file status
+                               }
+                       else if (sscanf ((char *) p, "%d", &j) > 0)
+                               {
+                                       dot = find_line (j);    // go to line # j
+                                       dot_skip_over_ws ();
+                               }
+                       else
+                               {                                                                                               // unrecognised cmd
+                                       ni ((Byte *) p);
+                               }
+#endif /* BB_FEATURE_VI_COLON */
+                       break;
+               case '<':                                                                       // <- Left  shift something
+               case '>':                                                                       // >- Right shift something
+                       cnt = count_lines (text, dot);  // remember what line we are on
+                       c1 = get_one_char ();                   // get the type of thing to delete
+                       find_range (&p, &q, c1);
+                       (void) yank_delete (p, q, 1, YANKONLY); // save copy before change
+                       p = begin_line (p);
+                       q = end_line (q);
+                       i = count_lines (p, q);         // # of lines we are shifting
+                       for (; i > 0; i--, p = next_line (p))
+                               {
+                                       if (c == '<')
+                                               {
+                                                       // shift left- remove tab or 8 spaces
+                                                       if (*p == '\t')
+                                                               {
+                                                                       // shrink buffer 1 char
+                                                                       (void) text_hole_delete (p, p);
+                                                               }
+                                                       else if (*p == ' ')
+                                                               {
+                                                                       // we should be calculating columns, not just SPACE
+                                                                       for (j = 0; *p == ' ' && j < tabstop; j++)
+                                                                               {
+                                                                                       (void) text_hole_delete (p, p);
+                                                                               }
+                                                               }
+                                               }
+                                       else if (c == '>')
+                                               {
+                                                       // shift right -- add tab or 8 spaces
+                                                       (void) char_insert (p, '\t');
+                                               }
+                               }
+                       dot = find_line (cnt);          // what line were we on
+                       dot_skip_over_ws ();
+                       end_cmd_q ();                                                   // stop adding to q
+                       break;
+               case 'A':                                                                       // A- append at e-o-l
+                       dot_end ();                                                             // go to e-o-l
+                       //**** fall thru to ... 'a'
+               case 'a':                                                                       // a- append after current char
+                       if (*dot != '\n')
+                               dot++;
+                       goto dc_i;
+                       break;
+               case 'B':                                                                       // B- back a blank-delimited Word
+               case 'E':                                                                       // E- end of a blank-delimited word
+               case 'W':                                                                       // W- forward a blank-delimited word
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dir = FORWARD;
+                       if (c == 'B')
+                               dir = BACK;
+                       if (c == 'W' || isspace (dot[dir]))
+                               {
+                                       dot = skip_thing (dot, 1, dir, S_TO_WS);
+                                       dot = skip_thing (dot, 2, dir, S_OVER_WS);
+                               }
+                       if (c != 'W')
+                               dot = skip_thing (dot, 1, dir, S_BEFORE_WS);
+                       break;
+               case 'C':                                                                       // C- Change to e-o-l
+               case 'D':                                                                       // D- delete to e-o-l
+                       save_dot = dot;
+                       dot = dollar_line (dot);        // move to before NL
+                       // copy text into a register and delete
+                       dot = yank_delete (save_dot, dot, 0, YANKDEL);  // delete to e-o-l
+                       if (c == 'C')
+                               goto dc_i;                                                      // start inserting
+#ifdef BB_FEATURE_VI_DOT_CMD
+                       if (c == 'D')
+                               end_cmd_q ();                                           // stop adding to q
+#endif /* BB_FEATURE_VI_DOT_CMD */
+                       break;
+               case 'G':                                                                       // G- goto to a line number (default= E-O-F)
+                       dot = end - 1;                                          // assume E-O-F
+                       if (cmdcnt > 0)
+                               {
+                                       dot = find_line (cmdcnt);       // what line is #cmdcnt
+                               }
+                       dot_skip_over_ws ();
+                       break;
+               case 'H':                                                                       // H- goto top line on screen
+                       dot = screenbegin;
+                       if (cmdcnt > (rows - 1))
+                               {
+                                       cmdcnt = (rows - 1);
+                               }
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd ('+');
+                               }                                                                                               // repeat cnt
+                       dot_skip_over_ws ();
+                       break;
+               case 'I':                                                                       // I- insert before first non-blank
+                       dot_begin ();                                                   // 0
+                       dot_skip_over_ws ();
+                       //**** fall thru to ... 'i'
+               case 'i':                                                                       // i- insert before current char
+               case VI_K_INSERT:                                       // Cursor Key Insert
+               dc_i:
+                       cmd_mode = 1;                                                   // start insrting
+                       psb ("-- Insert --");
+                       break;
+               case 'J':                                                                       // J- join current and next lines together
+                       if (cmdcnt-- > 2)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_end ();                                                             // move to NL
+                       if (dot < end - 1)
+                               {                                                                                               // make sure not last char in text[]
+                                       *dot++ = ' ';                                   // replace NL with space
+                                       while (isblnk (*dot))
+                                               {                                                                               // delete leading WS
+                                                       dot_delete ();
+                                               }
+                               }
+                       end_cmd_q ();                                                   // stop adding to q
+                       break;
+               case 'L':                                                                       // L- goto bottom line on screen
+                       dot = end_screen ();
+                       if (cmdcnt > (rows - 1))
+                               {
+                                       cmdcnt = (rows - 1);
+                               }
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd ('-');
+                               }                                                                                               // repeat cnt
+                       dot_begin ();
+                       dot_skip_over_ws ();
+                       break;
+               case 'M':                                                                       // M- goto middle line on screen
+                       dot = screenbegin;
+                       for (cnt = 0; cnt < (rows - 1) / 2; cnt++)
+                               dot = next_line (dot);
+                       break;
+               case 'O':                                                                       // O- open a empty line above
+                       //    0i\n ESC -i
+                       p = begin_line (dot);
+                       if (p[-1] == '\n')
+                               {
+                                       dot_prev ();
+               case 'o':                                                                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
+                                       dot_end ();
+                                       dot = char_insert (dot, '\n');
+                               }
+                       else
+                               {
+                                       dot_begin ();                                   // 0
+                                       dot = char_insert (dot, '\n');  // i\n ESC
+                                       dot_prev ();                                    // -
+                               }
+                       goto dc_i;
+                       break;
+               case 'R':                                                                       // R- continuous Replace char
+               dc5:
+                       cmd_mode = 2;
+                       psb ("-- Replace --");
+                       break;
+               case 'X':                                                                       // X- delete char before dot
+               case 'x':                                                                       // x- delete the current char
+               case 's':                                                                       // s- substitute the current char
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dir = 0;
+                       if (c == 'X')
+                               dir = -1;
+                       if (dot[dir] != '\n')
+                               {
+                                       if (c == 'X')
+                                               dot--;                                                  // delete prev char
+                                       dot = yank_delete (dot, dot, 0, YANKDEL);       // delete char
+                               }
+                       if (c == 's')
+                               goto dc_i;                                                      // start insrting
+                       end_cmd_q ();                                                   // stop adding to q
+                       break;
+               case 'Z':                                                                       // Z- if modified, {write}; exit
+                       // ZZ means to save file (if necessary), then exit
+                       c1 = get_one_char ();
+                       if (c1 != 'Z')
+                               {
+                                       indicate_error (c);
+                                       break;
+                               }
+                       if (file_modified == TRUE
+#ifdef BB_FEATURE_VI_READONLY
+                                       && vi_readonly == FALSE && readonly == FALSE
+#endif /* BB_FEATURE_VI_READONLY */
+                               )
+                               {
+                                       cnt = file_write (cfn, text, end - 1);
+                                       if (cnt == (end - 1 - text + 1))
+                                               {
+                                                       editing = 0;
+                                               }
+                               }
+                       else
+                               {
+                                       editing = 0;
+                               }
+                       break;
+               case '^':                                                                       // ^- move to first non-blank on line
+                       dot_begin ();
+                       dot_skip_over_ws ();
+                       break;
+               case 'b':                                                                       // b- back a word
+               case 'e':                                                                       // e- end of word
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dir = FORWARD;
+                       if (c == 'b')
+                               dir = BACK;
+                       if ((dot + dir) < text || (dot + dir) > end - 1)
+                               break;
+                       dot += dir;
+                       if (isspace (*dot))
+                               {
+                                       dot = skip_thing (dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
+                               }
+                       if (isalnum (*dot) || *dot == '_')
+                               {
+                                       dot = skip_thing (dot, 1, dir, S_END_ALNUM);
+                               }
+                       else if (ispunct (*dot))
+                               {
+                                       dot = skip_thing (dot, 1, dir, S_END_PUNCT);
+                               }
+                       break;
+               case 'c':                                                                       // c- change something
+               case 'd':                                                                       // d- delete something
+#ifdef BB_FEATURE_VI_YANKMARK
+               case 'y':                                                                       // y- yank   something
+               case 'Y':                                                                       // Y- Yank a line
+#endif /* BB_FEATURE_VI_YANKMARK */
+                       yf = YANKDEL;                                                   // assume either "c" or "d"
+#ifdef BB_FEATURE_VI_YANKMARK
+                       if (c == 'y' || c == 'Y')
+                               yf = YANKONLY;
+#endif /* BB_FEATURE_VI_YANKMARK */
+                       c1 = 'y';
+                       if (c != 'Y')
+                               c1 = get_one_char ();           // get the type of thing to delete
+                       find_range (&p, &q, c1);
+                       if (c1 == 27)
+                               {                                                                                               // ESC- user changed mind and wants out
+                                       c = c1 = 27;                                    // Escape- do nothing
+                               }
+                       else if (strchr ("wW", c1))
+                               {
+                                       if (c == 'c')
+                                               {
+                                                       // don't include trailing WS as part of word
+                                                       while (isblnk (*q))
+                                                               {
+                                                                       if (q <= text || q[-1] == '\n')
+                                                                               break;
+                                                                       q--;
+                                                               }
+                                               }
+                                       dot = yank_delete (p, q, 0, yf);        // delete word
+                               }
+                       else if (strchr ("^0bBeEft$", c1))
+                               {
+                                       // single line copy text into a register and delete
+                                       dot = yank_delete (p, q, 0, yf);        // delete word
+                               }
+                       else if (strchr ("cdykjHL%+-{}\r\n", c1))
+                               {
+                                       // multiple line copy text into a register and delete
+                                       dot = yank_delete (p, q, 1, yf);        // delete lines
+                                       if (c == 'c')
+                                               {
+                                                       dot = char_insert (dot, '\n');
+                                                       // on the last line of file don't move to prev line
+                                                       if (dot != (end - 1))
+                                                               {
+                                                                       dot_prev ();
+                                                               }
+                                               }
+                                       else if (c == 'd')
+                                               {
+                                                       dot_begin ();
+                                                       dot_skip_over_ws ();
+                                               }
+                               }
+                       else
+                               {
+                                       // could not recognize object
+                                       c = c1 = 27;                                    // error-
+                                       indicate_error (c);
+                               }
+                       if (c1 != 27)
+                               {
+                                       // if CHANGING, not deleting, start inserting after the delete
+                                       if (c == 'c')
+                                               {
+                                                       strcpy ((char *) buf, "Change");
+                                                       goto dc_i;                              // start inserting
+                                               }
+                                       if (c == 'd')
+                                               {
+                                                       strcpy ((char *) buf, "Delete");
+                                               }
+#ifdef BB_FEATURE_VI_YANKMARK
+                                       if (c == 'y' || c == 'Y')
+                                               {
+                                                       strcpy ((char *) buf, "Yank");
+                                               }
+                                       p = reg[YDreg];
+                                       q = p + strlen ((char *) p);
+                                       for (cnt = 0; p <= q; p++)
+                                               {
+                                                       if (*p == '\n')
+                                                               cnt++;
+                                               }
+                                       psb ("%s %d lines (%d chars) using [%c]",
+                                                        buf, cnt, strlen ((char *) reg[YDreg]), what_reg ());
+#endif /* BB_FEATURE_VI_YANKMARK */
+                                       end_cmd_q ();                                   // stop adding to q
+                               }
+                       break;
+               case 'k':                                                                       // k- goto prev line, same col
+               case VI_K_UP:                                                   // cursor key Up
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       dot_prev ();
+                       dot = move_to_col (dot, ccol + offset); // try stay in same col
+                       break;
+               case 'r':                                                                       // r- replace the current char with user input
+                       c1 = get_one_char ();                   // get the replacement char
+                       if (*dot != '\n')
+                               {
+                                       *dot = c1;
+                                       file_modified = TRUE;   // has the file been modified
+                               }
+                       end_cmd_q ();                                                   // stop adding to q
+                       break;
+               case 't':                                                                       // t- move to char prior to next x
+                       last_forward_char = get_one_char ();
+                       do_cmd (';');
+                       if (*dot == last_forward_char)
+                               dot_left ();
+                       last_forward_char = 0;
+                       break;
+               case 'w':                                                                       // w- forward a word
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       if (isalnum (*dot) || *dot == '_')
+                               {                                                                                               // we are on ALNUM
+                                       dot = skip_thing (dot, 1, FORWARD, S_END_ALNUM);
+                               }
+                       else if (ispunct (*dot))
+                               {                                                                                               // we are on PUNCT
+                                       dot = skip_thing (dot, 1, FORWARD, S_END_PUNCT);
+                               }
+                       if (dot < end - 1)
+                               dot++;                                                                  // move over word
+                       if (isspace (*dot))
+                               {
+                                       dot = skip_thing (dot, 2, FORWARD, S_OVER_WS);
+                               }
+                       break;
+               case 'z':                                                                       // z-
+                       c1 = get_one_char ();                   // get the replacement char
+                       cnt = 0;
+                       if (c1 == '.')
+                               cnt = (rows - 2) / 2;           // put dot at center
+                       if (c1 == '-')
+                               cnt = rows - 2;                                 // put dot at bottom
+                       screenbegin = begin_line (dot); // start dot at top
+                       dot_scroll (cnt, -1);
+                       break;
+               case '|':                                                                       // |- move to column "cmdcnt"
+                       dot = move_to_col (dot, cmdcnt - 1);    // try to move to column
+                       break;
+               case '~':                                                                       // ~- flip the case of letters   a-z -> A-Z
+                       if (cmdcnt-- > 1)
+                               {
+                                       do_cmd (c);
+                               }                                                                                               // repeat cnt
+                       if (islower (*dot))
+                               {
+                                       *dot = toupper (*dot);
+                                       file_modified = TRUE;   // has the file been modified
+                               }
+                       else if (isupper (*dot))
+                               {
+                                       *dot = tolower (*dot);
+                                       file_modified = TRUE;   // has the file been modified
+                               }
+                       dot_right ();
+                       end_cmd_q ();                                                   // stop adding to q
+                       break;
+                       //----- The Cursor and Function Keys -----------------------------
+               case VI_K_HOME:                                         // Cursor Key Home
+                       dot_begin ();
+                       break;
+                       // The Fn keys could point to do_macro which could translate them
+               case VI_K_FUN1:                                         // Function Key F1
+               case VI_K_FUN2:                                         // Function Key F2
+               case VI_K_FUN3:                                         // Function Key F3
+               case VI_K_FUN4:                                         // Function Key F4
+               case VI_K_FUN5:                                         // Function Key F5
+               case VI_K_FUN6:                                         // Function Key F6
+               case VI_K_FUN7:                                         // Function Key F7
+               case VI_K_FUN8:                                         // Function Key F8
+               case VI_K_FUN9:                                         // Function Key F9
+               case VI_K_FUN10:                                                // Function Key F10
+               case VI_K_FUN11:                                                // Function Key F11
+               case VI_K_FUN12:                                                // Function Key F12
+                       break;
+               }
+
+dc1:
+       // if text[] just became empty, add back an empty line
+       if (end == text)
+               {
+                       (void) char_insert (text, '\n');        // start empty buf with dummy line
+                       dot = text;
+               }
+       // it is OK for dot to exactly equal to end, otherwise check dot validity
+       if (dot != end)
+               {
+                       dot = bound_dot (dot);          // make sure "dot" is valid
+               }
+#ifdef BB_FEATURE_VI_YANKMARK
+       check_context (c);                                              // update the current context
+#endif /* BB_FEATURE_VI_YANKMARK */
+
+       if (!isdigit (c))
+               cmdcnt = 0;                                                                     // cmd was not a number, reset cmdcnt
+       cnt = dot - begin_line (dot);
+       // Try to stay off of the Newline
+       if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
+               dot--;
+}
+
+//----- The Colon commands -------------------------------------
+#ifdef BB_FEATURE_VI_COLON
+static Byte *
+get_one_address (Byte * p, int *addr)  // get colon addr, if present
+{
+       int st;
+       Byte *q;
+
+#ifdef BB_FEATURE_VI_YANKMARK
+       Byte c;
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+       Byte *pat, buf[BUFSIZ];
+#endif /* BB_FEATURE_VI_SEARCH */
+
+       *addr = -1;                                                                             // assume no addr
+       if (*p == '.')
+               {                                                                                                               // the current line
+                       p++;
+                       q = begin_line (dot);
+                       *addr = count_lines (text, q);
+#ifdef BB_FEATURE_VI_YANKMARK
+               }
+       else if (*p == '\'')
+               {                                                                                                               // is this a mark addr
+                       p++;
+                       c = tolower (*p);
+                       p++;
+                       if (c >= 'a' && c <= 'z')
+                               {
+                                       // we have a mark
+                                       c = c - 'a';
+                                       q = mark[(int) c];
+                                       if (q != NULL)
+                                               {                                                                               // is mark valid
+                                                       *addr = count_lines (text, q);  // count lines
+                                               }
+                               }
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+               }
+       else if (*p == '/')
+               {                                                                                                               // a search pattern
+                       q = buf;
+                       for (p++; *p; p++)
+                               {
+                                       if (*p == '/')
+                                               break;
+                                       *q++ = *p;
+                                       *q = '\0';
+                               }
+                       pat = (Byte *) strdup ((char *) buf);   // save copy of pattern
+                       if (*p == '/')
+                               p++;
+                       q = char_search (dot, pat, FORWARD, FULL);
+                       if (q != NULL)
+                               {
+                                       *addr = count_lines (text, q);
+                               }
+                       free (pat);
+#endif /* BB_FEATURE_VI_SEARCH */
+               }
+       else if (*p == '$')
+               {                                                                                                               // the last line in file
+                       p++;
+                       q = begin_line (end - 1);
+                       *addr = count_lines (text, q);
+               }
+       else if (isdigit (*p))
+               {                                                                                                               // specific line number
+                       sscanf ((char *) p, "%d%n", addr, &st);
+                       p += st;
+               }
+       else
+               {                                                                                                               // I don't reconise this
+                       // unrecognised address- assume -1
+                       *addr = -1;
+               }
+       return (p);
+}
+
+static Byte *
+get_address (Byte * p, int *b, int *e) // get two colon addrs, if present
+{
+       //----- get the address' i.e., 1,3   'a,'b  -----
+       // get FIRST addr, if present
+       while (isblnk (*p))
+               p++;                                                                                            // skip over leading spaces
+       if (*p == '%')
+               {                                                                                                               // alias for 1,$
+                       p++;
+                       *b = 1;
+                       *e = count_lines (text, end - 1);
+                       goto ga0;
+               }
+       p = get_one_address (p, b);
+       while (isblnk (*p))
+               p++;
+       if (*p == ',')
+               {                                                                                                               // is there a address seperator
+                       p++;
+                       while (isblnk (*p))
+                               p++;
+                       // get SECOND addr, if present
+                       p = get_one_address (p, e);
+               }
+ga0:
+       while (isblnk (*p))
+               p++;                                                                                            // skip over trailing spaces
+       return (p);
+}
+
+static void
+colon (Byte * buf)
+{
+       Byte c, *orig_buf, *buf1, *q, *r;
+       Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
+       int i, l, li, ch, st, b, e;
+       int useforce, forced;
+       struct stat st_buf;
+
+       // :3154        // if (-e line 3154) goto it  else stay put
+       // :4,33w! foo  // write a portion of buffer to file "foo"
+       // :w           // write all of buffer to current file
+       // :q           // quit
+       // :q!          // quit- dont care about modified file
+       // :'a,'z!sort -u   // filter block through sort
+       // :'f          // goto mark "f"
+       // :'fl         // list literal the mark "f" line
+       // :.r bar      // read file "bar" into buffer before dot
+       // :/123/,/abc/d    // delete lines from "123" line to "abc" line
+       // :/xyz/       // goto the "xyz" line
+       // :s/find/replace/ // substitute pattern "find" with "replace"
+       // :!<cmd>      // run <cmd> then return
+       //
+       if (strlen ((char *) buf) <= 0)
+               goto vc1;
+       if (*buf == ':')
+               buf++;                                                                                  // move past the ':'
+
+       forced = useforce = FALSE;
+       li = st = ch = i = 0;
+       b = e = -1;
+       q = text;                                                                                       // assume 1,$ for the range
+       r = end - 1;
+       li = count_lines (text, end - 1);
+       fn = cfn;                                                                                       // default to current file
+       memset (cmd, '\0', BUFSIZ);             // clear cmd[]
+       memset (args, '\0', BUFSIZ);    // clear args[]
+
+       // look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
+       buf = get_address (buf, &b, &e);
+
+       // remember orig command line
+       orig_buf = buf;
+
+       // get the COMMAND into cmd[]
+       buf1 = cmd;
+       while (*buf != '\0')
+               {
+                       if (isspace (*buf))
+                               break;
+                       *buf1++ = *buf++;
+               }
+       // get any ARGuments
+       while (isblnk (*buf))
+               buf++;
+       strcpy ((char *) args, (char *) buf);
+       buf1 = last_char_is ((char *) cmd, '!');
+       if (buf1)
+               {
+                       useforce = TRUE;
+                       *buf1 = '\0';                                                   // get rid of !
+               }
+       if (b >= 0)
+               {
+                       // if there is only one addr, then the addr
+                       // is the line number of the single line the
+                       // user wants. So, reset the end
+                       // pointer to point at end of the "b" line
+                       q = find_line (b);                              // what line is #b
+                       r = end_line (q);
+                       li = 1;
+               }
+       if (e >= 0)
+               {
+                       // we were given two addrs.  change the
+                       // end pointer to the addr given by user.
+                       r = find_line (e);                              // what line is #e
+                       r = end_line (r);
+                       li = e - b + 1;
+               }
+       // ------------ now look for the command ------------
+       i = strlen ((char *) cmd);
+       if (i == 0)
+               {                                                                                                               // :123CR goto line #123
+                       if (b >= 0)
+                               {
+                                       dot = find_line (b);    // what line is #b
+                                       dot_skip_over_ws ();
+                               }
+               }
+       else if (0)
+               {
+                       // } else if (strncmp((char *) cmd, "!", 1) == 0) {     // run a cmd
+                       // :!ls   run the <cmd>
+                       // (void) alarm(0);             // wait for input- no alarms
+                       place_cursor (rows - 1, 0, FALSE);      // go to Status line
+                       clear_to_eol ();                                        // clear the line
+                       cookmode ();
+                       system (orig_buf + 1);          // run the cmd
+                       rawmode ();
+                       Hit_Return ();                                          // let user see results
+                       // (void) alarm(3);             // done waiting for input
+               }
+       else if (strncmp ((char *) cmd, "=", i) == 0)
+               {                                                                                                               // where is the address
+                       if (b < 0)
+                               {                                                                                               // no addr given- use defaults
+                                       b = e = count_lines (text, dot);
+                               }
+                       psb ("%d", b);
+               }
+       else if (strncasecmp ((char *) cmd, "delete", i) == 0)
+               {                                                                                                               // delete lines
+                       if (b < 0)
+                               {                                                                                               // no addr given- use defaults
+                                       q = begin_line (dot);   // assume .,. for the range
+                                       r = end_line (dot);
+                               }
+                       dot = yank_delete (q, r, 1, YANKDEL);   // save, then delete lines
+                       dot_skip_over_ws ();
+               }
+       else if (0)
+               {
+                       // } else if (strncasecmp((char *) cmd, "edit", i) == 0) {      // Edit a file
+                       int sr;
+                       sr = 0;
+                       // don't edit, if the current file has been modified
+                       if (file_modified == TRUE && useforce != TRUE)
+                               {
+                                       psbs ("No write since last change (:edit! overrides)");
+                                       goto vc1;
+                               }
+                       if (strlen (args) > 0)
+                               {
+                                       // the user supplied a file name
+                                       fn = args;
+                               }
+                       else if (cfn != 0 && strlen (cfn) > 0)
+                               {
+                                       // no user supplied name- use the current filename
+                                       fn = cfn;
+                                       goto vc5;
+                               }
+                       else
+                               {
+                                       // no user file name, no current name- punt
+                                       psbs ("No current filename");
+                                       goto vc1;
+                               }
+
+                       // see if file exists- if not, its just a new file request
+                       if ((sr = stat ((char *) fn, &st_buf)) < 0)
+                               {
+                                       // This is just a request for a new file creation.
+                                       // The file_insert below will fail but we get
+                                       // an empty buffer with a file name.  Then the "write"
+                                       // command can do the create.
+                               }
+                       else
+                               {
+                                       if ((st_buf.st_mode & (S_IFREG)) == 0)
+                                               {
+                                                       // This is not a regular file
+                                                       psbs ("\"%s\" is not a regular file", fn);
+                                                       goto vc1;
+                                               }
+                                       if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0)
+                                               {
+                                                       // dont have any read permissions
+                                                       psbs ("\"%s\" is not readable", fn);
+                                                       goto vc1;
+                                               }
+                               }
+
+                       // There is a read-able regular file
+                       // make this the current file
+                       q = (Byte *) strdup ((char *) fn);      // save the cfn
+                       if (cfn != 0)
+                               free (cfn);                                                     // free the old name
+                       cfn = q;                                                                        // remember new cfn
+
+               vc5:
+                       // delete all the contents of text[]
+                       new_text (2 * file_size (fn));
+                       screenbegin = dot = end = text;
+
+                       // insert new file
+                       ch = file_insert (fn, text, file_size (fn));
+
+                       if (ch < 1)
+                               {
+                                       // start empty buf with dummy line
+                                       (void) char_insert (text, '\n');
+                                       ch = 1;
+                               }
+                       file_modified = FALSE;
+#ifdef BB_FEATURE_VI_YANKMARK
+                       if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0)
+                               {
+                                       free (reg[Ureg]);                       //   free orig line reg- for 'U'
+                                       reg[Ureg] = 0;
+                               }
+                       if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0)
+                               {
+                                       free (reg[YDreg]);              //   free default yank/delete register
+                                       reg[YDreg] = 0;
+                               }
+                       for (li = 0; li < 28; li++)
+                               {
+                                       mark[li] = 0;
+                               }                                                                                               // init the marks
+#endif /* BB_FEATURE_VI_YANKMARK */
+                       // how many lines in text[]?
+                       li = count_lines (text, end - 1);
+                       psb ("\"%s\"%s"
+#ifdef BB_FEATURE_VI_READONLY
+                                        "%s"
+#endif /* BB_FEATURE_VI_READONLY */
+                                        " %dL, %dC", cfn, (sr < 0 ? " [New file]" : ""),
+#ifdef BB_FEATURE_VI_READONLY
+                                        ((vi_readonly == TRUE || readonly == TRUE) ? " [Read only]" : ""),
+#endif /* BB_FEATURE_VI_READONLY */
+                                        li, ch);
+               }
+       else if (0)
+               {
+                       // } else if (strncasecmp((char *) cmd, "file", i) == 0) {      // what File is this
+                       if (b != -1 || e != -1)
+                               {
+                                       ni ((Byte *) "No address allowed on this command");
+                                       goto vc1;
+                               }
+                       if (strlen ((char *) args) > 0)
+                               {
+                                       // user wants a new filename
+                                       if (cfn != NULL)
+                                               free (cfn);
+                                       cfn = (Byte *) strdup ((char *) args);
+                               }
+                       else
+                               {
+                                       // user wants file status info
+                                       edit_status ();
+                               }
+               }
+       else if (strncasecmp ((char *) cmd, "features", i) == 0)
+               {                                                                                                               // what features are available
+                       // print out values of all features
+                       place_cursor (rows - 1, 0, FALSE);      // go to Status line, bottom of screen
+                       clear_to_eol ();                                        // clear the line
+                       cookmode ();
+                       show_help ();
+                       rawmode ();
+                       Hit_Return ();
+               }
+       else if (strncasecmp ((char *) cmd, "list", i) == 0)
+               {                                                                                                               // literal print line
+                       if (b < 0)
+                               {                                                                                               // no addr given- use defaults
+                                       q = begin_line (dot);   // assume .,. for the range
+                                       r = end_line (dot);
+                               }
+                       place_cursor (rows - 1, 0, FALSE);      // go to Status line, bottom of screen
+                       clear_to_eol ();                                        // clear the line
+                       write (1, "\r\n", 2);
+                       for (; q <= r; q++)
+                               {
+                                       c = *q;
+                                       if (c > '~')
+                                               standout_start ();
+                                       if (c == '\n')
+                                               {
+                                                       write (1, "$\r", 2);
+                                               }
+                                       else if (*q < ' ')
+                                               {
+                                                       write (1, "^", 1);
+                                                       c += '@';
+                                               }
+                                       write (1, &c, 1);
+                                       if (c > '~')
+                                               standout_end ();
+                               }
+#ifdef BB_FEATURE_VI_SET
+               vc2:
+#endif /* BB_FEATURE_VI_SET */
+                       Hit_Return ();
+               }
+       else if ((strncasecmp ((char *) cmd, "quit", i) == 0) ||        // Quit
+                                        (strncasecmp ((char *) cmd, "next", i) == 0))
+               {                                                                                                               // edit next file
+                       if (useforce == TRUE)
+                               {
+                                       // force end of argv list
+                                       if (*cmd == 'q')
+                                               {
+                                                       optind = save_argc;
+                                               }
+                                       editing = 0;
+                                       goto vc1;
+                               }
+                       // don't exit if the file been modified
+                       if (file_modified == TRUE)
+                               {
+                                       psbs ("No write since last change (:%s! overrides)",
+                                                               (*cmd == 'q' ? "quit" : "next"));
+                                       goto vc1;
+                               }
+                       // are there other file to edit
+                       /* if (*cmd == 'q' && optind < save_argc - 1) {
+                          psbs("%d more file to edit", (save_argc - optind - 1));
+                          goto vc1;
+                          }
+                          if (*cmd == 'n' && optind >= save_argc - 1) {
+                          psbs("No more files to edit");
+                          goto vc1;
+                          } */
+                       editing = 0;
+               }
+       else if (0)
+               {
+                       // } else if (strncasecmp((char *) cmd, "read", i) == 0) {      // read file into text[]
+                       fn = args;
+                       if (strlen ((char *) fn) <= 0)
+                               {
+                                       psbs ("No filename given");
+                                       goto vc1;
+                               }
+                       if (b < 0)
+                               {                                                                                               // no addr given- use defaults
+                                       q = begin_line (dot);   // assume "dot"
+                               }
+                       // read after current line- unless user said ":0r foo"
+                       if (b != 0)
+                               q = next_line (q);
+#ifdef BB_FEATURE_VI_READONLY
+                       l = readonly;                                                   // remember current files' status
+#endif
+                       ch = file_insert (fn, q, file_size (fn));
+#ifdef BB_FEATURE_VI_READONLY
+                       readonly = l;
+#endif
+                       if (ch < 0)
+                               goto vc1;                                                               // nothing was inserted
+                       // how many lines in text[]?
+                       li = count_lines (q, q + ch - 1);
+                       psb ("\"%s\""
+#ifdef BB_FEATURE_VI_READONLY
+                                        "%s"
+#endif /* BB_FEATURE_VI_READONLY */
+                                        " %dL, %dC", fn,
+#ifdef BB_FEATURE_VI_READONLY
+                                        ((vi_readonly == TRUE || readonly == TRUE) ? " [Read only]" : ""),
+#endif /* BB_FEATURE_VI_READONLY */
+                                        li, ch);
+                       if (ch > 0)
+                               {
+                                       // if the insert is before "dot" then we need to update
+                                       if (q <= dot)
+                                               dot += ch;
+                                       file_modified = TRUE;
+                               }
+               }
+       else if (strncasecmp ((char *) cmd, "rewind", i) == 0)
+               {                                                                                                               // rewind cmd line args
+                       if (file_modified == TRUE && useforce != TRUE)
+                               {
+                                       psbs ("No write since last change (:rewind! overrides)");
+                               }
+                       else
+                               {
+                                       // reset the filenames to edit
+                                       optind = fn_start - 1;
+                                       editing = 0;
+                               }
+#ifdef BB_FEATURE_VI_SET
+               }
+       else if (strncasecmp ((char *) cmd, "set", i) == 0)
+               {                                                                                                               // set or clear features
+                       i = 0;                                                                          // offset into args
+                       if (strlen ((char *) args) == 0)
+                               {
+                                       // print out values of all options
+                                       place_cursor (rows - 1, 0, FALSE);      // go to Status line, bottom of screen
+                                       clear_to_eol ();                        // clear the line
+                                       printf ("----------------------------------------\r\n");
+#ifdef BB_FEATURE_VI_SETOPTS
+                                       if (!autoindent)
+                                               printf ("no");
+                                       printf ("autoindent ");
+                                       if (!err_method)
+                                               printf ("no");
+                                       printf ("flash ");
+                                       if (!ignorecase)
+                                               printf ("no");
+                                       printf ("ignorecase ");
+                                       if (!showmatch)
+                                               printf ("no");
+                                       printf ("showmatch ");
+                                       printf ("tabstop=%d ", tabstop);
+#endif /* BB_FEATURE_VI_SETOPTS */
+                                       printf ("\r\n");
+                                       goto vc2;
+                               }
+                       if (strncasecmp ((char *) args, "no", 2) == 0)
+                               i = 2;                                                                  // ":set noautoindent"
+#ifdef BB_FEATURE_VI_SETOPTS
+                       if (strncasecmp ((char *) args + i, "autoindent", 10) == 0 ||
+                                       strncasecmp ((char *) args + i, "ai", 2) == 0)
+                               {
+                                       autoindent = (i == 2) ? 0 : 1;
+                               }
+                       if (strncasecmp ((char *) args + i, "flash", 5) == 0 ||
+                                       strncasecmp ((char *) args + i, "fl", 2) == 0)
+                               {
+                                       err_method = (i == 2) ? 0 : 1;
+                               }
+                       if (strncasecmp ((char *) args + i, "ignorecase", 10) == 0 ||
+                                       strncasecmp ((char *) args + i, "ic", 2) == 0)
+                               {
+                                       ignorecase = (i == 2) ? 0 : 1;
+                               }
+                       if (strncasecmp ((char *) args + i, "showmatch", 9) == 0 ||
+                                       strncasecmp ((char *) args + i, "sm", 2) == 0)
+                               {
+                                       showmatch = (i == 2) ? 0 : 1;
+                               }
+                       if (strncasecmp ((char *) args + i, "tabstop", 7) == 0)
+                               {
+                                       sscanf (strchr ((char *) args + i, '='), "=%d", &ch);
+                                       if (ch > 0 && ch < columns - 1)
+                                               tabstop = ch;
+                               }
+#endif /* BB_FEATURE_VI_SETOPTS */
+#endif /* BB_FEATURE_VI_SET */
+#ifdef BB_FEATURE_VI_SEARCH
+               }
+       else if (strncasecmp ((char *) cmd, "s", 1) == 0)
+               {                                                                                                               // substitute a pattern with a replacement pattern
+                       Byte *ls, *F, *R;
+                       int gflag;
+
+                       // F points to the "find" pattern
+                       // R points to the "replace" pattern
+                       // replace the cmd line delimiters "/" with NULLs
+                       gflag = 0;                                                              // global replace flag
+                       c = orig_buf[1];                                        // what is the delimiter
+                       F = orig_buf + 2;                                       // start of "find"
+                       R = (Byte *) strchr ((char *) F, c);    // middle delimiter
+                       if (!R)
+                               goto colon_s_fail;
+                       *R++ = '\0';                                                    // terminate "find"
+                       buf1 = (Byte *) strchr ((char *) R, c);
+                       if (!buf1)
+                               goto colon_s_fail;
+                       *buf1++ = '\0';                                         // terminate "replace"
+                       if (*buf1 == 'g')
+                               {                                                                                               // :s/foo/bar/g
+                                       buf1++;
+                                       gflag++;                                                        // turn on gflag
+                               }
+                       q = begin_line (q);
+                       if (b < 0)
+                               {                                                                                               // maybe :s/foo/bar/
+                                       q = begin_line (dot);   // start with cur line
+                                       b = count_lines (text, q);      // cur line number
+                               }
+                       if (e < 0)
+                               e = b;                                                                  // maybe :.s/foo/bar/
+                       for (i = b; i <= e; i++)
+                               {                                                                                               // so, :20,23 s \0 find \0 replace \0
+                                       ls = q;                                                         // orig line start
+                               vc4:
+                                       buf1 = char_search (q, F, FORWARD, LIMITED);    // search cur line only for "find"
+                                       if (buf1 != NULL)
+                                               {
+                                                       // we found the "find" pattern- delete it
+                                                       (void) text_hole_delete (buf1, buf1 + strlen ((char *) F) - 1);
+                                                       // inset the "replace" patern
+                                                       (void) string_insert (buf1, R); // insert the string
+                                                       // check for "global"  :s/foo/bar/g
+                                                       if (gflag == 1)
+                                                               {
+                                                                       if ((buf1 + strlen ((char *) R)) < end_line (ls))
+                                                                               {
+                                                                                       q = buf1 + strlen ((char *) R);
+                                                                                       goto vc4;       // don't let q move past cur line
+                                                                               }
+                                                               }
+                                               }
+                                       q = next_line (ls);
+                               }
+#endif /* BB_FEATURE_VI_SEARCH */
+               }
+       else if (strncasecmp ((char *) cmd, "version", i) == 0)
+               {                                                                                                               // show software version
+                       psb ("%s", vi_Version);
+               }
+       else if ((strncasecmp ((char *) cmd, "write", i) == 0) ||       // write text to file
+                                        (strncasecmp ((char *) cmd, "wq", i) == 0))
+               {                                                                                                               // write text to file
+                       // is there a file name to write to?
+                       /* if (strlen((char *) args) > 0) {
+                          fn = args;
+                          } */
+#ifdef BB_FEATURE_VI_READONLY
+                       if ((vi_readonly == TRUE || readonly == TRUE) && useforce == FALSE)
+                               {
+                                       psbs ("\"%s\" File is read only", fn);
+                                       goto vc3;
+                               }
+#endif /* BB_FEATURE_VI_READONLY */
+                       // how many lines in text[]?
+                       li = count_lines (q, r);
+                       ch = r - q + 1;
+                       // see if file exists- if not, its just a new file request
+                       if (useforce == TRUE)
+                               {
+                                       // if "fn" is not write-able, chmod u+w
+                                       // sprintf(syscmd, "chmod u+w %s", fn);
+                                       // system(syscmd);
+                                       forced = TRUE;
+                               }
+                       l = file_write (fn, q, r);
+                       if (useforce == TRUE && forced == TRUE)
+                               {
+                                       // chmod u-w
+                                       // sprintf(syscmd, "chmod u-w %s", fn);
+                                       // system(syscmd);
+                                       forced = FALSE;
+                               }
+                       psb ("\"%s\" %dL, %dC", fn, li, l);
+                       if (q == text && r == end - 1 && l == ch)
+                               file_modified = FALSE;
+                       if (cmd[1] == 'q' && l == ch)
+                               {
+                                       editing = 0;
+                               }
+#ifdef BB_FEATURE_VI_READONLY
+               vc3:;
+#endif /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_YANKMARK
+               }
+       else if (strncasecmp ((char *) cmd, "yank", i) == 0)
+               {                                                                                                               // yank lines
+                       if (b < 0)
+                               {                                                                                               // no addr given- use defaults
+                                       q = begin_line (dot);   // assume .,. for the range
+                                       r = end_line (dot);
+                               }
+                       text_yank (q, r, YDreg);
+                       li = count_lines (q, r);
+                       psb ("Yank %d lines (%d chars) into [%c]",
+                                        li, strlen ((char *) reg[YDreg]), what_reg ());
+#endif /* BB_FEATURE_VI_YANKMARK */
+               }
+       else
+               {
+                       // cmd unknown
+                       ni ((Byte *) cmd);
+               }
+vc1:
+       dot = bound_dot (dot);                          // make sure "dot" is valid
+       return;
+#ifdef BB_FEATURE_VI_SEARCH
+colon_s_fail:
+       psb (":s expression missing delimiters");
+       return;
+#endif
+
+}
+
+static void
+Hit_Return (void)
+{
+       char c;
+
+       standout_start ();                                              // start reverse video
+       write (1, "[Hit return to continue]", 24);
+       standout_end ();                                                        // end reverse video
+       while ((c = get_one_char ()) != '\n' && c != '\r')      /*do nothing */
+               ;
+       redraw (TRUE);                                                          // force redraw all
+}
+#endif /* BB_FEATURE_VI_COLON */
+
+//----- Synchronize the cursor to Dot --------------------------
+static void
+sync_cursor (Byte * d, int *row, int *col)
+{
+       Byte *beg_cur, *end_cur;                        // begin and end of "d" line
+       Byte *beg_scr, *end_scr;                        // begin and end of screen
+       Byte *tp;
+       int cnt, ro, co;
+
+       beg_cur = begin_line (d);                       // first char of cur line
+       end_cur = end_line (d);                         // last char of cur line
+
+       beg_scr = end_scr = screenbegin;        // first char of screen
+       end_scr = end_screen ();                        // last char of screen
+
+       if (beg_cur < screenbegin)
+               {
+                       // "d" is before  top line on screen
+                       // how many lines do we have to move
+                       cnt = count_lines (beg_cur, screenbegin);
+               sc1:
+                       screenbegin = beg_cur;
+                       if (cnt > (rows - 1) / 2)
+                               {
+                                       // we moved too many lines. put "dot" in middle of screen
+                                       for (cnt = 0; cnt < (rows - 1) / 2; cnt++)
+                                               {
+                                                       screenbegin = prev_line (screenbegin);
+                                               }
+                               }
+               }
+       else if (beg_cur > end_scr)
+               {
+                       // "d" is after bottom line on screen
+                       // how many lines do we have to move
+                       cnt = count_lines (end_scr, beg_cur);
+                       if (cnt > (rows - 1) / 2)
+                               goto sc1;                                                               // too many lines
+                       for (ro = 0; ro < cnt - 1; ro++)
+                               {
+                                       // move screen begin the same amount
+                                       screenbegin = next_line (screenbegin);
+                                       // now, move the end of screen
+                                       end_scr = next_line (end_scr);
+                                       end_scr = end_line (end_scr);
+                               }
+               }
+       // "d" is on screen- find out which row
+       tp = screenbegin;
+       for (ro = 0; ro < rows - 1; ro++)
+               {                                                                                                               // drive "ro" to correct row
+                       if (tp == beg_cur)
+                               break;
+                       tp = next_line (tp);
+               }
+
+       // find out what col "d" is on
+       co = 0;
+       do
+               {                                                                                                               // drive "co" to correct column
+                       if (*tp == '\n' || *tp == '\0')
+                               break;
+                       if (*tp == '\t')
+                               {
+                                       //         7       - (co %    8  )
+                                       co += ((tabstop - 1) - (co % tabstop));
+                               }
+                       else if (*tp < ' ')
+                               {
+                                       co++;                                                                   // display as ^X, use 2 columns
+                               }
+               }
+       while (tp++ < d && ++co);
+
+       // "co" is the column where "dot" is.
+       // The screen has "columns" columns.
+       // The currently displayed columns are  0+offset -- columns+ofset
+       // |-------------------------------------------------------------|
+       //               ^ ^                                ^
+       //        offset | |------- columns ----------------|
+       //
+       // If "co" is already in this range then we do not have to adjust offset
+       //      but, we do have to subtract the "offset" bias from "co".
+       // If "co" is outside this range then we have to change "offset".
+       // If the first char of a line is a tab the cursor will try to stay
+       //  in column 7, but we have to set offset to 0.
+
+       if (co < 0 + offset)
+               {
+                       offset = co;
+               }
+       if (co >= columns + offset)
+               {
+                       offset = co - columns + 1;
+               }
+       // if the first char of the line is a tab, and "dot" is sitting on it
+       //  force offset to 0.
+       if (d == beg_cur && *d == '\t')
+               {
+                       offset = 0;
+               }
+       co -= offset;
+
+       *row = ro;
+       *col = co;
+}
+
+//----- Text Movement Routines ---------------------------------
+static Byte *
+begin_line (Byte * p)                                          // return pointer to first char cur line
+{
+       while (p > text && p[-1] != '\n')
+               p--;                                                                                            // go to cur line B-o-l
+       return (p);
+}
+
+static Byte *
+end_line (Byte * p)                                                    // return pointer to NL of cur line line
+{
+       while (p < end - 1 && *p != '\n')
+               p++;                                                                                            // go to cur line E-o-l
+       return (p);
+}
+
+static Byte *
+dollar_line (Byte * p)                                 // return pointer to just before NL line
+{
+       while (p < end - 1 && *p != '\n')
+               p++;                                                                                            // go to cur line E-o-l
+       // Try to stay off of the Newline
+       if (*p == '\n' && (p - begin_line (p)) > 0)
+               p--;
+       return (p);
+}
+
+static Byte *
+prev_line (Byte * p)                                           // return pointer first char prev line
+{
+       p = begin_line (p);                                             // goto begining of cur line
+       if (p[-1] == '\n' && p > text)
+               p--;                                                                                            // step to prev line
+       p = begin_line (p);                                             // goto begining of prev line
+       return (p);
+}
+
+static Byte *
+next_line (Byte * p)                                           // return pointer first char next line
+{
+       p = end_line (p);
+       if (*p == '\n' && p < end - 1)
+               p++;                                                                                            // step to next line
+       return (p);
+}
+
+//----- Text Information Routines ------------------------------
+static Byte *
+end_screen (void)
+{
+       Byte *q;
+       int cnt;
+
+       // find new bottom line
+       q = screenbegin;
+       for (cnt = 0; cnt < rows - 2; cnt++)
+               q = next_line (q);
+       q = end_line (q);
+       return (q);
+}
+
+static int
+count_lines (Byte * start, Byte * stop)        // count line from start to stop
+{
+       Byte *q;
+       int cnt;
+
+       if (stop < start)
+               {                                                                                                               // start and stop are backwards- reverse them
+                       q = start;
+                       start = stop;
+                       stop = q;
+               }
+       cnt = 0;
+       stop = end_line (stop);                         // get to end of this line
+       for (q = start; q <= stop && q <= end - 1; q++)
+               {
+                       if (*q == '\n')
+                               cnt++;
+               }
+       return (cnt);
+}
+
+static Byte *
+find_line (int li)                                                     // find begining of line #li
+{
+       Byte *q;
+
+       for (q = text; li > 1; li--)
+               {
+                       q = next_line (q);
+               }
+       return (q);
+}
+
+//----- Dot Movement Routines ----------------------------------
+static void
+dot_left (void)
+{
+       if (dot > text && dot[-1] != '\n')
+               dot--;
+}
+
+static void
+dot_right (void)
+{
+       if (dot < end - 1 && *dot != '\n')
+               dot++;
+}
+
+static void
+dot_begin (void)
+{
+       dot = begin_line (dot);                         // return pointer to first char cur line
+}
+
+static void
+dot_end (void)
+{
+       dot = end_line (dot);                                   // return pointer to last char cur line
+}
+
+static Byte *
+move_to_col (Byte * p, int l)
+{
+       int co;
+
+       p = begin_line (p);
+       co = 0;
+       do
+               {
+                       if (*p == '\n' || *p == '\0')
+                               break;
+                       if (*p == '\t')
+                               {
+                                       //         7       - (co %    8  )
+                                       co += ((tabstop - 1) - (co % tabstop));
+                               }
+                       else if (*p < ' ')
+                               {
+                                       co++;                                                                   // display as ^X, use 2 columns
+                               }
+               }
+       while (++co <= l && p++ < end);
+       return (p);
+}
+
+static void
+dot_next (void)
+{
+       dot = next_line (dot);
+}
+
+static void
+dot_prev (void)
+{
+       dot = prev_line (dot);
+}
+
+static void
+dot_scroll (int cnt, int dir)
+{
+       Byte *q;
+
+       for (; cnt > 0; cnt--)
+               {
+                       if (dir < 0)
+                               {
+                                       // scroll Backwards
+                                       // ctrl-Y  scroll up one line
+                                       screenbegin = prev_line (screenbegin);
+                               }
+                       else
+                               {
+                                       // scroll Forwards
+                                       // ctrl-E  scroll down one line
+                                       screenbegin = next_line (screenbegin);
+                               }
+               }
+       // make sure "dot" stays on the screen so we dont scroll off
+       if (dot < screenbegin)
+               dot = screenbegin;
+       q = end_screen ();                                              // find new bottom line
+       if (dot > q)
+               dot = begin_line (q);                           // is dot is below bottom line?
+       dot_skip_over_ws ();
+}
+
+static void
+dot_skip_over_ws (void)
+{
+       // skip WS
+       while (isspace (*dot) && *dot != '\n' && dot < end - 1)
+               dot++;
+}
+
+static void
+dot_delete (void)                                                              // delete the char at 'dot'
+{
+       (void) text_hole_delete (dot, dot);
+}
+
+static Byte *
+bound_dot (Byte * p)                                           // make sure  text[0] <= P < "end"
+{
+       if (p >= end && end > text)
+               {
+                       p = end - 1;
+                       indicate_error ('1');
+               }
+       if (p < text)
+               {
+                       p = text;
+                       indicate_error ('2');
+               }
+       return (p);
+}
+
+//----- Helper Utility Routines --------------------------------
+
+//----------------------------------------------------------------
+//----- Char Routines --------------------------------------------
+/* Chars that are part of a word-
+ *    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
+ * Chars that are Not part of a word (stoppers)
+ *    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
+ * Chars that are WhiteSpace
+ *    TAB NEWLINE VT FF RETURN SPACE
+ * DO NOT COUNT NEWLINE AS WHITESPACE
+ */
+
+static Byte *
+new_screen (int ro, int co)
+{
+       int li;
+
+       if (screen != 0)
+               free (screen);
+       screensize = ro * co + 8;
+       screen = (Byte *) malloc (screensize);
+       // initialize the new screen. assume this will be a empty file.
+       screen_erase ();
+       //   non-existant text[] lines start with a tilde (~).
+       for (li = 1; li < ro - 1; li++)
+               {
+                       screen[(li * co) + 0] = '~';
+               }
+       return (screen);
+}
+
+static Byte *
+new_text (int size)
+{
+       if (size < 10240)
+               size = 10240;                                                           // have a minimum size for new files
+       if (text != 0)
+               {
+                       //text -= 4;
+                       free (text);
+               }
+       text = (Byte *) malloc (size + 8);
+       memset (text, '\0', size);              // clear new text[]
+       //text += 4;            // leave some room for "oops"
+       textend = text + size - 1;
+       //textend -= 4;         // leave some root for "oops"
+       return (text);
+}
+
+#ifdef BB_FEATURE_VI_SEARCH
+static int
+mycmp (Byte * s1, Byte * s2, int len)
+{
+       int i;
+
+       i = strncmp ((char *) s1, (char *) s2, len);
+#ifdef BB_FEATURE_VI_SETOPTS
+       if (ignorecase)
+               {
+                       i = strncasecmp ((char *) s1, (char *) s2, len);
+               }
+#endif /* BB_FEATURE_VI_SETOPTS */
+       return (i);
+}
+
+static Byte *
+char_search (Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
+{
+#ifndef REGEX_SEARCH
+       Byte *start, *stop;
+       int len;
+
+       len = strlen ((char *) pat);
+       if (dir == FORWARD)
+               {
+                       stop = end - 1;                                         // assume range is p - end-1
+                       if (range == LIMITED)
+                               stop = next_line (p);           // range is to next line
+                       for (start = p; start < stop; start++)
+                               {
+                                       if (mycmp (start, pat, len) == 0)
+                                               {
+                                                       return (start);
+                                               }
+                               }
+               }
+       else if (dir == BACK)
+               {
+                       stop = text;                                                    // assume range is text - p
+                       if (range == LIMITED)
+                               stop = prev_line (p);           // range is to prev line
+                       for (start = p - len; start >= stop; start--)
+                               {
+                                       if (mycmp (start, pat, len) == 0)
+                                               {
+                                                       return (start);
+                                               }
+                               }
+               }
+       // pattern not found
+       return (NULL);
+#else  /*REGEX_SEARCH */
+       char *q;
+       struct re_pattern_buffer preg;
+       int i;
+       int size, range;
+
+       re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
+       preg.translate = 0;
+       preg.fastmap = 0;
+       preg.buffer = 0;
+       preg.allocated = 0;
+
+       // assume a LIMITED forward search
+       q = next_line (p);
+       q = end_line (q);
+       q = end - 1;
+       if (dir == BACK)
+               {
+                       q = prev_line (p);
+                       q = text;
+               }
+       // count the number of chars to search over, forward or backward
+       size = q - p;
+       if (size < 0)
+               size = p - q;
+       // RANGE could be negative if we are searching backwards
+       range = q - p;
+
+       q = (char *) re_compile_pattern (pat, strlen ((char *) pat), &preg);
+       if (q != 0)
+               {
+                       // The pattern was not compiled
+                       psbs ("bad search pattern: \"%s\": %s", pat, q);
+                       i = 0;                                                                          // return p if pattern not compiled
+                       goto cs1;
+               }
+
+       q = p;
+       if (range < 0)
+               {
+                       q = p - size;
+                       if (q < text)
+                               q = text;
+               }
+       // search for the compiled pattern, preg, in p[]
+       // range < 0-  search backward
+       // range > 0-  search forward
+       // 0 < start < size
+       // re_search() < 0  not found or error
+       // re_search() > 0  index of found pattern
+       //            struct pattern    char     int    int    int     struct reg
+       // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
+       i = re_search (&preg, q, size, 0, range, 0);
+       if (i == -1)
+               {
+                       p = 0;
+                       i = 0;                                                                          // return NULL if pattern not found
+               }
+cs1:
+       if (dir == FORWARD)
+               {
+                       p = p + i;
+               }
+       else
+               {
+                       p = p - i;
+               }
+       return (p);
+#endif /*REGEX_SEARCH */
+}
+#endif /* BB_FEATURE_VI_SEARCH */
+
+static Byte *
+char_insert (Byte * p, Byte c) // insert the char c at 'p'
+{
+       if (c == 22)
+               {                                                                                                               // Is this an ctrl-V?
+                       p = stupid_insert (p, '^');     // use ^ to indicate literal next
+                       p--;                                                                                    // backup onto ^
+                       refresh (FALSE);                                        // show the ^
+                       c = get_one_char ();
+                       *p = c;
+                       p++;
+                       file_modified = TRUE;                   // has the file been modified
+               }
+       else if (c == 27)
+               {                                                                                                               // Is this an ESC?
+                       cmd_mode = 0;
+                       cmdcnt = 0;
+                       end_cmd_q ();                                                   // stop adding to q
+                       strcpy ((char *) status_buffer, " ");   // clear the status buffer
+                       if ((p[-1] != '\n') && (dot > text))
+                               {
+                                       p--;
+                               }
+               }
+       else if (c == erase_char)
+               {                                                                                                               // Is this a BS
+                       //     123456789
+                       if ((p[-1] != '\n') && (dot > text))
+                               {
+                                       p--;
+                                       p = text_hole_delete (p, p);    // shrink buffer 1 char
+#ifdef BB_FEATURE_VI_DOT_CMD
+                                       // also rmove char from last_modifying_cmd
+                                       if (strlen ((char *) last_modifying_cmd) > 0)
+                                               {
+                                                       Byte *q;
+
+                                                       q = last_modifying_cmd;
+                                                       q[strlen ((char *) q) - 1] = '\0';      // erase BS
+                                                       q[strlen ((char *) q) - 1] = '\0';      // erase prev char
+                                               }
+#endif /* BB_FEATURE_VI_DOT_CMD */
+                               }
+               }
+       else
+               {
+                       // insert a char into text[]
+                       Byte *sp;                                                                       // "save p"
+
+                       if (c == 13)
+                               c = '\n';                                                               // translate \r to \n
+                       sp = p;                                                                         // remember addr of insert
+                       p = stupid_insert (p, c);       // insert the char
+#ifdef BB_FEATURE_VI_SETOPTS
+                       if (showmatch && strchr (")]}", *sp) != NULL)
+                               {
+                                       showmatching (sp);
+                               }
+                       if (autoindent && c == '\n')
+                               {                                                                                               // auto indent the new line
+                                       Byte *q;
+
+                                       q = prev_line (p);              // use prev line as templet
+                                       for (; isblnk (*q); q++)
+                                               {
+                                                       p = stupid_insert (p, *q);      // insert the char
+                                               }
+                               }
+#endif /* BB_FEATURE_VI_SETOPTS */
+               }
+       return (p);
+}
+
+static Byte *
+stupid_insert (Byte * p, Byte c)       // stupidly insert the char c at 'p'
+{
+       p = text_hole_make (p, 1);
+       if (p != 0)
+               {
+                       *p = c;
+                       file_modified = TRUE;                   // has the file been modified
+                       p++;
+               }
+       return (p);
+}
+
+static Byte
+find_range (Byte ** start, Byte ** stop, Byte c)
+{
+       Byte *save_dot, *p, *q;
+       int cnt;
+
+       save_dot = dot;
+       p = q = dot;
+
+       if (strchr ("cdy><", c))
+               {
+                       // these cmds operate on whole lines
+                       p = q = begin_line (p);
+                       for (cnt = 1; cnt < cmdcnt; cnt++)
+                               {
+                                       q = next_line (q);
+                               }
+                       q = end_line (q);
+               }
+       else if (strchr ("^%$0bBeEft", c))
+               {
+                       // These cmds operate on char positions
+                       do_cmd (c);                                                             // execute movement cmd
+                       q = dot;
+               }
+       else if (strchr ("wW", c))
+               {
+                       do_cmd (c);                                                             // execute movement cmd
+                       if (dot > text)
+                               dot--;                                                                  // move back off of next word
+                       if (dot > text && *dot == '\n')
+                               dot--;                                                                  // stay off NL
+                       q = dot;
+               }
+       else if (strchr ("H-k{", c))
+               {
+                       // these operate on multi-lines backwards
+                       q = end_line (dot);                             // find NL
+                       do_cmd (c);                                                             // execute movement cmd
+                       dot_begin ();
+                       p = dot;
+               }
+       else if (strchr ("L+j}\r\n", c))
+               {
+                       // these operate on multi-lines forwards
+                       p = begin_line (dot);
+                       do_cmd (c);                                                             // execute movement cmd
+                       dot_end ();                                                             // find NL
+                       q = dot;
+               }
+       else
+               {
+                       c = 27;                                                                         // error- return an ESC char
+                       //break;
+               }
+       *start = p;
+       *stop = q;
+       if (q < p)
+               {
+                       *start = q;
+                       *stop = p;
+               }
+       dot = save_dot;
+       return (c);
+}
+
+static int
+st_test (Byte * p, int type, int dir, Byte * tested)
+{
+       Byte c, c0, ci;
+       int test, inc;
+
+       inc = dir;
+       c = c0 = p[0];
+       ci = p[inc];
+       test = 0;
+
+       if (type == S_BEFORE_WS)
+               {
+                       c = ci;
+                       test = ((!isspace (c)) || c == '\n');
+               }
+       if (type == S_TO_WS)
+               {
+                       c = c0;
+                       test = ((!isspace (c)) || c == '\n');
+               }
+       if (type == S_OVER_WS)
+               {
+                       c = c0;
+                       test = ((isspace (c)));
+               }
+       if (type == S_END_PUNCT)
+               {
+                       c = ci;
+                       test = ((ispunct (c)));
+               }
+       if (type == S_END_ALNUM)
+               {
+                       c = ci;
+                       test = ((isalnum (c)) || c == '_');
+               }
+       *tested = c;
+       return (test);
+}
+
+static Byte *
+skip_thing (Byte * p, int linecnt, int dir, int type)
+{
+       Byte c;
+
+       while (st_test (p, type, dir, &c))
+               {
+                       // make sure we limit search to correct number of lines
+                       if (c == '\n' && --linecnt < 1)
+                               break;
+                       if (dir >= 0 && p >= end - 1)
+                               break;
+                       if (dir < 0 && p <= text)
+                               break;
+                       p += dir;                                                                       // move to next char
+               }
+       return (p);
+}
+
+// find matching char of pair  ()  []  {}
+static Byte *
+find_pair (Byte * p, Byte c)
+{
+       Byte match, *q;
+       int dir, level;
+
+       match = ')';
+       level = 1;
+       dir = 1;                                                                                        // assume forward
+       switch (c)
+               {
+               case '(':
+                       match = ')';
+                       break;
+               case '[':
+                       match = ']';
+                       break;
+               case '{':
+                       match = '}';
+                       break;
+               case ')':
+                       match = '(';
+                       dir = -1;
+                       break;
+               case ']':
+                       match = '[';
+                       dir = -1;
+                       break;
+               case '}':
+                       match = '{';
+                       dir = -1;
+                       break;
+               }
+       for (q = p + dir; text <= q && q < end; q += dir)
+               {
+                       // look for match, count levels of pairs  (( ))
+                       if (*q == c)
+                               level++;                                                                // increase pair levels
+                       if (*q == match)
+                               level--;                                                                // reduce pair level
+                       if (level == 0)
+                               break;                                                                  // found matching pair
+               }
+       if (level != 0)
+               q = NULL;                                                                               // indicate no match
+       return (q);
+}
+
+#ifdef BB_FEATURE_VI_SETOPTS
+// show the matching char of a pair,  ()  []  {}
+static void
+showmatching (Byte * p)
+{
+       Byte *q, *save_dot;
+
+       // we found half of a pair
+       q = find_pair (p, *p);                          // get loc of matching char
+       if (q == NULL)
+               {
+                       indicate_error ('3');                   // no matching char
+               }
+       else
+               {
+                       // "q" now points to matching pair
+                       save_dot = dot;                                         // remember where we are
+                       dot = q;                                                                        // go to new loc
+                       refresh (FALSE);                                        // let the user see it
+                       (void) mysleep (40);                    // give user some time
+                       dot = save_dot;                                         // go back to old loc
+                       refresh (FALSE);
+               }
+}
+#endif /* BB_FEATURE_VI_SETOPTS */
+
+//  open a hole in text[]
+static Byte *
+text_hole_make (Byte * p, int size)    // at "p", make a 'size' byte hole
+{
+       Byte *src, *dest;
+       int cnt;
+
+       if (size <= 0)
+               goto thm0;
+       src = p;
+       dest = p + size;
+       cnt = end - src;                                                        // the rest of buffer
+       if (memmove (dest, src, cnt) != dest)
+               {
+                       psbs ("can't create room for new characters");
+               }
+       memset (p, ' ', size);                          // clear new hole
+       end = end + size;                                                       // adjust the new END
+       file_modified = TRUE;                                   // has the file been modified
+thm0:
+       return (p);
+}
+
+//  close a hole in text[]
+static Byte *
+text_hole_delete (Byte * p, Byte * q)  // delete "p" thru "q", inclusive
+{
+       Byte *src, *dest;
+       int cnt, hole_size;
+
+       // move forwards, from beginning
+       // assume p <= q
+       src = q + 1;
+       dest = p;
+       if (q < p)
+               {                                                                                                               // they are backward- swap them
+                       src = p + 1;
+                       dest = q;
+               }
+       hole_size = q - p + 1;
+       cnt = end - src;
+       if (src < text || src > end)
+               goto thd0;
+       if (dest < text || dest >= end)
+               goto thd0;
+       if (src >= end)
+               goto thd_atend;                                                 // just delete the end of the buffer
+       if (memmove (dest, src, cnt) != dest)
+               {
+                       psbs ("can't delete the character");
+               }
+thd_atend:
+       end = end - hole_size;                          // adjust the new END
+       if (dest >= end)
+               dest = end - 1;                                                 // make sure dest in below end-1
+       if (end <= text)
+               dest = end = text;                                      // keep pointers valid
+       file_modified = TRUE;                                   // has the file been modified
+thd0:
+       return (dest);
+}
+
+// copy text into register, then delete text.
+// if dist <= 0, do not include, or go past, a NewLine
+//
+static Byte *
+yank_delete (Byte * start, Byte * stop, int dist, int yf)
+{
+       Byte *p;
+
+       // make sure start <= stop
+       if (start > stop)
+               {
+                       // they are backwards, reverse them
+                       p = start;
+                       start = stop;
+                       stop = p;
+               }
+       if (dist <= 0)
+               {
+                       // we can not cross NL boundaries
+                       p = start;
+                       if (*p == '\n')
+                               return (p);
+                       // dont go past a NewLine
+                       for (; p + 1 <= stop; p++)
+                               {
+                                       if (p[1] == '\n')
+                                               {
+                                                       stop = p;                                       // "stop" just before NewLine
+                                                       break;
+                                               }
+                               }
+               }
+       p = start;
+#ifdef BB_FEATURE_VI_YANKMARK
+       text_yank (start, stop, YDreg);
+#endif /* BB_FEATURE_VI_YANKMARK */
+       if (yf == YANKDEL)
+               {
+                       p = text_hole_delete (start, stop);
+               }                                                                                                               // delete lines
+       return (p);
+}
+
+static void
+show_help (void)
+{
+       fprintf (stderr, "version: %s\n", vi_Version);
+       puts ("These features are available:"
+#ifdef BB_FEATURE_VI_SEARCH
+                               "\n\tPattern searches with / and ?"
+#endif /* BB_FEATURE_VI_SEARCH */
+#ifdef BB_FEATURE_VI_DOT_CMD
+                               "\n\tLast command repeat with \'.\'"
+#endif /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_YANKMARK
+                               "\n\tLine marking with  'x" "\n\tNamed buffers with  \"x"
+#endif /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_READONLY
+                               "\n\tReadonly if vi is called as \"view\""
+                               "\n\tReadonly with -R command line arg"
+#endif /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SET
+                               "\n\tSome colon mode commands with \':\'"
+#endif /* BB_FEATURE_VI_SET */
+#ifdef BB_FEATURE_VI_SETOPTS
+                               "\n\tSettable options with \":set\""
+#endif /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+                               "\n\tSignal catching- ^C" "\n\tJob suspend and resume with ^Z"
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+                               "\n\tAdapt to window re-sizes"
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+               );
+}
+
+static void
+print_literal (Byte * buf, Byte * s)   // copy s to buf, convert unprintable
+{
+       Byte c, b[2];
+
+       b[1] = '\0';
+       strcpy ((char *) buf, "");              // init buf
+       if (strlen ((char *) s) <= 0)
+               s = (Byte *) "(NULL)";
+       for (; *s > '\0'; s++)
+               {
+                       c = *s;
+                       if (*s > '~')
+                               {
+                                       strcat ((char *) buf, SOs);
+                                       c = *s - 128;
+                               }
+                       if (*s < ' ')
+                               {
+                                       strcat ((char *) buf, "^");
+                                       c += '@';
+                               }
+                       b[0] = c;
+                       strcat ((char *) buf, (char *) b);
+                       if (*s > '~')
+                               strcat ((char *) buf, SOn);
+                       if (*s == '\n')
+                               {
+                                       strcat ((char *) buf, "$");
+                               }
+               }
+}
+
+#ifdef BB_FEATURE_VI_DOT_CMD
+static void
+start_new_cmd_q (Byte c)
+{
+       // release old cmd
+       if (last_modifying_cmd != 0)
+               free (last_modifying_cmd);
+       // get buffer for new cmd
+       last_modifying_cmd = (Byte *) malloc (BUFSIZ);
+       memset (last_modifying_cmd, '\0', BUFSIZ);      // clear new cmd queue
+       // if there is a current cmd count put it in the buffer first
+       if (cmdcnt > 0)
+               sprintf ((char *) last_modifying_cmd, "%d", cmdcnt);
+       // save char c onto queue
+       last_modifying_cmd[strlen ((char *) last_modifying_cmd)] = c;
+       adding2q = 1;
+       return;
+}
+
+static void
+end_cmd_q ()
+{
+#ifdef BB_FEATURE_VI_YANKMARK
+       YDreg = 26;                                                                             // go back to default Yank/Delete reg
+#endif /* BB_FEATURE_VI_YANKMARK */
+       adding2q = 0;
+       return;
+}
+#endif /* BB_FEATURE_VI_DOT_CMD */
+
+#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
+static Byte *
+string_insert (Byte * p, Byte * s)     // insert the string at 'p'
+{
+       int cnt, i;
+
+       i = strlen ((char *) s);
+       p = text_hole_make (p, i);
+       strncpy ((char *) p, (char *) s, i);
+       for (cnt = 0; *s != '\0'; s++)
+               {
+                       if (*s == '\n')
+                               cnt++;
+               }
+#ifdef BB_FEATURE_VI_YANKMARK
+       psb ("Put %d lines (%d chars) from [%c]", cnt, i, what_reg ());
+#endif /* BB_FEATURE_VI_YANKMARK */
+       return (p);
+}
+#endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
+
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *
+text_yank (Byte * p, Byte * q, int dest)       // copy text into a register
+{
+       Byte *t;
+       int cnt;
+
+       if (q < p)
+               {                                                                                                               // they are backwards- reverse them
+                       t = q;
+                       q = p;
+                       p = t;
+               }
+       cnt = q - p + 1;
+       t = reg[dest];
+       if (t != 0)
+               {                                                                                                               // if already a yank register
+                       free (t);                                                                       //   free it
+               }
+       t = (Byte *) malloc (cnt + 1);  // get a new register
+       memset (t, '\0', cnt + 1);              // clear new text[]
+       strncpy ((char *) t, (char *) p, cnt);  // copy text[] into bufer
+       reg[dest] = t;
+       return (p);
+}
+
+static Byte
+what_reg (void)
+{
+       Byte c;
+       int i;
+
+       i = 0;
+       c = 'D';                                                                                        // default to D-reg
+       if (0 <= YDreg && YDreg <= 25)
+               c = 'a' + (Byte) YDreg;
+       if (YDreg == 26)
+               c = 'D';
+       if (YDreg == 27)
+               c = 'U';
+       return (c);
+}
+
+static void
+check_context (Byte cmd)
+{
+       // A context is defined to be "modifying text"
+       // Any modifying command establishes a new context.
+
+       if (dot < context_start || dot > context_end)
+               {
+                       if (strchr ((char *) modifying_cmds, cmd) != NULL)
+                               {
+                                       // we are trying to modify text[]- make this the current context
+                                       mark[27] = mark[26];    // move cur to prev
+                                       mark[26] = dot;                         // move local to cur
+                                       context_start = prev_line (prev_line (dot));
+                                       context_end = next_line (next_line (dot));
+                                       //loiter= start_loiter= now;
+                               }
+               }
+       return;
+}
+
+static Byte *
+swap_context (Byte * p)                                        // goto new context for '' command make this the current context
+{
+       Byte *tmp;
+
+       // the current context is in mark[26]
+       // the previous context is in mark[27]
+       // only swap context if other context is valid
+       if (text <= mark[27] && mark[27] <= end - 1)
+               {
+                       tmp = mark[27];
+                       mark[27] = mark[26];
+                       mark[26] = tmp;
+                       p = mark[26];                                                   // where we are going- previous context
+                       context_start = prev_line (prev_line (prev_line (p)));
+                       context_end = next_line (next_line (next_line (p)));
+               }
+       return (p);
+}
+#endif /* BB_FEATURE_VI_YANKMARK */
+
+static int
+isblnk (Byte c)                                                                        // is the char a blank or tab
+{
+       return (c == ' ' || c == '\t');
+}
+
+//----- Set terminal attributes --------------------------------
+static void
+rawmode (void)
+{
+       tcgetattr (0, &term_orig);
+       term_vi = term_orig;
+       term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG ON- allow intr's
+       term_vi.c_iflag &= (~IXON & ~ICRNL);
+       term_vi.c_oflag &= (~ONLCR);
+#ifndef linux
+       term_vi.c_cc[VMIN] = 1;
+       term_vi.c_cc[VTIME] = 0;
+#endif
+       erase_char = term_vi.c_cc[VERASE];
+       tcsetattr (0, TCSANOW, &term_vi);
+}
+
+static void
+cookmode (void)
+{
+       tcsetattr (0, TCSANOW, &term_orig);
+}
+
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+//----- See what the window size currently is --------------------
+static void
+window_size_get (int sig)
+{
+       int i;
+
+       i = ioctl (0, TIOCGWINSZ, &winsize);
+       if (i != 0)
+               {
+                       // force 24x80
+                       winsize.ws_row = 24;
+                       winsize.ws_col = 80;
+               }
+       if (winsize.ws_row <= 1)
+               {
+                       winsize.ws_row = 24;
+               }
+       if (winsize.ws_col <= 1)
+               {
+                       winsize.ws_col = 80;
+               }
+       rows = (int) winsize.ws_row;
+       columns = (int) winsize.ws_col;
+}
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+
+//----- Come here when we get a window resize signal ---------
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static void
+winch_sig (int sig)
+{
+       signal (SIGWINCH, winch_sig);
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get (0);
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+       new_screen (rows, columns);             // get memory for virtual screen
+       redraw (TRUE);                                                          // re-draw the screen
+}
+
+//----- Come here when we get a continue signal -------------------
+static void
+cont_sig (int sig)
+{
+       rawmode ();                                                                             // terminal to "raw"
+       *status_buffer = '\0';                          // clear the status buffer
+       redraw (TRUE);                                                          // re-draw the screen
+
+       signal (SIGTSTP, suspend_sig);
+       signal (SIGCONT, SIG_DFL);
+       kill (getpid (), SIGCONT);
+}
+
+//----- Come here when we get a Suspend signal -------------------
+static void
+suspend_sig (int sig)
+{
+       place_cursor (rows - 1, 0, FALSE);      // go to bottom of screen
+       clear_to_eol ();                                                        // Erase to end of line
+       cookmode ();                                                                    // terminal to "cooked"
+
+       signal (SIGCONT, cont_sig);
+       signal (SIGTSTP, SIG_DFL);
+       kill (getpid (), SIGTSTP);
+}
+
+//----- Come here when we get a signal ---------------------------
+static void
+catch_sig (int sig)
+{
+       signal (SIGHUP, catch_sig);
+       signal (SIGINT, catch_sig);
+       signal (SIGTERM, catch_sig);
+       longjmp (restart, sig);
+}
+
+static void
+alarm_sig (int sig)
+{
+       signal (SIGALRM, catch_sig);
+       longjmp (restart, sig);
+}
+
+//----- Come here when we get a core dump signal -----------------
+static void
+core_sig (int sig)
+{
+       signal (SIGQUIT, core_sig);
+       signal (SIGILL, core_sig);
+       signal (SIGTRAP, core_sig);
+       signal (SIGIOT, core_sig);
+       signal (SIGABRT, core_sig);
+       signal (SIGFPE, core_sig);
+       signal (SIGBUS, core_sig);
+       signal (SIGSEGV, core_sig);
+#ifdef SIGSYS
+       signal (SIGSYS, core_sig);
+#endif
+
+       dot = bound_dot (dot);                          // make sure "dot" is valid
+
+       longjmp (restart, sig);
+}
+#endif /* BB_FEATURE_VI_USE_SIGNALS */
+
+static int
+mysleep (int hund)                                                     // sleep for 'h' 1/100 seconds
+{
+       // Don't hang- Wait 5/100 seconds-  1 Sec= 1000000
+       FD_ZERO (&rfds);
+       FD_SET (0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = hund * 10000;
+       select (1, &rfds, NULL, NULL, &tv);
+       return (FD_ISSET (0, &rfds));
+}
+
+//----- IO Routines --------------------------------------------
+static Byte
+readit (void)                                                                          // read (maybe cursor) key from stdin
+{
+       Byte c;
+       int i, bufsiz, cnt;
+       unsigned int cmdindex;
+       struct esc_cmds
+       {
+               Byte *seq;
+               Byte val;
+       };
+
+       static struct esc_cmds esccmds[] = {
+               {(Byte *) "\eOA", (Byte) VI_K_UP},       // cursor key Up
+               {(Byte *) "\eOB", (Byte) VI_K_DOWN},     // cursor key Down
+               {(Byte *) "\eOC", (Byte) VI_K_RIGHT},    // Cursor Key Right
+               {(Byte *) "\eOD", (Byte) VI_K_LEFT},     // cursor key Left
+               {(Byte *) "\eOH", (Byte) VI_K_HOME},     // Cursor Key Home
+               {(Byte *) "\eOF", (Byte) VI_K_END},      // Cursor Key End
+               {(Byte *) "\e[A", (Byte) VI_K_UP},       // cursor key Up
+               {(Byte *) "\e[B", (Byte) VI_K_DOWN},     // cursor key Down
+               {(Byte *) "\e[C", (Byte) VI_K_RIGHT},    // Cursor Key Right
+               {(Byte *) "\e[D", (Byte) VI_K_LEFT},     // cursor key Left
+               {(Byte *) "\e[H", (Byte) VI_K_HOME},     // Cursor Key Home
+               {(Byte *) "\e[F", (Byte) VI_K_END},      // Cursor Key End
+               {(Byte *) "\e[2~", (Byte) VI_K_INSERT},  // Cursor Key Insert
+               {(Byte *) "\e[5~", (Byte) VI_K_PAGEUP},  // Cursor Key Page Up
+               {(Byte *) "\e[6~", (Byte) VI_K_PAGEDOWN},        // Cursor Key Page Down
+               {(Byte *) "\eOP", (Byte) VI_K_FUN1},     // Function Key F1
+               {(Byte *) "\eOQ", (Byte) VI_K_FUN2},     // Function Key F2
+               {(Byte *) "\eOR", (Byte) VI_K_FUN3},     // Function Key F3
+               {(Byte *) "\eOS", (Byte) VI_K_FUN4},     // Function Key F4
+               {(Byte *) "\e[15~", (Byte) VI_K_FUN5},   // Function Key F5
+               {(Byte *) "\e[17~", (Byte) VI_K_FUN6},   // Function Key F6
+               {(Byte *) "\e[18~", (Byte) VI_K_FUN7},   // Function Key F7
+               {(Byte *) "\e[19~", (Byte) VI_K_FUN8},   // Function Key F8
+               {(Byte *) "\e[20~", (Byte) VI_K_FUN9},   // Function Key F9
+               {(Byte *) "\e[21~", (Byte) VI_K_FUN10},  // Function Key F10
+               {(Byte *) "\e[23~", (Byte) VI_K_FUN11},  // Function Key F11
+               {(Byte *) "\e[24~", (Byte) VI_K_FUN12},  // Function Key F12
+               {(Byte *) "\e[11~", (Byte) VI_K_FUN1},   // Function Key F1
+               {(Byte *) "\e[12~", (Byte) VI_K_FUN2},   // Function Key F2
+               {(Byte *) "\e[13~", (Byte) VI_K_FUN3},   // Function Key F3
+               {(Byte *) "\e[14~", (Byte) VI_K_FUN4},   // Function Key F4
+       };
+
+#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
+
+       // (void) alarm(0);     // turn alarm OFF while we wait for input
+       // get input from User- are there already input chars in Q?
+       bufsiz = strlen ((char *) readbuffer);
+       if (bufsiz <= 0)
+               {
+               ri0:
+                       // the Q is empty, wait for a typed char
+                       bufsiz = read (0, readbuffer, BUFSIZ - 1);
+                       if (bufsiz < 0)
+                               {
+                                       if (errno == EINTR)
+                                               goto ri0;                                               // interrupted sys call
+                                       if (errno == EBADF)
+                                               editing = 0;
+                                       if (errno == EFAULT)
+                                               editing = 0;
+                                       if (errno == EINVAL)
+                                               editing = 0;
+                                       if (errno == EIO)
+                                               editing = 0;
+                                       errno = 0;
+                                       bufsiz = 0;
+                               }
+                       readbuffer[bufsiz] = '\0';
+               }
+       // return char if it is not part of ESC sequence
+       if (readbuffer[0] != 27)
+               goto ri1;
+
+       // This is an ESC char. Is this Esc sequence?
+       // Could be bare Esc key. See if there are any
+       // more chars to read after the ESC. This would
+       // be a Function or Cursor Key sequence.
+       FD_ZERO (&rfds);
+       FD_SET (0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 50000;                                             // Wait 5/100 seconds- 1 Sec=1000000
+
+       // keep reading while there are input chars and room in buffer
+       while (select (1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5))
+               {
+                       // read the rest of the ESC string
+                       i = read (0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
+                       if (i > 0)
+                               {
+                                       bufsiz += i;
+                                       readbuffer[bufsiz] = '\0';      // Terminate the string
+                               }
+               }
+       // Maybe cursor or function key?
+       for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++)
+               {
+                       cnt = strlen ((char *) esccmds[cmdindex].seq);
+                       i = strncmp ((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
+                       if (i == 0)
+                               {
+                                       // is a Cursor key- put derived value back into Q
+                                       readbuffer[0] = esccmds[cmdindex].val;
+                                       // squeeze out the ESC sequence
+                                       for (i = 1; i < cnt; i++)
+                                               {
+                                                       memmove (readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
+                                                       readbuffer[BUFSIZ - 1] = '\0';
+                                               }
+                                       break;
+                               }
+               }
+ri1:
+       c = readbuffer[0];
+       // remove one char from Q
+       memmove (readbuffer, readbuffer + 1, BUFSIZ - 1);
+       readbuffer[BUFSIZ - 1] = '\0';
+       // (void) alarm(3);     // we are done waiting for input, turn alarm ON
+       return (c);
+}
+
+//----- IO Routines --------------------------------------------
+static Byte
+get_one_char ()
+{
+       static Byte c;
+
+#ifdef BB_FEATURE_VI_DOT_CMD
+       // ! adding2q  && ioq == 0  read()
+       // ! adding2q  && ioq != 0  *ioq
+       // adding2q         *last_modifying_cmd= read()
+       if (!adding2q)
+               {
+                       // we are not adding to the q.
+                       // but, we may be reading from a q
+                       if (ioq == 0)
+                               {
+                                       // there is no current q, read from STDIN
+                                       c = readit ();                          // get the users input
+                               }
+                       else
+                               {
+                                       // there is a queue to get chars from first
+                                       c = *ioq++;
+                                       if (c == '\0')
+                                               {
+                                                       // the end of the q, read from STDIN
+                                                       free (ioq_start);
+                                                       ioq_start = ioq = 0;
+                                                       c = readit ();          // get the users input
+                                               }
+                               }
+               }
+       else
+               {
+                       // adding STDIN chars to q
+                       c = readit ();                                          // get the users input
+                       if (last_modifying_cmd != 0)
+                               {
+                                       // add new char to q
+                                       last_modifying_cmd[strlen ((char *) last_modifying_cmd)] = c;
+                               }
+               }
+#else  /* BB_FEATURE_VI_DOT_CMD */
+       c = readit ();                                                          // get the users input
+#endif /* BB_FEATURE_VI_DOT_CMD */
+       return (c);                                                                             // return the char, where ever it came from
+}
+
+static Byte *
+get_input_line (Byte * prompt) // get input line- use "status line"
+{
+       Byte buf[BUFSIZ];
+       Byte c;
+       int i;
+       static Byte *obufp = NULL;
+
+       strcpy ((char *) buf, (char *) prompt);
+       *status_buffer = '\0';                          // clear the status buffer
+       place_cursor (rows - 1, 0, FALSE);      // go to Status line, bottom of screen
+       clear_to_eol ();                                                        // clear the line
+       write (1, prompt, strlen ((char *) prompt));    // write out the :, /, or ? prompt
+
+       for (i = strlen ((char *) buf); i < BUFSIZ;)
+               {
+                       c = get_one_char ();                    // read user input
+                       if (c == '\n' || c == '\r' || c == 27)
+                               break;                                                                  // is this end of input
+                       if (c == erase_char)
+                               {                                                                                               // user wants to erase prev char
+                                       i--;                                                                    // backup to prev char
+                                       buf[i] = '\0';                          // erase the char
+                                       buf[i + 1] = '\0';              // null terminate buffer
+                                       write (1, "\b \b", 3);                    // erase char on screen
+                                       if (i <= 0)
+                                               {                                                                               // user backs up before b-o-l, exit
+                                                       break;
+                                               }
+                               }
+                       else
+                               {
+                                       buf[i] = c;                                             // save char in buffer
+                                       buf[i + 1] = '\0';              // make sure buffer is null terminated
+                                       write (1, buf + i, 1);  // echo the char back to user
+                                       i++;
+                               }
+               }
+       refresh (FALSE);
+       if (obufp != NULL)
+               free (obufp);
+       obufp = (Byte *) strdup ((char *) buf);
+       return (obufp);
+}
+
+static int
+file_size (Byte * fn)                                          // what is the byte size of "fn"
+{
+       struct stat st_buf;
+       int cnt, sr;
+
+       if (fn == 0 || strlen (fn) <= 0)
+               return (-1);
+       cnt = -1;
+       sr = stat ((char *) fn, &st_buf);       // see if file exists
+       if (sr >= 0)
+               {
+                       cnt = (int) st_buf.st_size;
+               }
+       return (cnt);
+}
+
+static int
+file_insert (Byte * fn, Byte * p, int size)
+{
+       int fd, cnt;
+
+       cnt = -1;
+#ifdef BB_FEATURE_VI_READONLY
+       readonly = FALSE;
+#endif /* BB_FEATURE_VI_READONLY */
+       if (fn == 0 || strlen ((char *) fn) <= 0)
+               {
+                       psbs ("No filename given");
+                       goto fi0;
+               }
+       if (size == 0)
+               {
+                       // OK- this is just a no-op
+                       cnt = 0;
+                       goto fi0;
+               }
+       if (size < 0)
+               {
+                       psbs ("Trying to insert a negative number (%d) of characters", size);
+                       goto fi0;
+               }
+       if (p < text || p > end)
+               {
+                       psbs ("Trying to insert file outside of memory");
+                       goto fi0;
+               }
+
+       // see if we can open the file
+#ifdef BB_FEATURE_VI_READONLY
+       if (vi_readonly == TRUE)
+               goto fi1;                                                                               // do not try write-mode
+#endif
+       fd = open ((char *) fn, O_RDWR);        // assume read & write
+       if (fd < 0)
+               {
+                       // could not open for writing- maybe file is read only
+#ifdef BB_FEATURE_VI_READONLY
+               fi1:
+#endif
+                       fd = open ((char *) fn, O_RDONLY);      // try read-only
+                       if (fd < 0)
+                               {
+                                       psbs ("\"%s\" %s", fn, "could not open file");
+                                       goto fi0;
+                               }
+#ifdef BB_FEATURE_VI_READONLY
+                       // got the file- read-only
+                       readonly = TRUE;
+#endif /* BB_FEATURE_VI_READONLY */
+               }
+       p = text_hole_make (p, size);
+       cnt = read (fd, p, size);
+       close (fd);
+       if (cnt < 0)
+               {
+                       cnt = -1;
+                       p = text_hole_delete (p, p + size - 1); // un-do buffer insert
+                       psbs ("could not read file \"%s\"", fn);
+               }
+       else if (cnt < size)
+               {
+                       // There was a partial read, shrink unused space text[]
+                       p = text_hole_delete (p + cnt, p + (size - cnt) - 1);   // un-do buffer insert
+                       psbs ("could not read all of file \"%s\"", fn);
+               }
+       if (cnt >= size)
+               file_modified = TRUE;
+fi0:
+       return (cnt);
+}
+
+static int
+file_write (Byte * fn, Byte * first, Byte * last)
+{
+       int fd, cnt, charcnt;
+
+       if (fn == 0)
+               {
+                       psbs ("No current filename");
+                       return (-1);
+               }
+       charcnt = 0;
+       // FIXIT- use the correct umask()
+       fd = open ((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
+       if (fd < 0)
+               return (-1);
+       cnt = last - first + 1;
+       charcnt = write (fd, first, cnt);
+       if (charcnt == cnt)
+               {
+                       // good write
+                       //file_modified= FALSE; // the file has not been modified
+               }
+       else
+               {
+                       charcnt = 0;
+               }
+       close (fd);
+       return (charcnt);
+}
+
+//----- Terminal Drawing ---------------------------------------
+// The terminal is made up of 'rows' line of 'columns' columns.
+// classicly this would be 24 x 80.
+//  screen coordinates
+//  0,0     ...     0,79
+//  1,0     ...     1,79
+//  .       ...     .
+//  .       ...     .
+//  22,0    ...     22,79
+//  23,0    ...     23,79   status line
+//
+
+//----- Move the cursor to row x col (count from 0, not 1) -------
+static void
+place_cursor (int row, int col, int opti)
+{
+       char cm1[BUFSIZ];
+       char *cm;
+       int l;
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+       char cm2[BUFSIZ];
+       Byte *screenp;
+       // char cm3[BUFSIZ];
+       int Rrow = last_row;
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+
+       memset (cm1, '\0', BUFSIZ - 1); // clear the buffer
+
+       if (row < 0)
+               row = 0;
+       if (row >= rows)
+               row = rows - 1;
+       if (col < 0)
+               col = 0;
+       if (col >= columns)
+               col = columns - 1;
+
+       //----- 1.  Try the standard terminal ESC sequence
+       sprintf ((char *) cm1, CMrc, row + 1, col + 1);
+       cm = cm1;
+       if (opti == FALSE)
+               goto pc0;
+
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+       //----- find the minimum # of chars to move cursor -------------
+       //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
+       memset (cm2, '\0', BUFSIZ - 1); // clear the buffer
+
+       // move to the correct row
+       while (row < Rrow)
+               {
+                       // the cursor has to move up
+                       strcat (cm2, CMup);
+                       Rrow--;
+               }
+       while (row > Rrow)
+               {
+                       // the cursor has to move down
+                       strcat (cm2, CMdown);
+                       Rrow++;
+               }
+
+       // now move to the correct column
+       strcat (cm2, "\r");                                             // start at col 0
+       // just send out orignal source char to get to correct place
+       screenp = &screen[row * columns];       // start of screen line
+       strncat (cm2, screenp, col);
+
+       //----- 3.  Try some other way of moving cursor
+       //---------------------------------------------
+
+       // pick the shortest cursor motion to send out
+       cm = cm1;
+       if (strlen (cm2) < strlen (cm))
+               {
+                       cm = cm2;
+               }                                                                                                               /* else if (strlen(cm3) < strlen(cm)) {
+                                                                                                                                  cm= cm3;
+                                                                                                                                  } */
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+pc0:
+       l = strlen (cm);
+       if (l)
+               write (1, cm, l);                                               // move the cursor
+}
+
+//----- Erase from cursor to end of line -----------------------
+static void
+clear_to_eol ()
+{
+       write (1, Ceol, strlen (Ceol)); // Erase from cursor to end of line
+}
+
+//----- Erase from cursor to end of screen -----------------------
+static void
+clear_to_eos ()
+{
+       write (1, Ceos, strlen (Ceos)); // Erase from cursor to end of screen
+}
+
+//----- Start standout mode ------------------------------------
+static void
+standout_start ()                                                              // send "start reverse video" sequence
+{
+       write (1, SOs, strlen (SOs));   // Start reverse video mode
+}
+
+//----- End standout mode --------------------------------------
+static void
+standout_end ()                                                                        // send "end reverse video" sequence
+{
+       write (1, SOn, strlen (SOn));   // End reverse video mode
+}
+
+//----- Flash the screen  --------------------------------------
+static void
+flash (int h)
+{
+       standout_start ();                                              // send "start reverse video" sequence
+       redraw (TRUE);
+       (void) mysleep (h);
+       standout_end ();                                                        // send "end reverse video" sequence
+       redraw (TRUE);
+}
+
+static void
+beep ()
+{
+       write (1, bell, strlen (bell)); // send out a bell character
+}
+
+static void
+indicate_error (char c)
+{
+#ifdef BB_FEATURE_VI_CRASHME
+       if (crashme > 0)
+               return;                                                                                 // generate a random command
+#endif /* BB_FEATURE_VI_CRASHME */
+       if (err_method == 0)
+               {
+                       beep ();
+               }
+       else
+               {
+                       flash (10);
+               }
+}
+
+//----- Screen[] Routines --------------------------------------
+//----- Erase the Screen[] memory ------------------------------
+static void
+screen_erase ()
+{
+       memset (screen, ' ', screensize);       // clear new screen
+}
+
+//----- Draw the status line at bottom of the screen -------------
+static void
+show_status_line (void)
+{
+       static int last_cksum;
+       int l, cnt, cksum;
+
+       cnt = strlen ((char *) status_buffer);
+       for (cksum = l = 0; l < cnt; l++)
+               {
+                       cksum += (int) (status_buffer[l]);
+               }
+       // don't write the status line unless it changes
+       if (cnt > 0 && last_cksum != cksum)
+               {
+                       last_cksum = cksum;                             // remember if we have seen this line
+                       place_cursor (rows - 1, 0, FALSE);      // put cursor on status line
+                       write (1, status_buffer, cnt);
+                       clear_to_eol ();
+                       place_cursor (crow, ccol, FALSE);       // put cursor back in correct place
+               }
+}
+
+//----- format the status buffer, the bottom line of screen ------
+// print status buffer, with STANDOUT mode
+static void
+psbs (char *format, ...)
+{
+       va_list args;
+
+       va_start (args, format);
+       strcpy ((char *) status_buffer, SOs);   // Terminal standout mode on
+       vsprintf ((char *) status_buffer + strlen ((char *) status_buffer), format,
+                                               args);
+       strcat ((char *) status_buffer, SOn);   // Terminal standout mode off
+       va_end (args);
+
+       return;
+}
+
+// print status buffer
+static void
+psb (char *format, ...)
+{
+       va_list args;
+
+       va_start (args, format);
+       vsprintf ((char *) status_buffer, format, args);
+       va_end (args);
+       return;
+}
+
+static void
+ni (Byte * s)                                                                          // display messages
+{
+       Byte buf[BUFSIZ];
+
+       print_literal (buf, s);
+       psbs ("\'%s\' is not implemented", buf);
+}
+
+static void
+edit_status (void)                                                     // show file status on status line
+{
+       int cur, tot, percent;
+
+       cur = count_lines (text, dot);
+       tot = count_lines (text, end - 1);
+       //    current line         percent
+       //   -------------    ~~ ----------
+       //    total lines            100
+       if (tot > 0)
+               {
+                       percent = (100 * cur) / tot;
+               }
+       else
+               {
+                       cur = tot = 0;
+                       percent = 100;
+               }
+       psb ("\"%s\""
+#ifdef BB_FEATURE_VI_READONLY
+                        "%s"
+#endif /* BB_FEATURE_VI_READONLY */
+                        "%s line %d of %d --%d%%--", (cfn != 0 ? (char *) cfn : "No file"),
+#ifdef BB_FEATURE_VI_READONLY
+                        ((vi_readonly == TRUE || readonly == TRUE) ? " [Read only]" : ""),
+#endif /* BB_FEATURE_VI_READONLY */
+                        (file_modified == TRUE ? " [modified]" : ""), cur, tot, percent);
+}
+
+//----- Force refresh of all Lines -----------------------------
+static void
+redraw (int full_screen)
+{
+       place_cursor (0, 0, FALSE);             // put cursor in correct place
+       clear_to_eos ();                                                        // tel terminal to erase display
+       screen_erase ();                                                        // erase the internal screen buffer
+       refresh (full_screen);                          // this will redraw the entire display
+}
+
+//----- Format a text[] line into a buffer ---------------------
+static void
+format_line (Byte * dest, Byte * src, int li)
+{
+       int co;
+       Byte c;
+
+       for (co = 0; co < MAX_SCR_COLS; co++)
+               {
+                       c = ' ';                                                                        // assume blank
+                       if (li > 0 && co == 0)
+                               {
+                                       c = '~';                                                        // not first line, assume Tilde
+                               }
+                       // are there chars in text[] and have we gone past the end
+                       if (text < end && src < end)
+                               {
+                                       c = *src++;
+                               }
+                       if (c == '\n')
+                               break;
+                       if (c < ' ' || c > '~')
+                               {
+                                       if (c == '\t')
+                                               {
+                                                       c = ' ';
+                                                       //       co %    8     !=     7
+                                                       for (; (co % tabstop) != (tabstop - 1); co++)
+                                                               {
+                                                                       dest[co] = c;
+                                                               }
+                                               }
+                                       else
+                                               {
+                                                       dest[co++] = '^';
+                                                       c |= '@';                                       // make it visible
+                                                       c &= 0x7f;                              // get rid of hi bit
+                                               }
+                               }
+                       // the co++ is done here so that the column will
+                       // not be overwritten when we blank-out the rest of line
+                       dest[co] = c;
+                       if (src >= end)
+                               break;
+               }
+}
+
+//----- Refresh the changed screen lines -----------------------
+// Copy the source line from text[] into the buffer and note
+// if the current screenline is different from the new buffer.
+// If they differ then that line needs redrawing on the terminal.
+//
+static void
+refresh (int full_screen)
+{
+       static int old_offset;
+       int li, changed;
+       Byte buf[MAX_SCR_COLS];
+       Byte *tp, *sp;                                                          // pointer into text[] and screen[]
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+       int last_li = -2;                                                       // last line that changed- for optimizing cursor movement
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get (0);
+#endif /* BB_FEATURE_VI_WIN_RESIZE */
+       sync_cursor (dot, &crow, &ccol);        // where cursor will be (on "dot")
+       tp = screenbegin;                                                       // index into text[] of top line
+
+       // compare text[] to screen[] and mark screen[] lines that need updating
+       for (li = 0; li < rows - 1; li++)
+               {
+                       int cs, ce;                                                             // column start & end
+                       memset (buf, ' ', MAX_SCR_COLS);        // blank-out the buffer
+                       buf[MAX_SCR_COLS - 1] = 0;      // NULL terminate the buffer
+                       // format current text line into buf
+                       format_line (buf, tp, li);
+
+                       // skip to the end of the current text[] line
+                       while (tp < end && *tp++ != '\n') /*no-op */ ;
+
+                       // see if there are any changes between vitual screen and buf
+                       changed = FALSE;                                        // assume no change
+                       cs = 0;
+                       ce = columns - 1;
+                       sp = &screen[li * columns];     // start of screen line
+                       if (full_screen == TRUE)
+                               {
+                                       // force re-draw of every single column from 0 - columns-1
+                                       goto re0;
+                               }
+                       // compare newly formatted buffer with virtual screen
+                       // look forward for first difference between buf and screen
+                       for (; cs <= ce; cs++)
+                               {
+                                       if (buf[cs + offset] != sp[cs])
+                                               {
+                                                       changed = TRUE;         // mark for redraw
+                                                       break;
+                                               }
+                               }
+
+                       // look backward for last difference between buf and screen
+                       for (; ce >= cs; ce--)
+                               {
+                                       if (buf[ce + offset] != sp[ce])
+                                               {
+                                                       changed = TRUE;         // mark for redraw
+                                                       break;
+                                               }
+                               }
+                       // now, cs is index of first diff, and ce is index of last diff
+
+                       // if horz offset has changed, force a redraw
+                       if (offset != old_offset)
+                               {
+                               re0:
+                                       changed = TRUE;
+                               }
+
+                       // make a sanity check of columns indexes
+                       if (cs < 0)
+                               cs = 0;
+                       if (ce > columns - 1)
+                               ce = columns - 1;
+                       if (cs > ce)
+                               {
+                                       cs = 0;
+                                       ce = columns - 1;
+                               }
+                       // is there a change between vitual screen and buf
+                       if (changed == TRUE)
+                               {
+                                       //  copy changed part of buffer to virtual screen
+                                       memmove (sp + cs, buf + (cs + offset), ce - cs + 1);
+
+                                       // move cursor to column of first change
+                                       if (offset != old_offset)
+                                               {
+                                                       // opti_cur_move is still too stupid
+                                                       // to handle offsets correctly
+                                                       place_cursor (li, cs, FALSE);
+                                               }
+                                       else
+                                               {
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+                                                       // if this just the next line
+                                                       //  try to optimize cursor movement
+                                                       //  otherwise, use standard ESC sequence
+                                                       place_cursor (li, cs, li == (last_li + 1) ? TRUE : FALSE);
+                                                       last_li = li;
+#else  /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+                                                       place_cursor (li, cs, FALSE);   // use standard ESC sequence
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+                                               }
+
+                                       // write line out to terminal
+                                       write (1, sp + cs, ce - cs + 1);
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+                                       last_row = li;
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+                               }
+               }
+
+#ifdef BB_FEATURE_VI_OPTIMIZE_CURSOR
+       place_cursor (crow, ccol, (crow == last_row) ? TRUE : FALSE);
+       last_row = crow;
+#else
+       place_cursor (crow, ccol, FALSE);
+#endif /* BB_FEATURE_VI_OPTIMIZE_CURSOR */
+
+       if (offset != old_offset)
+               old_offset = offset;
+}