From f626f44b02b35f9297b890c6776180b49df170a2 Mon Sep 17 00:00:00 2001 From: Joshua Kwan Date: Tue, 30 Dec 2003 21:30:32 +0000 Subject: [PATCH] initial commit of new dgamelaunch to CVS git-svn-id: svn://katsu.triplehelix.org/dgamelaunch/trunk@1 db0b04b0-f4d1-0310-9a6d-de3e77497b0e --- Bugs | 9 + COPYING | 339 ++++ Changelog | 119 ++ Makefile | 21 + README | 82 + dgamelaunch.c | 1097 +++++++++++ dgamelaunch.h | 27 + dgl-default-rcfile | 20 + io.c | 146 ++ io.h | 10 + last_char_is.c | 43 + stripgfx.c | 394 ++++ stripgfx.h | 7 + ttyplay.c | 405 ++++ ttyrec.c | 386 ++++ ttyrec.h | 15 + virus.c | 4487 ++++++++++++++++++++++++++++++++++++++++++++ 17 files changed, 7607 insertions(+) create mode 100644 Bugs create mode 100644 COPYING create mode 100644 Changelog create mode 100644 Makefile create mode 100644 README create mode 100644 dgamelaunch.c create mode 100644 dgamelaunch.h create mode 100644 dgl-default-rcfile create mode 100644 io.c create mode 100644 io.h create mode 100644 last_char_is.c create mode 100644 stripgfx.c create mode 100644 stripgfx.h create mode 100644 ttyplay.c create mode 100644 ttyrec.c create mode 100644 ttyrec.h create mode 100644 virus.c diff --git a/Bugs b/Bugs new file mode 100644 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 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. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + 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. + + + Copyright (C) 19yy + + 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. + + , 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 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 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 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 +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 index 0000000..e7c9654 --- /dev/null +++ b/dgamelaunch.c @@ -0,0 +1,1097 @@ +/* nethacklaunch.c + * + * (c)2001-3 M. Drew Streib + * 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 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 +#include +#include +#include +#include /* for flock() */ +#include /* ttyrec */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 0000000..1be1b9f --- /dev/null +++ b/dgamelaunch.h @@ -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 index 0000000..bb3f587 --- /dev/null +++ b/dgl-default-rcfile @@ -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 index 0000000..15a81ae --- /dev/null +++ b/io.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2000 Satoru Takabayashi + * 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 +#include +#include +#include +#include + +#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 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 index 0000000..64e9a9d --- /dev/null +++ b/last_char_is.c @@ -0,0 +1,43 @@ +/* + * busybox library eXtended function + * + * Copyright (C) 2001 Larry Doolittle, + * + * 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 + +/* 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 index 0000000..614abb0 --- /dev/null +++ b/stripgfx.c @@ -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 +#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 index 0000000..c96cad5 --- /dev/null +++ b/stripgfx.h @@ -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 index 0000000..b544b75 --- /dev/null +++ b/ttyplay.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2000 Satoru Takabayashi + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 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 + * - added Native Language Support + */ + +/* 2000-12-27 Satoru Takabayashi + * - modify `script' to create `ttyrec'. + */ + +/* + * script + */ +#include "dgamelaunch.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#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 +#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 + +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 index 0000000..b728b7f --- /dev/null +++ b/ttyrec.h @@ -0,0 +1,15 @@ +#ifndef __TTYREC_H__ +#define __TTYREC_H__ + +#include +#include + +typedef struct header +{ + struct timeval tv; + size_t len; +} +Header; + + +#endif diff --git a/virus.c b/virus.c new file mode 100644 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 + * Copyright (C) 2000, 2001 Sterling Huxley + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 textend) + { + strcat ((char *) msg, "end>textend "); + } + if (dot < text) + { + strcat ((char *) msg, "dot end) + { + strcat ((char *) msg, "dot>end "); + } + if (screenbegin < text) + { + strcat ((char *) msg, "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" + // :! // run 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 + // (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 *) "OA", (Byte) VI_K_UP}, // cursor key Up + {(Byte *) "OB", (Byte) VI_K_DOWN}, // cursor key Down + {(Byte *) "OC", (Byte) VI_K_RIGHT}, // Cursor Key Right + {(Byte *) "OD", (Byte) VI_K_LEFT}, // cursor key Left + {(Byte *) "OH", (Byte) VI_K_HOME}, // Cursor Key Home + {(Byte *) "OF", (Byte) VI_K_END}, // Cursor Key End + {(Byte *) "", (Byte) VI_K_UP}, // cursor key Up + {(Byte *) "", (Byte) VI_K_DOWN}, // cursor key Down + {(Byte *) "", (Byte) VI_K_RIGHT}, // Cursor Key Right + {(Byte *) "", (Byte) VI_K_LEFT}, // cursor key Left + {(Byte *) "", (Byte) VI_K_HOME}, // Cursor Key Home + {(Byte *) "", (Byte) VI_K_END}, // Cursor Key End + {(Byte *) "[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert + {(Byte *) "[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up + {(Byte *) "[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down + {(Byte *) "OP", (Byte) VI_K_FUN1}, // Function Key F1 + {(Byte *) "OQ", (Byte) VI_K_FUN2}, // Function Key F2 + {(Byte *) "OR", (Byte) VI_K_FUN3}, // Function Key F3 + {(Byte *) "OS", (Byte) VI_K_FUN4}, // Function Key F4 + {(Byte *) "[15~", (Byte) VI_K_FUN5}, // Function Key F5 + {(Byte *) "[17~", (Byte) VI_K_FUN6}, // Function Key F6 + {(Byte *) "[18~", (Byte) VI_K_FUN7}, // Function Key F7 + {(Byte *) "[19~", (Byte) VI_K_FUN8}, // Function Key F8 + {(Byte *) "[20~", (Byte) VI_K_FUN9}, // Function Key F9 + {(Byte *) "[21~", (Byte) VI_K_FUN10}, // Function Key F10 + {(Byte *) "[23~", (Byte) VI_K_FUN11}, // Function Key F11 + {(Byte *) "[24~", (Byte) VI_K_FUN12}, // Function Key F12 + {(Byte *) "[11~", (Byte) VI_K_FUN1}, // Function Key F1 + {(Byte *) "[12~", (Byte) VI_K_FUN2}, // Function Key F2 + {(Byte *) "[13~", (Byte) VI_K_FUN3}, // Function Key F3 + {(Byte *) "[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, " ", 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; +}