From: Joshua Kwan Date: Tue, 30 Dec 2003 21:30:32 +0000 (+0000) Subject: initial commit of new dgamelaunch to CVS X-Git-Tag: v1.6.1-roc-dev~697 X-Git-Url: https://skyeroc.xyz/gitweb/?a=commitdiff_plain;h=f626f44b02b35f9297b890c6776180b49df170a2;p=dgamelaunch initial commit of new dgamelaunch to CVS git-svn-id: svn://katsu.triplehelix.org/dgamelaunch/trunk@1 db0b04b0-f4d1-0310-9a6d-de3e77497b0e --- f626f44b02b35f9297b890c6776180b49df170a2 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; +}