pax_global_header00006660000000000000000000000064143317025270014516gustar00rootroot0000000000000052 comment=8c7086dd2385bb307cdadb893f2566b4e8da8ad9 rlwrap-0.46.1/000077500000000000000000000000001433170252700131155ustar00rootroot00000000000000rlwrap-0.46.1/.gitignore000066400000000000000000000004521433170252700151060ustar00rootroot00000000000000Makefile Makefile.in aclocal.m4 autom4te.cache/ config.h config.h.in config.log config.status configure doc/Makefile doc/Makefile.in doc/rlwrap.1 doc/rlwrap.man filters/Makefile filters/Makefile.in src/*.o src/.deps/ src/Makefile src/Makefile.in src/rlwrap stamp-h1 tools/ filters/RlwrapFilter.3pm rlwrap-0.46.1/AUTHORS000066400000000000000000000017261433170252700141730ustar00rootroot00000000000000Chet Ramey : author of the amazing readline library, around which rlwrap is but a thin wrapper. Hans Lub hanslub42@gmail.com: most of the rlwrap code, except for: - ptytty.c, wich was taken practically unchanged from rxvt (currently maintained by Geoff C. Wing ), together with the corresponding part of configure.ac - completion.c which contains the redblack tree implementation written by Damian Ivereigh (with contributions by Ben Woodard, Eric S. Raymond and Matthias Andree) Jon Olsson (jon at vexed.se) contributed patches to weed out unsafe strcat and strcpy calls Hisanobu Okuda ported the RlwrapFilter.pm perl module (and a number of example filters) to python, and contributed a new way of handling multi-part filter messages. Robert Kroeger contributed code to keep track of the rlwrapped command's working directory under OS X Yuri d'Elia contributed the code for the rlwrap-direct-keypress bindable readline commandrlwrap-0.46.1/BUGS000066400000000000000000000044611433170252700136050ustar00rootroot00000000000000Whenever you suspect a bug, if possible, please reconfigure with ./configure --enable-debug and run rlwrap with a -d7 option. This will create a file /tmp/rlwrap.debug that will help you (or me) find the problem. Many (most) bug reports, especially the really weird ones, stem from "version skew" on distributions (RedHat, Debian) that have separate development packages (e.g. readline and readline-dev). For some reason it can happen that the devel package is out of sync with the main package, and then really strange things may happen. So: always update your {readline, ncurses}-devel packages before you complain to me! * Gotcha's If rlwrap doesn't seem to do anything, chances are that the rlwrapped program already does its own line editing. Using the -a (--always-readline) option will make rlwrap use its own line editor. If such programs do their own completion, it will be unusable under rlwrap * General inadequacy and weak spots The more sophisticated the terminal handling of rlwrapped program (the "client") gets, the less rlwrap will be able to maintain its transparency. Of course, more sophisticated programs generally have less need for rlwrap. --- rlwrap cannot handle prompts that contain control characters (except ANSI colour), though you may not notice this until your cursor almost reaches end-of-line --- older QNX: If 'configure' complains "Oops! Your 'rm' program seems unable to run without file operands specified", export ACCEPT_INFERIOR_RM_PROGRAM=yes before running 'configure' --- The -m option uses the system() (3) library function to call an external editor. I'm not quite sure how system() handles signals like TSTP an WINCH (and "system" is a difficult name to Google for...) Re-sizing the terminal may confuse the editor -- The code that determines whether a cooked prompt should be overwritten (when it turns out to not have been a prompt) is needlessly complex and not quite correct (e.g. substitute prompts may be left standing when they should be erased). -- readline < 8.2 doesn't account for the fact that rl_deprep_terminal() outputs a "\r" when enable-bracketed-paste is on. rlwrap < 0.46.1 compensated for that, but will therefore mis-print accepted user input when compiled with readline-8.2 rlwrap-0.46.1 now *disables* bracketed paste when compiled with readline 8.1 or earlierrlwrap-0.46.1/COPYING000066400000000000000000000431101433170252700141470ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 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. rlwrap-0.46.1/ChangeLog000066400000000000000000000001511433170252700146640ustar00rootroot00000000000000rlwrap doesn't have a proper changelog (yet), look in NEWS to see what has been changed in which version rlwrap-0.46.1/INSTALL000066400000000000000000000100511433170252700141430ustar00rootroot00000000000000PREREQUISITES To build rlwrap, you'll need an ANSI C compiler (gcc will certainly do) and GNU readline 4.2 or later. rlwrap should compile on most unices and unix-like environments like linux, the BSDs, OS/X, solaris, HP/UX, QNX, AIX and cygwin (special thanks to PolarHome for their "dinosaur zoo" of ageing Unix systems!) INSTALLATION First, unzip the tarball or clone the git repository (git clone https://github.com/hanslub42/rlwrap.git). Then go to the rlwrap directory, and: autoreconf --install # only needed if there is no ./configure script then: ./configure make sudo make install CONFIGURATION OPTIONS configure can be called with a number of options (e.g. if you want to install rlwrap in a non-standard place). Do ./configure --help to see them all. Most of them are not specific to rlwrap, except the following: --enable-spy-on-readline (default: YES) If we want to keep the display tidy when re-sizing the terminal window or printing multi-line prompts, we have to know whether or not readline is in horizontal-scroll-mode (i.e. wheter long lines are scrolled or wrapped). At present, this involves a look at a private readline variable - if you feel guilty about that, disable this option. --enable-homegrown-redisplay (default: NO): some people report ddouble echoing of user input. Enabling this option will cure the problem - though the display will then flicker over slow connections (cf. BUGS) --enable-debug: (default: NO) Adds a --debug option to rlwrap's repertoire. This will make rlwrap write debug information to a file /tmp/rlwrap.debug (cf. the output of rlwrap --help for more info) --enable-proc-mountpoint (default: /proc) mountpoint for Linux-style procfs, used for determination of slave command's working directory. --enable-multibyte-aware (default: YES) Prevent rlwrap from seeing multibyte characters as multiple characters. UNINSTALLATION To uninstall, do 'make uninstall' INSTALLING AS NON-ROOT If you want to install rlwrap as non-root, you should call configure with the --prefix option, like: ./configure --prefix=$HOME after which 'make install' will install rlwrap in $HOME/bin You may have to install GNU readline (e.g using ./configure --prefix=$HOME in the readline source directory) rlwrap's configure script will not find this installation automatically, but ./configure --prefix=$HOME CFLAGS=-I$HOME/include CPPFLAGS=-I$HOME/include LDFLAGS=-L$HOME/lib should work. You can add '-static' to LDFLAGS if you want to build a statically linked rlwrap, like so: ./configure --prefix=$HOME CFLAGS=-I$HOME/include CPPFLAGS=-I$HOME/include LDFLAGS=-L$HOME/lib' -static' BUILD PROBLEMS If configure complains about missing Xyz (where Xyz can be ncurses, or readline) and you are sure that Xyz is installed on your system, consider installing Xyz-devel completion.c is made from completion.rb by the program rbgen (https://github.com/hanslub42/libredblack), but both files are included in the tarball and the repository; rbgen is only needed when you want to change completion.c. Sometimes, however, after a 'git pull', make will try to recompile completion.rb and complain about not finding rbgen. A fresh clone, or simply 'touch completion.c' will solve this. If the configure script is not found, you can recreate it, and all of the other files it needs, by: aclocal autoconf autoheader automake --add-missing or, even shorter: autoreconf --install On recent OS X sytems, libreadline is not the real thing, but a non-GNU replacement. If the linker complains about missing symbols, install GNU readline and try again (or, better, use a tool like brew) Even though rlwrap now uses the excellent pseudo-terminal (pty) handling code from rxvt, portable programming with ptys is something of a black art. The configure script tries to guess how ptys have to be created and handled on your system, but it may guess wrong. To quote rxvt's configure script: "if we don't guess right then it's up to the user", which means that you have to manually edit config.h, and save a copy of it somewhere, as configure will re-create config.h rlwrap-0.46.1/Makefile.am000066400000000000000000000041131433170252700151500ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in EXTRA_DIST = BUGS COPYING INSTALL README.md src/rlwrap.h src/redblack.h src/malloc_debug.h\ src/completion.rb doc/rlwrap.man.in test/testclient test/testit\ completions/testclient completions/coqtop\ filters/README filters/RlwrapFilter.pm filters/RlwrapFilter.3pm filters/count_in_prompt filters/pipeto\ filters/logger filters/null filters/unbackspace filters/pipeline filters/ftp_filter filters/handle_hotkeys filters/history_format\ filters/simple_macro filters/template filters/scrub_prompt filters/paint_prompt filters/censor_passwords filters/listing\ filters/paint_prompt.py filters/handle_hotkeys.py filters/logger.py filters/pipeto.py\ filters/rlwrapfilter.py filters/null.py filters/censor_passwords.py filters/edit_history\ filters/count_in_prompt.py filters/ftp_filter.py filters/debug_null filters/handle_sigwinch filters/outfilter\ filters/makefilter filters/dissect_prompt TESTS = test/testit SUBDIRS = doc src filters nobase_pkgdata_DATA = completions/testclient completions/coqtop\ filters/README filters/RlwrapFilter.pm filters/RlwrapFilter.3pm filters/count_in_prompt filters/pipeto\ filters/logger filters/null filters/unbackspace filters/pipeline filters/ftp_filter filters/handle_hotkeys filters/history_format\ filters/simple_macro filters/template filters/scrub_prompt filters/paint_prompt filters/censor_passwords filters/listing\ filters/paint_prompt.py filters/handle_hotkeys.py filters/logger.py filters/pipeto.py\ filters/rlwrapfilter.py filters/null.py filters/censor_passwords.py filters/edit_history\ filters/count_in_prompt.py filters/ftp_filter.py filters/debug_null filters/handle_sigwinch filters/outfilter\ filters/makefilter filters/dissect_prompt clean-local: rm -f *~ install-data-hook: chmod a+x $(DESTDIR)$(pkgdatadir)/filters/* rlwrap-0.46.1/NEWS000066400000000000000000000625651433170252700136320ustar00rootroot000000000000000.46 rlwrapfilter.py would not find collections.Callable on newer (>= 3.10) python rlwrapfilter.py would choke on rlwrap version strings (like 0.45.2) that cannot be converted by float() rlwrap could segfault or garble prompts if they contain a carriage return configure.ac would complain about obsolete macros with newest autoconf Bind TAB to self-insert if no completions are specified on the rlwrap command line 0.45.2 rlwrap enables bracketed-paste when instructed to do so by .inputrc, but would then never disable it at exit. rlwrap -z (i.e. rlwrap when using a filter) would still open /tmp/rlwrap.debug after forking the filter command, preventing other users from running it afterwards 0.45.1 rlwrap would always open /tmp/rlwrap.debug after forking child command, preventing other users from running it. Fix args to setitimer() call to prevent EINVAL error on return advise about --always-readline if in direct mode at first user ENTER keystroke check for I_SWROPT even if isastream() is present (compile would fail on Oracle linux) 0.45 rlwrap correctly handles bracketed paste --only-cook '!' enables "confident mode" where every possible prompt that matches a regexp is cooked immediately (so that even prompts that get printed when handling a large paste are cooked) --ansi-colour-aware (-A) didn't do anything at all. Now it recognises colour codes as well as common control codes like window titles. --ansi-colour-aware="!" will "bleach" the prompt, i.e. remove all colour codes --no-children (-N) now enables direct mode whenever the client switches to the alternate screen. This makes editors and pagers usable even when using --always-readline on non-linux systems when run inside an emacs shell buffer, rlwrap will execute the client instead of wrapping it (just as when stdin is not a terminal) --always-echo echoes user input even when the client has switched off ECHO. filter "makefilter" to easily employ shell commands (like sed, or grep) as rlwrap filters (extending/superseding "outfilter") filters can change (some) bindable and internal readline variables with a new RlwrapFilter method tweak_readline_oob() On AIX, rlwrap would quit if client wrote 0 bytes a round of testing on Polarhome to weed out some incompatibilities with older Unix systems 0.44 rlwrap doesn't (yet) work with bracketed-paste. As this is enabled by default from readline-8.1 onwards, rlwrap disables it, even if specified in .inputrc. A multi-line paste will therefore (still) behave as multiple 1-line pastes rlwrap is aware of multi-byte characters and correctly handles prompts (or things that look like prompts, e.g. progress indicators) that contain them, according to your locale. rlwrap filters can also filter signals (see RlwrapFilter(3pm)), changing them, or providing extra input to the rlwrapped command. Key *sequences* can be bound to rlwrap-direct-keypress (using a new readline command rlwrap-direct-prefix) (contributed by Yuri d'Elia) configure will correctly identify whether --mirror-arguments will work, even on 1-processor systems the handle_hotkey filter has a handler definition that enables fuzzy history search using fzf binding accept-line to a key would make that key mess up the display The debug log is more readable (e.g. by marking debug lines with "parent", "child" or "filter") 0.43 Added Hisanobu Okuda's rlwrapfilter.py python3 module and example filters. Filters can now be written in python as well as perl. If a filter was used, rlwrap would pass all input, output, history items, prompts, ... through the filter, even if it wouldn't change them. Now, at startup, filters (even filter pipelines) tell rlwrap which messages they handle, after which rlwrap won't bother them with anything else. Added bindable readline command rlwrap-direct-keypress that bypasses readline editing and sends its keypress directly to the rlwrapped command (like CTRL-G for the Erlang shell) Added bindable readline command rlwrap-hotkey that passes the current input buffer and history to the filter (or filter pipeline) specified with the '-z' option. This can be used e.g. to insert the current selection at the cursor position, or to edit (re-write) the history. This uncovered quite a few bugs and inconsistencies: - My ncurses' termcap emulation misses some codes (like term_cursor_hpos) that its terminfo has. rlwrap now always searches terminfo before termcap. - rlwrap was confused about the role of history_offset, resulting in muddled and unclear (although correct) code. - rlwrap --history-size -0 would clobber the history file (as per the manual - that has been updated as well) - rlwrap's ad hoc way of sending lists of strings to filters by interspersing them with TABS or spaces is becoming unwieldy, it has been replaced by a standard encoding .... (where the are fixed length hexadecimal numbers (this is a contribution by Hisanobu Okuda) Playing back a readline macro consisting of more than one line would crash with SIGSEGV rlwrap with negative --histsize would fail when there is no history file yet. An empty prompt would make $filter->{cumulative_output} miss its last line Pre-given (-P) input would only be put in input line after cooking timeout (usually 40 msec) One-shot (-o) rlwrap could accept more than one line when input in quick succession. rlwrap didn't delete the tempfiles used in a multi-line edit configure.ac now works even when cross-compiling (skipping some tests, but providing sensible defaults) --enable-pedantic-ansi is a new configure option separate from --enable-debug (it used to be implied by --enable-debug) --complete-filenames (-c) will now work on OS X and FreeBSD as well 0.42 Added --mirror-arguments (-U) option On SunOS tcgetattr(slave pty) failed with "Invalid argument" If the completion list contained two words, one of which a prefix of the other (e.g. "sea" and "seagull") the shorter one would be skipped when displaying a list of completions reading completion files (with the -f option, or from $RLWRAP_HOME/_completions) could fail with an incorrect ENOENT ("No such file or directory") rlwrap -z listing wouldn't list filters When both master and slave pty are unfit for sensing slave commands terminal settings, rlwrap now bails out with an error "cannot determine terminal mode of slave command" 0.41 Slightly late SIGCHLD could cause an I/O error on master pty Added -W (-polling) option to poll slave pty for changes in its interrupt character and ISIG flag. if $TERM is not found in termcap/terminfo database, use vt100 0.40 configure will now find tgetent() in libtinfo compiling with gcc -O2 made rlwrap hang after CTRL-D on empty line HP-UX 11 has weird tgetent() return values, confusing rlwrap On Solaris, rlwrap would sometimes fail with "TIOCSWINSZ failed on slave pty" Single quote ' is now word-breaking by default multi-line edit would mangle upper ASCII and UTF-8 (and still does that to UTF16 :( ) added --extra-char-after-completion and -multi-line-ext options rlwrap now recognises the 'rmcup' and 'rmkx' character sequences used by programs that use an alternate screen (like editors and pagers) to return from it. configure will now correctly determine pty type on SCO OpenServer rlwrap --no-children would leak file descriptors to /proc//wchan non-ASCII characters in multi-line input are no longer replaced by spaces after calling an external editor running rlwrap within emacs would crash (division by zero). rlwrap now bails out whenever terminal width == 0 added --enable-proc-mountpoint config option to use alternate linux-like proc filesystems (like in FreeBSD) for finding s working dir (-c option) and kernel function in which is sleeping (-N option) added prototype for copy_without_ignore_markers, fixing a segfault on NetBSD/amd64 commands final output before dying was lost on FreeBSD Filters now get complete echo lines even if the echo comes back in chunks 0.37 Commands that emit "status lines" using backspaces and carriage returns could confuse rlwrap removed test program kaboom.c that triggered an internal gcc error on armel platforms. rlwrap uses C strings internally, and thus cannot cope with command output that contains zero bytes (padding). It used to replace these with spaces, now the zero bytes are removed. if the RLWRAP_HOME is set, but $RLWRAP_HOME doesn't exist, rlwrap will create it typo: SIGERR instead of SIG_ERR in signals.c 0.36 Entering a line from vi command mode would echo the input twice Output from very busy commands would not always be printed on time When rlwrap kills itself after a command crash it will not dump core, in order to avoid clobbering command's much more interesting core dump. Premature filter death is now reported properly (it used to only say: "EOF reading from filter" or "Broken pipe writing to filter") 0.35 config.{guess,sub} have been updated to version 2009-12-13 Corrected array bounds error in my_putstr("") (which could make rlwrap write an extra newline when exiting, but might even crash on some systems) Many small improvements and fixes for multi-line input: Multi-line inputs are now written to the inferior command one line at a time, so that command's response (e.g. a continuation prompt) can be interleaved with the echo'ed (multi-line) input. Calling an external editor will no longer obliterate the prompt, and line/column positions are now correct. After a multi-line edit in vi-mode, the cursor will no longer end up one line too high. CTRL-D on an empty line was handed directly to command, but also (erroneously) put in readline's input buffer Many small fixes and improvements in signal handling: SIGSEGV, and other "error" signals like SIGFPE, are now unblocked all of the time, so that rlwrap can always clean up after a crash. Since version 0.25 rlrwap's transparency extends to signals: if the inferior command segfaults, rlwrap will kill itself with a SIGSEGV. In order to get the bug reports where they belong, rlwrap now reports explicitly that it has not crashed itself. rlwrap's call to sigaction forgot to set the signal mask (!) Continuing after CTRL-Z on QNX now wakes up command Added --one-shot (-o) and --only-cook (-O) options debug log is now in a format that works well with emacs' grep-mode rlwrap's bindable readline function names (like rlwrap-call-editor) are now in hyphen-style instead of underscore_style (use of the old_style_names will now be flagged as an error) Filters can now prevent a prompt from being cooked by "rejecting" it. Rlwrapfilter.pm would set $_ incorrectly in echo and output handlers. RlwrapFilter.pm manpage is now created by newer (and less buggy) version of pod2man Added EXAMPLES section and -t option to rlwrap manpage 0.34 Binding wide (e.g. utf-8) chars in .inputrc now works prefix arguments are now correctly reset (M-5 a b now yields aaaaab instead of aaaaabbbbb) 0.33 rlwrap incorrectly fed terminfo-style capnames ("dl1") instead of termcap codes ("dl") into tgetstr(). On newer Debian systems this exposed a bug where random garbage would be printed by rlwrap Hyphens in rlwrap manpage are now all properly escaped RlwrapFilter.pm now only re-sets $filter->cumulative_output when an INPUT message is received 0.32 Major new feature: filtering. Filters sit between rlwrap and the wrapped command, re-writing command output, input, prompts, history, and completion word lists. System-wide filters live in DATADIR/rlwrap/filters (where DATADIR = /usr/local/share by default, installation-dependent) Because of this, completions now live in DATADIR/rlwrap/completions (until now: DATADIR/rlwrap) To make filter writing easy, a perl module RlwrapFilter.pm has been added. It doesn't become part of your perl installation, but lives in DATADIR/rlwrap/filters rlwrap didn't properly check errno after reading from inferior pty. This could lead to a spurious "read error on master pty" Instead of using crusty old signal(), signal handlers are now set by sigaction() without SA_RESTART (BSD semantics) Different syscall-restarting behaviour among systems caused hard-to-trace bugs Now copies inferior pty's c_oflags to stdout before output. (some editors like joe would mess up the screen) prompt handling logic has been streamlined. Coloured prompt handling is reliable now, even for long prompts. At program exit, rlwrap now outputs a newline only when the client didn't. Added -g, -I, -N, -S, -w and -z options Removed -F option (and added a filter to replace it) -p option now takes colour names (-pYellow) rlwrap (and readline) uses C strings internally, which could cause problems with commands that output '\0' bytes. In direct mode, such characters are left untouched, but in readline mode they are replaced by spaces. the tools directory has been updated, so that configure will feel at home even on newer systems tested on SunOS, AIX, FreeBSD, HP/UX, QNX (thanks to polarhome.com), as well as cygwin and linux 0.30 rlwrap can now use putenv() on machines that don't have setenv() (like some Solaris systems) EOF on stdin (e.g. by pressing CTRL-D) would end prompt colouring. added -q option 0.29 added -A option to handle (ANSI-)coloured prompts added -p option to colourise uncoloured prompts added -t option to set terminal type for client command rlwrap now copies its terminal settings from the client even when this has put its terminal in single-keypress (uncooked) mode. A rlwrapped emacs will now respond to CTRL-C and CTRL-G as it should. fixed a long-standing bug where long output lines with the -r option would put mutilated words into the completion list. Drawback: prompts are not put into the completion list anymore (bug or feature?). rlwrap now handles output to the client before input from the client, and only handles keyboard input when all client I/O has been handled. This will make rlwrap a little better behaved when handling large chunks of (pasted) input, especially with colourised prompts error messages and warnings now include the rlwrap version number rlwrap now prints a warning when started in vi mode when the terminal is too dumb to support upwards cursor movement added a very simple custom malloc/free debugger for use with the --debug option. Rather fragile and not well tested, use with care. Whenever TERM is not set, rlwrap assumes vt100. Set TERM=dumb if you really have a dumb terminal. rlwrap now leaves the handling of multi-line prompts and edit buffers to readline (except when in horizontal-scroll mode or when configured with --enable-homegrown-redisplay). whenever --always-readline is set, SIGWINCH not passed to client command immediately, but only *after* accepting a line. multi-byte prompts and input no longer confuse rlwrap (provided your readline lib supports multi-byte characters) --spy-on-readline now enabled by default the configure script will now find term.h on cygwin dropped the assumption that tgetent() and friends are only ever declared in term.h; rlwrap now has proper terminal handling also under SunOS. the rlwrap source should again be fit for consumption by older (C91 compliant) compilers 0.28 fixed rlwrap bug that caused termcap problems on Fedora Core 6 (thanks Tung Nguyen) 0.27 when stdin is not a terminal, rlwrap will now exec() the specified command instead of complaining when stdout or stderr is not a terminal, rlwrap will re-open it to /dev/tty (the users terminal) after forking off the specified command (so "rlwrap cat > file" will work as a quick and dirty line editor) rlwrap now remembers inputs of length 1 -D option to tell rlwrap how agressively it should weed out duplicate history entries -H option added (history format string) Added temporary fix for termcap weirdness on Fedora Core 6 new -t option for a quick check of terminal capabilities (requires configuration with --enable-debug) rlwrap -s 0 will now zap history fixed broken reporting of unrecognised options 0.26 configure on FreeBSD 6.0 still didn't find libutil some files were unnecessarily kept open (thanks Stephan Springl) on each platform, rlwrap -h now accurately reflects whether rlwrap can use long options or optional arguments 0.25 rlwrap would print input twice when in vi-mode rlwrap under FreeBSD 6.0 now uses openpty() from libutil (config script fixed) -P option added (one-shot rlwrap with pre-given prompt) Until now, whem the underlying command was killed by a signal, rlwrap would just exit with exit code 0. Now rlwrap will cleanup, reset its signal handlers and then send the same signal to itself - so that rlwraps parent (usually a shell) doesn't see the difference with an un-rlwrapped command 0.24 rlwrap -r would mess up commands output (strtok() strikes again!) -i option added to make completion case-insensitive -m option added in order to handle multi-line input rlwrap now writes to underlying command using non-blocking writes and select(). This prevents deadlocks which could occur with very large inputs. corrected some manpage clumsiness 0.23 Completion word lists are now kept in red-black trees (cf. http://libredblack.sourceforge.net/), dramatically speeding up startup with large completion lists. rlwrap copies terminal settings from client, so that wrapping programs that manipulate their terminal (like emacs and vim) should be transparent. rlwrap -C1 is now accepted 0.22 Added key binding to enter a line while keeping it out of the history list (Control+O by default) 0.21 Added --history-filename option. Negative history size now means: don't write or truncate history Updated helper scripts (like config.sub) in ./tools multiple -f options again work correctly --enable-homegrown-redisplay configuration option added (kludge to circumvent display problems like sometimes reported on Solaris) All unsafe string handling functions (strcpy, sprintf,..) replaced by their safe equivalents - using strlcat and consorts when available. --enable-spy-on-readline configuration option to keep display tidy when resizing terminal 0.19 Fixed pty type finding code in configure.ac (newer FreeBSD's were recognised as cygwin) Helper scripts moved to separate ./tools directory 0.18 rlwrap could hang when trying to run a non-existent command. 0.17 EOF on stdin would send rlwrap into infinite loop. Small bugfixes in testclient, which now works with perl 5.8 and cygwin. 0.16 ptys can be found and correctly opend on many more systems (thanks to code taken from rxvt). Makefiles now generated by automake. Much beter debugging, -d option now takes optional bitmask to report only certain events. System-wide completion files now in $datadir/rlwrap (normally /usr/local/share/rlwrap). -C and -n options added, -a option can take an argument (to prevent password logging when using this option). assert() macro used for run-time consistency checks. CTRL-D on empty line now sends EOF to client even on SunOS. manpage displays correctly even when using troff instead of groff. Long options now in --dash-style instead of --underscore_style. 0.15 Fixed a bug where rlwrap would segfault with -f option. 0.14 Fixed a few portability problems. 0.13 Duplicate history entries are avoided at all times, even across invocations. Tries to chdir into the slave command's working directory before attempting filename completion (only on OS'es where this is found under /proc//cwd, like linux and SunOS). Now honours 'set horizontal-scroll-mode off' in .inputrc. Slave pty is never closed in parent (even after slave's death), preventing long timeouts in OSes with streams-based pty IO. Lots of small fixes to adapt to gcc 3.x's more finicky behaviour (e.g. avoiding multi-line strings, not automaticaly including system includes in Makefile.in). configure rewrites manpage to reflect rlwraps capabilities on each platform. history searching with CTRL-R (backwards-search-history) now works, !-completion is cleaned up (could even segfault in previous versions). SIGSEGV is now caught in order to reset terminal. 0.12 When slave pty's ECHO flag is unset (e.g. when entering a password) rlwrap now displays asterisks for every input character like this: Password: ****** Better handling of very long prompts, or very narrow terminal windows. If the prompt is wider than the current terminal, rlwrap assumes that it has wrapped around and uses the last (wrapped) line of it as the new prompt. Slave pty is opened (and remains open) in parent, allowing slave side to be monitored instead of master. testclient (a perl script) has been added, uncovering quite a few embarassing bugs. system-wide completion word lists (in $sysconfdir/rlwrap) can be used. 0.11 If the tcgetattr() call to determine the pty's echo mode fails at startup, rlwrap now sleeps for 1 second before trying again once. (on FreeBSD, the first call will normally fail, but (most of the time) not the second, due to a race condition that ought to be fixed by some form of synchronisation between parent and child) --libdir and --includedir for configure now work, as well as LDFLAGS=xxxx ./configure. Filename completion now works again (when -c option is set). User input is now echoed correctly when the pty's echo mode cannot be determined (in that case a warning is printed at startup that passwords will be saved in the history file). 0.10: logging (-l option) implemented. history_filename and completion_filename now live on the heap instead of in fixed-length buffers. 0.07: Readline mode is entered (by registering callback) with the first keypress of a new line, not before. All command output before that will just be written to stdout unchanged (long lines could become garbled in 0.06 and earlier). Signal handling (esp. SIGTSTP and SIGWINCH) is much improved. The -a option forces rlwrap to use readline, which is useful if you want to rlwrap a command that already uses readline. is now bound to menu-complete by default: it will cylce through all possible completions, instead of listing them all, as in 0.06. 0.06: Transparent mode (immediately handing down keypresses) is now automatic whenever the pty has its ICANON flag unset. readline version 4.2 is now mandatory. Cleanup of code, eliminating many bugs, possibly introducing others (ugh!) Application name (used by readline) is now set to command name. Duplicate history entries are not remembered. Manpage updated: environment variable RLWRAP_HOME is now documented. 0.04: When started in transparent mode, now properly returns to it after each readline edit. Senses the pty's ECHO flag. When this is unset, rlwrap doesn't echo user input. Neither is it put in the history list (which would contain passwords etc. otherwise). 0.03: Now uses ._history and ._completions files (by default in $HOME). 0.02: A couple of #ifdefs for portability (tested on BSDI and Digital Unix). 0.01: Initial version. rlwrap-0.46.1/README.md000066400000000000000000000074211433170252700144000ustar00rootroot00000000000000rlwrap v 0.46 October 2022 ## WHAT IT IS: `rlwrap` is a 'readline wrapper', a small utility that uses the [GNU Readline](https://tiswww.case.edu/php/chet/readline/rltop.html) library to allow the editing of keyboard input for any command. I couldn't find anything like it when I needed it, so I wrote this one back in 1999. By now, there are (and, in hindsight, even then there were) a number of good readline wrappers around, like `rlfe`, distributed as part of the GNU readline library, and the amazing `socat`. You should consider using `rlwrap` especially when you need user-defined completion (by way of completion word lists) and persistent history, or if you want to program 'special effects' using the filter mechanism. As it is often used with older or even obsolete software, `rlwrap` strives to compile and run on a fairly wide range of not necessarily recent Unix-like systems (FreeBSD, OSX, HP-UX, AIX, Solaris, QNX, cygwin, linux and probably quite a few more) This would not have been without [Polarhome](http://polarhome.com)'s now retired 'dinosaur zoo' of ageing Unix systems ## HOW TO USE IT: If $ displays the infamous `^[[D` when you press a left arrow key, or if you just want decent input history and completion, try: $ rlwrap [-options] You should not notice any difference compared to directly calling ` `, except that you now can edit ``'s input and recall its entire input history using the arrow keys. Input history is remembered accross invocations, separately for different ``s. `CTRL-R` will search the input history, like in `bash`. With the `-r` and `-f` options you can specify the list of words which `rlwrap` will use as possible completions, taking them from a file (`-f` option) or from ``'s past in/output (-r option). `rlwrap` continually monitors ``'s terminal settings, so that it can do the right thing when it asks for single keypresses or for a password. Commands that already use Readline, or a similar library, will always ask for (and get) single keypresses, so that `rlwrap`ping them doesn't have any noticeable effect. To overcome this, one can use rlwrap with the `--always-readline` (`-a`) option; `rlwrap` will then use its own line editing and history. Unforunately, in that case, `rlwrap` cannot detect whether `` asks for a password. This can be remedied by giving the password prompt (excluding trailing space and possibly the first few letters) as an argument to the -a option. ## EXAMPLES: Run netcat with command-line editing: rlwrap nc localhost 80 Run lprolog and use its saved input history and lib.pl to build a completion word list: rlwrap -f lib.pl -f . lprolog Run smbclient (which already uses readline), add all input and output to the completion list, complete local filenames, avoid showing (and storing) passwords: rlwrap -cra -assword: smbclient '\\PEANUT\C' ## INSTALLATION: Usually just ./configure; make sudo make install See the INSTALL file for more information. ## FILTERS Filters are `perl` or `python` plugins that enable complete (albeit somewhat fragile) control over `rlwrap`'s input and output, echo, prompt, history and completion. They aren't used a lot, and remain therefore somewhat untested. `rlwrap -z listing` lists the installed filters, `rlwrap -z ` displays a short help text for `` ## AUTHORS The GNU Readline library (written by Brian Fox and Chet Ramey) does all the hard work behind the scenes, the pty-handling code (written by Geoff C. Wing) was taken practically unchanged from rxvt, and completion word lists are managed by Damian Ivereigh's libredblack library. The rest was written by Hans Lub (hanslub42@gmail.com). ## HOMEPAGE https://github.com/hanslub42/rlwrap rlwrap-0.46.1/completions/000077500000000000000000000000001433170252700154515ustar00rootroot00000000000000rlwrap-0.46.1/completions/coqtop000066400000000000000000000072241433170252700167060ustar00rootroot00000000000000Abort About Abstract AccessOpaque Add Admit Admitted After All Analysis Apply Arguments Asymmetric Atomic Auto AutoInline Automatic Axiom Axioms Behavior Bind Blacklist Boolean Bracketing Bullet Bytecode Canonical Case Cd Chapter Check Class Clear Clearing Close CoFixpoint CoInductive Coercion Coercions Comment Comments Compact Compat Compatibility Compute Congruence Conjecture Conjunction Conservative Constant Constructor Constructors Context Contexts Contextual Conversion Corollary Debug Decidable Declaration Declare Default Defensive Definition Delimit Dependency Dependent Depth Derive Discriminate Dump Eauto Elimination Equality Equations Eta Eval Evars Example Existential Existing Explicit Export Extern Extract Extraction Fact Fail Field File Firstorder Fixpoint Flag Focus For From Function Function_debug Function_raw_tcc Functional Generalizable Global Goal Hide Hint Hints Hypotheses Hypothesis Hyps Identity If Iff Immediate Implicit Implicits Import Include Induction Inductive Infix Info Injection Inline Inlined Insertion Inspect Instance Instances Introduction Intuition Inversion Inversion_clear KeepSingleton Kernel Keyed L2R Language Last Lemma Let Level Library Limit Load LoadPath Local Locate Loose Ltac Ltac2 ML Matching Maximal Minimality Minimization Mode Module Module! Modulo Morphism Names Negation Next NoInline Nonrecursive Notation Notations Obligation Obligations Off On Opaque Open Optimize Order Parameter Parameters Parametric Parsing Path Pattern Patterns Polymorphism Prenex Preterm Primitive Print Printing Program Projection Projections Proof Proofs Proposition Propositions Pwd Qed RAKAM Rec Record Records Recursive Refine Regular Relation Remark Remove Require Reserved Reset Resolution Resolve Reversible Rewrite Rewriting Ring SafeImplicits Save Scheme Schemes Scope Search SearchPattern SearchRewrite Section Selector Semi Set Setoid Sharing Short Show Shrink Silent SimplIsCbn Solutions Solve Standard Strategy Strict Strongly Structure Subst Suggest Synth Tactic Tags Term Test Theorem Timeout ToSet ToUnset Transparent Trivial Type TypeExpand Typeclass Typeclasses Types Under Undo Unfocus Unfocused Unfold Unfolding Unification Unique Universal Universe Universes Unset Unused Used Using Variable Variables Variant Verbose View Width Wildcard a abstract absurd admit after all apply args arith as assert assoc assumption at auto autorewrite bool_congr by case case_eq case_type cbn cbv change clear clearbody cofix coinduction compare compute congr congruence constructor contradict contradiction cut cutrewrite decide decide_left decide_right decompose dependent destr_eq destruct destruct_all destruct_with_eqn discrR discriminate do done double eapply eassumption easy eauto econstructor edestruct eexists eleft elim elimtype enough equality erewrite eright esplit etransitivity exact exfalso exists fail false_hyp field file first firstorder flags fold for forall fourier fun functional generalize gfail goal guess have hnf idtac if in induction info injection instantiate interactive into intro intros intros! intuition inversion inversion_clear lapply last lazy lazy_match! lazymatch left let lia linear load loss lra match match? measure move multi_match! multimatch names nat_congr nat_norm nia now now_show nra nsatz omega only parsing pattern pose progress prolog prop_congr psatz quote rapply record red refine reflexivity remember rename repeat replace revert rewrite rewrite_all right ring romega s scope set setoid setoid_replace setoid_rewrite simpl simple simplify_eq solve specialize split split_Rabs split_Rmult stepl stepr subst suff suffices sum symmetry tauto transitivity trivial try tryif unfold unlock until using vm_compute wf with without wlog zify rlwrap-0.46.1/completions/testclient000066400000000000000000000024571433170252700175620ustar00rootroot00000000000000lorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia nonnumquam eiusmodi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quia nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaccati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. dot.dot comma,comma colon:colon semicolon;semicolonrlwrap-0.46.1/configure.ac000066400000000000000000000472401433170252700154120ustar00rootroot00000000000000dnl configuration script for rlwrap dnl Process this file with autoconf to produce configure. dnl dnl Copyright (C) 2000-2017 Hans Lub hanslub42@gmail.com dnl dnl This file is part of rlwrap dnl dnl rlwrap is free software; you can redistribute it and/or modify it dnl under the terms of the GNU General Public License as published by dnl the Free Software Foundation; either version 2, or (at your dnl option) any later version. dnl dnl rlwrap is distributed in the hope that it will be useful, but dnl WITHOUT ANY WARRANTY; without even the implied warranty of dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU dnl General Public License for more details. dnl dnl You should have received a copy of the GNU General Public License dnl along with GUILE; see the file COPYING. If not, write to the dnl Free Software Foundation, Inc., 59 Temple Place - Suite 330, dnl Boston, MA 02111-1307, USA. AC_INIT(rlwrap,0.46.1) AC_PREREQ(2.69) AC_CONFIG_AUX_DIR(tools) AC_CANONICAL_HOST AC_CONFIG_SRCDIR(src/main.c) AM_INIT_AUTOMAKE([foreign]) AC_PROG_INSTALL AC_CONFIG_HEADERS([config.h]) AC_PROG_MAKE_SET AM_SANITY_CHECK # option parsing for optional features opt_debug=no opt_homegrown_redisplay=no opt_spy_on_readline=yes opt_multibyte_aware=yes opt_proc_mountpoint=/proc AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [enable debugging (default=NO)]), opt_debug=$enableval) AC_ARG_ENABLE(pedantic-ansi, AS_HELP_STRING([--enable-pedantic-ansi], [compile with -ansi and -pedantic (default=NO)]), opt_pedantic_ansi=$enableval) AC_ARG_ENABLE(homegrown-redisplay, AS_HELP_STRING([--enable-homegrown-redisplay], [try this only if rlwrap messes up the prompt (default=NO)]), opt_homegrown_redisplay=$enableval) AC_ARG_ENABLE(multibyte-aware, AS_HELP_STRING([--enable-multibyte-aware], [make rlwrap aware of multibyte characters (default=yes)]), opt_multibyte_aware=$enableval) AC_ARG_ENABLE(spy-on-readline, AS_HELP_STRING([--enable-spy-on-readline], [use private readline variable to keep screen tidy when resizing terminal (default=yes)]), opt_spy_on_readline=$enableval) AC_ARG_ENABLE(proc-mountpoint, AS_HELP_STRING([--enable-proc-mountpoint], [specify mountpoint for Linux-style procfs (used for determination of command's PWD) (default=/proc)]), # ' opt_proc_mountpoint=$enableval) if test x$opt_debug = xyes ; then AC_DEFINE(DEBUG,1 ,Define to 1 to get debugging info) test -z "$CFLAGS" || AC_MSG_WARN([Because CFLAGS is set, configure will not add -g and -Wall to them - do it yourself if necessary]) test -z "$CFLAGS" && AC_SUBST(CFLAGS,"-O0 -g -Wall") # compile for debugging, and give plenty of warnings fi if test x$opt_pedantic_ansi = xyes ; then AC_SUBST(CFLAGS,"$CFLAGS -ansi -Wimplicit-function-declaration -Wimplicit -pedantic -Wextra") fi # wait with defining SPY_ON_READLINE until we know we can use _rl_horizontal_scroll_mode (cf below) AC_USE_SYSTEM_EXTENSIONS test x$opt_homegrown_redisplay = xyes && AC_DEFINE(HOMEGROWN_REDISPLAY,1 ,[Define to 1 to use homegrown_redisplay()]) test x$opt_multibyte_aware = xyes && AC_DEFINE(MULTIBYTE_AWARE,1 ,Define to 1 to be aware of wide chars in prompts) AC_CONFIG_FILES([Makefile filters/Makefile doc/Makefile src/Makefile doc/rlwrap.man]) # Checks for programs. AC_PROG_CC AC_PROG_CPP AC_PATH_PROG(PERL,perl) AC_CHECK_PROG(STRIP,strip,strip,true) # Checks for header files. AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([errno.h fcntl.h libgen.h libutil.h stdlib.h string.h sched.h sys/file.h sys/ioctl.h sys/wait.h sys/resource.h stddef.h ]) AC_CHECK_HEADERS([termios.h unistd.h stdint.h time.h sys/time.h getopt.h regex.h curses.h stropts.h termcap.h util.h]) AC_CHECK_HEADERS([ term.h ncurses/term.h], , , [#ifdef HAVE_CURSES_H #include #endif]) AC_CHECK_DECLS(PROC_PIDVNODEPATHINFO, , , [#include ]) have_freebsd_libprocstat=yes AC_CHECK_DECLS([procstat_open_sysctl, procstat_getprocs, procstat_getfiles, STAILQ_FOREACH] ,[] ,[have_freebsd_libprocstat=no], [#include #include #include #include #include ]) if test x$have_freebsd_libprocstat = xyes ; then AC_DEFINE(HAVE_FREEBSD_LIBPROCSTAT,1 ,[Define to 1 to use FREEBSD libprocstat]) PROCSTATLIB='-lprocstat' fi # Found this in configure.ac for 'top': # The third argument to tputs is a putc-like function that takes an # argument. On most systems that argument is an int, but on some (you # know who you are, Solaris!) it is a char. Determine which: _savedwerror_flag=$ac_c_werror_flag ac_c_werror_flag=yes # make $CC fail after warning AC_MSG_CHECKING([argument type of tputs putc function]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( [ #ifdef HAVE_CURSES_H # include # ifdef HAVE_TERM_H # include # else # ifdef HAVE_NCURSES_TERM_H /* cygwin? AIX? */ # include # endif # endif #else # ifdef HAVE_TERMCAP_H # include # endif #endif int f(char i) { }], [tputs("a", 1, f);])], [ac_cv_type_tputs_putc="char"], [ac_cv_type_tputs_putc="int"]) AC_MSG_RESULT($ac_cv_type_tputs_putc) AC_DEFINE_UNQUOTED(TPUTS_PUTC_ARGTYPE, $ac_cv_type_tputs_putc, [Define as the type for the argument to the putc function of tputs ('int' or 'char')]) ac_c_werror_flag=$_savedwerror_flag # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST AC_TYPE_PID_T # Checks for library functions. AC_PROG_GCC_TRADITIONAL # Let groff -Tman know whether we have long options # Up to 0.45, rlwrap would test if getopt() understands option strings like "a:b::c" with double colons # I now tend to think that that only happens if (POSIX) getopt() is implemented using (GNU) getopt_long(), so this test # is actually superfluous. I leave GETOPT_GROKS_OPTIONAL_ARGS alone for the moment, in case some getopt_long() # implementation that doesnt grok double colons turns up: AC_CHECK_FUNC([getopt_long], [AC_SUBST(HAVE_LONG_OPTS,[yes]) AC_DEFINE(GETOPT_GROKS_OPTIONAL_ARGS, 1, Define if your getopt() correctly understands double colons in the option string) AC_SUBST(HAVE_OPTIONAL_ARGS,yes)], [AC_SUBST(HAVE_LONG_OPTS,[no])] AC_SUBST(HAVE_OPTIONAL_ARGS,no)) AC_CHECK_FUNCS(basename dirname flock getopt_long isastream mkstemps pselect putenv readlink sched_yield ) AC_CHECK_FUNCS(setenv setitimer setsid setrlimit sigaction snprintf strlcpy strlcat strnlen system) # Try to determine the kind of pty support on this system # This is not so easy and may guess wrong; if this happens: # - edit config.h by hand (and keep a copy of it somewhere as configure will overwrite it), and # - *please* notify the author! (hanslub42@gmail.com) # Most of the code below is taken from rxvt-2.7.10 (Copyright (c) 1999-2001 # Geoff Wing ) # In the newest FreeBSD, we need openpty, which exists in libutil # NB: The rxvt configure script warns against this. Why? AC_CHECK_LIB(util, openpty) AC_CHECK_FUNCS(openpty getpty grantpt unlockpt) # AC_CACHE_CHECK(for getpt, ptyttylib_cv_func_getpt, # [AC_TRY_LINK([#define _GNU_SOURCE # #ifdef HAVE_STDLIB_H # # include # #endif], # [(void)getpt();], # ptyttylib_cv_func_getpt=yes, ptyttylib_cv_func_getpt=no)]) AC_CACHE_CHECK(for getpt, ptyttylib_cv_func_getpt, [AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #define _GNU_SOURCE #ifdef HAVE_STDLIB_H # include #endif ]], [[(void)getpt();]], ptyttylib_cv_func_getpt=yes, ptyttylib_cv_func_getpt=no)])]) if test x$ptyttylib_cv_func_getpt = xyes; then AC_DEFINE(HAVE_GETPT, 1, Define if you have _GNU_SOURCE getpt() ) fi AC_CACHE_CHECK(for pty/tty type, ptyttylib_cv_ptys, [if test x$ac_cv_func_openpty = xyes; then ptyttylib_cv_ptys=OPENPTY AC_CHECK_HEADERS([pty.h]) else if test x$ac_cv_func__getpty = xyes; then ptyttylib_cv_ptys=SGI4 else if test -c /dev/ttyp20 -a ! -c /dev/ptmx; then ptyttylib_cv_ptys=SCO else if test -c /dev/ptym/clone; then ptyttylib_cv_ptys=HPUX else if test x$ptyttylib_cv_func_getpt = xyes; then ptyttylib_cv_ptys=GLIBC else if test -c /dev/ptc -a -c /dev/pts; then ptyttylib_cv_ptys=PTC else if test -c /dev/ptc -a -d /dev/pts; then ptyttylib_cv_ptys=PTC else if test -c /dev/ptmx -a -c /dev/pts/0; then ptyttylib_cv_ptys=STREAMS else case "$host_os" in *cygwin*) ptyttylib_cv_ptys=STREAMS ;; *) ptyttylib_cv_ptys=PREHISTORIC ;; esac fi fi fi fi fi fi fi fi ]) if test x$ptyttylib_cv_ptys = xOPENPTY; then AC_DEFINE(PTYS_ARE_OPENPTY, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xSCO; then AC_DEFINE(PTYS_ARE_NUMERIC, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xSTREAMS; then AC_DEFINE(PTYS_ARE_PTMX, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xPTC; then AC_DEFINE(PTYS_ARE_PTC, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xSGI4; then AC_DEFINE(PTYS_ARE__GETPTY, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xCONVEX; then AC_DEFINE(PTYS_ARE_GETPTY, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xGLIBC; then AC_DEFINE(PTYS_ARE_GETPT, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xHPUX; then AC_DEFINE(PTYS_ARE_CLONE, 1, Define for this pty type) fi if test x$ptyttylib_cv_ptys = xPREHISTORIC -o x$ptyttylib_cv_ptys = xHPUX -o x$ptyttylib_cv_ptys = xGLIBC; then AC_DEFINE(PTYS_ARE_SEARCHED, 1, Define for this pty type) fi AC_MSG_NOTICE(checking for pty ranges) ptys=`echo /dev/pty??` pch1=`echo $ptys | tr ' ' '\012' | sed -e 's/^.*\(.\).$/\1/g' | sort -u | tr -d '\012'` pch2=`echo $ptys | tr ' ' '\012' | sed -e 's/^.*\(.\)$/\1/g' | sort -u | tr -d '\012'` if test x$pch1 != x; then AC_DEFINE_UNQUOTED(PTYCHAR1, "$pch1", Define for first char in devptyXX) fi if test x$pch2 != x; then AC_DEFINE_UNQUOTED(PTYCHAR2, "$pch2", Define for second char in devptyXX) fi # Checks whether readline needs additional libs. AC_CHECK_FUNC(tgetent, [READLINE_SUPPLIB=], [AC_CHECK_LIB(tinfo, tgetent, [READLINE_SUPPLIB=-ltinfo], [AC_CHECK_LIB(curses, tgetent, [READLINE_SUPPLIB=-lcurses], [AC_CHECK_LIB(ncurses, tgetent, [READLINE_SUPPLIB=-lncurses], [AC_CHECK_LIB(termcap, tgetent, [READLINE_SUPPLIB=-ltermcap], [AC_MSG_WARN(No termcap nor curses library found)]) ]) ]) ]) ]) AC_CHECK_LIB(readline,readline,[READLINELIB=-lreadline], AC_MSG_ERROR([ You need the GNU readline library(ftp://ftp.gnu.org/gnu/readline/ ) to build this program! ]), [$READLINE_SUPPLIB]) LIBS="$LIBS $PROCSTATLIB $READLINELIB $READLINE_SUPPLIB" AC_CHECK_FUNCS(tigetnum) AC_CHECK_HEADER([readline/readline.h],[],AC_MSG_ERROR([readline.h not found - you may need to install a readline development package])) # AC_EGREP_RL_HEADER_AND_CHECK_FUNC(function_or_variable, [code using function_or_variable], HAVE_FUNCTION_OR_VARIABLE) # This macro looks for , both as a declaration in header.h, and as a symbol in $LIBS # ------------------------------------------------------------------------------------------ # @@ Haal hier TRY_LINK uit! # AC_DEFUN([AC_EGREP_RL_HEADER_AND_CHECK_FUNC], # [ AC_MSG_CHECKING([whether your readline headers and library know about $1]) # AC_EGREP_HEADER($1, [readline/readline.h], in_header=1, in_header=0) # AC_TRY_LINK([ #include # #include "readline/readline.h" ], # [$2], in_lib=1, in_lib=0) # if test x$in_lib = x1 -a x$in_header = x1 ; then # AC_DEFINE($3, 1 ,[Define to 1 if your readline lib has $1]) # last_check=yes # else # last_check=no # fi # remark= # if test x$in_lib = x0 -a x$in_header = x1 ; then # remark=" (only in headers, not in lib. Hmmmmm....)" # fi # AC_MSG_RESULT([$last_check$remark]) # ]) AC_DEFUN([AC_EGREP_RL_HEADER_AND_CHECK_FUNC], [ AC_MSG_CHECKING([whether your readline headers and library know about $1]) AC_EGREP_HEADER($1, [readline/readline.h], in_header=1, in_header=0) AC_LINK_IFELSE( [AC_LANG_PROGRAM([[ #include #include "readline/readline.h" ]], [[$2]])], in_lib=1, in_lib=0) if test x$in_lib = x1 -a x$in_header = x1 ; then AC_DEFINE($3, 1 ,[Define to 1 if your readline lib has $1]) last_check=yes else last_check=no fi remark= if test x$in_lib = x0 -a x$in_header = x1 ; then remark=" (only in headers, not in lib. Hmmmmm....)" fi AC_MSG_RESULT([$last_check$remark]) ]) AC_DEFUN([AC_MY_ERROR], [AC_MSG_ERROR([ ******************************************************************************** $1 ******************************************************************************** ])]) AC_EGREP_RL_HEADER_AND_CHECK_FUNC([rl_set_screen_size], [rl_set_screen_size(25,80)], [HAVE_RL_SET_SCREEN_SIZE]) test $last_check = no && AC_MY_ERROR([You need a more recent version (at least 4.2) of the readline library (ftp://ftp.gnu.org/gnu/readline/) to build this program!]) AC_EGREP_RL_HEADER_AND_CHECK_FUNC([rl_basic_quote_characters], [printf("%s", rl_basic_quote_characters)], [HAVE_RL_BASIC_QUOTE_CHARS]) test $last_check = no && AC_MY_ERROR([[The readline library that I found is incomplete, probably just a wrapper around libedit (like on Mac OS X) Please install GNU Readline and re-configure with the correct CFLAGS and LDFLAGS (cf. the instructions in INSTALL)]]) AC_EGREP_RL_HEADER_AND_CHECK_FUNC([rl_variable_value], [rl_variable_value("blah")], [HAVE_RL_VARIABLE_VALUE]) AC_EGREP_RL_HEADER_AND_CHECK_FUNC([rl_readline_version],[printf("%d",rl_readline_version)], [HAVE_RL_READLINE_VERSION]) AC_EGREP_RL_HEADER_AND_CHECK_FUNC([rl_executing_keyseq],[printf("%s", rl_executing_keyseq)], [HAVE_RL_EXECUTING_KEYSEQ]) # rlwrap tries to read a global (but private) readline variable _rl_horizontal_scroll_mode if the the option spy-on-realine is enabled # Depending on the linker (or linker options like gcc's -fvisibility=xxx) it may or may not be visible: AC_MSG_CHECKING([whether the private symbol _rl_horizontal_scroll_mode is visble in your readline libs]) # AC_TRY_LINK([#include # extern int _rl_horizontal_scroll_mode;], # [printf("%d", _rl_horizontal_scroll_mode)], # found_rl_horiz=yes, found_rl_horiz=no) AC_LINK_IFELSE( [AC_LANG_PROGRAM( [[ #include extern int _rl_horizontal_scroll_mode;]], [[printf("%d", _rl_horizontal_scroll_mode)]])], found_rl_horiz=yes, found_rl_horiz=no) AC_MSG_RESULT($found_rl_horiz) if test $opt_spy_on_readline = yes -a $found_rl_horiz = no ; then AC_MSG_WARN([I can't find _rl_horizontal_scroll_mode: disabling spy-on-readline now]) opt_spy_on_readline=no fi test x$opt_spy_on_readline = xyes && AC_DEFINE(SPY_ON_READLINE,1 ,Define to 1 to use private _rl_horizontal_scroll_mode) # check for existence of myself under $opt_proc_mountpoint/$$/cwd If the user specified --disable-proc-mountpoint we'll still do these tests # but they wil fail unless /no is a procfs mountpoint ;-) echo "Will rlwrap find command's working directory under ${opt_proc_mountpoint}//cwd? let's see..." if test x$cross_compiling = xyes; then AC_DEFINE(HAVE_PROC_PID_CWD, 0, [Define to 1 if //cwd is a link to working directory of process ]) AC_SUBST(HAVE_PROC_PID_CWD,[no]) echo AC_MSG_WARN([This test doesn't work when cross-compiling. Edit HAVE_PROC_PID_CWD in config.h by hand. Guessing: ]) else AC_CHECK_FILES($opt_proc_mountpoint/$$/cwd/configure.ac, [AC_DEFINE(HAVE_PROC_PID_CWD, 1 ,[Define to 1 if //cwd is a link to working directory of process ])] ,[]) fi #AC_MSG_RESULT([$HAVE_PROC_PID_CWD]) AC_DEFINE_UNQUOTED(PROC_MOUNTPOINT, "[$opt_proc_mountpoint]", [Define to location of Linux-style procfs mountpoint if provided and valid]) AC_MSG_CHECKING([whether we can find command line under //cmdline and mirror it by overwriting our own *argv (this may take a few secs)]) # We won't exactly do that, but instead write a test program that writes a random string to *argv (making sure it is longer than argv[0]) # and then verify that we can see the same string under //cmdline # If so, we enable the --mirror-args option random_nonsense1="fymaytskjqhjeeimtmjqvqbnrnfutgnkehkmqqealklsoswyepxugcckimqdwbwfqdhdsdxfxiagqbnm" random_nonsense2="iraliuuklajcorjfunavdsytdiydjaoaeauiapidtoxogfplrxfmfcmajikwenafwcownmoodxjljhsv" AC_DEFINE_UNQUOTED(RANDOM_NONSENSE2, "$random_nonsense2", nonsense arg to overwrite conftest argv with) if test $cross_compiling = "yes"; then AC_DEFINE(ENABLE_MIRROR_ARGS, 0, [Define if overwriting argv changes args visible in ps (1)]) AC_MSG_WARN([This test doesn't work when cross-compiling. Edit ENABLE_MIRROR_ARGS in config.h by hand. Guessing:]) result="no" else AC_LANG_CONFTEST( [AC_LANG_SOURCE([[#include #include int main(int argc, char **argv) { memcpy(*argv, "a\0" RANDOM_NONSENSE2, strlen(RANDOM_NONSENSE2) + 2); return(sleep(10)); } ]])]) result="no" $CC -o conftest conftest.c ./conftest $random_nonsense1 >/dev/null 2>&1 & CONFTEST_PID=$! for N in 1 2 3 ; do # retry a few times because of a possible race (on 1-processor systems) where ./conftest has been forked by the shell, ... # ... but not yet executed (which will make the following test fail) if grep $random_nonsense2 $opt_proc_mountpoint/$CONFTEST_PID/cmdline >/dev/null 2>&1; then AS_ECHO(["#define ENABLE_MIRROR_ARGS 1"]) >>confdefs.h result="yes" break fi sleep 1 # shorter sleep is not portable done kill $CONFTEST_PID fi AC_MSG_RESULT($result) if test x$cross_compiling = xyes; then cat < rlwrap.1 rlwrap-0.46.1/doc/rlwrap.man.in000066400000000000000000000641231433170252700163010ustar00rootroot00000000000000.TH rlwrap 1 "October 20, 2022" .SH NAME rlwrap \- readline wrapper .de OP .ie \\n(.$-1 .RI "[\ \fB\\$1\fP " "\\$2" "\ ]" .el .RB "[\ " "\\$1" "\ ]" .. .de OB .ie '@HAVE_LONG_OPTS@'yes' .B \\$1[\\$3], \\$2[=\\$3] .el .B \\$1 \\$3 .. .de OA .ie '@HAVE_OPTIONAL_ARGS@'yes' The argument is optional; if given, it has to \ directly follow a short option without an intervening space (\fB\\$1\\$3\fP) and a long option with an equals sign (\fB\\$2=\\$3\fP). .el On this system, optional arguments don't work, so, if necessary, use a (dummy) argument (\fB\\$1 \\$4\fP). .. .de OL .ie '@HAVE_LONG_OPTS@'yes' .B \\$1, \\$2 \\$3 .el .B \\$1 \\$3 .. .SH SYNOPSIS .B rlwrap .I [rlwrap\-options] .I command \.\.\. .SH DESCRIPTION \fBrlwrap\fP runs the specified \fIcommand\fP, intercepting user input in order to provide \fBreadline\fP's line editing, persistent history and completion. \fBrlwrap\fP tries (and \fIalmost\fP succeeds) to be completely transparent \- you (or your shell) shouldn't notice any difference between \fBcommand\fP and \fBrlwrap command\fP \- except the added readline functionality, of course. This should even hold true when you are re\-directing, piping and sending signals from and to \fIcommand\fP, and when \fIcommand\fP manipulates its terminal settings, working directory or (with the \fB-U\fP option) command line. There are many options to add (programmable) completion, handle multi\-line input, colour and re\-write prompts. If you don't need them (and you probably don't), you can skip the rest of this manpage, although some of those options could make your command line quite a bit more comfortable... .SH OPTIONS .TP .OB \-a \-\-always\-readline \fIpassword_prompt\fP Always remain in "readline mode" (see below), regardless of \fIcommand\fP's terminal settings. If \fBrlwrap\fP "does nothing" this is the option to use, as \fIcommand\fP is apparently already doing its own line editing. \fBNB\fP: With this option, rlwrap will echo (and save) passwords, unless you give \fIcommand\fP's password prompt as an argument. .OA \-a \-\-always\-readline Password: brumbleslurgh The \fB\-N\fP (\fB\-\-no\-children\fP) option can be used to avoid wrapping pagers and editors called from \fIcommand\fP; this should make them much more usable .TP .OB \-A \-\-ansi\-colour\-aware \fI!\fP Prompts that use colour, or use other CSI codes to e.g. set window titles or enable bracketed-paste will confuse rlwrap, especially at the end of long input lines. This option will usually (but by no means always) make rlwrap better behaved in such cases. Giving '!' as an argument will make \fBrlwrap\fP remove all color codes from the prompt. .OA \-A \-\-ansi\-colour\-aware ! '' .TP .OL \-b \-\-break\-chars \fIlist_of_characters\fP Consider the specified characters word\-breaking (whitespace is always word\-breaking). This determines what is considered a "word", both when completing and when building a completion word list from files specified by \fB\-f\fP options following (not preceding!) it. Default list (){}[],'+\-=&^%$#@";|\\ Unless \-c is specified, \" "quote to keep emacs happy / and \. (period) are included in the default list. .TP .OL \-c \-\-complete\-filenames Complete filenames (filename completion is always case\-sensitive, even with the \-i option). On Linux, OS X, FreeBSD and Solaris \fBrlwrap\fP will keep track of \fIcommand\fP's working directory, so that relative filenames will be completed as one would expect. .TP .OL \-C \-\-command\-name \fIcommand_name|N\fP Use \fIcommand_name\fP instead of \fIcommand\fP to determine the names of history and completion files, and to initialise readline (as specified in ~/.inputrc). A numeric argument N > 0 means: use the Nth argument counting backwards from the end of the argument list .TP .OL \-D \-\-history\-no\-dupes \fIn\fP How aggressively to weed out duplicate entries from the input history. If \fIn\fP = \fB0\fP, all inputs are kept in the history list, if \fIn\fP = \fB1\fP (this is the default) consecutive duplicates are dropped from the list, while \fIn\fP = \fB2\fP will make \fBrlwrap\fP drop all previous occurrences (in the current session) of the current input from the list. .TP .OL \-e \-\-extra-char-after-completion \fIchar\fP By default, rlwrap appends a space after any inserted completion text. Use this option to change this to '' (don't insert anything) or some other character. .TP .OL \-E \-\-always-echo By default, \fBrlwrap\fP avoids displaying passwords by echoing '***' whenever the client clears the ECHO flag on its input. This option will make \fBrlwrap\fP ignore this ECHO flag and always echo the user's input. (\fB-aPassword:\fP will still work as expected) .TP .OL \-f \-\-file \fIfile\fP Split \fIfile\fP into words (using the default word-breaking characters, or those specified by \-\-break\-chars), and add them to the completion word list. This option can be given more than once, and \fIadds\fP to the default completion list in $RLWRAP_HOME or @DATADIR@/rlwrap/completions. Specifying \fB\-f .\fP will make \fBrlwrap\fP use the current history file as a completion word list. .TP .OL \-g \-\-forget\-matching \fIregexp\fP Forget (i.e. never put into the history list) input lines that match the POSIX 1003.2 regular expression \fIregexp\fP. The match is always case\-insensitive. \fBperl\fP-style character classes like '\\d' are not recognised, use '[:digit:]'. For more about regular expressions, see \fBregex (7)\fP .TP .OL \-h \-\-help Print a short help message. .TP .OL \-H \-\-history\-filename \fIfile\fP Read command history from \fIfile\fP (and write it back there if \-\-histsize >= 0) .TP .OL \-i \-\-case\-insensitive Ignore case when completing (filename completion remains case\-sensitive). This option has to come before any \-f options. .TP .OL \-I \-\-pass\-sigint\-as\-sigterm Send a TERM signal to \fIcommand\fP when an INT is received (e.g. when you press CTRL\-C). .TP .OL \-l \-\-logfile \fIfile\fP When in readline mode, append \fIcommand\fP's output (including echo'ed user input) to \fIfile\fP (creating \fIfile\fP when it doesn't exist). .TP .OB \-m \-\-multi\-line \fInewline_substitute\fP Enable multi\-line input using a "newline substitute" character sequence (" \\ ", [space\-backslash\-space] by default). Newline substitutes are translated to newlines before sending the input to \fIcommand\fP. With this option, you can call an external editor $RLWRAP_EDITOR on the (expanded) current input with the \fIrlwrap_call_editor\fP key (CTRL\-^ by default) .OA \-m \-\-multi\-line ';;' "' \\\\ '" .TP .OL \-M \-\-multi\-line\-ext \fI.ext\fP Call multi-line-editor on temporary files with filename extension \fI.ext\fP (useful for e.g. automatic syntax colouring) .TP .OL \-n \-\-no\-warnings Don't print warnings. .TP .OL \-N \-\-no\-children When \fBrlwrap\fP is invoked with the \fB--always-readline\fP option, editors and pagers that are called by the client will be pretty unusable, as they will see your keypresses only if you press ENTER. \fBrlwrap -N\fP will avoid this problem by switching to direct mode if it thinks \fIcommand\fP is waiting for one of its children. .TP .OL \-o \-\-one\-shot Send an EOF to \fIcommand\fP after accepting the first line of input .TP .OL \-O \-\-only\-cook \fIregexp\fP Only ever "cook" prompts that match \fIregexp\fP, which can be preceded by '!', meaning that \fBall\fP matching candidate prompts will be cooked immediately ("confident mode"). .TP .OB \-p \-\-prompt\-colour \fIcolour\fP Use one of the colour names \fIblack, red, green, yellow, blue, cyan, purple (=magenta)\fP or \fIwhite\fP, or an ANSI\-conformant to colour any prompt displayed by \fIcommand\fP. An uppercase colour name (\fIYellow\fP or \fIYELLOW\fP ) gives a bold prompt. Prompts that already contain (colour) escape sequences or one of the readline "ignore markers" (ASCII 0x01 and 0x02) are not coloured. This option implies \-\-ansi\-colour\-aware. You can also use a \fIcolour spec\fP of the form ;[;], for example \-p'1;31' will give a bold red prompt on the current background (this is the default when no argument is given). Google for 'ANSI color' to learn more about colour codes. .OA \-p \-\-prompt\-colour 'Red' 'Red' .TP .OL \-P \-\-pre\-given \fItext\fP Start \fBrlwrap\fP with \fItext\fP in its edit buffer (this will automatically set the \-\-always\-readline option). .TP .OL \-q \-\-quote\-characters \fIlist_of_characters\fP Assume that the given characters act as quotes, e.g. when matching parentheses. Take care to escape the list properly for your shell (example: \-q "\\"'", which happens to be the default, or \-q "\\"" which will be better for lisp-like input) .TP .OL \-r \-\-remember Put all words seen on in\- and output on the completion list. .TP .OL \-R \-\-renice Make \fBrlwrap\fP nicer than \fIcommand\fP (cf \fBnice (1)\fP). This may prevent \fBrlwrap\fP from interrupting \fIcommand\fP to display a prompt when \fIcommand\fP is still "thinking" about what to output next. .TP .OL \-s \-\-histsize \fIN\fP Limit the history list to N entries, truncating the history file (default: 300). A negative size \-N (even \-0) means the same as N, but treats the history file as read\-only. .TP .OL \-S \-\-substitute\-prompt \fIprompt\fP Substitute the specified prompt for \fIcommand\fP's own prompt. Mainly useful when \fIcommand\fP doesn't have a prompt. .TP .OL \-t \-\-set\-term\-name \fIname\fP Set \fIcommand\fP's TERM to \fIname\fP. Programs that confuse \fBrlwrap\fP with fancy screen control codes can sometimes be tamed by specifying \fB\-t dumb\fP .TP .OL \-U \-\-mirror-arguments (linux only) Keep track of \fIcommand\fP's arguments as seen by the \fBps (1)\fP command, and mirror them in \fBrlwrap\fP's own arguments This can be useful for commands that overwrite command-line password arguments that would be exposed by \fBrlwrap\fP without this option. The mirroring takes place after the first user input, or every few milliseconds, if you use the \fB\-\-polling\fp option. .TP .OL \-v \-\-version Print rlwrap version. .TP .OL \-w \-\-wait\-before\-prompt \fItimeout\fP In order to determine if \fIcommand\fP's last output is a prompt, \fBrlwrap\fP waits \fItimeout\fP milliseconds after receiving it. Only when no more output has arrived, it is cooked (coloured, filtered and/or replaced by a substitute prompt) and displayed as a prompt. Before this the prompt is displayed "uncooked". Most users won't notice, but heavy cookers can prepend the timeout with a minus sign, making rlwrap hold back the prompt until it has been cooked ("patient mode"). This will prevent flashing of the prompt, but it will also interfere with long output lines and make switches from direct to readline mode less reliable. Default timeout: 40 ms .TP .OL \-W \-\-polling EXPERIMENTAL: Wake up every \fItimeout\fP millisecs, where \fItimeout\fP is the same as for the \-w (\-\-wait\-before\-prompt) option, 40 ms by default. This is used to sense the slave's interrupt character and ISIG flag and to adjust stdin's terminal settings accordingly, even before you press a key. Try this option e.g. when CTRL-C acts differently on \fIcommand\fP with, and without, rlwrap. .TP .OL \-z \-\-filter \fIsome_filter\fP Use \fIsome_filter\fP to change \fBrlwrap\fP's behaviour. Filters can be used to keep certain input out of the history, to change the prompt, to implement simple macros, programmable hotkeys for e.g. fuzzy history search, and programmable completion. \fBrlwrap\fP comes with a \fBperl\fP and a \fBpython\fP module to make filter writing easy. (cf. \fBRlwrapFilter(3pm)\fP for the perl module, the python one is very similar) A number of example filters are installed in the directory @DATADIR@/rlwrap/filters. rlwrap \-z listing lists all currently installed filters, while rlwrap \-z some_filter displays information about \fIsome_filter\fP If \fIsome_filter\fP needs arguments, you should quote the whole filter command line: .nf rlwrap \-z 'some_filter args' command ... rlwrap \-z 'pipeline filter1 ... : filter2 ... : ...' command ... .fi If this command line contains shell metacharacters, \fBrlwrap\fP passes it to the system shell for parsing. As filters have to follow a special protocol, shell commands like \fBsed\fP and \fBgrep\fP cannot be used as \fBrwlrap\fP filters. They can, however, be converted into filters by the \fBmakefilter\fP filter: rlwrap \-z 'makefilter egrep \-i \-\-color "error|$"' command will color all occurrences of "error" (or "Error") in \fIcommand\fP's output, while rlwrap \-z 'makefilter --message-type history sed -e s"/whisky/lemonade/"' command sanitises your drinking history. Both filters can be combined using the \fBpipeline\fP filter, of course. .SH EXAMPLES .TP 3 Run \fBnc\fP (netcat) with command\-line editing and history .B rlwrap\ nc .TP Wrap \fBsmbclient\fP (which uses readline itself), keep passwords out of the history and don't interfere with pagers (like \fBless\fP) called by smbclient. .B rlwrap\ \-aPassword:\ \-N\ smbclient //PEANUT/C .TP Wrap \fIsensitive_app\fP, hide password from \fBps\fP (if \fIsensitive_app\fP does so) and keep all input that starts with a space out of history: .B rlwrap -g '^ ' -U sensitive_app --password MySeCrEt .TP Wrap \fBgauche\fP (a Scheme interpreter) with a bold blue prompt, enable multi\-line editing (using .scm as filename extension) and don't consider single quotes as quotes (so that the parentheses in e.g. (print 'q) match) .B rlwrap\ \-pBlue \-m\ \-M\ .scm\ \-q'"' gosh " .TP Wrap \fBsqlite3\fP, use the \fBpipeto\fP filter to be able to pipe the output of SQL commands through \fBgrep\fP and/or \fBless\fP, complete (case\-insensitively) on the SQL keywords in 'sql_words' .B rlwrap\ \-a\ \-z\ pipeto\ \-i\ \-f\ sql_words\ sqlite3\ contacts.db .TP In a shell script, use \fBrlwrap\fP in 'one\-shot' mode as a replacement for \fBread\fP .B order=$(rlwrap\ -pYellow\ \-S\ 'Your pizza?\ '\ \-H past_orders\ \-P\ Margherita\ \-o\ cat) .SH DIRECT MODE AND READLINE MODE Most simple console commands put your terminal either in "cooked" or in "raw" mode. In cooked mode the terminal will wait until you press the ENTER key before handing the entire line to the program, in raw mode every key you press is handed down immediately. In cooked mode you generally can use the backspace key, but not the arrow keys, to edit your input. When you \fBrlwrap\fP \fIcommand\fP, \fBrlwrap\fP will run it a in a separate session, under its own (controlling) "pseudo\-terminal" (pty), and monitor this pty to see whether it is in raw, or in cooked mode. In the first case, \fBrlwrap\fP will copy all input and output directly between \fIcommand\fP and your terminal ("direct mode"). In the second case, \fBrlwrap\fP will use readline to edit your input ("readline mode"), and monitor \fBcommand\fP's output \- every last line that doesn't end with a newline is a potential prompt. How it handles such a candidate prompt depends on its being in "patient" or "impatient" mode, see below. Simple console commands use cooked mode whenever they want whole input lines, and raw mode when they want single keypresses. Those are the progams for which \fBrlwrap\fP is most useful. More sophisticated commands have their own line editor and hence use raw mode all the time. With those commands, \fBrlwrap\fP will appear to "do nothing". Therefore, if \fBrlwrap\fP is in direct mode when the user presses ENTER for the first time it will give a warning that it needs \fB\-\-always\-readline\fP to do anything at all (warnings can be suppressed with the \fB\-n\fP option) .SH PATIENT, IMPATIENT AND CONFIDENT MODE If \fIcommand\fP writes a lot of output, it tends to be written (and read) in "chunks". Not all chunks will end with a newline, and we need to distinguish their last lines ("candidate prompts") from real prompts, especially if we want to re\-write ("cook") prompts. \fBrlwrap\fP solves this (almost) by waiting a little, to see if there is more to come. "A little" is 40 msec by default, but this can be changed with the \fB\-w\fP option. Normally \fBrlwrap\fP writes the candidate prompt as soon as it is received, replacing it with a "cooked" version after the wait time. This is called "impatient" mode. If you don't like the flashing effect (which can become annoying when you "cook" the prompt heavily) you can put \fBrlwrap\fP in "patient mode" by specifying a negative value with \fB\-w\fP (e.g. \-w \-40). Rlwrap will then hold back the prompt and only print if after cooking. If prompts always match some regular expression you can specify "confident mode" with \fB--only-cook='!'\fP (note the exclamation mark). Then all candidate prompts that match (and only those) will be cooked immediately. They will, however, not be "uncooked" if more output arrives, which can happen if they weren't prompts after all. Confident mode doesn't work with a negative value for the \fB-w\fP option. .SH COOKING PROMPTS If and when \fBrlwrap\fP decides that it has a prompt, it will perform a number of actions on it, depending on the given options: filtering (\fB\-z\fP), substituting (\fB\-S\fP) and colouring (\fB\-p\fP), in this order. The resulting "cooked" prompt is then printed (after erasing the "raw" prompt, if necessary) .SH SPECIAL KEYS AND BINDABLE COMMANDS .TP .B Control + O Accept the current line, but don't put it in the history list. This action has a \fBreadline\fP command name \fIrlwrap\-accept\-line\-and\-forget\fP .TP .B Control + ^ Use an external editor (see RLWRAP_EDITOR below) to edit the current input (this will only work if the \-m option is set). This action has a \fBreadline\fP command name \fIrlwrap\-call\-editor\fP .TP .B (Not currently bound) Any key (or key sequence, see below) can be bound to the \fBreadline\fP command \fIrlwrap-direct-keypress\fP. This key (or keys) will then always be sent directly to \fIcommand\fP, even when \fBrlwrap\fP is not in direct mode. .TP .B (Not currently bound) Any key or key combination can be bound to the \fBreadline\fP command \fIrlwrap-direct-prefix\fP. This makes it possible to define multi-key direct keypresses by defining their first key(s) as a 'direct prefix' .TP .B (Not currently bound) Any key can be bound to the \fBreadline\fP command \fIrlwrap-hotkey\fP. This key will then cause the current input line and the current history to be filtered (cf. \fBRlwrapFilter(3pm)\fP) through the current filter (hence be a no-op when there is no filter), which then can re-write the input line, move the cursor and update the history. After that, the user can still edit the resulting input. .TP .B (Not currently bound) \fIrlwrap-hotkey-without-history\fP acts like \fIrlwrap-hotkey\fP, but the history (which can be quite large) is not passed to the filter. This is more efficient if the filter wouldn't do anything useful with the history anyway. .PP The special keys were chosen for no other reason than that they are not currently bound to any readline action. If you don't like them, (or your window manager swallows them) they (and the other 4 commands) can be re\-bound more sensibly by including lines like the following in your \fB~/.inputrc\fP: .PP .nf "\eM\-\eC\-m": rlwrap\-accept\-line\-and\-forget # ESC\-ENTER to accept but keep out of history "\eC\-x": rlwrap\-call\-editor # CTRL\-x e to edit (multi-line) input in editor of your choice $if erl # (only) for the Erlang shell: "\eC\-g": rlwrap\-direct\-keypress # pass CTRL\-g directly to enter 'user switch' command $endif "\eC\-t": rlwrap\-direct\-prefix # make it possible to define direct keypresses that start with CTRL\-t ... "\eC\-tx": rlwrap\-direct\-keypress # ... in that case: pass CTRL\-t + x directly. "\eC\-y": rlwrap\-hotkey\-without\-history # CTRL\-y to filter input line (and e.g. insert X selection) .fi .PP cf. the \fBreadline(3)\fP manpage. (NB: take care to not use keys that are already caught by your window manager, or by the terminal driver, like CTRL+S, as \fBrlwrap\fP will never see those) .SH ENVIRONMENT .TP \fBRLWRAP_HOME\fP: directory in which the history and completion files are kept. .TP \fBRLWRAP_EDITOR\fP (or else \fBEDITOR\fP, or else \fBVISUAL\fP): editor to use for multi\-line input (and rlwrap-edit-history). Example: .PP .nf export RLWRAP_EDITOR="vi\ +%L" export RLWRAP_EDITOR="vim\ '+call\ cursor(%L,%C)'" export RLWRAP_EDITOR="emacs +%L:%C %F" .fi .PP The first example above is the default; %L and %C are replaced by line and column numbers corresponding to the cursor position in \fBrlwrap\fP's edit buffer, %F is replaced by name of the (temporary) file. If %F is not used, this name is put after the (expanded) $RLWAP_EDITOR .TP \fBRLWRAP_FILTERDIR\fP: Any executable along your PATH can in theory be used as a filter, but because filters have to follow a rather outlandish protocol (cf. \fBRlwrapFilter (3)\fP) it is a good idea to keep them separate. This is why \fBrlwrap\fP adds a special filter directory in front of $PATH just before launching a filter. By default, this is @DATADIR@/rlwrap/filters, but $RLWRAP_FILTERDIR is used instead, if set. .SH SIGNALS .PP A number of signals are forwarded to \fIcommand\fP: HUP INT QUIT USR1 USR2 TERM and (by way of resizing \fIcommand\fP's terminal) WINCH. Some care is taken to handle TSTP (usually a result of a CTRL\-Z from the terminal) sensibly \- for example, after suspending \fBrlwrap\fP in the middle of a line edit, continuing (by typing 'fg') will land you at the exact spot where you suspended it. A filter can be used to modify/ignore signals, or send output "out of band" to the rlwrapped command. Filters (except those that filter signals) that take more than 1 second to respond can be interrupted by a CTRL\-C from the terminal (although \fBrlwrap\fP will not survive this) If \fIcommand\fP changes the keystrokes that send a particular signal from the keyboard (like emacs, which uses CTRL\-G instead of CTRL\-C) \fBrlwrap\fP will do the same (but only after the next keystroke - use the \fB\-\-polling\fP option to make rlwrap more transparent in this respect) When \fIcommand\fP is killed by a signal, \fBrlwrap\fP will clean up, reset its signal handlers an then commit suicide by sending the same signal to itself. This means that your shell sees the same exit status as it would have seen without \fBrlwrap\fP. .SH REDIRECTION When the standard input is not a terminal (or when run inside an emacs buffer), editing input doesn't make sense, so \fBrlwrap\fP will ignore all options and simply execute \fIcommand\fP in place of itself. When stdout (or stderr) is not a terminal, rlwrap will re\-open it to /dev/tty (the users terminal) after it has started \fIcommand\fP, so that \fIcommand\fP's output is redirected as expected, but keyboard input and \fBrlwrap\fP error messages are still visible. The upshot of this is that \fBrlwrap\fP \fIcommand\fP behaves more or less like \fIcommand\fP when redirecting. .SH EXIT STATUS non\-zero after a \fBrlwrap\fP error, or else \fIcommand\fP's exit status. \fBrlwrap\fP will always leave the terminal in a tidy state, even after a crash. .SH FILES \fBrlwrap\fP expects its history and completion files in $RLWRAP_HOME, but uses .dotfiles in the user's home directory if this variable is not set. This will quickly become messy if you use \fBrlwrap\fP for many different commands. .TP $RLWRAP_HOME/\fIcommand\fP_history, ~/.\fIcommand\fP_history History for \fIcommand\fP (remember that \fIcommand\fP may be overridden by the \fB\-\-command\-name\fP (or \fB\-C\fP) option) .TP $RLWRAP_HOME/\fIcommand\fP_completions, ~/.\fIcommand\fP_completions Per\-user completion word list for \fIcommand\fP. \fBrlwrap\fP never writes into this list, but one can use \fB\-l\fP \fIlogfile\fP and then \fB\-f\fP \fIlogfile\fP to simulate the effect of a \fB\-r\fP option that works across invocations. .TP @DATADIR@/rlwrap/completions/\fIcommand\fP System\-wide completion word list for \fIcommand\fP. This file is only consulted if the per\-user completion word list is not found. .TP $INPUTRC, ~/.inputrc Individual \fBreadline\fP initialisation file (See \fBreadline\fP (3) for its format). \fBrlwrap\fP sets its \fIapplication name\fP to \fIcommand\fP (this can be overridden by the \fB\-C\fP option), enabling different behaviours for different commands. One could e.g. put the following lines in \fB~/.inputrc\fP: .RS .nf .if t .ft CW $if coqtop set show\-all\-if\-ambiguous On $endif .if t .ft P .fi making \fBrlwrap\fP show all completions whenever it runs \fBcoqtop\fP .SH BUGS and LIMITATIONS Though it is flexible, delivers the goods (readline functionality), and adheres to the Unix "many small tools" paradigm, \fBrlwrap\fP is a kludge. It doesn't know anything about \fIcommand\fP's internal state, which makes context\-sensitive completion impossible. Using the GNU Readline library from within \fIcommand\fP is still by far the best option. Also, as "it takes two to tango" there is no way for \fBrlwrap\fP to synchronise its internal state with \fIcommand\fP, resulting in a number of subtle race conditions, where e.g. \fIcommand\fP may have changed the state of its terminal before \fBrlwrap\fP has read \fIcommand\fP output that was written before the state change. You will notice these races especially on a busy machine and with heavy "cooking" and filtering, when suddenly (and unpredictably) prompts or command output are garbled or incorrectly coloured. \fBrlwrap\fP can try, but often fails to, handle prompts that contain control characters (prompts, and the effect of \fB-A\fP and \fB\-t\fP, can be analysed by the filter \fBdissect_prompt\fP). If \fB\-A\fP (\fB--ansi-colour-aware\fP) doesn't help, a filter may be needed to clean up the prompt. Specifying \fB--set-term-name\fP with a simpler, of even dumb, terminal may also help. .SH VERSION This manpage documents rlwrap version @VERSION@ .SH AUTHORS The GNU Readline library (written by Brian Fox and Chet Ramey) does all the hard work behind the scenes, the pty\-handling code (written by Geoff C. Wing) was taken practically unchanged from \fBrxvt\fP, and completion word lists are managed by Damian Ivereigh's \fBlibredblack\fP library. The rest was written by Hans Lub (hanslub42@gmail.com). .SH SEE ALSO .TP .B readline(3), RlwrapFilter(3pm) \" Local variables: \" mode:nroff \" End: rlwrap-0.46.1/filters/000077500000000000000000000000001433170252700145655ustar00rootroot00000000000000rlwrap-0.46.1/filters/Makefile.am000077500000000000000000000002621433170252700166240ustar00rootroot00000000000000man_MANS = RlwrapFilter.3pm # EXTRA_DIST = $(man_MANS) # CLEANFILES = $(man_MANS) RlwrapFilter.3pm: RlwrapFilter.pm pod2man --section=3pm RlwrapFilter.pm > RlwrapFilter.3pm rlwrap-0.46.1/filters/README000066400000000000000000000001261433170252700154440ustar00rootroot00000000000000The filters in this directory have been written to test rlwrap, not to be practical. rlwrap-0.46.1/filters/RlwrapFilter.pm000077500000000000000000001074231433170252700175520ustar00rootroot00000000000000package RlwrapFilter; require 5.006; use strict; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $AUTOLOAD); sub when_defined($@); my $previous_tag = -1; my $echo_has_been_handled = 0; my $saved_output = ""; require Exporter; require AutoLoader; @ISA = qw(Exporter AutoLoader); @EXPORT = qw(TAG_INPUT TAG_OUTPUT TAG_HISTORY TAG_COMPLETION TAG_PROMPT TAG_HOTKEY TAG_SIGNAL DEBUG_FILTERING DEBUG_RANDOM_DELAY); $VERSION = '0.01'; use Carp; # constants for every tag we know about use constant MAX_TAG => 255; use constant TAG_INPUT => 0; use constant TAG_OUTPUT => 1; use constant TAG_HISTORY => 2; use constant TAG_COMPLETION => 3; use constant TAG_PROMPT => 4; use constant TAG_HOTKEY => 5; use constant TAG_SIGNAL => 6; use constant TAG_WHAT_ARE_YOUR_INTERESTS => 127; use constant TAG_IGNORE => 251; use constant TAG_ADD_TO_COMPLETION_LIST => 252; use constant TAG_REMOVE_FROM_COMPLETION_LIST => 253; use constant TAG_OUTPUT_OUT_OF_BAND => 254; use constant TAG_ERROR => 255; # to bitwise AND with $ENV{RLWRAP_DEBUG}, the value of the rlwrap --debug option: use constant DEBUG_FILTERING => 16; use constant DEBUG_RANDOM_DELAY => 1024; use constant REJECT_PROMPT => "_THIS_CANNOT_BE_A_PROMPT_"; # we want to behave differently when running outside rlwrap my $we_are_running_under_rlwrap = defined $ENV{RLWRAP_COMMAND_PID}; # die() and warn() must communicate via rlwrap, not via STDERR (unless we're running under perl -c) unless ($^C){ $SIG{__DIE__} = \&die_with_error_message; $SIG{__WARN__} = \&warn_with_info_message; } # automagically have a setter/getter for every key of %$self sub AUTOLOAD { my $self = shift; my $type = ref($self) or croak "$self is not an object"; my $name = $AUTOLOAD; $name =~ s/.*://; # strip fully-qualified portion unless (exists $self->{$name} ) { croak "There is no `$name' setter/getter in class $type"; } if (@_) { return $self->{$name} = shift; } else { return $self->{$name}; } } # open communication lines with rlwrap (or with the terminal when not running under rlwrap) if ($we_are_running_under_rlwrap) { open CMD_IN, ">&" . $ENV{RLWRAP_MASTER_PTY_FD}; open CMD_OUT, "<&" . $ENV{RLWRAP_MASTER_PTY_FD}; open FILTER_IN, "<&" . $ENV{RLWRAP_INPUT_PIPE_FD}; open FILTER_OUT, ">&" . $ENV{RLWRAP_OUTPUT_PIPE_FD}; } else { open CMD_IN, ">&STDOUT"; open CMD_OUT, "<&STDIN"; open FILTER_IN, "<&STDIN"; open FILTER_OUT, ">&STDOUT"; } # create filter object sub new { my ($this, %init) = @_; my $class = ref($this) || $this; my $self = {}; my @accessors = qw(initialiser help_text input_handler output_handler prompt_handler echo_handler message_handler history_handler hotkey_handler completion_handler signal_handler echo_handler message_handler cloak_and_dagger_verbose cumulative_output prompts_are_never_empty minimal_rlwrap_version); foreach my $acc (@accessors) { $self->{$acc} = ""; } bless $self, $class; foreach my $key (keys %init) { croak "There is no `$key' attribute in class $class" unless defined $self->{$key}; $self -> {$key} = $init{$key}; $self -> minimal_rlwrap_version($self->{$key}) if $key eq "minimal_rlwrap_version"; } return $self; } # event loop sub run { my ($self) = @_; if($ENV{RLWRAP_COMMAND_PID} == 0) { # when called as rlwrap -z (with no command) .. write_message(TAG_OUTPUT_OUT_OF_BAND, $self -> help_text . "\n"); # ... send help text } while(1) { my ($tag, $message) = read_message(); $message = when_defined $self -> message_handler, "$message", $tag; # ignore return value my $response = $message; # a response that is identical to the message signals: don't do anything if ($tag == TAG_INPUT) { $response = when_defined $self -> input_handler, "$message"; } elsif ($tag == TAG_OUTPUT) { $response = $self -> handle_output($message); } elsif ($tag == TAG_HISTORY) { $response = when_defined $self -> history_handler, "$message"; } elsif ($tag == TAG_HOTKEY and $self -> hotkey_handler) { my @params = split_rlwrap_message($message); my @result = &{$self -> hotkey_handler}(@params); $response = merge_fields(@result); } elsif ($tag == TAG_COMPLETION and $self -> completion_handler) { my ($line, $prefix, @completions) = split_rlwrap_message($message); @completions = &{$self -> completion_handler}($line, $prefix, @completions); $response = merge_fields($line, $prefix, @completions); # The handler only returns a (revised) list of completions. We add the (original) $line and $prefix } elsif ($tag == TAG_PROMPT) { if ($message eq REJECT_PROMPT or ($self -> {prompts_are_never_empty} and $message eq "")) { write_message($tag, REJECT_PROMPT); # don't update and don't reset next; } if ($ENV{RLWRAP_IMPATIENT} and $self->{cumulative_output} !~ /\n$/) { # cumulative output contains prompt: chop it off! $self->{cumulative_output} =~ s/(? prompt_handler, "$message"; croak "prompts may not contain newlines!" if $response =~ /\n/; } elsif ($tag == TAG_SIGNAL) { $response = when_defined($self -> signal_handler, $message); } elsif ($tag == TAG_WHAT_ARE_YOUR_INTERESTS) { $response = $self -> add_interests($message); } # shouldn't the next "and" be an "or"? @@@ unless (out_of_band($tag) and ($tag == TAG_PROMPT and $response eq REJECT_PROMPT)) { $self -> {previous_tag} = $tag; $self -> {previous_message} = $message; } write_message($tag, $response); } } # when_defined \&f, x, y, ... returns f(x, y, ...) if f is defined, x otherwise sub when_defined($@) { my $maybe_ref_to_sub = shift; local $_ = $_[0] ; # convenient when using anonymous subs as handlers: $filter -> blah_handler(sub{$_ if /blah/}); if ($maybe_ref_to_sub) { if ((my $type = ref($maybe_ref_to_sub)) ne 'CODE') { croak "improper handler <$maybe_ref_to_sub> of type $type (expected a ref to a sub)"; } return &{$maybe_ref_to_sub}(@_); } else { return $_; } } # When the filter starts, it tells rlwrap its interests as a string 'yyny..' (1 + TAG_MAX chars, 1 for each tag) # when receiving a message 'nnynn...' the follwoing function changes 'n' to 'y' for those message types that the # filter handles,so that at the end of the pipeline the message reflects the interests of all filters in the # pipeline sub add_interests { my ($self, $message) = @_; my @interested = split //, $message; for (my $tag = 0; $tag < @interested; $tag++) { next if $interested[$tag] eq 'y'; # a preceding filter in the pipeline has already shown interest $interested[$tag] = 'y' if ($tag == TAG_INPUT and $self -> input_handler) or ($tag == TAG_OUTPUT and ($self -> output_handler or $self -> echo_handler)) # echo is the first OUTPUT after INPUT or ($tag == TAG_HISTORY and ($self -> history_handler or $self -> echo_handler)) # to determine which OUTPUT is echo, we need to see INPUT or ($tag == TAG_COMPLETION and $self -> completion_handler) or ($tag == TAG_PROMPT and $self -> prompt_handler) or ($tag == TAG_HOTKEY and $self -> hotkey_handler) or ($tag == TAG_SIGNAL and $self -> signal_handler); } return join '', @interested; } sub out_of_band { my($tag) = @_; return $tag > 128; } # split output in echo and the rest and call the appropriate handlers on them sub handle_output { my ($self, $message) = @_; my ($echo, $handled_echo, $nl); if (defined $self -> {previous_tag} and $self -> {previous_tag} == TAG_INPUT) { $self->{cumulative_output} = ""; $echo_has_been_handled = 0; } if (not $echo_has_been_handled) { if ($message !~ /\n/) { $saved_output .= $message; # save all output until we have one *whole* echo line return ""; } else { # ... then process it $message = $saved_output . $message; $echo_has_been_handled = 1; $saved_output = ""; ($echo, $nl, $message) = ($message =~ /^([^\n\r]*)(\r?\n)?(.*)?/s); #@@@ This doesn't work for multi-line input! $handled_echo = when_defined $self -> echo_handler, "$echo"; } } $self->{cumulative_output} .= $message; return $handled_echo . $nl . (when_defined $self -> output_handler, "$message"); } sub read_until { # read chunks from pty pointed to by $fh until either inactive for $timeout or # $stoptext is seen at end-of-chunk my ($fh, $stoptext, $timeout) = @_; my ($res); while (1){ my $chunk = read_chunk($fh, $timeout); return $res unless $chunk; # got "" back: timeout $res .= $chunk; return $res if $res =~ /$stoptext$/; } } # read chunk from pty pointed to by $fh with timeout $timeout sub read_chunk { my ($fh, $timeout) = @_; my ($rin, $rout, $chunk); vec($rin, fileno($fh), 1) = 1; my ($nfound, undef) = select($rout=$rin, undef, undef, $timeout); if ($nfound > 0) { my $nread = sysread($fh, $chunk, 256); if ($nread > 0) { return $chunk; } } return ""; } # keep reading until $count total bytes were read from filehandle $fh sub read_patiently { my($fh, $count) = @_; my $already_read = 0; my $result; while($already_read < $count) { my $nread = sysread($fh, $result, $count-$already_read, $already_read); if ($nread == 0) { # rlwrap (or the rlwrapped command) has put down the # telephone - we're going to die anyway. Don't complain. exit(0); } elsif ($nread < 0) { die_with_errormessage("error reading: $!"); } $already_read += $nread; } return $result; } # keep writing until all bytes from $buffer were written to $fh sub write_patiently { my($fh, $buffer) = @_; my $already_written = 0; my $count = length($buffer); while($already_written < $count) { my $nwritten = syswrite($fh, $buffer, $count-$already_written, $already_written); if ($nwritten <= 0) { die_with_errormessage("error writing: $!\n"); } $already_written += $nwritten; } } # read message (tag, length word and contents) from FILTER_IN sub read_message { return read_from_stdin() unless $we_are_running_under_rlwrap; my $tag = unpack("C", read_patiently(*FILTER_IN,1)); my $length = unpack("L",read_patiently(*FILTER_IN,4)); my $message = read_patiently(*FILTER_IN, $length); $message =~ s/\n$//; return ($tag, $message); } sub write_message { my($tag, $message) = @_; return write_to_stdout($tag, $message) unless $we_are_running_under_rlwrap; $message ||= ""; # allow undefined messages write_patiently(*FILTER_OUT, pack("C", $tag)); write_patiently(*FILTER_OUT, pack("L", (length $message) + 1)); write_patiently(*FILTER_OUT, "$message\n"); } sub read_from_stdin { my ($tag, $prompt, $tagname, $message); while (not defined $tag) { print $prompt; ($tagname, $message) = ( =~ /(\S+) (.*?)\r?\n/); exit unless $tagname; $message =~ s/\\t/\t/g; # allow TABs to be input as '\t' $message =~ s/\\n/\n/g; # the same for newlines $tag = name2tag(undef, $tagname); # call as function, not method $prompt = "again > "; } return ($tag, $message) } sub write_to_stdout { my($tag, $message) = @_; print tag2name(undef, $tag) . " $message\n"; } sub add_to_completion_list { my ($self, @words) = @_; write_message(TAG_ADD_TO_COMPLETION_LIST, join(' ', @words)); } sub remove_from_completion_list { my ($self, @words) = @_; write_message(TAG_REMOVE_FROM_COMPLETION_LIST, join(' ', @words)); } sub cwd { my ($self) = @_; my $command_pid = $ENV{RLWRAP_COMMAND_PID}; my $pwd = "/proc/$command_pid/cwd"; croak "cannot read commands working directory as $pwd doesn't exist" unless -e $pwd; return (-l $pwd ? readlink ($pwd) : $pwd); } # have a private chat with the rlwrapped command. This relies very much om the assumption that command stops # talking, and only listens, when it has displayed the $prompt sub cloak_and_dagger { my ($self, $question, $prompt, $timeout) = @_; $prompt ||= $self -> last('prompt'); write_patiently(*CMD_IN, "$question\n"); $self -> send_output_oob("cloak_and_dagger question: $question\n") if $self -> {cloak_and_dagger_verbose}; my $response = read_until(*CMD_OUT, $prompt, $timeout); $response =~ s/.*?\n//; # chop off echoed question; $response =~ s/$prompt$//; # chop off prompt; $self -> send_output_oob("cloak_and_dagger response: $response\n") if $self -> {cloak_and_dagger_verbose}; return $response; } # Commands return messages asynchronously and may time out # when invoked by multiple `cloak_and_dagger`. You may want to # drop their unused output at some later time: # rlwrap_filter.cloak_and_dagger($command1, $prompt, $timeout) # rlwrap_filter.cloak_and_dagger($command2, $prompt, $timeout) ... # sleep(1) # rlwrap_filter.vacuum_stale_message($prompt, $timeout) sub vacuum_stale_message { my ($self, $prompt, $timeout) = @_; return read_until(*CMD_OUT, $prompt, $timeout); } sub tag2name { my ($self, $tag) = @_; for my $name (qw(TAG_REMOVE_FROM_COMPLETION_LIST TAG_ADD_TO_COMPLETION_LIST TAG_INPUT TAG_PROMPT TAG_COMPLETION TAG_HOTKEY TAG_SIGNAL TAG_HISTORY TAG_WHAT_ARE_YOUR_INTERESTS TAG_OUTPUT_OUT_OF_BAND TAG_ERROR TAG_IGNORE TAG_OUTPUT)) { return $name if (eval "$tag == $name"); } croak "unknown tag $tag"; } sub name2tag { my ($self, $name ) = @_; my $tag = eval uc $name; #croak "unknown tagname $name " if $@; return $tag; } sub send_output_oob { my ($self, $text) = @_; write_message(TAG_OUTPUT_OUT_OF_BAND, $text); } sub send_ignore_oob { my ($self, $text) = @_; write_message(TAG_IGNORE, $text); } sub tweak_readline_oob { my ($self, $rl_tweak, @args) = @_; my %nargs = (rl_variable_bind => 2, rl_completer_word_break_characters => 1, rl_completer_quote_characters => 1, rl_filename_completion_desired => 1); # the list can be extended in future versions die "tweak_readline_oob() called with unknown readline function or variable '$rl_tweak'\n" unless $nargs{$rl_tweak}; die "tweak_readline_oob($rl_tweak,...) should be called with $nargs{$rl_tweak} more args\n" if @args != $nargs{$rl_tweak}; $self -> send_ignore_oob("@" . join("::", ($rl_tweak, @args, "\n"))); } sub die_with_error_message { my ($error_message) = @_; die $error_message if $^S; # make die() within eval do the right thing my $myself = $0; $myself =~ s#^.*/([^.]+)$#$1#; write_message(TAG_ERROR, "$myself: $error_message"); sleep 2; exit 1; } sub warn_with_info_message { my ($warning) = @_; my $myself = $0; $myself =~ s#^.*/([^.]+)$#$1#; write_message(TAG_OUTPUT_OUT_OF_BAND, "$myself: $warning"); } sub minimal_rlwrap_version { my ($self, $wanted) = @_; my $found = $ENV{RLWRAP_VERSION} || "0.34"; die "This filter requires rlwrap version $wanted or newer!\n" unless !$we_are_running_under_rlwrap or $wanted le $found; } sub command_line { my $commandline = $ENV{RLWRAP_COMMAND_LINE}; return (wantarray ? split /\s+/, $commandline : $commandline); } sub running_under_rlwrap { return $we_are_running_under_rlwrap; } sub prompt_rejected { my ($self) = @_; $self->minimal_rlwrap_version("0.35"); return REJECT_PROMPT; } sub name { my ($name) = ($0 =~ m#([^/]+)$#); $name ||= $0; return $name; } use constant DIGIT_NUMBER => 8; # This has to be the same number as DIGITS_NUMBER in src/string_utils.c:914 sub split_rlwrap_message { my ($message) = @_; my @fields = (); while(length($message) != 0){ my $lenstr = substr($message, 0, DIGIT_NUMBER, ""); my $len = hex($lenstr); my $field = substr($message, 0, $len, ""); push(@fields, $field); } return @fields; } sub merge_fields { my (@fields) = @_; my $message = ""; foreach my $field (@fields) { my $lenstr = sprintf("%0" . DIGIT_NUMBER . "x", length($field)); $message = $message . $lenstr . $field; } return $message; } 1 __END__ =head1 NAME RlwrapFilter - Perl class for B filters =head1 SYNOPSIS use lib $ENV{RLWRAP_FILTERDIR}; use RlwrapFilter; $filter = new RlwrapFilter; $filter -> output_handler(sub {s/apple/orange/; $_}); # re-write output $filter -> prompt_handler(\&pimp_the_prompt); # change prompt $filter -> history_handler(sub {s/with password \w+/with password ****/; $_}); # keep passwords out of history $filter -> run; =head1 DESCRIPTION B (1) (L) is a tiny utility that sits between the user and any console command, in order to bestow readline capabilities (line editing, history recall) to commands that don't have them. Since version 0.32, rlwrap can use filters to script almost every aspect of rlwrap's interaction with the user: changing the history, re-writing output and input, calling a pager or computing completion word lists from the current input. Filters can be combined in a pipeline using the special B filter. B makes it very simple to write rlwrap filters in perl. A filter only needs to instantiate a RlwrapFilter object, change a few of its default handlers and then call its 'run' method. There is also a Python 3 module B, distributed together with B, that provides more or less the same API as its B counterpart. =head1 PUBLIC METHODS =head2 CONSTRUCTOR =over 4 =item $f = new RlwrapFilter =item $f = RlwrapFilter -> new(prompt_handler => sub {"Hi! > "}, minimal_rlwrap_version => "0.35", ...) Return a new RlwrapFilter object. =back =head2 SETTING/GETTING HANDLERS Handlers are user-defined callbacks that specify one or more of an RlwrapFilter object's handler methods (handle_input, handle_prompt) They get called from the 'run' method in response to a message sent from B. Messages consist of a tag indicating which handler should be called (e.g. TAG_INPUT, TAG_HISTORY) and the message text. Usually, a filter overrides only one or at most two methods. =head2 CALLING CONVENTIONS In many cases (e.g. TAG_INPUT, TAG_OUTPUT, TAG_PROMPT) the message text is a simple string. Their handlers are called with the message text (i.e. the un-filtered input, output, prompt) as their only argument. For convenience, $_ is set to the same value. They should return the re-written message text. Some handlers (those for TAG_COMPLETION and TAG_HOTKEY) are a little more complex: their message text (accessible via $_) is a tab-separated list of fields; they get called with multiple arguments and are evaluated in list context. The message handlers are called in a fixed cyclic order: prompt, completion, history, input, echo, output, prompt, ... etc ad infinitum. Rlwrap may always skip a handler when in direct mode; on the other hand, completion and output handlers may get called more than once in succession. If a handler is left undefined, the result is as if the message text were returned unaltered (in fact, B knows when this is the case and won't even bother to send the message) It is important to note that the filter, and hence all its handlers, are bypassed when I is in direct mode, i.e. when it asks for single keystrokes (and also, for security reasons, when it doesn't echo, e.g. when asking for a password). If you don't want this to happen, use B to force B to remain in readline mode and to apply the filter to I of I's in- and output. This will make editors and pagers (which respond to single keystrokes) unusable, unless you use rlwrap's B<-N> option (linux only) The getters/setters for the respective handlers are listed below: =over 4 =item $handler = $f -> prompt_handler, $f -> prompt_handler(\&handler) The prompt handler re-writes prompts and gets called when rlwrap decides it is time to "cook" the prompt, by default some 40 ms after the last output has arrived. Of course, B cannot read the mind of I, so what looks like a prompt to B may actually be the beginning of an output line that took I a little longer to formulate. If this is a problem, specify a longer "cooking" time with rlwrap's B<-w> option, use the B method or "reject" the prompt (cf. the B method) =item $handler = $f -> completion_handler, $f -> completion_handler(\&handler) The completion handler gets called with three arguments: the entire input line, the prefix (partial word to complete), and rlwrap's own completion list. It should return a (possibly revised) list of completions. As an example, suppose the user has typed "She played for AETABE". The handler will be called like this: myhandler("She played for A", "A", "Arsenal", "Arendal", "Anderlecht") it could then return a list of stronger clubs: ("Ajax", "AZ67", "Arnhem") =item $handler = $f -> history_handler, $f -> history_handler(\&handler) Every input line is submitted to this handler, the return value is put in rlwrap's history. Returning an empty or undefined value will keep the input line out of the history. =item $handler = $f -> hotkey_handler, $f -> hotkey_handler(\&handler) If, while editing an input line, the user presses a key that is bound to "rlwrap_hotkey" in B<.inputrc>, the handler is called with five arguments: the hotkey, the prefix (i.e. the part of the current input line before the cursor), the remaining part of the input line (postfix), the history as one string ("line 1\nline 2\n...line N", and the history position. It has to return a similar list, except that the first element will be printed in the "echo area" if it is changed from its original value. B if the current input line is "pea soup" (with the cursor on the space), and the user presses CTRL+P, which happens to be bound to "rlwrap-hotkey" in B<.inputrc>, the handler is called like this: my_handler("\0x10", "pea", " soup", "tomato soup\nasparagus..", 12) # 16 = CTRL-P If you prefer peanut soup, the handler should return ("Mmmm!", "peanut", " soup", "asparagus..", 11) after which the input line will be "peanut soup" (with the cursor again on the space), the echo area will display "Mmmm!", and any reference to inferior soups will have been purged from the history. If the returned input line ends with a newline B will immediately accept the result. =item $handler = $f -> input_handler, $f -> input_handler(\&handler) Every input line (which may consist of multiple \n-separated lines, when using bracketed paste) is submitted to this handler, The handler's return value is written to I's pty (pseudo-terminal). =item $handler = $f -> echo_handler, $f -> echo_handler(\&handler) The first line of output that is read back from I's pty is the echo'ed input line. If your input handler alters the input line, it is the altered input that will be echo'ed back. If you don't want to confuse the user, use an echo handler that returns your original input. If you use rlwrap in --multi-line mode, additional echo lines will have to be handled by the output handler =item $handler = $f -> output_handler, $f -> output_handler(\&handler) All I output after the echo line is submitted to the output handler (including newlines). This handler may get called many times in succession, dependent on the size of I's write() calls, and the whims of your system's scheduler. Therefore your handler should be prepared to rewrite your output in "chunks", where you even don't have the guarantee that the chunks contain entire unbroken lines. If you want to handle I's entire output in one go, you can specify an output handler that returns an empty string, and then use $filter -> cumulative_output in your prompt handler to send the re-written output "out-of-band" just before the prompt: $filter -> output_handler(sub {""}); $filter -> prompt_handler( sub{ $filter -> send_output_oob(mysub($filter -> cumulative_output)); "Hi there > " }); Note that when rlwrap is run in --multi-line mode the echo handler will still only handle the first echo line. The remainder will generally be echoed back preceded by a continuation prompt; it is up to the output handler what to do with it. =item $handler = $f -> signal_handler, $f -> signal_handler(\&handler) As B is transparent to signals, signals get passed on to I. This handler gets called (as handler($signo)) for signals SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGCONT, SIGUSR1, SIGUSR2, and SIGWINCH, before the signal is delivered. It receives (and should return) $signo as a string. The returned signal is delivered to I; return "0" to ignore the signal altogether. Output can be written out-of-band (to B) or cloak_and_dagger (to I, see below) =item $handler = $f -> message_handler, $f -> message_handler(\&handler) This handler gets called (as handler($message, $tag)) for every incoming message, and every tag (including out-of-band tags), before all other handlers. Its return value is ignored, but it may be useful for logging and debugging purposes. The $tag is an integer that can be converted to a tag name by the 'tag2name' method =back =head2 OTHER METHODS =over 4 =item $f -> help_text("Usage...") Set the help text for this filter. It will be displayed by rlwrap -z . The second line of the help text is used by C; it should be a short description of what the filter does. =item $f -> minimal_rlwrap_version("x.yy") Die unless rlwrap is version x.yy or newer =item $dir = $f -> cwd return the name of I's current working directory. This uses the /proc filesystem, and may only work on newer linux systems (on older linux and on Solaris, it will return something like "/proc/12345/cwd", useful to find the contents of I's working directory, but not its name) =item $text = $f -> cumulative_output return the current cumulative output. All (untreated) output gets appended to the cumulative output after the output_handler has been called. The cumulative output starts with a fresh slate with every OUTPUT message that directly follows an INPUT message (ignoring out-of-band messages and rejected prompts) When necessary (i.e. when B is in "impatient mode") the prompt is removed from $filter->cumulative_output by the time the prompt handler is called. =item $tag = $f -> previous_tag The tag of the last preceding in-band message. A tag is an integer between 0 and 255, its name can be found with the following method: =item $name = $f -> tag2name($tag) Convert the tag (an integer) to its name (e.g. "TAG_PROMPT") =item $name = $f -> name2tag($tag) Convert a valid tag name like "TAG_PROMPT" to a tag (an integer) =item $f -> send_output_oob($text) Make rlwrap display C<$text>. C<$text> is sent "out-of-band" : B will not see it until just after it has sent the next message to the filter =item $f -> send_ignore_oob($text) Send an out-of-band TAG_IGNORE message to rlwrap. B will silently discard it, but it can be useful when debugging filters =item $f -> tweak_readline_oob($readline_function, @parameters) Send a specially formatted out-of-band message in order to tweak readline (i.e. to make B call a B function or set a B variable). See the GNU B documentation for details. At this moment, the following tweaks are recognised: $filter -> tweak_readline_oob("rl_variable_bind", $rl_variable_name, $value); # ... only for bindable readline variables like those in .inputrc $filter -> tweak_readline_oob("rl_completer_word_break_characters", $chars); $filter -> tweak_readline_oob("rl_completer_quote_characters", $chars); $filter -> tweak_readline_oob("rl_filename_completion_desired", "0" or "1"); The parameters should not contain "::" (two consecutive colons). This method can be called at any moment, even before $filter -> run =item $f -> add_to_completion_list(@words) =item $f -> remove_from_completion_list(@words) Permanently add or remove the words in C<@words> to/from rlwrap's completion list. =item $f -> cloak_and_dagger($question, $prompt, $timeout); Send C<$question> to I's input and read back everything that comes back until C<$prompt> is seen at "end-of-chunk", or no new chunks arrive for $timeout seconds, whichever comes first. Return the response (without the final C<$prompt>). B remains completely unaware of this conversation. =item $f -> cloak_and_dagger_verbose($verbosity) If $verbosity evaluates to a true value, make rlwrap print all questions sent to I by the C method, and I's responses. By default, $verbosity = 0; setting it to 1 will mess up the screen but greatly facilitate the (otherwise rather tricky) use of C =item $self -> prompt_rejected A special text ("_THIS_CANNOT_BE_A_PROMPT_") to be returned by a prompt handler to "reject" the prompt. This will make rlwrap skip cooking the prompt. $self->previous_tag and $self->cumulative_output will not be touched. =item $text = $f -> prompts_are_never_empty($val) If $val evaluates to a true value, automatically reject empty prompts. =item $f -> command_line In scalar context: the rlwrapped command and its arguments as a string ("command -v blah") in list context: the same as a list ("command", "-v", "blah") =item $f -> running_under_rlwrap Whether the filter is run by B, or directly from the command line =item $f -> run Start an event loop that reads rlwrap's messages from the input pipe, calls the appropriate handlers and writes the result to the output pipe. This method never returns. =back =head1 LOW LEVEL PROTOCOL B communicates with a filter through messages consisting of a tag byte (TAG_OUTPUT, TAG_PROMPT etc. - to inform the filter of what is being sent), an unsigned 32-bit integer containing the length of the message, the message text and an extra newline. For every message sent, rlwrap expects, and waits for an answer message with the same tag. Sending back a different (in-band) tag is an error and instantly kills rlwrap, though filters may precede their answer message with "out-of-band" messages to output text (TAG_OUTPUT_OUT_OF_BAND), report errors (TAG_ERROR), and to manipulate the completion word list (TAG_ADD_TO_COMPLETION_LIST and TAG_REMOVE_FROM_COMPLETION_LIST) Out-of-band messages are not serviced by B until right after it has sent the next in-band message - the communication with the filter is synchronous and driven by rlwrap. Messages are received and sent via two pipes. STDIN, STDOUT and STDERR are still connected to the user's terminal, and you can read and write them directly, though this may mess up the screen and confuse the user unless you are careful. A filter can even communicate with the rlwrapped command behind rlwrap's back (cf the cloak_and_dagger() method) The protocol uses the following tags (tags E 128 are out-of-band) TAG_INPUT 0 TAG_OUTPUT 1 TAG_HISTORY 2 TAG_COMPLETION 3 TAG_PROMPT 4 TAG_HOTKEY 5 TAG_SIGNAL 6 TAG_WHAT_ARE_YOUR_INTERESTS 127 TAG_IGNORE 251 TAG_ADD_TO_COMPLETION_LIST 252 TAG_REMOVE_FROM_COMPLETION_LIST 253 TAG_OUTPUT_OUT_OF_BAND 254 TAG_ERROR 255 To see how this works, you can eavesdrop on the protocol using the B filter. The constants TAG_INPUT, ... are exported by the RlwrapFilter.pm module. TAG_WHAT_ARE_YOUR_INTERESTS is only ever used internally, to prevent the exchange of messages that won't be handled by the filter anyway. It will be seen by the general message handler, and therefore show up (exactly once, at program start) in the output of e.g. the B filter. =head1 SIGNALS As STDIN is still connected to the users teminal, one might expect the filter to receive SIGINT, SIGTERM, SIGTSTP directly from the terminal driver if the user presses CTRL-C, CTRL-Z etc Normally, we don't want this - it would confuse rlwrap, and the user (who thinks she is talking straight to the rlwapped command) probably meant those signals to be sent to the command itself. For this reason the filter starts with all signals blocked. Filters that interact with the users terminal (e.g. to run a pager) should unblock signals like SIGTERM, SIGWINCH. =head1 FILTER LIFETIME The filter is started by B after I, and stays alive as long as B runs. Filter methods are immediately usable. When I exits, the filter stays around for a little longer in order to process I's last words. As calling the cwd and cloak_and_dagger methods at that time will make the filter die with an error, it may be advisable to wrap those calls in eval{} If a filter calls die() it will send an (out-of-band) TAG_ERROR message to rlwrap before exiting. rlwrap will then report the message and exit (just after its next in-band message - out-of-band messages are not always processed immediately) die() within an eval() sets $@ as usual. =head1 ENVIRONMENT Before calling a filter, B sets the following environment variables: RLWRAP_FILTERDIR directory where RlwrapFilter.pm and most filters live (set by rlwrap, can be overridden by the user before calling rlwrap) PATH rlwrap automatically adds $RLWRAP_FILTERDIR to the front of filter's PATH RLWRAP_VERSION rlwrap version (e.g. "0.35") RLWRAP_COMMAND_PID process ID of the rlwrapped command RLWRAP_COMMAND_LINE command line of the rlwrapped command RLWRAP_IMPATIENT whether rlwrap is in "impatient mode" (cf rlwrap (1)). In impatient mode, the candidate prompt is filtered through the output handler (and displayed before being overwritten by the cooked prompt). RLWRAP_INPUT_PIPE_FD File descriptor of input pipe. For internal use only RLWRAP_OUTPUT_PIPE_FD File descriptor of output pipe. For internal use only RLWRAP_MASTER_PTY_FD File descriptor of command's pty. RLWRAP_BREAK_CHARS The characters rlwrap considers word-breaking (cf. the --break-chars option in rlwrap (1)) RLWRAP_DEBUG The value of the --debug (-d) option given to rlwrap =head1 DEBUGGING FILTERS While RlwrapFilter.pm makes it easy to write simple filters, debugging them can be a problem. A couple of useful tricks: =head2 LOGGING When running a filter, the in- and outgoing messages can be logged by the B filter, using a pipeline: rlwrap -z 'pipeline logger incoming : my_filter : logger outgoing' command =head2 RUNNING WITHOUT B When called by rlwrap, filters get their input from $RLWRAP_INPUT_PIPE_FD and write their output to $RLWRAP_OUTPUT_PIPE_FD, and expect and write messages consisting of a tag byte, a 32-bit length and the message proper. This is not terribly useful when running a filter directly from the command line (outside rlwrap), even if we set the RLWRAP_*_FD ourselves. Therefore, when run directly from the command line, a filter expects input messages on its standard input of the form TAG_PROMPT myprompt > (i.a. a tag name, one space and a message followed by a newline. The message will not contain the final newline) and it will respond in the same way on its standard output. Of course, B can help with the tedious typing of tag names: rlwrap -f tagnames filter_to_be_debugged Because B cannot put TABs and newlines in input lines, filters will convert '\t' and '\n' into TAB and newline when run directly from the command line. =head1 SEE ALSO B (1), B (3) rlwrap-0.46.1/filters/censor_passwords000077500000000000000000000006441433170252700201150ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $filter = new RlwrapFilter; $filter -> help_text("rlwrap -H history -z censor_passwords sqlplus scott\@:1521/ORCL\n" . "This filter removes the password from SQL 'identified by' clauses\n"); $filter -> history_handler(sub { s/(identified\s+by\s+)(\S+)/$1xXxXxXxX/ig; $_}); $filter -> run; rlwrap-0.46.1/filters/censor_passwords.py000077500000000000000000000021271433170252700205420ustar00rootroot00000000000000#!/usr/bin/env python3 """ a demo for masking sensitive input in the history Usage: rlwrap -H history -f censor_passwords.py sqlplus scott@:1521/ORCL SQL > create user test identified by secret; """ import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter import re filter = rlwrapfilter.RlwrapFilter() filter.help_text = "rlwrap -H history -z censor_passwords.py sqlplus scott@:1521/ORCL\n"\ +"This filter removes the password from SQL 'identified by' clauses\n" filter.history_handler = lambda x: re.sub(r'(identified\s+by\s+)(\S+)', r'\1xXxXxXxX', x) # uncomment the following line for ldapmodify #filter.history_handler = lambda x: re.sub(r'(userpassword:\s+)(\S+)', r'\1xXxXxXxX', x, flags=re.IGNORECASE) # Sample usage: # rlwrap -H history -f censor_passwords.py ldapmodify -D -w # dn: uid=user1, ou=people, dc=redhat, dc=com # objectclass: inetOrgPerson # uid: user3 # cn: user3 # sn: user3 # userpassword: password filter.run() rlwrap-0.46.1/filters/count_in_prompt000077500000000000000000000012771433170252700177410ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $N = 0; my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text("Usage: rlwrap -z $name \n". "replace prompt by simple counter\n". "(demonstrates some simple prompt-munging techniques)"); $filter -> prompt_handler (\&munge_prompt); # This handler doesn't do anything, but it ensures that output is regeitered in $filter -> preious_tag: $filter -> output_handler (sub {$_}); $filter -> run; sub munge_prompt { my ($prompt) = @_; if ($filter -> previous_tag == TAG_OUTPUT) { $N++; return "prompt $N > "; } else { return $prompt; } } rlwrap-0.46.1/filters/count_in_prompt.py000077500000000000000000000016241433170252700203640ustar00rootroot00000000000000#!/usr/bin/env python3 """ a demo for simple prompt-munging Usage: rlwrap -z ./count_in_prompt.py telnet """ import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter N = 0 filter = rlwrapfilter.RlwrapFilter() def munge_prompt(prompt): global N if (prompt and filter.previous_tag == rlwrapfilter.TAG_OUTPUT): N = N + 1 return "prompt {0} > ".format(N) else: return prompt filter.help_text = '\n'.join( ["Usage: rlwrap -z {0} ".format(__file__), "replace prompt by simple counter", "(demonstrates some simple prompt-munging techniques)"] ) filter.prompt_handler = munge_prompt # This handler doesn't do anything, but it ensures that output is regeitered in $filter -> preious_tag: filter.output_handler = lambda output: output filter.run() rlwrap-0.46.1/filters/debug_null000077500000000000000000000030671433170252700166410ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $filter = new RlwrapFilter; $filter -> help_text("Usage: rlwrap [-options] -z null \n". "a filter that does nothing, but can test the effect of (slow) filters\n". "(only if debug flags 'filtering' and 'random delay' are set" ); $filter -> minimal_rlwrap_version("0.44"); my $process_all_messages = $ENV{RLWRAP_DEBUG} & DEBUG_FILTERING; my $delay_all_responses = $ENV{RLWRAP_DEBUG} & DEBUG_RANDOM_DELAY; sub wait_a_little_and_return { my($retval) = @_; if ($delay_all_responses) { select(undef, undef, undef, 0.1*rand()); # Emulate a filter that takes a lot of time to respond } return($retval); } if ($process_all_messages) { # make this filter "interested" in all message types (important for debugging) $filter -> send_output_oob("debugging filtering: all message types will be processed" . ($delay_all_responses ? " and delayed.\n" : ".\n")); #$filter -> output_handler(sub {wait_a_little_and_return($_)}); $filter -> echo_handler(sub {wait_a_little_and_return($_)}); $filter -> input_handler(sub {wait_a_little_and_return($_)}); $filter -> signal_handler(sub {wait_a_little_and_return($_)}); $filter -> prompt_handler(sub {wait_a_little_and_return($_)}); $filter -> history_handler(sub {wait_a_little_and_return($_)}); $filter -> completion_handler(sub {shift;shift; wait_a_little_and_return(), return @_}); } else { # just keep the unused handlers undefined } $filter -> run; rlwrap-0.46.1/filters/dissect_prompt000077500000000000000000000014731433170252700175570ustar00rootroot00000000000000#!/usr/bin/env python3 """a demo for doing nothing""" import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter filter = rlwrapfilter.RlwrapFilter() filter.help_text = "Usage: rlwrap [-options] -z dissect_prompt.py \n"\ + "show hidden control codes in a 's prompt" def dissect_prompt(prompt): filter.send_output_oob("Prompt as ASCII, including non-printable characters: " + ascii(prompt) + "\n") if '\x01' in list(prompt): filter.send_output_oob("N.B.: '\\x01' and '\\x02' are added by to rlwrap to inform readline that the enclosed codes don't take up space\n") return prompt filter.prompt_handler = dissect_prompt filter.run() rlwrap-0.46.1/filters/edit_history000077500000000000000000000041241433170252700172220ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use POSIX qw(:signal_h); use File::Slurp; use strict; # We want any editor to receive SIGWINCH. # SIGWINCH is not in POSIX, which means that POSIX.pm doesn't # know about it. We use 'kill -l' to find it. my $raw_input; my @signals = split /\s+/, `kill -l`; # yuck! for (my $signo = 1; $signals[$signo-1]; $signo++) { if ($signals[$signo-1] eq 'WINCH') { my $sigset_unblock = POSIX::SigSet->new($signo); unless (defined sigprocmask(SIG_UNBLOCK, $sigset_unblock)) { die "Could not unblock signals: $!\n"; } } } my $tmpdir = $ENV{TMP} || $ENV{TEMP} || "/tmp"; my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text(< Any hotkey bound to 'rlwrap-hotkey' will blah... DOC $filter -> hotkey_handler(\&hotkey); $filter -> run; sub hotkey { my ($key, $prefix, $postfix, $history, $histpos) = @_; my $editfile = "$tmpdir/history.$$.txt"; my $lineno = $histpos + 1; my $colno = length($prefix) + 1; $history ||= " "; #writefile crashes if called on an empty string.... write_file($editfile , $history); my $editor = $ENV{RLWRAP_EDITOR} || "vi +%L"; $editor =~ s/%L/$lineno/; $editor =~ s/%C/$colno/; system("$editor $editfile"); my @lines = read_file($editfile); my (@new_history, $counter, $empty_counter, $last_counter, $last_empty_counter); foreach my $line (@lines) { $line =~ s/\t//g; $line =~ s/^\s+//; $line =~ s/\s+$//; if ($line) { if ($empty_counter > 0) { # remember position of last line after an empty line, # and the number of empty lines: ($last_counter, $last_empty_counter) = ($counter, $empty_counter); } $empty_counter = 0; $counter++; # We count 0-based, so increment only now push @new_history, $line; } else { $empty_counter++; } } if ($last_empty_counter) { $histpos = $last_counter; $prefix = $new_history[$histpos]; $postfix = ""; } return ($key, $prefix, $postfix, (join "\n", @new_history), $histpos); } rlwrap-0.46.1/filters/ftp_filter000077500000000000000000000076771433170252700166720ustar00rootroot00000000000000#!/usr/bin/env perl # this filter demonstrates a (rather whacky and extreme) use of 'cloak_and_dagger' # cloak_and_dagger is used for # - finding the working directories (local and remote) # - finding the legal ftp commands # - completing (local or remote) filenames and directories use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $at_first_prompt = 1; my $ftp_prompt = "ftp> "; my @ftp_commands; my %completion_types = (cd => ['remote', 'directories'], lcd => ['local', 'directories'], get => ['remote','files','local','directories'], put => ['local', 'files','remote','directories']); my $filter = new RlwrapFilter; my ($local_dir_filename_column, $remote_dir_filename_column); $filter -> help_text("usage: rlwrap [-aword:] -z ftp_filter ftp\n" . "run plain Netkit ftp with completion for commands, local and remote files\n" . "(demo filter to show the use of the cloak_and_dagger method)"); $filter -> prompt_handler(\&prompt); $filter -> completion_handler(\&complete); $filter -> cloak_and_dagger_verbose(0); # set to 1 to spy on cloak_and_dagger dialogue die "This filter works only with plain vanilla ftp\n" unless !$ENV{RLWRAP_COMMAND_PID} or $ENV{RLWRAP_COMMAND_LINE} =~ /^ftp/; $filter -> run; ############################ subroutines #################################### sub listing { my ($dir, $where, $what) = @_; $dir ||= "."; my $command = ($where eq "local" ? "!ls -la $dir|cat" : "ls $dir"); my @lines = split /\r?\n/, $filter -> cloak_and_dagger($command, $ftp_prompt, 2); my $colnoref = ($where eq "local" ? \$local_dir_filename_column : \$remote_dir_filename_column); if (not $$colnoref) { # find out which column of listing has the filename my $dotdotline = (grep /\.\./, @lines)[0]; # .. should always be there #print STDERR $dotdotline; my @fields = split /\s+/, $dotdotline; for (my $i = 0; $i < @fields; $i++) { if ($fields[$i] eq "..") { $$colnoref = $i; last; } } die "couldn't determine filename column of $where listing\n" unless defined $$colnoref; } my $pattern = ($what eq "directories" ? "^d" : "^-"); @lines = grep /$pattern/, @lines if $$colnoref > 0; # makes only sense if there is a column with drwxr-xr-x my @results = map {(split /\s+/, $_)[$$colnoref] } @lines; return @results; } sub pwd { my($where) = @_; my $command = ($where eq "local" ? "!pwd" : "pwd"); my $result = $filter -> cloak_and_dagger($command, $ftp_prompt, 1); my $pattern = ($where eq "local" ? "(.*?)\r?\n" : '"(.*?)"'); my ($pwd) = ($result =~ /$pattern/); return $pwd ; } sub prompt { my ($prompt) = @_; if ($prompt eq $ftp_prompt) { commands() unless @ftp_commands; } else { return $prompt; } my ($local, $remote) = map {pwd($_)} qw(local remote); $local =~ s/^$ENV{HOME}/~/; my $rtext = ($remote ? "(remote: $remote)" : "(not connected)"); return "$local $rtext > "; } sub test { my $listing = join ', ', listing (".", "remote", "directories"); $filter -> send_output_oob("\n Hier: <$listing>\n"); } sub commands { my $help_text = $filter -> cloak_and_dagger("help", $ftp_prompt, 0.5); @ftp_commands = grep /^[a-z]\w/, (split /\s+/, $help_text); $at_first_prompt = 0; } sub complete { my($line, $prefix, @completions) =@_; my $nwords = scalar split /\s+/, $line; $nwords++ unless $prefix; # TAB at start of a new (empty) argument if ($nwords <= 1) { push @completions, grep /^$prefix/, @ftp_commands; return @completions; } my ($command) = ($line =~ /\s*(\S+)/); my (undef, $dir, $name_prefix) = ($prefix =~ m#((.*)/)?([^/]*)#); $dir ||= "."; my $narg = $nwords-2; if ($completion_types{$command}->[2*$narg]) { my @candidates = listing($dir, $completion_types{$command}->[2*$narg], $completion_types{$command}->[2*$narg+1]); push @completions, grep /^$name_prefix/, @candidates; return @completions; } return @completions; } rlwrap-0.46.1/filters/ftp_filter.py000077500000000000000000000130431433170252700173010ustar00rootroot00000000000000#!/usr/bin/env python3 # this filter demonstrates a (rather whacky and extreme) use of 'cloak_and_dagger' # cloak_and_dagger is used for # - finding the working directories (local and remote) # - finding the legal ftp commands # - completing (local or remote) filenames and directories """ A vanilla ftp is not bundled in modern Linux today. For example, ftp on Fedora 21 is readline-enabled. $ ldd /usr/bin/ftp linux-vdso.so.1 => (0x00007ffd015e7000) libreadline.so.6 => /lib64/libreadline.so.6 (0x00007fd213777000) libncurses.so.5 => /lib64/libncurses.so.5 (0x00007fd21354f000) libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007fd213324000) libc.so.6 => /lib64/libc.so.6 (0x00007fd212f67000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fd212d63000) /lib64/ld-linux-x86-64.so.2 (0x00007fd2139dd000) However, the feature can be disabled by '-e' option in Fedora case. This example was tested on the pair of ftp bundled in RHEL6/7, Fedora21 and vsftpd in RHEL6. Please see `man ftp` to find out how to disable it, if this filter does not work for you. """ import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter import re ############################ subroutines #################################### def listing (dir, where, what): dir = '.' if dir == None else dir command = '!ls -la {0}|cat'.format(dir) if where == 'local' else 'ls -la' lines = re.split(r'\r?\n', filter.cloak_and_dagger(command, ftp_prompt, 2)) if dir_filename_column[where] == None: # find out which column of listing has the filename dotdotline = [l for l in lines if re.search(r'(^|\s+)\.\.(\s|$)', l)][0]; # .. should always be there fields = re.split(r'\s+', dotdotline) try: dir_filename_column[where] = fields.index("..") except ValueError as e: rlwrapfilter.rlwrap_error( "couldn't determine filename column of $where listing", e) pattern = "^d" if what == "directories" else "^-" lines = [l for l in lines if re.search(pattern, l)] # makes only sense if there is a column with drwxr-xr-x results = [re.split('\s+', l)[dir_filename_column[where]] for l in lines] return results def pwd(where): command = "!pwd" if where == "local" else "pwd" result = filter.cloak_and_dagger(command, ftp_prompt, 1) pattern = "(.*?)\r?\n" if where == "local" else '"(.*?)"' m = re.search(pattern, result) pwd = m.group(1) return pwd def prompt_handler(prompt): if prompt == ftp_prompt: if len(ftp_commands) == 0: ftp_commands.extend(commands()) else: return prompt local = pwd('local') remote = pwd('remote') local = local.replace(os.environ['HOME'], '~') rtext = '(remote: ' + remote + ')' if remote else '(not connected)' return '{0} {1}> '.format(local,rtext) """ sub test { my $listing = join ', ', listing (".", "remote", "directories"); $filter -> send_output_oob("\n Hier: <$listing>\n"); } """ def commands(): help_text = filter.cloak_and_dagger("help", ftp_prompt, 0.5) lines = help_text.split('\n') del lines[0:2] # remove the first 2 lines commands = [command for command in re.split(r'\s+', ''.join(lines))] return commands def complete_handler(line, prefix, completions): nwords = len(re.split('\s+', line)) if prefix == None: nwords += 1 # TAB at start of a new (empty) argument if nwords <= 1: completions.extend([c for c in ftp_commands if re.search(r'^' + prefix, c)]) return completions command = re.search('\s*(\S+)', line).group(1) prefix_match = re.search('((.*)/)?([^/]*)', prefix) dir = prefix_match.group(2) name_prefix = prefix_match.group(3) #dir = '.' if dir == None else dir dir = '' if dir == None else dir narg = nwords-2 try: completion_types[command][narg*2] candidates = listing(dir, completion_types[command][2*narg], completion_types[command][2*narg+1]) completions.extend([os.path.join(dir, c) for c in candidates if re.search(r'^' + name_prefix, c)]) except ValueError as e: pass completions = list(set(completions)) return completions if __name__ == '__main__': if __file__ == "./doctest": import doctest doctest.testmod() sys.exit(0) ftp_prompt = "ftp> " ftp_commands = [] completion_types = { 'cd' : ['remote', 'directories'], 'lcd' : ['local', 'directories'], 'get' : ['remote','files','local','directories'], 'put' : ['local', 'files','remote','directories'] } filter = rlwrapfilter.RlwrapFilter() dir_filename_column = {'local':None, 'remote':None} #dir_filename_column['local'] = None #dir_filename_column['remote'] = None filter.help_text = '\n'.join([ "usage: rlwrap -c [-aword:] -z ftp_filter.py ftp (-e) [hostname]", "run plain Netkit ftp with completion for commands, local and remote files", "(demo filter to show the use of the cloak_and_dagger method)"]) filter.prompt_handler = prompt_handler filter.completion_handler = complete_handler filter.cloak_and_dagger_verbose = False # set to True to spy on cloak_and_dagger dialogue if (not 'RLWRAP_COMMAND_PID' in os.environ) or re.match(r'^ftp', os.environ['RLWRAP_COMMAND_LINE']): raise SystemExit("This filter works only with plain vanilla ftp\n") filter.run() rlwrap-0.46.1/filters/handle_hotkeys000077500000000000000000000211641433170252700175200ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; use POSIX qw(:signal_h); # change the table below if you like, but don't forget to bind the corresponding keys to 'rlwrap-hotkey' in your .inputrc my $keymap = { '\C-y' => \&yank_clipboard, '\C-n' => \&edit_history, '\C-p' => \&peco_history, '\C-f' => \&fzf_history, '\C-t' => \&date_in_echo_area, '\M-\C-m' => \&date_in_echo_area # test a multi-byte hotkey (ESC-ENTER) }; my @tempfiles; # list of files to be cleaned on exit; ############################################ The Filter ################################################# my $filter = new RlwrapFilter; # populate a hash %$handlers, with the actual hotkeys (not their readline key notation like '\M\C-m') as keys: my $handlers; foreach my $keyseq (keys %$keymap) { $handlers -> {translate_from_readline_keynotation($keyseq)} = $keymap -> {$keyseq}; } my $name = $filter -> name; $filter -> help_text("Usage: rlwrap -z $name \n". "handle hotkeys (but only if bound to 'rlwrap-hotkey' in your .inputrc):\n" . document_all_hotkeys()); $filter -> hotkey_handler(\&hotkey); $filter -> run; # A hotkey handler is called with five parameters: # 1: the key sequence that triggered rlwrap-handle-hotkey # 2: the prefix, i.e. the input line up to (but not including) the cursor # 3: the postfix: the rest of the input line (without a concluding newline, of course) # 4: the whole history (all lines, oldest first, interspersed with newlines: "line 1\nline2\n ...line N") # 5: the history position (as a line number) at the moment of the hotkey press (oldest line = 0) # # If the hotkey was bound to "rlwrap-hotkey-without-history" the last two parameters will be empty and can be ignored # The return value is a similar list (where all values may be changed: the input line could be re-written, the history # revised, etc. The first parameter makes no sense as a return value: if it is empty, or changed from its original # value, its contents will pe put in the "echo area". If the key sequence was bound to rlwrap-hotkey-without-history the # history is not passed to the handler, and the last two elements of the returned list are ignored. # # If the postfix is returned with a concluding newline, the resulting input line is accepted immediately, otherwise # it is put in readline's input buffer again, with the cursor at the beginning of the returned postfix # # Summary: ($echo, $prefix, $postfix, $history, $histpos) = handler($key, $prefix, $postfix, $history, $histpos) # generic hotkey handler, that dispatches on the value of $key (using the hash %$keymap defined at the top of this file sub hotkey { my ($keyseq, @other_params) = @_; # key = e.g. "" my $handler = $handlers -> {$keyseq}; return ($keyseq, @other_params) unless $handler; # a filter further downstream may want to handle this hotkey my @result = &$handler(0, @other_params); return @result; } ############################# A few handlers ############################################### # # After dispatch (on the value of $key) by the hotkey() function the value of $key is not relevant anymore. # its place is now taken by a parameter $doc : # # ($echo, $prefix, $postfix, $history, $histpos) = myfunc(0, $prefix, $postfix, $history, $histpos) # "docstring" = myfunc(1, @not_interesting) sub yank_clipboard { my ($doc, $prefix, $postfix, @boring) = @_; $doc and return "insert from clipboard"; my $selection = safe_backtick(qw(xsel -o)); return ("", $prefix . $selection, $postfix, @boring); } sub date_in_echo_area { my ($doc, @boring) = @_; $doc and return "show current time in echo area"; my $date = safe_backtick(qw(date +%H:%M)); return ("($date) ", @boring); } my $instance = 0; sub edit_history { my ($doc, $prefix, $postfix, $history, $histpos) = @_; my $editor = $ENV{RLWRAP_EDITOR} || "vi +%L"; $doc and return "edit current history with '$editor' - add empty line (lines) to select (accept) the following line"; $histpos =~ /\d/ or die "$histpos is not a number - did you bind this key to 'rlwrap-hotkey-without-history'?"; $instance++; my $editfile = ($ENV{TMP} || $ENV{TEMP} || "/tmp") . "/history.$$.$instance.txt"; push @tempfiles, $editfile; my $lineno = $histpos + 1; my $colno = length($prefix) + 1; $history ||= " "; # write_file() crashes if called on an empty string.... write_file($editfile , $history); $editor =~ s/%L/$lineno/; $editor =~ s/%C/$colno/; system("$editor $editfile"); my @lines = read_file($editfile); unlink $editfile; my (@new_history, $counter, $empty_counter, $last_counter, $last_empty_counter); foreach my $line (@lines) { $line =~ s/\t//g; $line =~ s/^\s+//; $line =~ s/\s+$//; if ($line) { if ($empty_counter > 0) { # remember position of last line after an empty line, # and the number of empty lines: ($last_counter, $last_empty_counter) = ($counter, $empty_counter); } $empty_counter = 0; $counter++; # We count 0-based, so increment only now push @new_history, $line; } else { $empty_counter++; } } if ($last_empty_counter) { $histpos = $last_counter; $prefix = $new_history[$histpos]; $prefix .= $last_empty_counter > 1 ? "\n" : ""; $postfix = ""; } return ("", $prefix, $postfix, (join "\n", @new_history), $histpos); } sub split_off_last_word { # split_off_last_word("In the gener") = ["In the ", "gener"] my ($string) = @_; my $break_chars = $ENV{RLWRAP_BREAK_CHARS}; $break_chars =~ s/([\[\]])/\\$1/g; # escape any [ and ] $break_chars ||= ' '; # prevent perl from choking on the regex /[]/ in the next line my @words = split /[$break_chars]/, $string; my $last_word = $words[-1]; return [substr($string, 0, length($string) - length($last_word)), $last_word]; } sub fuzzy_filter_history { my ($command, $doc, $prefix, $postfix, $history, $histpos) = @_; $doc and return "use $command to choose from history entries that match current input before cursor"; my $editfile = ($ENV{TMP} || $ENV{TEMP} || "/tmp") . "/history.$$.txt"; my $lineno = $histpos + 1; my $colno = length($prefix) + 1; $history ||= " "; # write_file crashes if called on an empty string.... write_file($editfile , $history); my ($first_chunk, $last_word) = @{split_off_last_word($prefix)}; my $select_1 = `cat $editfile | $command --select-1 --query "$last_word"`; chomp $select_1; return ("", $first_chunk . $select_1, $postfix, $history, $histpos); } sub peco_history { return fuzzy_filter_history("peco", @_); } sub fzf_history { return fuzzy_filter_history("fzf", @_); } ############################## helper functions ######################################################### sub document_all_hotkeys { my $doclist; foreach my $keyseq (sort keys %$keymap) { $doclist .= "$keyseq: " . &{$keymap -> {$keyseq}}(1) . "\n"; } my $inputrc = "$ENV{HOME}/.inputrc"; $doclist .= "Currently bound hotkeys in $inputrc:\n"; $doclist .= safe_backtick("grep", "rlwrap-hotkey", $inputrc); return $doclist; } sub safe_backtick { my @command_line = @_; my $command_line = join ' ', @command_line; open my $pipefh, '-|' or exec @command_line or die "$command_line failed: $!\n"; my $result; { local $/; # slurp all output in one go $result = <$pipefh>; close $pipefh; } chomp $result; # chop off last newline return $result } # Translate from Readline "\C-x" notation to corresponding key. E.g. translate_from_readline_keynotation("\C-m") = '\0x13' sub translate_from_readline_keynotation { my ($keyseq) = @_; $keyseq =~ s/\\C-(.)/translate_control($1)/ge; $keyseq =~ s/\\M-/\e/g; # @@@ this allows nonsense like "\C-\M-" return $keyseq; } # translate_control("m") == translate_control("M") == '\0x13' etc. sub translate_control { my ($ctrlkey) = @_; $ctrlkey = uc $ctrlkey; # Don't discriminate between \C-M and \C-m return pack("c", unpack("c", $ctrlkey) - 64); } # Use home-grown {read,write}_file rather than depending on File::Slurp sub read_file { my($file) = @_; open IN, "$file" or die "Cannot read $file: $!\n"; my @result; while() { push @result, $_; } close IN; return @result; } sub write_file { my ($file, $content) = @_; open OUT, ">$file" or die "Cannot write $file:$!\n"; print OUT $content; close OUT; } sub END { foreach my $f (@tempfiles) { -f $f and unlink $f; } } rlwrap-0.46.1/filters/handle_hotkeys.py000077500000000000000000000175651433170252700201610ustar00rootroot00000000000000#!/usr/bin/env python3 """handle hotkeys""" import subprocess import sys import os import tempfile import re if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter # A hotkey handler is called with five parameters: # 1: the key (e.g. 14 for CTRL+M) # 2: the prefix, i.e. the input line up to (but not including) the cursor # 3: the postfix: the rest of the input line (without a concluding newline, of course) # 4: the whole history (all lines, oldest first, interspersed with newlines: "line 1\nline2\n ...line N") # 5: the history position at the moment of the hotkey press (oldest line = 0) # # If the hotkey was bound to "rlwrap-hotkey-without-history" the last two parameters will be empty and can be ignored # The return value is a similar list (where all values may be changed: the input line could be re-written, # the history revised, etc. The first parameter makes no sense as a return value: if it is != $key # it will pe put in the "echo area". If the key was bound to rlwrap-hotkey-without-history the last two elements of the # returned list are ignored. # # If the postfix is returned with a concluding newline, the resulting input line is accepted immediately, otherwise # it is put in readlines input buffer again, with the cursor at the beginning of the returned postfix # # Summary: ($echo, $prefix, $postfix, $history, $histpos) = handler($key, $prefix, $postfix, $history, $histpos) # generic hotkey handler, that dispatches on the value of $key (using the hash %$keymap defined at the top of this file @rlwrapfilter.intercept_error def hotkey(key, *other_params): # key = e.g. "" ukey = uncontrol(key) if ukey not in keymap: return (key, *other_params) # a filter further downstream may want to handle this hotkey handler = keymap[ukey] result = handler(0, *other_params) return result ############################# A few handlers ############################################### # # After dispatch (on the value of $key) by the hotkey() function the value of $key is not relevant anymore. # its place is now taken by a parameter $doc : # # ($echo, $prefix, $postfix, $history, $histpos) = myfunc(0, $prefix, $postfix, $history, $histpos) # "docstring" = myfunc(1, @not_interesting) def yank_clipboard(doc, prefix, postfix, *boring): if doc: return "insert from clipboard" selection = safe_backtick(qw('xsel -o')) return ("", prefix + selection, postfix, *boring) def date_in_echo_area(doc, *boring): if doc: return "show current time in echo area" date = safe_backtick(qw('date +%H:%M')) return ("({0}) ".format(date), *boring) def edit_history(doc, prefix, postfix, history, histpos): if doc: return "edit current history" if not histpos.isdigit(): sys.exit("$histpos is not a number - did you bind this key to 'rlwrap-hotkey-without-history'?") editfile = tempfile.NamedTemporaryFile() editfilename = editfile.name lineno = int(histpos) + 1 colno = len(prefix) + 1 editfile.write(history.encode(sys.stdin.encoding)) editfile.flush() if 'RLWRAP_EDITOR' in os.environ: editor = os.environ['RLWRAP_EDITOR'] else: editor = "vi +%L" editor = editor.replace('%L', str(lineno)) editor = editor.replace('%C', str(colno)) os.system(editor + ' ' + editfilename) editfile.seek(0,0) lines = map(lambda l: l.decode(sys.stdin.encoding), editfile.readlines()) new_history = [] counter = 0 empty_counter = 0 last_counter = 0 last_empty_counter = 0 for line in lines: line = line.replace('\t', '') line = line.lstrip() line = line.rstrip() if not line == '': if (empty_counter > 0): # remember position of last line after an empty line, # and the number of empty lines: (last_counter, last_empty_counter) = (counter, empty_counter) empty_counter = 0; counter = counter + 1 # We count 0-based, so increment only now new_history.append(line) else: empty_counter = empty_counter + 1 if last_empty_counter > 0: histpos = str(last_counter) prefix = new_history[last_counter] postfix = "" return ("", prefix, postfix, '\n'.join(new_history), histpos) def split_off_last_word(string): '''split_off_last_word("In the gener") = ("In the ", "gener") ''' break_chars = os.environ['RLWRAP_BREAK_CHARS'] if 'RLWRAP_BREAK_CHARS' in os.environ else " \t\n" # old rlwrap with newer filter - use a sensible default break_chars = re.sub(r'([\[\]])', r'\\1', break_chars) break_chars = break_chars or ' ' # prevent python from choking on a bad regex '[]' in the next line words = re.split('[{}]'.format(break_chars), string) last_word = words[-1] return (string[0:len(string)-len(last_word)], last_word) def fuzzy_filter_history(doc, prefix, postfix, history, histpos, command): '''filter history through command (either 'peco' or the very similar 'fzf') ''' if doc: return "{} current history".format(command) first_chunk, last_word = split_off_last_word(prefix) command_line = [command, '--select-1', '--query', last_word ] select_1 = '' with subprocess.Popen(command_line ,stdin=subprocess.PIPE ,stdout=subprocess.PIPE ,universal_newlines=True) as p: (select_1, error) = p.communicate(input=history) select_1 = select_1.rstrip() return ("", first_chunk + select_1, postfix, history, histpos) def peco_history(doc, prefix, postfix, history, histpos): return fuzzy_filter_history(doc, prefix, postfix, history, histpos, 'peco') def fzf_history(doc, prefix, postfix, history, histpos): return fuzzy_filter_history(doc, prefix, postfix, history, histpos, 'fzf') # change the table below if you like, but don't forget to bind the corresponding keys to 'rlwrap-hotkey' in your .inputrc, or the hotkeys won't work! keymap = { "y" : yank_clipboard, "n" : edit_history, "p" : peco_history, "f" : fzf_history, "r" : peco_history, "t" : date_in_echo_area } ############################## helper functions ######################################################### def qw(s): # return tuple(s.split()) return s.split() def document_all_hotkeys(): doclist = '' dontcare = (None, None, None, None) # dummy arguments for getting the docstring for k in 'abcdefghijklmnopqrstuvwxyz': try: handler = keymap[k] if (handler): doclist = doclist + "CTRL+{0}: ".format(k) + handler(1, *dontcare) + "\n" except: pass inputrc = "{0}/.inputrc".format(os.environ['HOME']) doclist = doclist + "Currently bound hotkeys in .inputrc:\n" doclist = doclist + safe_backtick(["grep", "rlwrap-hotkey", inputrc]) return doclist def safe_backtick(command_args): with subprocess.Popen(command_args, stdout=subprocess.PIPE, universal_newlines=True) as p: #result = map(lambda b: b.decode("utf-8"), p.stdout.readlines()) (result, error) = p.communicate() return ''.join(result).rstrip() # give back corresponding CTRL-key. E.g: control("m") = "\0x13" def uncontrol(key): return chr(ord(key) + 64).lower() ############################################ The Filter ################################################# if __name__ == '__main__': rlwrap_filter = rlwrapfilter.RlwrapFilter() name = rlwrap_filter.name rlwrap_filter.help_text = '\n'.join([ "Usage: rlwrap -z {0} \n".format(name), "handle hotkeys (but only if bound to 'rlwrap-hotkey' in your .inputrc):\n", document_all_hotkeys() ]) rlwrap_filter.hotkey_handler = hotkey rlwrap_filter.run() rlwrap-0.46.1/filters/handle_sigwinch000077500000000000000000000036031433170252700176430ustar00rootroot00000000000000#!/usr/bin/env python3 """a demo of using a filter to handle signals. I am still not convined of the usefulness of signal filtering""" import sys, os, signal, argparse, re if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') parser = argparse.ArgumentParser() parser.add_argument("format", help='output format', nargs='?', default="LINES=%L;COLUMNS=%C") parser.add_argument('-v', help='be verbose (show formatted output, and response)', action='store_true', dest = 'verbose') parser.add_argument('-p', help='expected prompt', default="$ ", dest = 'prompt') import rlwrapfilter filter = rlwrapfilter.RlwrapFilter() filter.help_text = "Usage: rlwrap [-rlwrap-options] -z 'handle_sigwinch [options] []' \n"\ + "translates a window change signal into a command for apps that don't handle the signal\n"\ + "outputs to 's stdin, where %L is replaced by the new height, \n"\ + "and %C with the new width (default: 'export LINES=%L COLUMNS=%C')\n\nFilter options:\n" \ + parser.format_help() filter.minimal_rlwrap_version = 0.44 args = parser.parse_args() filter.cloak_and_dagger_verbose = args.verbose def handle_signal(signo): if int(signo) == int(signal.SIGWINCH): # signo is passed as a string, e.g. "11" new_cols, new_lines = os.get_terminal_size() # stdin is still the users terminal output = re.sub('%L', str(new_lines), args.format) output = re.sub('%C', str(new_cols), output) filter.cloak_and_dagger(output, args.prompt, 0.1) # cloak_and_dagger: we don't want to see the dialogue (except when verbose) return signo # don't mess with the signal itself filter.signal_handler = handle_signal filter.run() rlwrap-0.46.1/filters/history_format000077500000000000000000000033341433170252700175670ustar00rootroot00000000000000#!/usr/bin/env perl # filter to implement the old -F (--history-format) option, which was badly implemented and never # used anyway. The simplicity of this script shows the usefulness of the filter concept. # As a bonus, it is much easier to modify than the original rlwrap source code use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use POSIX qw(strftime); use strict; my ($raw_input, $prompt); my $filter = new RlwrapFilter; my $name = $filter -> name; my $format = join " ", @ARGV; # you may specify the format without quotes (as in -z 'history_format -- %D') my ($prefix) = ($format =~ /^(.*?)\%/); $prefix ||= $format; $filter -> help_text("Usage: rlwrap -z '$name ' \n". "Append to every history item, and strip it off again when input is accepted\n" . "escape codes in will be replaced: %D by the current working directory,\n" . "%P by the current prompt and %C by the command name, and all the remaining escape codes\n" . "as understood by strftime (3)") ; $filter -> input_handler(\&strip_off); $filter -> echo_handler(sub{$raw_input}); $filter -> history_handler(sub {strip_off($_) . " " . expand($format)}); $filter -> prompt_handler(sub{$prompt = $_}); $filter -> run; sub expand { my ($format) = @_; my $expanded = POSIX::strftime($format, localtime(time)); my $pwd = eval {$filter -> cwd}; $pwd = "?" if $@; my $command_name = ($filter -> command_line)[0]; $expanded =~ s/%P/$prompt/g; $expanded =~ s/%C/$command_name/g; $expanded =~ s/%D/$pwd/g; return $expanded; } sub strip_off { ($raw_input) = @_; my $stripped = $raw_input; $stripped =~ s/ ?\Q$prefix\E.*//; return $stripped; } rlwrap-0.46.1/filters/listing000077500000000000000000000023341433170252700161660ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or ".");; use RlwrapFilter; use strict; my $filter = new RlwrapFilter; my $name = $filter -> name; my $command_line = $filter -> command_line; $ENV{RLWRAP_FILTERDIR} or die "RLWRAP_FILTERDIR is not defined!\n"; chdir $ENV{RLWRAP_FILTERDIR} or die "cannot chdir to $ENV{RLWRAP_FILTERDIR}: $!\n"; my $debug=$ENV{RLWRAP_DEBUG}&DEBUG_FILTERING; if ($command_line =~ /\w/) { die("This filter is only used to get a list of filters via 'rlwrap -z listing'\n". "It is not usable as a filter (and would do nothing useful)\n"); } $filter -> send_output_oob("The following filters can be found in $ENV{RLWRAP_FILTERDIR}\n"); while (<*>) { next if /.pm$|^\#|~$|^$name$/ or (not -x); #$ my $fname = $_; my $info = get_info($fname); next unless $info; my $line = sprintf("%-30.30s %s\n", $fname, $info); $filter -> send_output_oob($line); } $filter -> run; sub get_info { my ($fname) = @_; delete $ENV{RLWRAP_COMMAND_PID}; # make filter talk over STDIN and STDOUT open INFO, "cat /dev/null | ./$fname 2>/dev/null |"; ; # throw away 1st line my $info = ; # get hold of 2nd line chomp($info); close INFO; return ($? ? undef : $info); } rlwrap-0.46.1/filters/logger000077500000000000000000000032001433170252700157650ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; use Getopt::Std; our ($opt_l); getopts("l"); my $logfile = $ARGV[0] || "/tmp/filterlog.$$"; open LOG, ">$logfile" or die "Couldn't write to $logfile: $!\n"; my $oldfh = select(LOG); $| = 1; select($oldfh); # flush LOG after each write my $filter = new RlwrapFilter(message_handler => \&logit); $filter -> help_text( "Usage: rlwrap -z 'logger [-l] logfile' \n" . "log messages to a file (for debugging)\n". "give logfile name as an argument, -l for long format\n" . "useful in a pipeline (rlwrap -z 'pipeline logger in:filter:logger out')"); $filter -> run; # a message_handler is seldom used (as it cannot change messages, only examine them) # It gets called with the tag as its second argument sub logit { my ($message, $tag) = @_; my $tagname = $filter -> tag2name($tag); $tagname =~ s/^TAG_//; my $mangled = $message; $mangled =~ s/\n/\\n/g; # make unprintable characters printable $mangled =~ s/\r/\\r/g; $mangled =~ s/\t/\\t/g; $mangled =~ s/\x1b/\\e/g; $mangled =~ s/([[:cntrl:]])/sprintf("\\x%02x", unpack("C", $1))/ge; format LOG = @<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $tagname, $mangled ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $mangled ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $mangled . if ($opt_l) { print LOG "$tagname $mangled\n"; } else { write LOG; } return $message; } rlwrap-0.46.1/filters/logger.py000077500000000000000000000032211433170252700164170ustar00rootroot00000000000000#!/usr/bin/env python3 """ a demo for logging communcations between rlrwap and filter Usage: rlwrap -z './logger.py -l logger.log' telnet """ import os import sys if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter import argparse import re filter = rlwrapfilter.RlwrapFilter() parser = argparse.ArgumentParser() parser.add_argument('--logfile', '-l', nargs='?', type=argparse.FileType('a'), default=open("/tmp/filterlog." + str(os.getpid()), mode='a')) args = parser.parse_args() fd = args.logfile # a message_handler is seldom used (as it cannot change messages, only examine them) # It gets called with the tag as its second argument def logit(message, tag): tagname = filter.tag2name(tag) tagname = re.sub(r'^TAG_', '', tagname) mangled = message mangled = re.sub(r'\n', r'\\n', mangled) mangled = re.sub(r'\r', r'\\r', mangled) mangled = re.sub(r'\t', r'\\t', mangled) mangled = re.sub(r'\x1b', r'\\e', mangled) if (args.logfile): fd.write("{0} {1}\n".format(tagname, mangled)) else: fd.write("{0:10s} {1}\n".format(tagname, mangled)) fd.flush() return message filter = rlwrapfilter.RlwrapFilter(message_handler=logit); filter.help_text = "Usage: rlwrap -z 'logger [-l] logfile' \n"\ + "log messages to a file (for debugging)\n"\ + "give logfile name as an argument, -l for long format\n"\ + "useful in a pipeline "\ + "(rlwrap -z 'pipeline logger in:filter:logger out')" filter.run() rlwrap-0.46.1/filters/makefilter000077500000000000000000000137521433170252700166460ustar00rootroot00000000000000#!/usr/bin/env python3 """a filter that uses pexpect to send rlwrap filter messages through an external command""" import sys import os import signal import argparse import pexpect import time import shlex sys.path.append(os.environ['RLWRAP_FILTERDIR']) import rlwrapfilter test_without_filter_command = False # only for debuggging: make filter_command a NOP ########## parse filter arguments ################# parser = argparse.ArgumentParser(description='filter arguments:', add_help=False, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--message_type', nargs='?', choices=['input', 'output', 'history', 'prompt', 'echo'], help="message type to filter", default="output") parser.add_argument('--timeout', nargs='?', type=float, default = 0.5, help="timeout for response") parser.add_argument('--stick-around', help = "don't die immediately after error, so that rlwrap can show the error message", action='store_true') parser.add_argument('command_line', metavar=' [args...]', nargs=argparse.REMAINDER, help='external filter_command to turn into filter') args = parser.parse_args() command = args.command_line[0] if args.command_line else None ######### instantiate a RlwrapFilter ############ filter = rlwrapfilter.RlwrapFilter() filter.help_text = """\ Usage: rlwrap [-options] -z 'makefilter [-options]' ... convert into rlwrap filter (e.g. to colourise output or censor history) important: *must* output exactly *one* line for each input line. (except possibly a startup message) filter """ + parser.format_help() filter.minimal_rlwrap_version = 0.45 ########## spawn filter_command ################# filter_command = None command_has_spoken = False def handle_child_death(signum, frame): advice = (f"\nConsider using 'makefilter --stick-around ...' to see more error messages" if not args.stick_around and not command_has_spoken else "") filter.error(f"{args.command_line[0]} has died." + advice) signal.signal(signal.SIGCHLD, handle_child_death) def spawn_filter_command(): global filter_command command_line = args.command_line if args.stick_around: # replace " " by "sh -c ' ' || sleep 1" # in order to see messages at death quoted_command_line = list(map(lambda s : shlex.quote(s), args.command_line)) quoted_command_line += ['||', 'sleep', '1'] command_as_string = " ".join(quoted_command_line) command_line = ["sh", "-c", command_as_string] filter_command= pexpect.spawn(command_line[0], command_line[1:], echo=False, timeout=args.timeout, encoding='utf-8') filter_command.delaybeforesend = None filter_command.timeout = 0.25 # read welcome banner (or, more importantly: error message) welcome_or_errormessage = "" while True: try: welcome_or_errormessage += filter_command.readline() except pexpect.TIMEOUT: break if welcome_or_errormessage: filter.send_output_oob(welcome_or_errormessage) filter_command.timeout = args.timeout #print(filter_command.__dict__) return filter_command ######### handlers ##################### @rlwrapfilter.intercept_error_with_message(f"Error in communication with {command}") def pass_single_line_through_filter_command(line, strip=True): """ pass one single (output, history, prompt,...) line through filter_command. use strip = True for messages that don't end in CRNL, such as prompts, history and echo ''' """ global filter_command, command_has_spoken if test_without_filter_command: result = line else: if not filter_command: spawn_filter_command() try: filter_command.sendline(line) result = filter_command.readline() command_has_spoken =True except pexpect.TIMEOUT: filter.error(f"timeout readling from {filter_command}") except pexpect.EOF as e: filter.error(f"EOF readling from {filter_command}") return result.rstrip("\r\n") if strip else result assembled_chunks = "" def pass_chunks_through_filter_command_line_by_line(chunk): """ pass chunk through filter_command, assembling and spliting multiple chunks into lines if needed use global variable to retain assembled chunks between invocation this should probably be done in rlwrapfilter eventually: filter.present_output_as_lines = True/False """ global assembled_chunks assembled_chunks += chunk # chunk may contain zero, one or several CRNLs: lines = assembled_chunks.split('\r\n'); # keep last (possibly empty) non CRNL-terminated chunk, which may turn out to have been # a prompt, in which case erase_assembled_chunks() will be called to forget about it, cf. below: assembled_chunks = lines[-1] output = "" for line in lines[0:-1] : output += pass_single_line_through_filter_command(line, False) return output def erase_assembled_chunks(prompt): """ prompt_handler: if we recognise a prompt, we need to clear assembled_chunks since they don't need to be treated as output anymore """ global assembled_chunks assembled_chunks = "" return prompt ############ determine which handler to actitvate #################### if args.message_type == 'output': filter.output_handler = pass_chunks_through_filter_command_line_by_line filter.prompt_handler = erase_assembled_chunks elif args.message_type == 'input': filter.input_handler = pass_single_line_through_filter_command elif args.message_type == 'prompt': filter.prompt_handler = pass_single_line_through_filter_command elif args.message_type == 'echo': filter.echo_handler = pass_single_line_through_filter_command elif args.message_type == 'history': filter.history_handler = pass_single_line_through_filter_command ########### main loop: ################### filter.run() rlwrap-0.46.1/filters/null000077500000000000000000000004351433170252700154670ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; # Filter version of "Hello World": my $filter = new RlwrapFilter; $filter -> help_text("Usage: rlwrap [-options] -z null \n". "a filter that does nothing"); $filter -> run; rlwrap-0.46.1/filters/null.py000077500000000000000000000006101433170252700161110ustar00rootroot00000000000000#!/usr/bin/env python3 """a demo for doing nothing""" import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter filter = rlwrapfilter.RlwrapFilter() filter.help_text = "Usage: rlwrap [-options] -z null.py \n"\ + "a filter that does nothing" filter.run() rlwrap-0.46.1/filters/outfilter000077500000000000000000000017541433170252700165370ustar00rootroot00000000000000#! /usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $filter = new RlwrapFilter; my $name = $filter->name; $filter->help_text("Usage: rlwrap -z '$name [ ... ]' \n" . "Filter output through \n" . "e.g. 'rlwrap -z \"outfilter grep -E --color 'blah|\$'\" command' will colour all blah's in commands output"); # prevent quoting headaches foreach my $arg (@ARGV) { $arg = "'$arg'"; } my $filter_command = join ' ', @ARGV; die $filter -> help_text . "\n" if not $filter_command and $ENV{RLWRAP_COMMAND_PID}; $filter->output_handler(sub {""}); $filter->prompt_handler(\&prompt); $filter->run; sub prompt { my $prompt = shift; my $output = $filter->cumulative_output; $output =~ s/\r//g; open (PIPE, "| $filter_command") or die "Failed to create pipe: $!"; print PIPE $output; close PIPE; return $prompt; } rlwrap-0.46.1/filters/paint_prompt000077500000000000000000000071351433170252700172350ustar00rootroot00000000000000#!/usr/bin/env perl # this filter tests rlwrap's tolerance for very heavily cooked prompts. # it uses the seldom-used xterm 256-colour mode (which may or may not work in your # other terminal emulators like rxvt) use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $X11_colour_table = "/usr/share/X11/rgb.txt"; # change this if your rgb.txt is somewhere else my $colours_were_declared; my $rampsize = 72; # my urxvt cannot do more my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text("Usage: rlwrap -z '$name --' \n". "paint the prompt in colour gradient between X11 colours and \n". "spaces in colour names must be replaced by underscores, e.g. lemon_chiffon--pale_turquoise"); $filter -> prompt_handler(\&paint); $filter -> run; sub paint { my ($bland) = @_; die "unpainted prompt contains ESC codes, consider using rlwrap --ansi-aware (-A)\n" if $bland =~ /\e/; init() unless $colours_were_declared; return apply_ramp($bland, $rampsize); } sub colour2rgb { my ($colourname) = @_; if (not open COLOURS, $X11_colour_table) { warn "Cannot read $X11_colour_table: trying to find another rgb.txt\n"; undef $X11_colour_table; my $rgbs = `locate rgb.txt` or die "cound not use 'locate' to find rgb.txt\n"; my @rgbs = split /\r?\n/, $rgbs; foreach my $rgb (sort {length($a) <=> length($b) } @rgbs) { next unless $rgb; # skip empty list element $filter -> send_output_oob("trying $rgb ...\n"); if (open COLOURS, $rgb) { $X11_colour_table = $rgb; # remember for next colour last; } } die "cound not find readable rgb.txt\n" unless $X11_colour_table; $filter -> send_output_oob("Edit $ENV{RLWRAP_FILTERDIR}/$name to get rid of these messages\n"); } while () { my ($R, $G, $B) = /(\d+)\s+(\d+)\s+(\d+)\s+$colourname/i; return ($R, $G, $B) if defined $R; } die "Colour <$colourname> not found in $X11_colour_table\n"; } sub declare_rgb { my ($index, $r, $g, $b) = @_; foreach my $arg (qw($index $r $g $b)) { die "arg $arg out of range\n" if eval "\$$arg > 255 or \$$arg < 0"; } $filter -> send_output_oob( sprintf("\x1b]4;%d;rgb:%2.2x/%2.2x/%2.2x\x1b\\", $index, $r,$g,$b)); # This declares colour # $index to have RGB vaues $r,$g,$b } sub init { my ($col1, $col2) = ($ARGV[0] =~ /(.*)--(.*)/); die ($filter->help_text) unless $col2; foreach ($col1, $col2) { s/_/ /g; } declare_colour_ramp($rampsize, colour2rgb($col1), colour2rgb($col2)); } sub declare_colour_ramp { my($size, $r1, $g1, $b1, $r2, $g2, $b2) = @_; for (my $i = 0; $i< $size; $i++) { declare_rgb(16+$i, ramp($r1, $r2, $i/$size), ramp($g1, $g2, $i/$size), ramp($b1, $b2, $i/$size)); } $colours_were_declared = 1; } sub ramp { # intermediate value 100*$frac % between $val1 and $val2 my ($val1, $val2, $frac) = @_; return int ($val1 + $frac * ($val2-$val1)); } sub apply_ramp { my ($text, $rampsize) = @_; return $text unless $text =~ /\S/; my @text = split //, $text; # text -> ('t', 'e', 'x', 't') for (my $i = 0; $i < @text; $i++){ my $colour = 16 + int(($rampsize -1) * $i / @text); die "colour index $colour out of range" if $colour < 16 or $colour >= 16 + $rampsize; $text[$i]= "\x01\x1b[38;5;${colour}m\x02" . $text[$i]; # ESC[38;5;$colour is the xterm code to swithc to $colour, the \x01 and \x02 are # readline "ignore codes" to tell readline that the sequence is unprintable } return (join '', @text) . "\x01\x1b[0m\x02"; } rlwrap-0.46.1/filters/paint_prompt.py000077500000000000000000000054471433170252700176700ustar00rootroot00000000000000#!/usr/bin/env python3 """ a demo for painting prompt this filter tests rlwrap's tolerance for very heavily cooked prompts. it uses the seldom-used xterm 256-colour mode (which may or may not work in your other terminal emulators like rxvt) try `rlwrap -z './paint_prompt.py 0087ff--ff0000' telnet` """ import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter import re rampsize = 0xe7 - 0x10; # works on my gnome terminal # create table to map rgb to xterm color id, eg, # rgb2xterm['000000'] == '16', rgb2xterm['00005f'] == '17',...etc rgb2xterm = {} xtermcolor = 16 for r in (0, 0x5f, 0x87, 0xaf, 0xdf, 0xff): for g in (0, 0x5f, 0x87, 0xaf, 0xdf, 0xff): for b in (0, 0x5f, 0x87, 0xaf, 0xdf, 0xff): rgb = hex((r<<16) + (g<<8) + b)[2:] # remove the prefix '0x' rgb2xterm[rgb] = str(xtermcolor) xtermcolor += 1 filter = rlwrapfilter.RlwrapFilter() name = filter.name filter.help_text = '\n'.join([ "Usage: rlwrap -z '$name -- ", "paint the prompt in colour gradient between colours and ", "colour names must be a 6 digit hex code of rgb,", "e.g. 00ff00--ff0000" ]) m = re.match(r'(.*)--(.*)', sys.argv[1]) colour1 = m.group(1) colour2 = m.group(2) def ramp(val1, val2, frac): """intermediate value 100*$frac % between $val1 and $val2""" val = int(val1 + frac * (val2-val1)) # round off if (0 <= val < (0+0x5f)/2): val = 0x0 elif ((0+0x5f)/2<= val < (0x5f+0x87)/2): val = 0x5f elif ((0x5f+0x87)/2<= val < (0x87+0xaf)/2): val = 0x87 elif ((0x87+0xaf)/2<= val < (0xaf+0xdf)/2): val = 0xaf elif ((0xaf+0xdf)/2<= val < (0xdf+0xff)/2): val = 0xdf elif ((0xdf+0xff)/2<= val <= 0xff): val = 0xff return val def intermediate_color(frac): r1 = int(colour1[0:2], 16) g1 = int(colour1[2:4], 16) b1 = int(colour1[4:6], 16) r2 = int(colour2[0:2], 16) g2 = int(colour2[2:4], 16) b2 = int(colour2[4:6], 16) r = ramp(r1, r2, frac) g = ramp(g1, g2, frac) b = ramp(b1, b2, frac) rgb = (r<<16) + (g<<8) + b return hex(rgb)[2:] # remove the prefix '0x' def apply_ramp(text): buf = [] # text -> ['t', 'e', 'x', 't'] i = 0 for c in text: # ESC[38;5;$colour is the xterm code to swithc to $colour, the \x01 and \x02 are # readline "ignore codes" to tell readline that the sequence is unprintable buf.append( ''.join([ "\x01\x1b[38;5;{0}m\x02".format(rgb2xterm[intermediate_color(i/len(text))]), c ])) i += 1 return ''.join(buf) + "\x01\x1b[0m\x02" filter.prompt_handler = apply_ramp filter.run() rlwrap-0.46.1/filters/pipeline000077500000000000000000000124531433170252700163250ustar00rootroot00000000000000#!/usr/bin/env perl # This is a rather complicated filter to build pipelines from other filters. It is # also useful to test signal and filter death handling by rlwrap. use POSIX; use strict; use Config; use Data::Dumper; use lib $ENV{RLWRAP_FILTERDIR}; use RlwrapFilter; use strict; my %sig_num; my @sig_name; # print help text when run as 'rlwrap -z pipeline' (without command) if ($ENV{RLWRAP_COMMAND_PID}== 0) { RlwrapFilter ->new(help_text => help()) -> run; exit; # not reached } # use signal *names* when reporting errors init_signal_names(); # pipeline cannot be used stand-alone (wouldn't be useful anyway) die "I need an argument. Use the 'null' filter if you need a NOP\n" unless @ARGV; # split command line into pipe segments my $pipeline = join ' ', @ARGV; my @components = split /\s*(? [$n] -> [0], $ins_and_outs -> [$n] -> [1]); } my $Npids = @pids; # keep only the output pipe open (for error messages) foreach my $fd (@all_pipes) { POSIX::close($fd) unless $fd == $ENV{RLWRAP_OUTPUT_PIPE_FD}; } # now wait for them all to die for (my $Ndeadpids = 0; $Ndeadpids < $Npids; $Ndeadpids ++) { my $deadpid; eval { $SIG{ALRM} = sub { die "alarm\n" }; alarm 1 if $Ndeadpids; # become impatient after first child has died $deadpid = wait(); if ($deadpid> 0 and $Ndeadpids == 0) { # first dead child - report it my $exit_value = $? >> 8; my $signal_num = $? & 127; my $dumped_core = $? & 128; my $signal_name = $sig_name[$signal_num]; $signal_name = ($signal_name ? "SIG$signal_name" : "signal -$signal_num"); warn "child filter '$pids{$deadpid}' (PID $deadpid) died with exit value $exit_value" . ($signal_num ? " (killed by $signal_name)" : "") . ($dumped_core ? " (core dumped)" : "") . "\n"; } alarm 0; }; last if ($@); # timeout or strange error delete $pids{"$deadpid"}; # cross $pid from list of living } # OK, waited long enough. Zap the die-hards now! $SIG{CHLD} = 'IGNORE'; foreach my $pid (keys %pids) { kill -9, $pid; } ############################## subroutines ################################ sub get_pipes { my ($how_many) = @_; my @pipes; for (my $i = 0; $i < $how_many; $i++) { my ($read, $write) = POSIX::pipe() or die "Couldn't allocate pipe: $!\n"; push @pipes, [$write, $read]; # input end first! } return \@pipes; } sub spawn_component { my ($command_line, $input_pipe_fd, $output_pipe_fd) = @_; my @command_and_args = split /\s/, $command_line; if (my $pid = fork()) { die "fork error " unless $pid > 0; $pids{"$pid"} = $command_line; return $pid; } else { $ENV{RLWRAP_INPUT_PIPE_FD} = $input_pipe_fd; # connect child to the right pipes $ENV{RLWRAP_OUTPUT_PIPE_FD} = $output_pipe_fd; foreach my $fd (@all_pipes) { # close all other pipes POSIX::close($fd) unless is_in ($fd, $input_pipe_fd, $output_pipe_fd); } exec @command_and_args; # the original FILTER_IN and FILTER_OUT are still open (they were duped with ">&$fd", not re-used with ">&=$fd" # this is important as the DIE handler uses FILTER_OUT die "Couldn't exec '$command_and_args[0]': $!\n"; } } sub init_signal_names { unless($Config{sig_name} && $Config{sig_num}) { return; } else { my @names = split ' ', $Config{sig_name}; @sig_num{@names} = split ' ', $Config{sig_num}; foreach (@names) { $sig_name[$sig_num{$_}] ||= $_; } } } ######################### some lispy list handlers ##################### sub braid_a_chain { # braid_a_chain(1,[2,3,4,5],6) = [[1,2],[3,4],[5,6]] my ($first, $listref, $last) = @_; return [[$first, $last]] if (not ($listref and $listref -> [0])); my ($next_in, $next_out) = @{$listref->[0]}; shift @$listref; # my $chain = braid_a_chain($next_out, $listref, $last); unshift @$chain, [$first, $next_in] ; return $chain; } sub flatten { # flatten ([[1], [2,3,4,], ["a"]]) = (1,2,3,4,"a") my ($x) = @_; my @flattened; return $x unless ref($x) eq 'ARRAY'; foreach my $el (@$x) { push @flattened, flatten($el); } return @flattened } sub is_in { # test for list membership my($el, @set) = @_; foreach my $member (@set) { return 1 if $el == $member; } return 0; } sub help { my (undef, $myself) = ($0 =~ m#(.*)/([^/]+)#); $myself ||= $0; return < combines the effects of 2 or more filters messages will be passed through filter_1, ..., filter_n. Use a backslash to pass a ':' that is not meant as a pipe symbol, e.g: rlwrap -z '$myself prompt hello\\: : logger out' command EOF } rlwrap-0.46.1/filters/pipeto000077500000000000000000000050701433170252700160150ustar00rootroot00000000000000#!/usr/bin/env perl # This is maybe the most practical of the filter examples. Is is also # a test for rlwrap's signal handling. # # At present, a CTRL-C in a pager will also kill rlwrap (bad) use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use POSIX qw(:signal_h); use strict; # We want any piped pager to receive SIGWINCH. # SIGWINCH is not in POSIX, which means that POSIX.pm doesn't # know about it. We use 'kill -l' to find it. my $raw_input; my @signals = split /\s+/, `kill -l`; # yuck! for (my $signo = 1; $signals[$signo-1]; $signo++) { if ($signals[$signo-1] eq 'WINCH') { my $sigset_unblock = POSIX::SigSet->new($signo); unless (defined sigprocmask(SIG_UNBLOCK, $sigset_unblock)) { die "Could not unblock signals: $!\n"; } } } my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text(< Allow piping of 's interactive output through pagers or other shell commands. When input of the form " | " is seen, is given to to interpret, and the output is piped to instead of being displayed by rlwrap. For a slightly contrived example, let's say you are running the Gauche Scheme shell within rlwrap, as \`rlwrap -z pipeto gosh', and defined a verbose version of fact as in the Gauche documentation: (define (fact n) (if (zero? n) 1 (* n #?=(fact (- n 1))))) then you can type \`(fact 32) | less' to see the evaluation steps of the function call paged through less. DOC my $pipeline; my $prompt; my $out_chunkno = 0; my $wait_text = "wait ..."; $filter -> prompts_are_never_empty(1); $filter -> input_handler(\&input); $filter -> output_handler(\&output); $filter -> prompt_handler(\&prompt); $filter -> echo_handler(sub {$raw_input}); $filter -> run; sub input { my $input; $raw_input = $_; ($input, undef, $pipeline) = /([^|]*)(\|(.*))?/; return $input; } sub output { # Replace first chunk by $wait_text return $pipeline ? ($out_chunkno++ == 0 ? $wait_text : "") : $_; } sub prompt { my ($prompt) = @_; $out_chunkno = 0; if ($pipeline) { # Erase $wait_text and go to new line $filter -> send_output_oob ("\x08" x length($wait_text). "\n"); local $SIG{PIPE} = 'IGNORE'; # we don't want to die if the pipeline quits open PIPELINE, "| $pipeline"; print PIPELINE $filter->cumulative_output;; close PIPELINE; # this waits until pipeline has finished undef $pipeline; $filter ->send_output_oob("\n"); # start prompt on new line } return $prompt; } rlwrap-0.46.1/filters/pipeto.py000077500000000000000000000035321433170252700164450ustar00rootroot00000000000000#!/usr/bin/env python3 # this is maybe the most practical of the filter examples. Is is also a test for rlwraps signal handling. # At present, a CTRL-C in a pager will also kill rlwrap (bad) import sys import os if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter import re filter = rlwrapfilter.RlwrapFilter() me = filter.name raw_input = '' filter.help_text = '\n'.join([ "Usage: rlwrap -z {0} ".format(me), "Allow piping of 's interactive output through pagers or other shell commands.", "When input of the form \"| shell pipeline\" is seen, 's following output is sent through the pipeline\n" ]) pipeline = '' prompt = '' out_chunkno = 0 wait_text = "wait ..." def input(message): global raw_input global pipeline input = None raw_input = message (input, bar, pipeline) = re.match(r'([^\|]*)(\|(.*))?', message).groups() return input def output(message): global out_chunkno if (pipeline): if (out_chunkno == 0): out_chunkno += 1 return wait_text else: return "" else: return message # replace first chunk by $wait_text def prompt_handler(message): global pipeline prompt = message out_chunkno = 0 if (pipeline): filter.send_output_oob("\x08" * len(wait_text) + "\n") # erase $wait_text and go to new line pipe = os.popen(pipeline, 'w') pipe.write(filter.cumulative_output) pipe.close() pipeline = None filter.send_output_oob("\n") # start prompt on new line return prompt filter.prompts_are_never_empty = True filter.input_handler = input filter.output_handler = output filter.prompt_handler = prompt_handler filter.echo_handler = (lambda x: raw_input) filter.run() rlwrap-0.46.1/filters/rlwrapfilter.md000066400000000000000000000117711433170252700176330ustar00rootroot00000000000000Python library for [rlwrap](https://github.com/hanslub42/rlwrap) filters ========================================================================== Synopsis ------------------- ``` import os import sys if 'RLWRAP_FILTERDIR' in os.environ: sys.path.append(os.environ['RLWRAP_FILTERDIR']) else: sys.path.append('.') import rlwrapfilter filter = rlwrapfilter.RlwrapFilter(message_handler=do_something) filter.help_text = 'useful help' filter.output_handler = lambda x: re.sub('apple', 'orange', x) # re−write output filter.prompt_handler = munge_prompt filter.completion_handler = complete_handler filter.history_handler = lambda x: re.sub(r'(identified\s+by\s+)(\S+)', r'\\1xXxXxXxX', x) filter.run() ``` Description ------------------- This is an [RlwrapFilter](https://github.com/hanslub42/rlwrap/wiki/RlwrapFilter.pm-manpage) clone written in Python. The focus is on providing the same API's and usage of the original Perl version [RlwrapFilter](https://github.com/hanslub42/rlwrap/wiki/RlwrapFilter.pm-manpage) as possible. Module contents ------------------- The module defines a class, several functions, variables, constants, and an exception. ### class rlwrapfilter.**RlwrapFilter** ``` f = rlwrapfilter.RlwrapFilter() f = rlwrapfilter.RlwrapFilter(prompt_handler = lambda x: "Hi! > ", minimal_rlwrap_version = 0.35, ...) ``` returns a new RlwrapFilter object. ### exception rlwrapfilter.**RlwrapFilterError** RlwrapFilter object ------------------- ### handlers Handler is an attribute to store a callable object that get called from `run` method. ##### prompt_handler handles a prompt. It should accept one string argument and return a string as a prompt. ##### completion_handler handles a completion list. It should accept `line`, `prefix`, `completions`: |arguments|type|description| |---------|----|-----------| |`line`|string|string itself you entered to a prompt| |`prefix`|string|prefix for completion. Generally, a last part of `line`.| |`comletions`|list|list of completion candidates| and return a handled list of completion candidates. ##### history_handler handles a history. It should accept one string argument and return a string as a history. ##### input_handler handles a input you entered to a prompt. It should accept one string argument and return a string as a input. ##### echo_handler handles an echo (a string which echo-back to your screen as you entered something to a prompt). It should accept one string argument and return a string as an echo. ##### output_handler handles a output which rlwrapped command returns. It should accept one string argument and return a string as a output. ##### message_handler handles a message, a piece of communications which come and go between rlwrap filter and rlwrap. It should accept one string argument and one integer argument(tag) and return a string as a message. ### other attributes [RlwrapFilter](#class) instances have the following attribute: ##### help_text An attribute to store a useful help text. It is shown by `rlwrap -z`. ##### minimal_rlwrap_version stores a minimal required version for your rlwrap filter. As you set a float value, it immediately warns unless the version of rlwrap is higher than or quals to the value. ##### cumulative_output stores the cumulative output. It is immutable. ##### previous_tag stores the previous tag. It is immutable. ##### prompt_rejected stores `_THIS_CANNOT_BE_A_PROMPT_`. Immutable. ##### command_line stores the rlwrapped command and its arguments. Immutable. ##### running_under_rlwrap True if a filter is running under [rlwrap](https://github.com/hanslub42/rlwrap). Otherwise, False. The default value is False. ##### cloak_and_dagger_verbose The verbosity of the `cloak_and_dagger` method. Boolean. The default value is False. ##### prompts_are_never_empty If True, it rejects an empty prompt. The default value is False. ### methods [RlwrapFilter](#class) instances have the following methods: ##### send_output_oob(text) sends text to rlwrap to show text. ##### send_ignore_oob(text) sends text to rlwrap, but rlwrap discards it. It is useful for debugging purpose. ##### add_to_completion_list(words) ##### remove_from_completion_list(words) adds/removes the words in `words` to/from a list of complition candidates. ##### cloak_and_dagger(question, prompt, timeout) sends `question` to command's input and read back everything that comes back until `prompt` is seen at "end-of-chunk", or no new chunks arrive for `timeout` seconds, whichever comes first. Return the response (without the final $prompt). rlwrap remains completely unaware of this conversation. ##### run() starts an event loop. ##### cwd() returns os.getcwd() Install ------------------- ``` $ git clone https://github.com/hokuda/rlwrapfilter.py.git $ cd rlwrapfilter.py $ sudo python3 ./setup.py install ``` Acknowledgement ------------------- Many thanks to [Hans Lub](https://github.com/hanslub42) for creating [the great tool](https://github.com/hanslub42/rlwrap). rlwrap-0.46.1/filters/rlwrapfilter.py000077500000000000000000000543471433170252700176740ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Python 3 library for [rlwrap](https://github.com/hanslub42/rlwrap) filters * Synopsis filter = rlwrapfilter.RlwrapFilter(message_handler=do_something) filter.help_text = 'useful help' filter.output_handler = lambda x: re.sub('apple', 'orange', x) # re−write output filter.prompt_handler = munge_prompt filter.completion_handler = complete_handler filter.history_handler = lambda x: re.sub(r'(identified\s+by\s+)(\S+)', r'\\1xXxXxXxX', x) filter.run() This is an [RlwrapFilter](https://github.com/hanslub42/rlwrap/wiki/RlwrapFilter.pm-manpage) clone written in Python 3. The focus is on providing the same API's and usage of the Perl version of [RlwrapFilter](https://github.com/hanslub42/rlwrap/wiki/RlwrapFilter.pm-manpage) as possible. [rlwrap](https://github.com/hanslub42/rlwrap) is a tiny utility that sits between the user and any console command, in order to bestow readline capabilities (line editing, history recall) to commands that don't have them. Since version 0.32, rlwrap can use filters to script almost every aspect of rlwrap's interaction with the user: changing the history, re-writing output and input, calling a pager or computing completion word lists from the current input. rlwrapfilter.py makes it very simple to write rlwrap filters in Python 3. A filter only needs to instantiate a RlwrapFilter object, change a few of its default handlers and then call its 'run' method. """ import sys import os import io import types import time import struct import select import re import traceback import binascii try: from collections.abc import Callable except ImportError: from collections import Callable import numbers TAG_INPUT = 0 TAG_OUTPUT = 1 TAG_HISTORY = 2 TAG_COMPLETION = 3 TAG_PROMPT = 4 TAG_HOTKEY = 5 TAG_SIGNAL = 6 TAG_WHAT_ARE_YOUR_INTERESTS = 127 TAG_IGNORE = 251 TAG_ADD_TO_COMPLETION_LIST = 252 TAG_REMOVE_FROM_COMPLETION_LIST = 253 TAG_OUTPUT_OUT_OF_BAND = 254 TAG_ERROR = 255 REJECT_PROMPT = '_THIS_CANNOT_BE_A_PROMPT_' # we want to behave differently when running outside rlwrap we_are_running_under_rlwrap = 'RLWRAP_COMMAND_PID' in os.environ # rlwrap version rlwrap_version_string = os.environ.get('RLWRAP_VERSION', "0.41") # e.g. 0.45.2b major, rest = rlwrap_version_string.split('.', 1) # 0, 45.2b rest = re.sub(r'[^\d]','', rest) # 452 rlwrap_version = float(major + '.' + rest) # 0.452 # open communication lines with rlwrap (or with the terminal when not running under rlwrap) if (we_are_running_under_rlwrap): CMD_IN = int(os.environ['RLWRAP_MASTER_PTY_FD']) CMD_OUT = int(os.environ['RLWRAP_MASTER_PTY_FD']) FILTER_IN = int(os.environ['RLWRAP_INPUT_PIPE_FD']) FILTER_OUT = int(os.environ['RLWRAP_OUTPUT_PIPE_FD']) else: CMD_IN = sys.stdout.fileno() CMD_OUT = sys.stdin.fileno() FILTER_IN = sys.stdout.fileno() FILTER_OUT = sys.stdin.fileno() def when_defined(maybe_ref_to_sub, *args): """ ` when_defined(f, x, y, ...) returns f(x, y, ...) if f is defined, x otherwise """ if (maybe_ref_to_sub is not None): try: return maybe_ref_to_sub(*args) except Exception as e: send_error( "improper handler <{0}> of type {1} (expected a ref to a sub)\n{2}" .format(maybe_ref_to_sub, type(maybe_ref_to_sub), traceback.format_exc()),e) else: return args[0] def out_of_band(tag): return tag > 128 def read_until(fh, stoptext, timeout, prompt_search_from=0, prompt_search_to=None): """ read chunks from pty pointed to by fh until either inactive for timeout or stoptext is seen at end-of-chunk """ res = '' while (True): chunk = read_chunk(fh, timeout); if(not chunk): # got "" back: timeout #send_warn("read_until: timeout") return res res = res + chunk # multi-line mode so that "^" matches a head of each line slice = res[prompt_search_from:prompt_search_to] if re.search(stoptext, slice, re.MULTILINE): return res def read_chunk(fh, timeout): """ read chunk from pty pointed to by fh with timeout if timed out, returns 0-length string """ if (len(select.select([fh], [], [], timeout)[0]) > 0): chunk = os.read(fh, 2**16); # read up-to 2^16=65536 bytes return chunk.decode(sys.stdin.encoding, errors="ignore") return "" def read_patiently(fh, count): """ keep reading until count total bytes were read from filehandle fh """ already_read = 0 buf = bytearray() while(already_read < count): buf += os.read(fh, count-already_read) nread = len(buf) if (nread == 0): break already_read += nread return buf def write_patiently(fh, buffer): """ keep writing until all bytes from $buffer were written to $fh """ already_written = 0 count = len(buffer) while(already_written < count): try: nwritten = os.write(fh, buffer[already_written:]) if (nwritten <= 0): send_error("error writing: {0}".format(str(buffer))) already_written = already_written + nwritten except BrokenPipeError: # quit when rlwrap dies sys.exit(1) def read_message(): """ read message (tag, length word and contents) from FILTER_IN """ if not we_are_running_under_rlwrap: return read_from_stdin() tag = int.from_bytes(read_patiently(FILTER_IN,1), sys.byteorder) length = int.from_bytes(read_patiently(FILTER_IN,4), sys.byteorder) message = read_patiently(FILTER_IN, length).decode(sys.stdin.encoding, errors = "ignore") # \Z matches only at the end of the string in python message = re.sub(r'\n\Z', '', str(message or "")) return tag, message def write_message(tag, message): if (not we_are_running_under_rlwrap): return write_to_stdout(tag, message) message = '\n' if message is None else message + '\n' # allow undefined message bmessage = bytearray(message, sys.stdin.encoding) length = len(bmessage) write_patiently(FILTER_OUT, tag.to_bytes(1, sys.byteorder, signed=False)) write_patiently(FILTER_OUT, length.to_bytes(4, sys.byteorder, signed=False)) write_patiently(FILTER_OUT, bmessage) def read_from_stdin(): tag = None prompt = None tagname = None while (tag is None): try: m = re.match("(\S+) (.*?)\r?\n", sys.stdin.readline()) except KeyboardInterrupt: sys.exit() if not m: sys.exit() tagname, message = m.groups() message.replace("\\t","\t").replace("\\n","\n") tag = name2tag(tagname) return tag, message def name2tag(name): """ Convert a valid tag name like " TAG_PROMPT " to a tag (an integer) """ try: tag = eval(name) except Exception as e: raise SystemExit('unknown tagname {0}'.format(name)) return tag def tag2name(tag): """ Convert the tag (an integer) to its name (e.g. " TAG_PROMPT ") """ for name in ['TAG_REMOVE_FROM_COMPLETION_LIST', 'TAG_ADD_TO_COMPLETION_LIST', 'TAG_WHAT_ARE_YOUR_INTERESTS', 'TAG_INPUT', 'TAG_PROMPT', 'TAG_COMPLETION', 'TAG_HOTKEY', 'TAG_SIGNAL', 'TAG_HISTORY', 'TAG_OUTPUT_OUT_OF_BAND', 'TAG_ERROR', 'TAG_IGNORE', 'TAG_OUTPUT']: if (eval('{0} == {1}'.format(str(tag), name))): return name def write_to_stdout(tag, message): print('{0}: {1}'.format(tag2name(tag), message)) def send_warn(message): """ send message to rlwrap. """ write_message(TAG_OUTPUT_OUT_OF_BAND, "{0}: {1}".format(__name__, message)) def send_error(message,e = None): """ send message to rlwrap, and raise e. """ write_message(TAG_OUTPUT_OUT_OF_BAND if e else TAG_ERROR, "{0}: {1}".format(__name__, message)) if e: raise e else: time.sleep(2) # timeout doesn't matter much because rlwrap will kill us anyway exit() def intercept_error(func): """ A decorator to intercept an exception, send the message to rlwrap, and raise an exception. """ def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: write_message( TAG_ERROR, "{0}: {1}".format(__name__, '/'.join(map(str,e.args))) ) raise e return wrapper def intercept_error_with_message(message=None): """ A decorator (-factory) to intercept an exception, send the message to rlwrap, print a message and exit (or re-raise the exception, if message = None) N.B: decorators, hence also are evaluated at compile time. @intercept_error_with_message (f"This script crashed after {sec} seconds") doesn't make sense. """ def intercept_error_closure(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: complete_message = "{0}: {1}".format(__name__, '/'.join(map(str,e.args))) if message == None else "{0}\n{1}".format(message, e) write_message(TAG_ERROR, complete_message) if message: exit() else: raise e return wrapper return intercept_error_closure def is_string(value): return isinstance(value, str) or value == None def is_boolean(value): return isinstance(value, bool) or value == None def is_integer(value): return isinstance(value, int) or value == None def is_float(value): return isinstance(value, numbers.Number) or value == None def is_callable(value): return isinstance(value, Callable) or value == None @intercept_error def test_intercept(): print('test intercept!!! + + + +') raise Exception('test exception = = = = = .........') DIGIT_NUMBER=8 def split_rlwrap_message(message): bmessage = bytes(message, sys.stdin.encoding) fields = [] while(len(bmessage) != 0): blen = bmessage[:DIGIT_NUMBER] bmessage = bmessage[DIGIT_NUMBER:] length = int(str(blen, sys.stdin.encoding), base=16) bfield = bmessage[:length] bmessage = bmessage[length:] fields.append(str(bfield, sys.stdin.encoding)) return fields def merge_fields(fields): message = "" for field in fields: length = len(bytes(field, sys.stdin.encoding)) lenstr = format(length, '0' + str(DIGIT_NUMBER) + 'x') message = message + lenstr + field return message class RlwrapFilterError(Exception): """ A custom exception for rlwrap """ def __init__(self, *args): super().__init__(args) class RlwrapFilter: """ class for rlwrap filters """ def __setattr__(self, name, value): if not name in self._fields: self.warn("There is no '{0}' attribute in class {1}\n".format(name, self.__class__.__name__)) is_valid_type = self._field_types[name] if not is_valid_type(value): self.warn("{0} should not be '{1}'\n".format(name, type(value))) if name == 'minimal_rlwrap_version' and (value > rlwrap_version): self.error("requires rlwrap version {0} or newer.\n".format(str(value))) self.__dict__[name] = value """ def __getattr__(self, name): if(name in self.fields): return self.__dict__[name] elif(name in self.handlers): return self.__dict__[name] else: send_error("There is no '{0}' attribute in class {1}" .format(name, self.__class__.__name__)) """ def __init__(self, **kwargs): self.__dict__['_field_types'] = { 'input_handler':is_callable, 'output_handler':is_callable, 'prompt_handler':is_callable, 'hotkey_handler':is_callable, 'signal_handler':is_callable, 'echo_handler':is_callable, 'message_handler':is_callable, 'history_handler':is_callable, 'completion_handler':is_callable, 'help_text':is_string, 'cloak_and_dagger_verbose':is_boolean, 'cumulative_output':is_string, 'prompts_are_never_empty':is_boolean, 'previous_tag':is_integer, 'previous_message':is_string, 'echo_has_been_handled':is_boolean, 'saved_output':is_string, 'prompt_rejected':is_string, 'command_line':is_string, 'running_under_rlwrap':is_boolean, 'minimal_rlwrap_version':is_float, 'name':is_string } self.__dict__['_fields'] = self.__dict__['_field_types'].keys() for field in self._fields: self.__dict__[field] = None self.previous_tag = -1 self.echo_has_been_handled = False self.saved_output = '' self.cumulative_output = '' self.minimal_rlwrap_version = rlwrap_version self.command_line = os.environ.get('RLWRAP_COMMAND_LINE') self.running_under_rlwrap = 'RLWRAP_COMMAND_PID' in os.environ self.name = os.path.basename(sys.argv[0]) for key in kwargs: exec('self.{0} = kwargs[key]'.format(key)) def handle_output(self, message): """ split output in echo and the rest and call the appropriate handlers on them """ (handled_echo, nl) = ('', '') if (self.previous_tag is not None and self.previous_tag == TAG_INPUT): self.cumulative_output = "" self.echo_has_been_handled = False if (not self.echo_has_been_handled): if (not re.search(r'\n', message)): # save all output until we have one *whole* echo line self.saved_output = self.saved_output + message return "" else: # ... then process it message = self.saved_output + message self.echo_has_been_handled = True self.saved_output = "" (echo, nl, message) = re.match(r'^([^\n\r]*)(\r?\n)?(.*)?', message, re.DOTALL).groups() handled_echo = when_defined(self.echo_handler, echo) self.cumulative_output = self.cumulative_output + message return handled_echo + str(nl or "") + str(when_defined(self.output_handler, message)) def add_to_completion_list(self, *args): write_message(TAG_ADD_TO_COMPLETION_LIST, ' '.join(args)) def remove_from_completion_list(self, *args): write_message(TAG_REMOVE_FROM_COMPLETION_LIST, ' '.join(args)) def cloak_and_dagger(self, question, prompt, timeout, prompt_search_from=0, prompt_search_to=None): """ have a private chat with the rlwrapped command. This relies very much om the assumption that command stops. talking, and only listens, when it has displayed the prompt """ write_patiently(CMD_IN, bytearray(question + "\n", sys.stdin.encoding)) if (self.cloak_and_dagger_verbose): self.send_output_oob("cloak_and_dagger question: {0}\n".format(question)) response = read_until(CMD_OUT, prompt, timeout, prompt_search_from=prompt_search_from, prompt_search_to=prompt_search_to) response = re.sub('^.*?\n', '', response) # chop off echoed question; response = re.sub('{0}$'.format(prompt), '', response) # chop off prompt; if (self.cloak_and_dagger_verbose): self.send_output_oob("cloak_and_dagger response: {0}\n".format(response)) return response def vacuum_stale_message(self, prompt, timeout): """ Some command returns messages asynchronously and tends to delay message when invoking multiple `cloak_and_dagger`. You may want to drop message at such time. rlwrap_filter.cloak_and_dagger(command1, prompt, timeout) rlwrap_filter.cloak_and_dagger(command2, prompt, timeout) ... time.sleep(1) rlwrap_filter.vacuum_stale_message(prompt, timeout) """ response = read_until(CMD_OUT, prompt, timeout) return response def add_interests(self, message): interested = list(message) tag2handler = {TAG_OUTPUT : self.output_handler or self.echo_handler, # echo is the first OUTPUT after an INPUT TAG_INPUT : self.input_handler or self.echo_handler, # so to determine what is ECHO we need to see INPUT... TAG_HISTORY : self.history_handler, TAG_COMPLETION : self.completion_handler, TAG_PROMPT : self.prompt_handler, TAG_HOTKEY : self.hotkey_handler, TAG_SIGNAL : self.signal_handler} for tag in range(0, len(message)): if interested[tag] == 'y': continue # a preceding filter in the pipeline has already shown interest if tag2handler[tag] is not None: interested[tag] = 'y' return ''.join(interested) def name2tag(self, name): """ Convert a valid tag name like " TAG_PROMPT " to a tag (an integer) """ return name2tag(name) def tag2name(self, tag): """ Convert the tag (an integer) to its name (e.g. " TAG_PROMPT ") """ return tag2name(tag) def warn(self, message): """ send message to rlwrap. """ send_warn(message) def error(self, message,e = None): """ send message to rlwrap, and raise e. """ send_error(message, e) def send_output_oob(self, text): write_message(TAG_OUTPUT_OUT_OF_BAND, text) def send_ignore_oob(self, text): write_message(TAG_IGNORE, text) def tweak_readline_oob(self, rl_tweak, *args): nargs = {'rl_variable_bind' : 2, 'rl_completer_word_break_characters' : 1, 'rl_completer_quote_characters' : 1, 'rl_filename_completion_desired' : 1} # the above list can be extended in future versions if rl_tweak not in nargs: self.error("tweak_readline_oob() called with unknown/unimplemented readline function '{}'".format(rl_tweak)) if len(args) != nargs[rl_tweak]: self.error("tweak_readline_oob({},...) should be called with exactly {} args".format(rl_tweak, nargs[rl_tweak] + 1)) self.send_ignore_oob("@" + "::".join((rl_tweak,) + args + ("\n",))) def cwd(self): return os.getcwd() def run(self): """ event loop """ # $RLWRAP_COMMAND_PID can be undefined (e.g. when run interactively, or under rlwrap -z listing # or == "0" (when rlwrap is called without a command name, like in rlwrap -z filter.py) # In both cases: print help text if os.environ.get('RLWRAP_COMMAND_PID') in [None, '0']: write_message(TAG_OUTPUT_OUT_OF_BAND, self.help_text + '\n') while(True): tag, message = read_message() message = when_defined(self.message_handler, message, tag) # ignore return value if (tag == TAG_INPUT): response = when_defined(self.input_handler, message) elif (tag == TAG_OUTPUT): response = self.handle_output(message) elif (tag == TAG_HISTORY): response = when_defined(self.history_handler, message) elif (tag == TAG_COMPLETION): if (self.completion_handler is not None): params = split_rlwrap_message(message) (line, prefix, completions) = (params[0], params[1], params[2:]) completions = self.completion_handler(line, prefix, completions) response = merge_fields([line, prefix] + completions) else: response = message elif (tag == TAG_HOTKEY): if (self.hotkey_handler is not None): params = split_rlwrap_message(message) result = self.hotkey_handler(*params) response = merge_fields(result) else: response = message elif (tag == TAG_PROMPT): if (message == REJECT_PROMPT or (self.prompts_are_never_empty is not None and message == '')): write_message(tag,REJECT_PROMPT); # don't update and don't reset next if (os.environ.get('RLWRAP_IMPATIENT') and not re.search('\n$', self.cumulative_output)): # cumulative output contains prompt: chop it off! # s/[^\n]*$// takes way too long on big strings, # what is the optimal regex to do this? self.cumulative_output = re.sub('(? name; $filter -> help_text("Usage: rlwrap -z '$name [-o]' \n". "removes all junk from prompt\n" ); $filter -> prompt_handler(\&scrub); $filter -> run; sub scrub { my ($dirty) = @_; my $clean = $dirty; $clean =~ s/^.*\r//; $clean =~ s/\x1b]0;.*?\x07//g; # remove xterm title control sequence $clean =~ s/\x1b.*?m//g; # remove colour codes $clean =~ s/\x1b\[K//g; # clear to end of line in xterm $clean =~ s/[\x01-\x1f]//g; $clean =~ /[[control:]]/ and die "couldn't clean promt: still dirty\n"; while ($clean =~ s/.\x08//g) {}; # let backspaces eat the chars before them return $clean; } rlwrap-0.46.1/filters/simple_macro000077500000000000000000000043061433170252700171700ustar00rootroot00000000000000#!/usr/bin/env perl # this filter tests the use of echo, completion and input handlers # it completes by returning a macro expansion which doens't need to start with the prefix # (zsh users are used to this) use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my(%expansions, $last_raw_input); my $saved_macros = $ARGV[0]; read_macros(); $SIG{__DIE__} = \&save_macros; my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text(help()); $filter -> input_handler(\&expand_and_record_macros); # expand macros in input $filter -> echo_handler(sub {$last_raw_input}); # but echo the raw (un-expanded) input $filter -> completion_handler(\&complete_macro); $filter -> run; sub expand_and_record_macros { my ($unexpanded) = @_; my $expanded = $last_raw_input = $unexpanded; $expanded =~ s/\$(\w+)(\(\((.*?)\)\))?/findmacro($1,$3)/ge; return $expanded; } sub findmacro { my($macro,$expansion) = @_; return $expansions{$macro} = $expansion if $expansion; return $expansions{$macro}; } sub complete_macro { my($line, $prefix, @completions) = @_; # print STDERR "prefix <$prefix>\n"; if ($prefix =~ /^\$(\w+)$/) { my $expansion = $expansions{$1}; unshift @completions, $expansion if $expansion; } return @completions; } sub read_macros { return unless $saved_macros; if (-f $saved_macros) { -w $saved_macros or die "$saved_macros exists but is not writable!\n"; open MACROS, $saved_macros or die "$saved_macros exists but is not readable!\n"; while() { chomp; my($macro, $expansion) = /(\S+)\s+(.*)/; $expansions{$macro} = $expansion; } } } sub save_macros { return unless $saved_macros; open MACROS, ">$saved_macros" or die "cannot write to $saved_macros: $!\n"; # ths error may be lost foreach my $macro (sort keys %expansions) { print MACROS "$macro $expansions{$macro}\n"; } } sub help { return <]' simple on-the-fly macro processing type \$macro to use a macro type \$macro to expand it right now ('\$' should not be word-breaking, so you may need to call rlwrap with -b '') \$macro((ex pan sion)) to (re-)define macros are kept in between sessions EOF } rlwrap-0.46.1/filters/template000077500000000000000000000004011433170252700163210ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text("Usage: rlwrap -z $name \n". "filter template"); $filter -> run; rlwrap-0.46.1/filters/unbackspace000077500000000000000000000011411433170252700167670ustar00rootroot00000000000000#!/usr/bin/env perl use lib ($ENV{RLWRAP_FILTERDIR} or "."); use RlwrapFilter; use strict; my $filter = new RlwrapFilter; my $name = $filter -> name; $filter -> help_text("Usage: rlwrap -z $name \n". "remove backspaces from output\n". "(after letting them 'eat' their preceding characters)"); $filter -> echo_handler(\&eat_backspaces); $filter -> output_handler(\&eat_backspaces); $filter -> run; sub eat_backspaces { while(s/.\x08//g) {} # let them eat s/\x08//g; # remove the rest s/\rrb_cmp)(s, t, e) #undef RB_INLINE #define RB_ENTRY(name) rb##name #endif /* RB_CUSTOMIZE */ #ifndef RB_STATIC #define RB_STATIC #endif /* Modes for rblookup */ #define RB_NONE -1 /* None of those below */ #define RB_LUEQUAL 0 /* Only exact match */ #define RB_LUGTEQ 1 /* Exact match or greater */ #define RB_LULTEQ 2 /* Exact match or less */ #define RB_LULESS 3 /* Less than key (not equal to) */ #define RB_LUGREAT 4 /* Greater than key (not equal to) */ #define RB_LUNEXT 5 /* Next key after current */ #define RB_LUPREV 6 /* Prev key before current */ #define RB_LUFIRST 7 /* First key in index */ #define RB_LULAST 8 /* Last key in index */ /* For rbwalk - pinched from search.h */ typedef enum { preorder, postorder, endorder, leaf } VISIT; struct RB_ENTRY(lists) { const struct RB_ENTRY(node) *rootp; const struct RB_ENTRY(node) *nextp; }; #define RBLIST struct RB_ENTRY(lists) struct RB_ENTRY(tree) { #ifndef RB_CUSTOMIZE /* comparison routine */ int (*rb_cmp)(const void *, const void *, const void *); /* config data to be passed to rb_cmp */ const void *rb_config; /* root of tree */ #endif /* RB_CUSTOMIZE */ struct RB_ENTRY(node) *rb_root; }; #ifndef RB_CUSTOMIZE RB_STATIC struct RB_ENTRY(tree) *rbinit(int (*)(const void *, const void *, const void *), const void *); #else RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void); #endif /* RB_CUSTOMIZE */ #ifndef no_delete RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(delete)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_find RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(find)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_lookup RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(lookup)(int, const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_search RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(search)(const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_destroy RB_STATIC void RB_ENTRY(destroy)(struct RB_ENTRY(tree) *); #endif #ifndef no_walk RB_STATIC void RB_ENTRY(walk)(const struct RB_ENTRY(tree) *, void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *); #endif #ifndef no_readlist RB_STATIC RBLIST *RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *); RB_STATIC const RB_ENTRY(data_t) *RB_ENTRY(readlist)(RBLIST *); RB_STATIC void RB_ENTRY(closelist)(RBLIST *); #endif /* Some useful macros */ #define rbmin(rbinfo) RB_ENTRY(lookup)(RB_LUFIRST, NULL, (rbinfo)) #define rbmax(rbinfo) RB_ENTRY(lookup)(RB_LULAST, NULL, (rbinfo)) #define _REDBLACK_H #endif /* _REDBLACK_H */ /* * * $Log: redblack.h,v $ * Revision 1.9 2003/10/24 01:31:21 damo * Patches from Eric Raymond: %prefix is implemented.  Various other small * changes avoid stepping on global namespaces and improve the documentation. * * Revision 1.8 2003/10/23 04:18:47 damo * Fixed up the rbgen stuff ready for the 1.3 release * * Revision 1.7 2002/08/26 03:11:40 damo * Fixed up a bunch of compiler warnings when compiling example4 * * Tidies up the Makefile.am & Specfile. * * Renamed redblack to rbgen * * Revision 1.6 2002/08/26 01:03:35 damo * Patch from Eric Raymond to change the way the library is used:- * * Eric's idea is to convert libredblack into a piece of in-line code * generated by another program. This should be faster, smaller and easier * to use. * * This is the first check-in of his code before I start futzing with it! * * Revision 1.5 2002/01/30 07:54:53 damo * Fixed up the libtool versioning stuff (finally) * Fixed bug 500600 (not detecting a NULL return from malloc) * Fixed bug 509485 (no longer needs search.h) * Cleaned up debugging section * Allow multiple inclusions of redblack.h * Thanks to Matthias Andree for reporting (and fixing) these * * Revision 1.4 2000/06/06 14:43:43 damo * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. * Added two new examples * * Revision 1.3 2000/05/24 06:45:27 damo * Converted everything over to using const * Added a new example1.c file to demonstrate the worst case scenario * Minor fixups of the spec file * * Revision 1.2 2000/05/24 06:17:10 damo * Fixed up the License (now the LGPL) * * Revision 1.1 2000/05/24 04:15:53 damo * Initial import of files. Versions are now all over the place. Oh well * */ static char rcsid[]="$Id: redblack.c,v 1.9 2003/10/24 01:31:21 damo Exp $"; /* Redblack balanced tree algorithm Copyright (C) Damian Ivereigh 2000 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for details. 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 Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Implement the red/black tree structure. It is designed to emulate ** the standard tsearch() stuff. i.e. the calling conventions are ** exactly the same */ #include #include #include #include "redblack.h" #define assert(expr) /* Uncomment this if you would rather use a raw sbrk to get memory ** (however the memory is never released again (only re-used). Can't ** see any point in using this these days. */ /* #define USE_SBRK */ enum nodecolour { BLACK, RED }; struct RB_ENTRY(node) { struct RB_ENTRY(node) *left; /* Left down */ struct RB_ENTRY(node) *right; /* Right down */ struct RB_ENTRY(node) *up; /* Up */ enum nodecolour colour; /* Node colour */ #ifdef RB_INLINE RB_ENTRY(data_t) key; /* User's key (and data) */ #define RB_GET(x,y) &x->y #define RB_SET(x,y,v) x->y = *(v) #else const RB_ENTRY(data_t) *key; /* Pointer to user's key (and data) */ #define RB_GET(x,y) x->y #define RB_SET(x,y,v) x->y = v #endif /* RB_INLINE */ }; /* Dummy (sentinel) node, so that we can make X->left->up = X ** We then use this instead of NULL to mean the top or bottom ** end of the rb tree. It is a black node. ** ** Initialization of the last field in this initializer is left implicit ** because it could be of any type. We count on the compiler to zero it. */ struct RB_ENTRY(node) RB_ENTRY(_null)={&RB_ENTRY(_null), &RB_ENTRY(_null), &RB_ENTRY(_null), BLACK}; #define RBNULL (&RB_ENTRY(_null)) #if defined(USE_SBRK) static struct RB_ENTRY(node) *RB_ENTRY(_alloc)(); static void RB_ENTRY(_free)(struct RB_ENTRY(node) *); #else static struct RB_ENTRY(node) *RB_ENTRY(_alloc)() {return (struct RB_ENTRY(node) *) malloc(sizeof(struct RB_ENTRY(node)));} static void RB_ENTRY(_free)(struct RB_ENTRY(node) *x) {free(x);} #endif /* These functions are always needed */ static void RB_ENTRY(_left_rotate)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); static void RB_ENTRY(_right_rotate)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); static struct RB_ENTRY(node) *RB_ENTRY(_successor)(const struct RB_ENTRY(node) *); static struct RB_ENTRY(node) *RB_ENTRY(_predecessor)(const struct RB_ENTRY(node) *); static struct RB_ENTRY(node) *RB_ENTRY(_traverse)(int, const RB_ENTRY(data_t) * , struct RB_ENTRY(tree) *); /* These functions may not be needed */ #ifndef no_lookup static struct RB_ENTRY(node) *RB_ENTRY(_lookup)(int, const RB_ENTRY(data_t) * , struct RB_ENTRY(tree) *); #endif #ifndef no_destroy static void RB_ENTRY(_destroy)(struct RB_ENTRY(node) *); #endif #ifndef no_delete static void RB_ENTRY(_delete)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); static void RB_ENTRY(_delete_fix)(struct RB_ENTRY(node) **, struct RB_ENTRY(node) *); #endif #ifndef no_walk static void RB_ENTRY(_walk)(const struct RB_ENTRY(node) *, void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *, int); #endif #ifndef no_readlist static RBLIST *RB_ENTRY(_openlist)(const struct RB_ENTRY(node) *); static const RB_ENTRY(data_t) * RB_ENTRY(_readlist)(RBLIST *); static void RB_ENTRY(_closelist)(RBLIST *); #endif /* ** OK here we go, the balanced tree stuff. The algorithm is the ** fairly standard red/black taken from "Introduction to Algorithms" ** by Cormen, Leiserson & Rivest. Maybe one of these days I will ** fully understand all this stuff. ** ** Basically a red/black balanced tree has the following properties:- ** 1) Every node is either red or black (colour is RED or BLACK) ** 2) A leaf (RBNULL pointer) is considered black ** 3) If a node is red then its children are black ** 4) Every path from a node to a leaf contains the same no ** of black nodes ** ** 3) & 4) above guarantee that the longest path (alternating ** red and black nodes) is only twice as long as the shortest ** path (all black nodes). Thus the tree remains fairly balanced. */ /* * Initialise a tree. Identifies the comparison routine and any config * data that must be sent to it when called. * Returns a pointer to the top of the tree. */ #ifndef RB_CUSTOMIZE RB_STATIC struct RB_ENTRY(tree) * rbinit(int (*cmp)(const void *, const void *, const void *), const void *config) #else RB_STATIC struct RB_ENTRY(tree) *RB_ENTRY(init)(void) #endif /* RB_CUSTOMIZE */ { struct RB_ENTRY(tree) *retval; char c; c=rcsid[0]; /* This does nothing but shutup the -Wall */ if ((retval=(struct RB_ENTRY(tree) *) malloc(sizeof(struct RB_ENTRY(tree))))==NULL) return(NULL); #ifndef RB_CUSTOMIZE retval->rb_cmp=cmp; retval->rb_config=config; #endif /* RB_CUSTOMIZE */ retval->rb_root=RBNULL; return(retval); } #ifndef no_destroy RB_STATIC void RB_ENTRY(destroy)(struct RB_ENTRY(tree) *rbinfo) { if (rbinfo==NULL) return; if (rbinfo->rb_root!=RBNULL) RB_ENTRY(_destroy)(rbinfo->rb_root); free(rbinfo); } #endif /* no_destroy */ #ifndef no_search RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(search)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; if (rbinfo==NULL) return(NULL); x=RB_ENTRY(_traverse)(1, key, rbinfo); return((x==RBNULL) ? NULL : RB_GET(x, key)); } #endif /* no_search */ #ifndef no_find RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(find)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; if (rbinfo==NULL) return(NULL); /* If we have a NULL root (empty tree) then just return */ if (rbinfo->rb_root==RBNULL) return(NULL); x=RB_ENTRY(_traverse)(0, key, rbinfo); return((x==RBNULL) ? NULL : RB_GET(x, key)); } #endif /* no_find */ #ifndef no_delete RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(delete)(const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; const RB_ENTRY(data_t) * y; if (rbinfo==NULL) return(NULL); x=RB_ENTRY(_traverse)(0, key, rbinfo); if (x==RBNULL) { return(NULL); } else { y=RB_GET(x, key); RB_ENTRY(_delete)(&rbinfo->rb_root, x); return(y); } } #endif /* no_delete */ #ifndef no_walk RB_STATIC void RB_ENTRY(walk)(const struct RB_ENTRY(tree) *rbinfo, void (*action)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *arg) { if (rbinfo==NULL) return; RB_ENTRY(_walk)(rbinfo->rb_root, action, arg, 0); } #endif /* no_walk */ #ifndef no_readlist RB_STATIC RBLIST * RB_ENTRY(openlist)(const struct RB_ENTRY(tree) *rbinfo) { if (rbinfo==NULL) return(NULL); return(RB_ENTRY(_openlist)(rbinfo->rb_root)); } RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(readlist)(RBLIST *rblistp) { if (rblistp==NULL) return(NULL); return(RB_ENTRY(_readlist)(rblistp)); } RB_STATIC void RB_ENTRY(closelist)(RBLIST *rblistp) { if (rblistp==NULL) return; RB_ENTRY(_closelist)(rblistp); } #endif /* no_readlist */ #ifndef no_lookup RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(lookup)(int mode, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x; /* If we have a NULL root (empty tree) then just return NULL */ if (rbinfo==NULL || rbinfo->rb_root==NULL) return(NULL); x=RB_ENTRY(_lookup)(mode, key, rbinfo); return((x==RBNULL) ? NULL : RB_GET(x, key)); } #endif /* no_lookup */ /* --------------------------------------------------------------------- */ /* Search for and if not found and insert is true, will add a new ** node in. Returns a pointer to the new node, or the node found */ static struct RB_ENTRY(node) * RB_ENTRY(_traverse)(int insert, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x,*y,*z; int cmp; int found=0; int cmpmods(); y=RBNULL; /* points to the parent of x */ x=rbinfo->rb_root; /* walk x down the tree */ while(x!=RBNULL && found==0) { y=x; /* printf("key=%s, RB_GET(x, key)=%s\n", key, RB_GET(x, key)); */ #ifndef RB_CUSTOMIZE cmp=RB_CMP(key, RB_GET(x, key), rbinfo->rb_config); #else cmp=RB_CMP(key, RB_GET(x, key)); #endif /* RB_CUSTOMIZE */ if (cmp<0) x=x->left; else if (cmp>0) x=x->right; else found=1; } if (found || !insert) return(x); if ((z=RB_ENTRY(_alloc)())==NULL) { /* Whoops, no memory */ return(RBNULL); } RB_SET(z, key, key); z->up=y; if (y==RBNULL) { rbinfo->rb_root=z; } else { #ifndef RB_CUSTOMIZE cmp=RB_CMP(RB_GET(z, key), RB_GET(y, key), rbinfo->rb_config); #else cmp=RB_CMP(RB_GET(z, key), RB_GET(y, key)); #endif /* RB_CUSTOMIZE */ if (cmp<0) y->left=z; else y->right=z; } z->left=RBNULL; z->right=RBNULL; /* colour this new node red */ z->colour=RED; /* Having added a red node, we must now walk back up the tree balancing ** it, by a series of rotations and changing of colours */ x=z; /* While we are not at the top and our parent node is red ** N.B. Since the root node is garanteed black, then we ** are also going to stop if we are the child of the root */ while(x != rbinfo->rb_root && (x->up->colour == RED)) { /* if our parent is on the left side of our grandparent */ if (x->up == x->up->up->left) { /* get the right side of our grandparent (uncle?) */ y=x->up->up->right; if (y->colour == RED) { /* make our parent black */ x->up->colour = BLACK; /* make our uncle black */ y->colour = BLACK; /* make our grandparent red */ x->up->up->colour = RED; /* now consider our grandparent */ x=x->up->up; } else { /* if we are on the right side of our parent */ if (x == x->up->right) { /* Move up to our parent */ x=x->up; RB_ENTRY(_left_rotate)(&rbinfo->rb_root, x); } /* make our parent black */ x->up->colour = BLACK; /* make our grandparent red */ x->up->up->colour = RED; /* right rotate our grandparent */ RB_ENTRY(_right_rotate)(&rbinfo->rb_root, x->up->up); } } else { /* everything here is the same as above, but ** exchanging left for right */ y=x->up->up->left; if (y->colour == RED) { x->up->colour = BLACK; y->colour = BLACK; x->up->up->colour = RED; x=x->up->up; } else { if (x == x->up->left) { x=x->up; RB_ENTRY(_right_rotate)(&rbinfo->rb_root, x); } x->up->colour = BLACK; x->up->up->colour = RED; RB_ENTRY(_left_rotate)(&rbinfo->rb_root, x->up->up); } } } /* Set the root node black */ (rbinfo->rb_root)->colour = BLACK; return(z); } #ifndef no_lookup /* Search for a key according to mode (see redblack.h) */ static struct RB_ENTRY(node) * RB_ENTRY(_lookup)(int mode, const RB_ENTRY(data_t) *key, struct RB_ENTRY(tree) *rbinfo) { struct RB_ENTRY(node) *x,*y; int cmp; int found=0; y=RBNULL; /* points to the parent of x */ x=rbinfo->rb_root; if (mode==RB_LUFIRST) { /* Keep going left until we hit a NULL */ while(x!=RBNULL) { y=x; x=x->left; } return(y); } else if (mode==RB_LULAST) { /* Keep going right until we hit a NULL */ while(x!=RBNULL) { y=x; x=x->right; } return(y); } /* walk x down the tree */ while(x!=RBNULL && found==0) { y=x; /* printf("key=%s, RB_GET(x, key)=%s\n", key, RB_GET(x, key)); */ #ifndef RB_CUSTOMIZE cmp=RB_CMP(key, RB_GET(x, key), rbinfo->rb_config); #else cmp=RB_CMP(key, RB_GET(x, key)); #endif /* RB_CUSTOMIZE */ if (cmp<0) x=x->left; else if (cmp>0) x=x->right; else found=1; } if (found && (mode==RB_LUEQUAL || mode==RB_LUGTEQ || mode==RB_LULTEQ)) return(x); if (!found && (mode==RB_LUEQUAL || mode==RB_LUNEXT || mode==RB_LUPREV)) return(RBNULL); if (mode==RB_LUGTEQ || (!found && mode==RB_LUGREAT)) { if (cmp>0) return(RB_ENTRY(_successor)(y)); else return(y); } if (mode==RB_LULTEQ || (!found && mode==RB_LULESS)) { if (cmp<0) return(RB_ENTRY(_predecessor)(y)); else return(y); } if (mode==RB_LUNEXT || (found && mode==RB_LUGREAT)) return(RB_ENTRY(_successor)(x)); if (mode==RB_LUPREV || (found && mode==RB_LULESS)) return(RB_ENTRY(_predecessor)(x)); /* Shouldn't get here */ return(RBNULL); } #endif /* no_lookup */ #ifndef no_destroy /* * Destroy all the elements blow us in the tree * only useful as part of a complete tree destroy. */ static void RB_ENTRY(_destroy)(struct RB_ENTRY(node) *x) { if (x!=RBNULL) { if (x->left!=RBNULL) RB_ENTRY(_destroy)(x->left); if (x->right!=RBNULL) RB_ENTRY(_destroy)(x->right); RB_ENTRY(_free)(x); } } #endif /* no_destroy */ /* ** Rotate our tree thus:- ** ** X rb_left_rotate(X)---> Y ** / \ / \ ** A Y <---rb_right_rotate(Y) X C ** / \ / \ ** B C A B ** ** N.B. This does not change the ordering. ** ** We assume that neither X or Y is NULL */ static void RB_ENTRY(_left_rotate)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *y; assert(x!=RBNULL); assert(x->right!=RBNULL); y=x->right; /* set Y */ /* Turn Y's left subtree into X's right subtree (move B)*/ x->right = y->left; /* If B is not null, set it's parent to be X */ if (y->left != RBNULL) y->left->up = x; /* Set Y's parent to be what X's parent was */ y->up = x->up; /* if X was the root */ if (x->up == RBNULL) { *rootp=y; } else { /* Set X's parent's left or right pointer to be Y */ if (x == x->up->left) { x->up->left=y; } else { x->up->right=y; } } /* Put X on Y's left */ y->left=x; /* Set X's parent to be Y */ x->up = y; } static void RB_ENTRY(_right_rotate)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *y) { struct RB_ENTRY(node) *x; assert(y!=RBNULL); assert(y->left!=RBNULL); x=y->left; /* set X */ /* Turn X's right subtree into Y's left subtree (move B) */ y->left = x->right; /* If B is not null, set it's parent to be Y */ if (x->right != RBNULL) x->right->up = y; /* Set X's parent to be what Y's parent was */ x->up = y->up; /* if Y was the root */ if (y->up == RBNULL) { *rootp=x; } else { /* Set Y's parent's left or right pointer to be X */ if (y == y->up->left) { y->up->left=x; } else { y->up->right=x; } } /* Put Y on X's right */ x->right=y; /* Set Y's parent to be X */ y->up = x; } /* Return a pointer to the smallest key greater than x */ static struct RB_ENTRY(node) * RB_ENTRY(_successor)(const struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *y; if (x->right!=RBNULL) { /* If right is not NULL then go right one and ** then keep going left until we find a node with ** no left pointer. */ for (y=x->right; y->left!=RBNULL; y=y->left); } else { /* Go up the tree until we get to a node that is on the ** left of its parent (or the root) and then return the ** parent. */ y=x->up; while(y!=RBNULL && x==y->right) { x=y; y=y->up; } } return(y); } /* Return a pointer to the largest key smaller than x */ static struct RB_ENTRY(node) * RB_ENTRY(_predecessor)(const struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *y; if (x->left!=RBNULL) { /* If left is not NULL then go left one and ** then keep going right until we find a node with ** no right pointer. */ for (y=x->left; y->right!=RBNULL; y=y->right); } else { /* Go up the tree until we get to a node that is on the ** right of its parent (or the root) and then return the ** parent. */ y=x->up; while(y!=RBNULL && x==y->left) { x=y; y=y->up; } } return(y); } #ifndef no_delete /* Delete the node z, and free up the space */ static void RB_ENTRY(_delete)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *z) { struct RB_ENTRY(node) *x, *y; if (z->left == RBNULL || z->right == RBNULL) y=z; else y=RB_ENTRY(_successor)(z); if (y->left != RBNULL) x=y->left; else x=y->right; x->up = y->up; if (y->up == RBNULL) { *rootp=x; } else { if (y==y->up->left) y->up->left = x; else y->up->right = x; } if (y!=z) { RB_SET(z, key, RB_GET(y, key)); } if (y->colour == BLACK) RB_ENTRY(_delete_fix)(rootp, x); RB_ENTRY(_free)(y); } /* Restore the reb-black properties after a delete */ static void RB_ENTRY(_delete_fix)(struct RB_ENTRY(node) **rootp, struct RB_ENTRY(node) *x) { struct RB_ENTRY(node) *w; while (x!=*rootp && x->colour==BLACK) { if (x==x->up->left) { w=x->up->right; if (w->colour==RED) { w->colour=BLACK; x->up->colour=RED; rb_left_rotate(rootp, x->up); w=x->up->right; } if (w->left->colour==BLACK && w->right->colour==BLACK) { w->colour=RED; x=x->up; } else { if (w->right->colour == BLACK) { w->left->colour=BLACK; w->colour=RED; RB_ENTRY(_right_rotate)(rootp, w); w=x->up->right; } w->colour=x->up->colour; x->up->colour = BLACK; w->right->colour = BLACK; RB_ENTRY(_left_rotate)(rootp, x->up); x=*rootp; } } else { w=x->up->left; if (w->colour==RED) { w->colour=BLACK; x->up->colour=RED; RB_ENTRY(_right_rotate)(rootp, x->up); w=x->up->left; } if (w->right->colour==BLACK && w->left->colour==BLACK) { w->colour=RED; x=x->up; } else { if (w->left->colour == BLACK) { w->right->colour=BLACK; w->colour=RED; RB_ENTRY(_left_rotate)(rootp, w); w=x->up->left; } w->colour=x->up->colour; x->up->colour = BLACK; w->left->colour = BLACK; RB_ENTRY(_right_rotate)(rootp, x->up); x=*rootp; } } } x->colour=BLACK; } #endif /* no_delete */ #ifndef no_walk static void RB_ENTRY(_walk)(const struct RB_ENTRY(node) *x, void (*action)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *arg, int level) { if (x==RBNULL) return; if (x->left==RBNULL && x->right==RBNULL) { /* leaf */ (*action)(RB_GET(x, key), leaf, level, arg); } else { (*action)(RB_GET(x, key), preorder, level, arg); RB_ENTRY(_walk)(x->left, action, arg, level+1); (*action)(RB_GET(x, key), postorder, level, arg); RB_ENTRY(_walk)(x->right, action, arg, level+1); (*action)(RB_GET(x, key), endorder, level, arg); } } #endif /* no_walk */ #ifndef no_readlist static RBLIST * RB_ENTRY(_openlist)(const struct RB_ENTRY(node) *rootp) { RBLIST *rblistp; rblistp=(RBLIST *) malloc(sizeof(RBLIST)); if (!rblistp) return(NULL); rblistp->rootp=rootp; rblistp->nextp=rootp; if (rootp!=RBNULL) { while(rblistp->nextp->left!=RBNULL) { rblistp->nextp=rblistp->nextp->left; } } return(rblistp); } static const RB_ENTRY(data_t) * RB_ENTRY(_readlist)(RBLIST *rblistp) { const RB_ENTRY(data_t) *key=NULL; if (rblistp!=NULL && rblistp->nextp!=RBNULL) { key=RB_GET(rblistp->nextp, key); rblistp->nextp=RB_ENTRY(_successor)(rblistp->nextp); } return(key); } static void rb_closelist(RBLIST *rblistp) { if (rblistp) free(rblistp); } #endif /* no_readlist */ #if defined(RB_USE_SBRK) /* Allocate space for our nodes, allowing us to get space from ** sbrk in larger chucks. */ static struct RB_ENTRY(node) *rbfreep=NULL; #define RB_ENTRY(NODE)ALLOC_CHUNK_SIZE 1000 static struct RB_ENTRY(node) * RB_ENTRY(_alloc)() { struct RB_ENTRY(node) *x; int i; if (rbfreep==NULL) { /* must grab some more space */ rbfreep=(struct RB_ENTRY(node) *) sbrk(sizeof(struct RB_ENTRY(node)) * RB_ENTRY(NODE)ALLOC_CHUNK_SIZE); if (rbfreep==(struct RB_ENTRY(node) *) -1) { return(NULL); } /* tie them together in a linked list (use the up pointer) */ for (i=0, x=rbfreep; iup = (x+1); } x->up=NULL; } x=rbfreep; rbfreep = rbfreep->up; #ifdef RB_ALLOC RB_ALLOC(ACCESS(x, key)); #endif /* RB_ALLOC */ return(x); } /* free (dealloc) an RB_ENTRY(node) structure - add it onto the front of the list ** N.B. RB_ENTRY(node) need not have been allocated through rb_alloc() */ static void RB_ENTRY(_free)(struct RB_ENTRY(node) *x) { #ifdef RB_FREE RB_FREE(ACCESS(x, key)); #endif /* RB_FREE */ x->up=rbfreep; rbfreep=x; } #endif #if 0 int RB_ENTRY(_check)(struct RB_ENTRY(node) *rootp) { if (rootp==NULL || rootp==RBNULL) return(0); if (rootp->up!=RBNULL) { fprintf(stderr, "Root up pointer not RBNULL"); dumptree(rootp, 0); return(1); } if (RB_ENTRY(_check)1(rootp)) { RB_ENTRY(dumptree)(rootp, 0); return(1); } if (RB_ENTRY(count_black)(rootp)==-1) { RB_ENTRY(dumptree)(rootp, 0); return(-1); } return(0); } int RB_ENTRY(_check1)(struct RB_ENTRY(node) *x) { if (x->left==NULL || x->right==NULL) { fprintf(stderr, "Left or right is NULL"); return(1); } if (x->colour==RED) { if (x->left->colour!=BLACK && x->right->colour!=BLACK) { fprintf(stderr, "Children of red node not both black, x=%ld", x); return(1); } } if (x->left != RBNULL) { if (x->left->up != x) { fprintf(stderr, "x->left->up != x, x=%ld", x); return(1); } if (rb_check1(x->left)) return(1); } if (x->right != RBNULL) { if (x->right->up != x) { fprintf(stderr, "x->right->up != x, x=%ld", x); return(1); } if (rb_check1(x->right)) return(1); } return(0); } RB_ENTRY(count_black)(struct RB_ENTRY(node) *x) { int nleft, nright; if (x==RBNULL) return(1); nleft=RB_ENTRY(count_black)(x->left); nright=RB_ENTRY(count_black)(x->right); if (nleft==-1 || nright==-1) return(-1); if (nleft != nright) { fprintf(stderr, "Black count not equal on left & right, x=%ld", x); return(-1); } if (x->colour == BLACK) { nleft++; } return(nleft); } RB_ENTRY(dumptree)(struct RB_ENTRY(node) *x, int n) { char *prkey(); if (x!=NULL && x!=RBNULL) { n++; fprintf(stderr, "Tree: %*s %ld: left=%ld, right=%ld, colour=%s, key=%s", n, "", x, x->left, x->right, (x->colour==BLACK) ? "BLACK" : "RED", prkey(RB_GET(x, key))); RB_ENTRY(dumptree)(x->left, n); RB_ENTRY(dumptree)(x->right, n); } } #endif /* * $Log: redblack.c,v $ * Revision 1.9 2003/10/24 01:31:21 damo * Patches from Eric Raymond: %prefix is implemented.  Various other small * changes avoid stepping on global namespaces and improve the documentation. * * Revision 1.8 2002/08/26 05:33:47 damo * Some minor fixes:- * Stopped ./configure warning about stuff being in the wrong order * Fixed compiler warning about const (not sure about this) * Changed directory of redblack.c in documentation * * Revision 1.7 2002/08/26 03:11:40 damo * Fixed up a bunch of compiler warnings when compiling example4 * * Tidies up the Makefile.am & Specfile. * * Renamed redblack to rbgen * * Revision 1.6 2002/08/26 01:03:35 damo * Patch from Eric Raymond to change the way the library is used:- * * Eric's idea is to convert libredblack into a piece of in-line code * generated by another program. This should be faster, smaller and easier * to use. * * This is the first check-in of his code before I start futzing with it! * * Revision 1.5 2002/01/30 07:54:53 damo * Fixed up the libtool versioning stuff (finally) * Fixed bug 500600 (not detecting a NULL return from malloc) * Fixed bug 509485 (no longer needs search.h) * Cleaned up debugging section * Allow multiple inclusions of redblack.h * Thanks to Matthias Andree for reporting (and fixing) these * * Revision 1.4 2000/06/06 14:43:43 damo * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. * Added two new examples * * Revision 1.3 2000/05/24 06:45:27 damo * Converted everything over to using const * Added a new example1.c file to demonstrate the worst case scenario * Minor fixups of the spec file * * Revision 1.2 2000/05/24 06:17:10 damo * Fixed up the License (now the LGPL) * * Revision 1.1 2000/05/24 04:15:53 damo * Initial import of files. Versions are now all over the place. Oh well * */ /* rbgen generated code ends here */ #line 77 "completion.rb" /* forward declarations */ static struct rbtree *completion_tree; static char *my_history_completion_function(char *prefix, int state); static void print_list(void); static void my_rbdestroy(struct rbtree *rb) { /* destroy rb tree, freeing the keys first */ const char *key, *lastkey; for (key = rbmin(rb); key; lastkey = key, key = rblookup(RB_LUGREAT, key, rb), free((void *)lastkey)) rbdelete(key, rb); rbdestroy(rb); } static void print_list(void) { const char *word; RBLIST *completion_list = rbopenlist(completion_tree); /* uses mymalloc() internally, so no chance of getting a NULL pointer back */ printf("Completions:\n"); while ((word = rbreadlist(completion_list))) printf("%s\n", word); rbcloselist(completion_list); } static char *rbtree_to_string(const struct rbtree *rb, int max_items) { const char *word; char *result = NULL; int i; RBLIST *list = rbopenlist(rb); for (i = 0; (word = rbreadlist(list)) && (i < max_items); i++) { if (i > 0) result = append_and_free_old(result, ", "); result = append_and_free_old(result, word); } if (i >= max_items) result = append_and_free_old(result, "..."); rbcloselist(list); return result; } void init_completer(void) { completion_tree = rbinit(); } void add_word_to_completions(const char *word) { rbsearch(mysavestring(word), completion_tree); /* the tree stores *pointers* to the words, we have to allocate copies of them ourselves freeing the tree will call free on the pointers to the words valgrind reports the copies as lost, I don't understand this.' */ } void remove_word_from_completions(const char *word) { free((char *) rbdelete(word, completion_tree)); /* why does rbdelete return a const *? I want to be able to free it! */ } void feed_line_into_completion_list(const char *line) { char **words = split_with(line, rl_basic_word_break_characters); char **plist, *word; for(plist = words;(word = *plist); plist++) add_word_to_completions(word); free_splitlist(words); } void feed_file_into_completion_list(const char *completions_file) { FILE *compl_fp; char buffer[BUFFSIZE]; if ((compl_fp = fopen(completions_file, "r")) == NULL) myerror(FATAL|USE_ERRNO, "Could not open %s", completions_file); while (fgets(buffer, BUFFSIZE - 1, compl_fp) != NULL) { buffer[BUFFSIZE - 1] = '\0'; /* make sure buffer is properly terminated (it should be anyway, according to ANSI) */ feed_line_into_completion_list(buffer); } if (! feof(compl_fp) && ferror(compl_fp)) /* at least in GNU libc, errno will be set in this case. If not, no harm is done */ myerror(FATAL|USE_ERRNO, "Couldn't read completions from %s", completions_file); fclose(compl_fp); /* print_list(); */ } #define COMPLETE_FILENAMES 1 #define COMPLETE_FROM_LIST 2 #define COMPLETE_USERNAMES 4 #define FILTER_COMPLETIONS 8 #define COMPLETE_PARANORMALLY 16 /* read user's thoughts */ int get_completion_type(void) { /* some day, this function will inspect the current line and make rlwrap complete differently according to the word *preceding* the one we're completing ' */ return (COMPLETE_FROM_LIST | (complete_filenames ? COMPLETE_FILENAMES : 0) | (filter_pid ? FILTER_COMPLETIONS : 0)); } /* helper function for my_completion_function */ static int is_prefix(const char *s0, const char *s1) { /* s0 is prefix of s1 */ const char *p0, *p1; int count; for (count = 0, p0 = s0, p1 = s1; *p0; count++, p0++, p1++) { char c0 = completion_is_case_sensitive ? *p0 : tolower(*p0); char c1 = completion_is_case_sensitive ? *p1 : tolower(*p1); if (c0 != c1 || count == BUFFSIZE) return FALSE; } return TRUE; } /* See readline doumentation: this function is called by readline whenever a completion is needed. The first time state == 0, whwnever the user presses TAB to cycle through the list, my_completion_function() is called again, but then with state != 0 It should return the completion, which then will be freed by readline (so we'll hand back a copy instead of the real thing) ' */ char * my_completion_function(char *prefix, int state) { static struct rbtree *scratch_tree = NULL; static RBLIST *scratch_list = NULL; /* should remain unchanged between invocations */ int completion_type, count; const char *word; const char *completion; rl_completion_append_character = *extra_char_after_completion; /* if (*prefix == '!') return my_history_completion_function(prefix + 1, state); */ if (state == 0) { /* first time we're called for this prefix ' */ if (scratch_list) rbcloselist(scratch_list); if (scratch_tree) my_rbdestroy(scratch_tree); scratch_tree = rbinit(); /* allocate scratch_tree. We will use this to get a sorted list of completions */ /* now find all possible completions: */ completion_type = get_completion_type(); DPRINTF2(DEBUG_ALL, "completion_type: %d, filter_pid: %d", completion_type, filter_pid); if (completion_type & COMPLETE_FROM_LIST) { for (word = rblookup(RB_LUGTEQ, prefix, completion_tree); /* start with first word >= prefix */ word && is_prefix(prefix, word); /* as long as prefix is really prefix of word */ word = rblookup(RB_LUGREAT, word, completion_tree)) { /* find next word in list */ rbsearch(mysavestring(word), scratch_tree); /* insert fresh copy of the word */ /* DPRINTF1(DEBUG_COMPLETION, "Adding %s to completion list ", word); */ } } if (completion_type & COMPLETE_FILENAMES) { change_working_directory(); DPRINTF1(DEBUG_COMPLETION, "Starting milking of rl_filename_completion_function, prefix = <%s> ", prefix); for (count = 0; (word = copy_and_free_string_for_malloc_debug(rl_filename_completion_function(prefix, count))); count++) { /* using rl_filename_completion_function means that completing filenames will always be case-sensitive */ DPRINTF1(DEBUG_COMPLETION, "Adding <%s> to completion list ", word); rbsearch(word, scratch_tree); } } scratch_list = rbopenlist(scratch_tree); /* OK, we now have our list with completions. We may have to filter it ... */ if (completion_type & FILTER_COMPLETIONS) { char *filter_food = NULL; char *filtered, **filtered_components, **plist; int count; /* build the "filter food" (input for the filter) as a field list .... */ filter_food = append_field_and_free_old(filter_food, rl_line_buffer); filter_food = append_field_and_free_old(filter_food, prefix); while((completion = rbreadlist(scratch_list))) filter_food = append_field_and_free_old(filter_food, completion); filtered = pass_through_filter(TAG_COMPLETION, filter_food); free(filter_food); rbcloselist(scratch_list); DPRINTF1(DEBUG_ALL, "Filtered: %s", mangle_string_for_debug_log(filtered, 40)); filtered_components = split_filter_message(filtered, &count); free(filtered); if ( count <2 || strcmp(filtered_components[0], rl_line_buffer) || strcmp(filtered_components[1], prefix)) myerror(FATAL|NOERRNO, "filter has illegally messed with completion message\n"); /* it should ONLY have changed the completion word list */ my_rbdestroy(scratch_tree); /* burn the old scratch tree (but leave the completion tree alone) */ scratch_tree = rbinit(); /* now grow a new one */ for(plist = filtered_components + 2; *plist; plist++) { if (!**plist) continue; /* empty space at beginning or end of the word list results in an empty word, ignore those now */ rbsearch(mysavestring(*plist), scratch_tree); /* add the filtered completions to the new scratch tree */ DPRINTF1(DEBUG_COMPLETION, "Adding %s to completion list ", *plist); } free_splitlist(filtered_components); scratch_list = rbopenlist(scratch_tree); /* flatten the tree into a new list */ DPRINTF1(DEBUG_COMPLETION, "scratch list: %s", rbtree_to_string(scratch_tree, 6)); } /* if (completion_type & FILTER_COMPLETIONS) */ } /* if state == 0 */ /* we get here each time the user presses TAB to cycle through the list */ assert(scratch_tree != NULL); assert(scratch_list != NULL); if ((completion = rbreadlist(scratch_list))) { /* read next possible completion */ struct stat buf; char *copy_for_readline = malloc_foreign(strlen(completion)+1); strcpy(copy_for_readline, completion); rl_filename_completion_desired = rl_filename_quoting_desired = (stat(completion, &buf) ? FALSE : TRUE); DPRINTF1(DEBUG_COMPLETION, "Returning completion to readline: <%s>", copy_for_readline); return copy_for_readline; /* we cannot just return the original as readline will free it (and make rlwrap explode) */ } else { return NULL; } } static char * my_history_completion_function(char *prefix, int state) { while (next_history()); if (state || history_search_prefix(prefix, -1) < 0) return NULL; return mysavestring(current_history()->line); } /* The following sets edit modes for GNU EMACS Local Variables: mode:c End: */ rlwrap-0.46.1/src/completion.rb000066400000000000000000000262161433170252700164110ustar00rootroot00000000000000/* Completion.c is generated from completion.rb by the program rbgen (cf. http://libredblack.sourceforge.net/) completion.rb: maintaining the completion list, my_completion_function() (callback for readline lib) 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #pragma GCC diagnostic ignored "-Wunused-but-set-variable" #pragma GCC diagnostic ignored "-Wunused-function" #include "rlwrap.h" #ifdef assert #undef assert #endif int completion_is_case_sensitive = 1; int compare(const char *string1, const char *string2) { const char *p1; const char *p2; int count; for (p1 = string1, p2 = string2, count = 0; *p1 && *p2 && count < BUFFSIZE; p1++, p2++, count++) { char c1 = completion_is_case_sensitive ? *p1 : tolower(*p1); char c2 = completion_is_case_sensitive ? *p2 : tolower(*p2); if (c1 != c2) return (c1 < c2 ? -1 : 1); } if ((*p1 && *p2) || (!*p1 && !*p2)) return 0; return (*p1 ? 1 : -1); } #ifdef malloc # undef malloc #endif #define malloc(x) mymalloc(x) /* This is a bit evil, but there is no other way to make libredblack use mymalloc() */ /* This file has to be processed by the program rbgen */ %%rbgen %type char %cmp compare %access pointer %static %omit %%rbgen /* forward declarations */ static struct rbtree *completion_tree; static char *my_history_completion_function(char *prefix, int state); static void print_list(void); static void my_rbdestroy(struct rbtree *rb) { /* destroy rb tree, freeing the keys first */ const char *key, *lastkey; for (key = rbmin(rb); key; lastkey = key, key = rblookup(RB_LUGREAT, key, rb), free((void *)lastkey)) rbdelete(key, rb); rbdestroy(rb); } static void print_list(void) { const char *word; RBLIST *completion_list = rbopenlist(completion_tree); /* uses mymalloc() internally, so no chance of getting a NULL pointer back */ printf("Completions:\n"); while ((word = rbreadlist(completion_list))) printf("%s\n", word); rbcloselist(completion_list); } static char *rbtree_to_string(const struct rbtree *rb, int max_items) { const char *word; char *result = NULL; int i; RBLIST *list = rbopenlist(rb); for (i = 0; (word = rbreadlist(list)) && (i < max_items); i++) { if (i > 0) result = append_and_free_old(result, ", "); result = append_and_free_old(result, word); } if (i >= max_items) result = append_and_free_old(result, "..."); rbcloselist(list); return result; } void init_completer(void) { completion_tree = rbinit(); } void add_word_to_completions(const char *word) { rbsearch(mysavestring(word), completion_tree); /* the tree stores *pointers* to the words, we have to allocate copies of them ourselves freeing the tree will call free on the pointers to the words valgrind reports the copies as lost, I don't understand this.' */ } void remove_word_from_completions(const char *word) { free((char *) rbdelete(word, completion_tree)); /* why does rbdelete return a const *? I want to be able to free it! */ } void feed_line_into_completion_list(const char *line) { char **words = split_with(line, rl_basic_word_break_characters); char **plist, *word; for(plist = words;(word = *plist); plist++) add_word_to_completions(word); free_splitlist(words); } void feed_file_into_completion_list(const char *completions_file) { FILE *compl_fp; char buffer[BUFFSIZE]; if ((compl_fp = fopen(completions_file, "r")) == NULL) myerror(FATAL|USE_ERRNO, "Could not open %s", completions_file); while (fgets(buffer, BUFFSIZE - 1, compl_fp) != NULL) { buffer[BUFFSIZE - 1] = '\0'; /* make sure buffer is properly terminated (it should be anyway, according to ANSI) */ feed_line_into_completion_list(buffer); } if (! feof(compl_fp) && ferror(compl_fp)) /* at least in GNU libc, errno will be set in this case. If not, no harm is done */ myerror(FATAL|USE_ERRNO, "Couldn't read completions from %s", completions_file); fclose(compl_fp); /* print_list(); */ } #define COMPLETE_FILENAMES 1 #define COMPLETE_FROM_LIST 2 #define COMPLETE_USERNAMES 4 #define FILTER_COMPLETIONS 8 #define COMPLETE_PARANORMALLY 16 /* read user's thoughts */ int get_completion_type(void) { /* some day, this function will inspect the current line and make rlwrap complete differently according to the word *preceding* the one we're completing ' */ return (COMPLETE_FROM_LIST | (complete_filenames ? COMPLETE_FILENAMES : 0) | (filter_pid ? FILTER_COMPLETIONS : 0)); } /* helper function for my_completion_function */ static int is_prefix(const char *s0, const char *s1) { /* s0 is prefix of s1 */ const char *p0, *p1; int count; for (count = 0, p0 = s0, p1 = s1; *p0; count++, p0++, p1++) { char c0 = completion_is_case_sensitive ? *p0 : tolower(*p0); char c1 = completion_is_case_sensitive ? *p1 : tolower(*p1); if (c0 != c1 || count == BUFFSIZE) return FALSE; } return TRUE; } /* See readline doumentation: this function is called by readline whenever a completion is needed. The first time state == 0, whwnever the user presses TAB to cycle through the list, my_completion_function() is called again, but then with state != 0 It should return the completion, which then will be freed by readline (so we'll hand back a copy instead of the real thing) ' */ char * my_completion_function(char *prefix, int state) { static struct rbtree *scratch_tree = NULL; static RBLIST *scratch_list = NULL; /* should remain unchanged between invocations */ int completion_type, count; const char *word; const char *completion; rl_completion_append_character = *extra_char_after_completion; /* if (*prefix == '!') return my_history_completion_function(prefix + 1, state); */ if (state == 0) { /* first time we're called for this prefix ' */ if (scratch_list) rbcloselist(scratch_list); if (scratch_tree) my_rbdestroy(scratch_tree); scratch_tree = rbinit(); /* allocate scratch_tree. We will use this to get a sorted list of completions */ /* now find all possible completions: */ completion_type = get_completion_type(); DPRINTF2(DEBUG_ALL, "completion_type: %d, filter_pid: %d", completion_type, filter_pid); if (completion_type & COMPLETE_FROM_LIST) { for (word = rblookup(RB_LUGTEQ, prefix, completion_tree); /* start with first word >= prefix */ word && is_prefix(prefix, word); /* as long as prefix is really prefix of word */ word = rblookup(RB_LUGREAT, word, completion_tree)) { /* find next word in list */ rbsearch(mysavestring(word), scratch_tree); /* insert fresh copy of the word */ /* DPRINTF1(DEBUG_COMPLETION, "Adding %s to completion list ", word); */ } } if (completion_type & COMPLETE_FILENAMES) { change_working_directory(); DPRINTF1(DEBUG_COMPLETION, "Starting milking of rl_filename_completion_function, prefix = <%s> ", prefix); for (count = 0; (word = copy_and_free_string_for_malloc_debug(rl_filename_completion_function(prefix, count))); count++) { /* using rl_filename_completion_function means that completing filenames will always be case-sensitive */ DPRINTF1(DEBUG_COMPLETION, "Adding <%s> to completion list ", word); rbsearch(word, scratch_tree); } } scratch_list = rbopenlist(scratch_tree); /* OK, we now have our list with completions. We may have to filter it ... */ if (completion_type & FILTER_COMPLETIONS) { char *filter_food = NULL; char *filtered, **filtered_components, **plist; int count; /* build the "filter food" (input for the filter) as a field list .... */ filter_food = append_field_and_free_old(filter_food, rl_line_buffer); filter_food = append_field_and_free_old(filter_food, prefix); while((completion = rbreadlist(scratch_list))) filter_food = append_field_and_free_old(filter_food, completion); filtered = pass_through_filter(TAG_COMPLETION, filter_food); free(filter_food); rbcloselist(scratch_list); DPRINTF1(DEBUG_ALL, "Filtered: %s", mangle_string_for_debug_log(filtered, 40)); filtered_components = split_filter_message(filtered, &count); free(filtered); if ( count <2 || strcmp(filtered_components[0], rl_line_buffer) || strcmp(filtered_components[1], prefix)) myerror(FATAL|NOERRNO, "filter has illegally messed with completion message\n"); /* it should ONLY have changed the completion word list */ my_rbdestroy(scratch_tree); /* burn the old scratch tree (but leave the completion tree alone) */ scratch_tree = rbinit(); /* now grow a new one */ for(plist = filtered_components + 2; *plist; plist++) { if (!**plist) continue; /* empty space at beginning or end of the word list results in an empty word, ignore those now */ rbsearch(mysavestring(*plist), scratch_tree); /* add the filtered completions to the new scratch tree */ DPRINTF1(DEBUG_COMPLETION, "Adding %s to completion list ", *plist); } free_splitlist(filtered_components); scratch_list = rbopenlist(scratch_tree); /* flatten the tree into a new list */ DPRINTF1(DEBUG_COMPLETION, "scratch list: %s", rbtree_to_string(scratch_tree, 6)); } /* if (completion_type & FILTER_COMPLETIONS) */ } /* if state == 0 */ /* we get here each time the user presses TAB to cycle through the list */ assert(scratch_tree != NULL); assert(scratch_list != NULL); if ((completion = rbreadlist(scratch_list))) { /* read next possible completion */ struct stat buf; char *copy_for_readline = malloc_foreign(strlen(completion)+1); strcpy(copy_for_readline, completion); rl_filename_completion_desired = rl_filename_quoting_desired = (stat(completion, &buf) ? FALSE : TRUE); DPRINTF1(DEBUG_COMPLETION, "Returning completion to readline: <%s>", copy_for_readline); return copy_for_readline; /* we cannot just return the original as readline will free it (and make rlwrap explode) */ } else { return NULL; } } static char * my_history_completion_function(char *prefix, int state) { while (next_history()); if (state || history_search_prefix(prefix, -1) < 0) return NULL; return mysavestring(current_history()->line); } /* The following sets edit modes for GNU EMACS Local Variables: mode:c End: */ rlwrap-0.46.1/src/filter.c000066400000000000000000000331671433170252700153470ustar00rootroot00000000000000/* filter.c : spawning a filter and using it to re-write input, output, history and * (C) 2000-2007 Hans Lub * * 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; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * You may contact the author by e-mail: hanslub42@gmail.com */ /* A filter is an external program run by rlwrap in order to examine and possibly re-write user input, command output, prompts, history entries and completion requests. There can be at most one filter, but this can be a pipeline ('pipeline filter1 : filter2') The filter communicates with rlwrap by reading and writing messages on two pipes. A message is a byte sequence as follows: Tag 1 byte (can be TAG_INPUT, TAG_OUTPUT, TAG_HISTORY, TAG_COMPLETION, TAG_PROMPT, TAG_OUTPUT_OUT_OF_BAND, TAG_ERROR) Length 4 bytes (length of text + closing newline) Text bytes '\n' (so that the filter can be line buffered without hanging rlwrap) Communication is synchronous: after sending a message (and only then) rlwrap waits for an answer, which must have the same tag, but may be preceded by one or more "out of band" messages. If the filter is slow or hangs, rlwrap does the same. A filter can (and should) signal an error by using TAG_ERROR (which will terminate rlwrap). Filter output on stderr is displayed normally, but will mess up the display. Length may be 0. (Example: If we have a prompt-less command, rlwrap will send an empty TAG_PROMPT message, and the filter can send a fancy prompt back. The converse is also possible, of course) A few environment variables are used to inform the filter about file descriptors etc. */ #include "rlwrap.h" static int filter_input_fd = -1; static int filter_output_fd = -1; pid_t filter_pid = 0; static int expected_tag = -1; static char*read_from_filter(int tag); static void write_message(int fd, int tag, const char *string, const char *description); static void write_to_filter(int tag, const char *string); static char* tag2description(int tag); static char *read_tagless(void); void handle_out_of_band(int tag, char *message); static void mypipe(int filedes[2]) { int retval; retval = pipe(filedes); if (retval < 0) myerror(FATAL|USE_ERRNO, "Couldn't create pipe"); } void spawn_filter(const char *filter_command) { int input_pipe_fds[2]; int output_pipe_fds[2]; mypipe(input_pipe_fds); filter_input_fd = input_pipe_fds[1]; /* rlwrap writes filter input to this */ mypipe(output_pipe_fds); filter_output_fd = output_pipe_fds[0]; /* rlwrap reads filter output from here */ DPRINTF1(DEBUG_FILTERING, "preparing to spawn filter <%s>", filter_command); assert(!command_pid || signal_handlers_were_installed); /* if there is a command, then signal handlers are installed */ fflush(NULL); if ((filter_pid = fork()) < 0) myerror(FATAL|USE_ERRNO, "Cannot spawn filter '%s'", filter_command); else if (filter_pid == 0) { /* child */ int signals_to_allow[] = {SIGPIPE, SIGCHLD, SIGALRM, SIGUSR1, SIGUSR2, 0}; char **argv; i_am_filter = TRUE; if (debug) my_fopen(&debug_fp, DEBUG_FILENAME, "a+", "debug log"); unblock_signals(signals_to_allow); /* when we run a pager from a filter we want to catch these */ DEBUG_RANDOM_SLEEP; /* set environment for filter (it needs to know at least the file descriptors for its input and output) */ DPRINTF1(DEBUG_FILTERING, "getenv{RLWRAP_FILTERDIR} = <%s>", strifnull(getenv("RLWRAP_FILTERDIR"))); if ((! getenv("RLWRAP_FILTERDIR")) || (! *getenv("RLWRAP_FILTERDIR"))) mysetenv("RLWRAP_FILTERDIR", add2strings(DATADIR,"/rlwrap/filters")); mysetenv("PATH", add3strings(getenv("RLWRAP_FILTERDIR"),":",getenv("PATH"))); mysetenv("RLWRAP_VERSION", VERSION); mysetenv("RLWRAP_COMMAND_PID", as_string(command_pid)); mysetenv("RLWRAP_COMMAND_LINE", command_line); if (impatient_prompt) mysetenv("RLWRAP_IMPATIENT", "1"); mysetenv("RLWRAP_INPUT_PIPE_FD", as_string(input_pipe_fds[0])); mysetenv("RLWRAP_OUTPUT_PIPE_FD", as_string(output_pipe_fds[1])); mysetenv("RLWRAP_MASTER_PTY_FD", as_string(master_pty_fd)); mysetenv("RLWRAP_BREAK_CHARS", rl_basic_word_break_characters); mysetenv("RLWRAP_DEBUG", as_string(debug)); close(filter_input_fd); close(filter_output_fd); /* @@@TODO: split the command in words (possibly quoted when containing spaces). DONT use the shell (|, < and > are never used on filter command lines */ if (scan_metacharacters(filter_command, "'|\"><")) { /* if filter_command contains shell metacharacters, let the shell unglue them */ char *exec_command = add3strings("exec", " ", filter_command); argv = list4("sh", "-c", exec_command, NULL); DPRINTF1(DEBUG_FILTERING, "exec_command = <%s>", exec_command); } else { /* if not, split and feed to execvp directly (cheaper, better error message) */ argv = split_with(filter_command, " "); } assert(argv[0]); if(execvp(argv[0], argv) < 0) { char *sorry = add3strings("Cannot exec filter '", argv[0], add2strings("': ", strerror(errno))); write_message(output_pipe_fds[1], TAG_ERROR, sorry, "to stdout"); /* this will kill rlwrap */ mymicrosleep(100 * 1000); /* 100 sec for rlwrap to go away should be enough */ exit (-1); } assert(!"not reached"); } else { /* parent */ DEBUG_RANDOM_SLEEP; mysignal(SIGPIPE, SIG_IGN, NULL); /* ignore SIGPIPE - we have othere ways to deal with filter death */ DPRINTF1(DEBUG_FILTERING, "spawned filter with pid %d", filter_pid); close (input_pipe_fds[0]); close (output_pipe_fds[1]); } } void kill_filter(void) { int status; assert (filter_pid && filter_input_fd); close(filter_input_fd); /* filter will see EOF and should exit */ myalarm(40); /* give filter 0.04seconds to go away */ if(!filter_is_dead && /* filter's SIGCHLD hasn't been caught */ waitpid(filter_pid, &status, WNOHANG) < 0 && /* interrupted .. */ WTERMSIG(status) == SIGALRM) { /* .. by alarm (and not e.g. by SIGCHLD) */ myerror(WARNING|NOERRNO, "filter didn't die - killing it now"); } if (filter_pid) kill(filter_pid, SIGKILL); /* do this as a last resort */ myalarm(0); } char *filters_last_words(void) { assert (filter_is_dead); return read_from_filter(TAG_OUTPUT); } int filter_is_interested_in(int tag) { static char *interests = NULL; assert(tag <= MAX_INTERESTING_TAG); if (!interests) { char message[MAX_INTERESTING_TAG + 2]; int i; mymicrosleep(500); /* Kludge - shouldn't the filter talk first - so we know it's alive? */ for (i=0; i <= MAX_INTERESTING_TAG; i++) message[i] = 'n'; message[i] = '\0'; interests = pass_through_filter(TAG_WHAT_ARE_YOUR_INTERESTS, message); if(interests[TAG_SIGNAL] == 'y') myerror(WARNING|NOERRNO, "this filter handles signals, which means that signals are blocked during filter processing\n" "if the filter hangs, you won't be able to interrupt with e.g. CTRL-C (use kill -9 %d instead) ", getpid()); } return (interests[tag] == 'y'); } static int user_frustration_signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGALRM}; char *pass_through_filter(int tag, const char *buffer) { char *filtered; assert(!out_of_band(tag)); if (filter_pid ==0 || (tag < MAX_INTERESTING_TAG && !filter_is_interested_in(tag))) return mysavestring(buffer); if (tag == TAG_WHAT_ARE_YOUR_INTERESTS || /* only evaluate next alternative if interests are known */ !filter_is_interested_in(TAG_SIGNAL)) /* signal handling filters will get an "unexpected tag" error when the signal arrives during filter processing */ unblock_signals(user_frustration_signals); /* allow users to use CTRL-C, but only after uninterruptible_msec */ DPRINTF3(DEBUG_FILTERING, "to filter (%s, %d bytes) %s", tag2description(tag), (int) strlen(buffer), M(buffer)); write_to_filter((expected_tag = tag), buffer); filtered = read_from_filter(tag); DPRINTF3(DEBUG_FILTERING, "from filter (%s, %d bytes) %s", tag2description(tag), (int) strlen(filtered), M(filtered)); block_all_signals(); return filtered; } static char *read_from_filter(int tag) { uint8_t tag8; DEBUG_RANDOM_SLEEP; assert (!out_of_band(tag)); while (read_patiently2(filter_output_fd, &tag8, sizeof(uint8_t), 1000, "from filter"), out_of_band(tag8)) handle_out_of_band(tag8, read_tagless()); if (tag8 != tag) myerror(FATAL|NOERRNO, "Tag mismatch, expected %s from filter, but got %s", tag2description(tag), tag2description(tag8)); return read_tagless(); } static char *read_tagless(void) { uint32_t length32; char *buffer; read_patiently2(filter_output_fd, &length32, sizeof(uint32_t), 1000, "from filter"); buffer = mymalloc(length32); read_patiently2(filter_output_fd, buffer, length32, 1000,"from filter"); if (buffer[length32 -1 ] != '\n') myerror(FATAL|USE_ERRNO, "filter output without closing newline"); buffer[length32 -1 ] = '\0'; return buffer; } static bool starts_with(const char *str, const char *prefix) { return mystrstr(str, prefix) == str; } static void maybe_tweak_readline(const char*message) { /* a bit kludgey, but easy to extend */ char **words; if (message[0] != '@') return; words = split_with(message, "::"); /* words and its elements are allocated on the heap */ /* parameter checking should be done in the {python,perl} modules - if not, the assertions below may fail */ assert(words[1] != NULL); if (starts_with(message, "@rl_completer_word_break_characters::")) rl_completer_word_break_characters = words[1]; if (starts_with(message, "@rl_completer_quote_characters::")) rl_completer_quote_characters = words[1]; if (starts_with(message, "@rl_filename_completion_desired::")) rl_filename_completion_desired = my_atoi(words[1]); if (starts_with(message, "@rl_variable_bind::")) { /* "@rl_variable_bind::rl_variable_name::value::\n" */ assert(words[2] != NULL); rl_variable_bind(words[1], words[2]); /* no need for error handling: readline will complain if necessary */ } /* feel free to extend this list (but make sure to modify the {perl,python} modules accordingly! */ } void handle_out_of_band(int tag, char *message) { int split_em_up = FALSE; DPRINTF3(DEBUG_FILTERING, "received out-of-band (%s, %d bytes) %s", tag2description(tag), (int) strlen(message), M(message)); switch (tag) { case TAG_ERROR: if (expected_tag == TAG_COMPLETION) /* start new line when completing (looks better) */ fprintf(stderr, "\n"); /* @@@ error reporting (still) uses buffered I/O */ WONTRETURN(myerror(FATAL|NOERRNO, message)); case TAG_OUTPUT_OUT_OF_BAND: my_putstr(message); break; case TAG_ADD_TO_COMPLETION_LIST: case TAG_REMOVE_FROM_COMPLETION_LIST: split_em_up = TRUE; break; case TAG_IGNORE: maybe_tweak_readline(message); break; default: WONTRETURN(myerror(FATAL|USE_ERRNO, "out-of-band message with unknown tag %d: <%20s>", tag, message)); } if (split_em_up) { char **words = split_with(message, " \n\t"); char **plist, *word; for(plist = words;(word = *plist); plist++) if (tag == TAG_ADD_TO_COMPLETION_LIST) add_word_to_completions(word); else remove_word_from_completions(word); free_splitlist(words); } free(message); } static void write_to_filter(int tag, const char *string) { write_message(filter_input_fd, tag, string, "to filter"); } static void write_message(int fd, int tag, const char *string, const char *description) { uint8_t tag8 = tag; uint32_t length32 = strlen(string) + 1; write_patiently2(fd, &tag8, sizeof (uint8_t) , 1000, description); write_patiently2(fd, &length32, sizeof(uint32_t), 1000, description); write_patiently2(fd, string, length32 - 1 , 1000, description); write_patiently2(fd, "\n", 1 , 1000, description); } static char* tag2description(int tag) { switch (tag) { case TAG_INPUT: return "INPUT"; case TAG_OUTPUT: return "OUTPUT"; case TAG_HISTORY: return "HISTORY"; case TAG_COMPLETION: return "COMPLETION"; case TAG_PROMPT: return "PROMPT"; case TAG_HOTKEY: return "HOTKEY"; case TAG_SIGNAL: return "SIGNAL"; case TAG_WHAT_ARE_YOUR_INTERESTS: return "WHAT_ARE_YOUR_INTERESTS"; case TAG_IGNORE: return "TAG_IGNORE"; case TAG_ADD_TO_COMPLETION_LIST: return "ADD_TO_COMPLETION_LIST"; case TAG_REMOVE_FROM_COMPLETION_LIST:return "REMOVE_FROM_COMPLETION_LIST"; case TAG_OUTPUT_OUT_OF_BAND: return "OUTPUT_OUT_OF_BAND"; case TAG_ERROR: return "ERROR"; default: return as_string(tag); } } rlwrap-0.46.1/src/main.c000066400000000000000000001452771433170252700150140ustar00rootroot00000000000000/* main.c: main(), initialisation and cleanup * (C) 2000-2009 Hans Lub * * 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; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * You may contact the author by e-mail: hanslub42@gmail.com */ #include "rlwrap.h" /* global vars */ /* variables set via command line options */ int always_readline = FALSE; /* -a option: always be in readline mode */ char *password_prompt_search_string = NULL; /* (part of) password prompt (argument of -a option) */ int ansi_colour_aware = FALSE; /* -A option: make readline aware of ANSI colour codes in prompt */ int bleach_the_prompt = FALSE; /* -A!: remove all ANSI colour codes in prompt */ int complete_filenames = FALSE; /* -c option: whether to complete file names */ int debug = 0; /* -d option: debugging mask */ char *extra_char_after_completion = " "; /* -e option: override readlines's default completion_append_char (space) */ int always_echo = FALSE; /* -E option: always echo, even if client has ECHO off */ int history_duplicate_avoidance_policy = ELIMINATE_SUCCESIVE_DOUBLES; /* -D option: whether and how to avoid duplicate history entries */ char *history_format = NULL; /* -F option: format to append to history entries */ char *forget_regexp = NULL; /* -g option: keep matching input out of history */ int pass_on_sigINT_as_sigTERM = FALSE; /* -I option: send a SIGTERM to client when a SIGINT is received */ char *multi_line_tmpfile_ext = NULL; /* -M option: tmpfile extension for multi-line editor */ int nowarn = FALSE; /* -n option: suppress warnings */ int commands_children_not_wrapped = FALSE; /* -N option: always use direct mode when is waiting */ int one_shot_rlwrap = FALSE; /* -o option: whether to close the pty after writing the first line to */ char *prompt_regexp = NULL; /* -O option: only ever "cook" prompts matching this regexp */ bool regexp_means_prompt = FALSE; /* -O! all candidate prompts that match rhe regexp are prompts */ int colour_the_prompt = FALSE; /* -p option: whether we should paint the prompt */ int renice = FALSE; /* -R option: whether to be nicer than command */ int mirror_arguments = FALSE; /* -U option: whether to mirror command's arguments */ int wait_before_prompt = 40; /* -w option: how long we wait before deciding we have a cookable prompt (in msec)) */ int polling = FALSE; /* -W option: always give select() a small (=wait_before_prompt) timeout. */ int impatient_prompt = TRUE; /* show raw prompt as soon as possible, even before we cook it. may result in "flashy" prompt */ char *substitute_prompt = NULL; /* -S option: substitute our own prompt for s */ char *filter_command = NULL; /* -z option: pipe prompts, input, output, history and completion requests through an external filter */ /* variables for global bookkeeping */ int master_pty_fd; /* master pty (rlwrap uses this to communicate with client) */ FILE *debug_fp = NULL; /* filehandle of debugging log */ char *program_name = "rlwrap", *command_name;/* "rlwrap" (or whatever has been symlinked to rlwrap) and (base-)name of command */ char *rlwrap_command_line = ""; /* rlwrap command line (rlwrap -options command */ char *command_line = ""; /* command */ int within_line_edit = FALSE; /* TRUE while user is editing input */ int screen_is_alternate = FALSE; /* TRUE after client has sent smcup, FALSE after rmcup */ pid_t command_pid = 0; /* pid of child (client), or 0 before child is born */ int i_am_child = FALSE; /* Am I child or parent? after forking, child will set this to TRUE */ int i_am_filter = FALSE; /* After forking, filter will set this to TRUE */ int ignore_queued_input = FALSE; /* read and then ignore all characters in input queue until it is empty (i.e. read would block) */ int received_WINCH = FALSE; /* flag set in SIGWINCH signal handler: start line edit as soon as possible */ int prompt_is_still_uncooked = TRUE; /* The main loop consults this variable to determine the select() timeout: when TRUE, it is a few millisecs, if FALSE, it is infinite. TRUE just after receiving command output (when we still don't know whether we have a prompt), and, importantly, at startup (so that substitute prompts get displayed even with programs that don't have a startup message, such as cat) */ int we_just_got_a_signal_or_EOF = FALSE; /* When we got a signal or EOF, and the program sends something that ends in a newline, take it as a response to user input - i.e. preserve a cooked prompt and just print the new output after it */ int rlwrap_already_prompted = FALSE; int accepted_lines = 0; /* number of lines accepted (used for one-shot rlwrap) */ bool user_has_typed_first_NL = FALSE; /* When the *first* user NL is typed in direct mode, we probably need --always-readline: give a warning, .. */ bool advise_always_readline = FALSE; /* ... which has to be given a bit later in order to not mess up the screen */ /* private variables */ static char *history_filename = NULL; static int histsize = 300; static int write_histfile = TRUE; static char *completion_filename, *default_completion_filename; static char *full_program_name; static int last_option_didnt_have_optional_argument = FALSE; static int last_opt = -1; static char *client_term_name = NULL; /* we'll set TERM to this before exec'ing client command */ static int feed_history_into_completion_list = FALSE; /* * Since version 0.24, rlwrap only writes to master_pty * asynchronously, keeping a queue of pending output. The readline * line handler calls put_in_output_queue(user_input) , while * main_loop calls flush_output_queue() as long as there is something * in the queue. */ static char *output_queue; /* NULL when empty */ /* private functions */ static void init_rlwrap(char *command_line); static void fork_child(char *command_name, char **argv); static char *read_options_and_command_name(int argc, char **argv); static void main_loop(void); /* options */ #ifdef GETOPT_GROKS_OPTIONAL_ARGS static char optstring[] = "+:a::A::b:cC:d::D:e:Ef:F:g:hH:iIl:nNM:m::oO:p::P:q:rRs:S:t:TUvw:Wz:"; /* +: is not really documented. configure checks wheteher it works as expected if not, GETOPT_GROKS_OPTIONAL_ARGS is undefined. @@@ */ #else static char optstring[] = "+:a:A:b:cC:d:D:e:Ef:F:g:hH:iIl:nNM:m:oO:p:P:q:rRs:S:t:TUvw:Wz:"; #endif #ifdef HAVE_GETOPT_LONG static struct option longopts[] = { {"always-readline", optional_argument, NULL, 'a'}, {"ansi-colour-aware", optional_argument, NULL, 'A'}, {"break-chars", required_argument, NULL, 'b'}, {"complete-filenames", no_argument, NULL, 'c'}, {"command-name", required_argument, NULL, 'C'}, {"debug", optional_argument, NULL, 'd'}, {"extra-char-after-completion", required_argument, NULL, 'e'}, {"always-echo", no_argument, NULL, 'E'}, {"history-no-dupes", required_argument, NULL, 'D'}, {"file", required_argument, NULL, 'f'}, {"history-format", required_argument, NULL, 'F'}, {"forget-matching", required_argument, NULL, 'g'}, {"help", no_argument, NULL, 'h'}, {"history-filename", required_argument, NULL, 'H'}, {"case-insensitive", no_argument, NULL, 'i'}, {"pass-sigint-as-sigterm", no_argument, NULL, 'I'}, {"logfile", required_argument, NULL, 'l'}, {"multi-line", optional_argument, NULL, 'm'}, {"multi-line-ext", required_argument, NULL, 'M'}, {"no-warnings", no_argument, NULL, 'n'}, {"no-children", no_argument, NULL, 'N'}, {"one-shot", no_argument, NULL, 'o'}, {"only-cook", required_argument, NULL, 'O'}, {"prompt-colour", optional_argument, NULL, 'p'}, {"pre-given", required_argument, NULL, 'P'}, {"quote-characters", required_argument, NULL, 'q'}, {"remember", no_argument, NULL, 'r'}, {"renice", no_argument, NULL, 'R'}, {"histsize", required_argument, NULL, 's'}, {"substitute-prompt", required_argument, NULL, 'S'}, {"set-terminal-name", required_argument, NULL, 't'}, {"test-terminal", no_argument, NULL, 'T'}, {"mirror-arguments", no_argument, NULL, 'U'}, {"version", no_argument, NULL, 'v'}, {"wait-before-prompt", required_argument, NULL, 'w'}, {"polling", no_argument, NULL, 'W'}, {"filter", required_argument, NULL, 'z'}, {0, 0, 0, 0} }; #endif /* helper function to run a unit test whenever UNIT_TEST is #defined, e.g. by "make clean; make CFLAGS='-DUNIT_TEST=my_test'" * example of such an unit test (can be defined in any of the .c files): * * my_test(int argc, char **argv, test_stage stage) { * if (test_stage == TEST_AFTER_OPTION_PARSING) { * // test whatever you like * exit(0); * } * } */ #ifdef UNIT_TEST static void run_unit_test(int argc, char **argv, test_stage stage) { extern void UNIT_TEST(int argc, char **argv, test_stage stage); #define VALUE_AS_STRING(v) NAME_AS_STRING(v) #define NAME_AS_STRING(n) #n if(stage == TEST_AT_PROGRAM_START) { myerror(WARNING|NOERRNO, "running unit test %s()", VALUE_AS_STRING(UNIT_TEST)); } UNIT_TEST(argc, argv, stage); } #else static void run_unit_test(int UNUSED(argc), char ** UNUSED(argv), test_stage UNUSED(stage)) { /*do nothing */ } #endif /* * main function. initialises everything and calls main_loop(), * which never returns */ int main(int argc, char **argv) { char *command_name; if (!(setlocale (LC_ALL, "") && setlocale(LC_COLLATE, "C")))/* ANSI C says that all programs start by default in the standard `C' locale... */ myerror(WARNING|NOERRNO, "could not set locale"); /* ... To use the locales specified by the environment, we must call setlocale. */ /* LC_COLLATE = 'C' because we use character ranges as byte ranges in regexps */ run_unit_test(argc, argv,TEST_AT_PROGRAM_START); rlwrap_command_line = unsplit_with(argc, argv, " "); init_completer(); /* Harvest options and leave optind pointing to first non-option argument: */ command_name = read_options_and_command_name(argc, argv); /* by now, optind points to slave , and &argv[optind] is 's argv. Remember slave command line: */ command_line = unsplit_with(argc - optind, argv + optind, " "); run_unit_test(argc - optind, argv + optind, TEST_AFTER_OPTION_PARSING); /* argv points at the first non-option rlwrap argument */ /* if stdin is not a tty, or we're inside emacs, just execute : */ if (!isatty(STDIN_FILENO) || getenv("INSIDE_EMACS")) if (execvp(argv[optind], &argv[optind]) < 0) myerror(FATAL|USE_ERRNO, "Cannot execute %s", argv[optind]); init_rlwrap(rlwrap_command_line); install_signal_handlers(); block_all_signals(); fork_child(command_name, argv); /* this will unblock most signals most of the time */ init_readline(""); last_minute_checks(); run_unit_test(0,NULL, TEST_AFTER_READLINE_INIT); if (filter_command) spawn_filter(filter_command); run_unit_test(argc - optind, argv + optind, TEST_AFTER_SPAWNING_SLAVE_COMMAND); /* argv points at the first non-option rlwrap argument */ main_loop(); return 42; /* The Answer, but, sadly, we'll never get there.... */ } /* * create pty pair and fork using my_pty_fork; parent returns immediately; child * executes the part of rlwrap's command line that remains after * read_options_and_command_name() has harvested rlwrap's own options */ static void fork_child(char *UNUSED(command_name), char **argv) { char *arg = argv[optind], *p; int pid; if (mirror_arguments) mirror_args_init(&argv[optind]); pid = my_pty_fork(&master_pty_fd, &saved_terminal_settings, &winsize); if (pid > 0) /* parent: */ return; else { /* child: */ DPRINTF1(DEBUG_TERMIO, "preparing to execute %s", arg); close_open_files_without_writing_buffers(); if (client_term_name) mysetenv("TERM", client_term_name); if (execvp(argv[optind], &argv[optind]) < 0) { if (last_opt > 0 && last_option_didnt_have_optional_argument) { /* e.g. 'rlwrap -a Password: sqlpus' will try to exec 'Password:' */ for (p=" '; !(){}"; *p; p++) /* does arg need shell quoting? */ if (strchr(arg,*p)) { arg = add3strings("'", arg,"'"); /* quote it */ break; } fprintf(stderr, "Did you mean '%s' to be an option argument?\nThen you should write -%c%s, without the space(s)\n", argv[optind], last_opt, arg); } myerror(FATAL|USE_ERRNO, "Cannot execute %s", argv[optind]); /* stillborn child, parent will live on and display child's last gasps */ } } } /* * main loop: listen on stdin (for user input) and master pty (for command output), * and try to write output_queue to master_pty (if it is not empty) * This function never returns. */ void main_loop(void) { int nfds; fd_set readfds; fd_set writefds; int nread; char buf[BUFFSIZE], *timeoutstr, *old_raw_prompt, *new_output_minus_prompt; int promptlen = 0; int leave_prompt_alone; sigset_t no_signals_blocked; int seen_EOF = FALSE; struct timespec select_timeout, *select_timeoutptr; struct timespec immediately = { 0, 0 }; /* zero timeout when child is dead */ struct timespec wait_a_little = {0, 0xBadf00d }; /* tv_usec field will be filled in when initialising */ struct timespec *forever = NULL; wait_a_little.tv_nsec = 1000 * 1000 * wait_before_prompt; sigemptyset(&no_signals_blocked); pass_through_filter(TAG_OUTPUT,""); /* If something is wrong with filter, get the error NOW */ set_echo(FALSE); /* This will also put the terminal in CBREAK mode */ /* ------------------------------ main loop -------------------------------*/ while (TRUE) { /* listen on both stdin and pty_fd */ FD_ZERO(&readfds); FD_SET(STDIN_FILENO, &readfds); FD_SET(master_pty_fd, &readfds); /* try to write output_queue to master_pty (but only if it is nonempty) */ FD_ZERO(&writefds); if (output_queue_is_nonempty()) FD_SET(master_pty_fd, &writefds); DPRINTF1(DEBUG_AD_HOC, "prompt_is_still_uncooked = %d", prompt_is_still_uncooked); if (command_is_dead || ignore_queued_input) { select_timeout = immediately; select_timeoutptr = &select_timeout; timeoutstr = "immediately"; } else if (prompt_is_still_uncooked || polling) { select_timeout = wait_a_little; select_timeoutptr = &select_timeout; timeoutstr = "wait_a_little"; } else { select_timeoutptr = forever; /* NULL */ timeoutstr = "forever"; } DPRINTF2(DEBUG_TERMIO, "calling select() with timeout %s %s ...", timeoutstr, within_line_edit ? "(within line edit)" : ""); nfds = my_pselect(1 + master_pty_fd, &readfds, &writefds, NULL, select_timeoutptr, &no_signals_blocked); /* DPRINTF3(DEBUG_TERMIO, "select() returned %d (stdin|pty in|pty out = %03d), within_line_edit=%d", nfds, */ /* 100*(FD_ISSET(STDIN_FILENO, &readfds)?1:0) + 10*(FD_ISSET(master_pty_fd, &readfds)?1:0) + (FD_ISSET(master_pty_fd, &writefds)?1:0), */ /* within_line_edit); */ DPRINTF5(DEBUG_TERMIO, "... returning %d%s %s %s %s" , nfds , nfds > 0 ? ": " : "." , nfds > 0 && FD_ISSET(STDIN_FILENO, &readfds) ? "stdin ready for input" : "" , nfds > 0 && FD_ISSET(master_pty_fd, &readfds) ? "pty master ready for input": "" , nfds > 0 && FD_ISSET(master_pty_fd, &writefds) ? "output queue nonempty and pty master ready for output" : ""); assert(!filter_pid || filter_is_dead || kill(filter_pid,0) == 0); assert(command_is_dead || kill(command_pid,0) == 0); /* check flags that may have been set by signal handlers */ if (filter_is_dead) filters_last_words(); /* will call myerror with last words */ if (received_WINCH) { /* received_WINCH flag means we've had a WINCH while within_line_edit was FALSE */ DPRINTF0(DEBUG_READLINE, "Starting line edit as a result of WINCH "); within_line_edit = TRUE; restore_rl_state(); received_WINCH = FALSE; continue; } if (nfds < 0) { /* exception */ if (errno == EINTR || errno == 0) { /* interrupted by signal, or by a cygwin bug (errno == 0) :-( */ continue; } else myerror(FATAL|USE_ERRNO, "select received exception"); } else if (nfds == 0) { /* timeout, which can only happen when .. */ if (ignore_queued_input) { /* ... we have read all the input keystrokes that should be ignored (i.e. those that accumulated on stdin while we were calling an external editor) */ ignore_queued_input = FALSE; continue; } else if (command_is_dead) { /* ... or else, if child is dead, ... */ DPRINTF2(DEBUG_SIGNALS, "select returned 0, command_is_dead=%d, commands_exit_status=%d", command_is_dead, commands_exit_status); cleanup_rlwrap_and_exit(EXIT_SUCCESS); } else if (prompt_is_still_uncooked) { /* cooking time? */ if (we_just_got_a_signal_or_EOF) { we_just_got_a_signal_or_EOF = FALSE; /* 1. If we got a signal/EOF before cooking time, we don't need special action to preserve the cooked prompt. 2. Reset we_just_got_a_signal_or_EOF after a signal or EOF that didn't kill command */ continue; } if (!skip_rlwrap()) { /* ... or else, it is time to cook the prompt */ if (pre_given && accepted_lines == 0) { /* input_buffer and point have already been set in init_readline() */ DPRINTF0(DEBUG_READLINE, "Starting line edit (because of -P option)"); within_line_edit = TRUE; restore_rl_state(); continue; } move_cursor_to_start_of_prompt(ERASE); /* cooked prompt may be shorter than raw prompt, hence the ERASE */ /* move and erase before cooking, as we need to move/erase according to the raw prompt */ cook_prompt_if_necessary(); DPRINTF2(DEBUG_READLINE,"After cooking, raw_prompt=%s, cooked=%s", M(saved_rl_state.raw_prompt), M(saved_rl_state.cooked_prompt)); my_putstr(saved_rl_state.cooked_prompt); rlwrap_already_prompted = TRUE; } prompt_is_still_uncooked = FALSE; } else if (polling) { completely_mirror_slaves_special_characters(); if (mirror_arguments) mirror_args(command_pid); continue; } else { myerror(FATAL|NOERRNO, "unexpected select() timeout"); } } else if (nfds > 0) { /* Hah! something to read or write */ /* -------------------------- read pty --------------------------------- */ /* Always first read and process the slave command's output, even if there is input waiting on stdin (which may happen when pasting a lot of text). E.g. when pasting "a\nb\nc" into "rlwrap cat" we want a a b b c c and not a b c a b c */ if (FD_ISSET(master_pty_fd, &readfds)) { /* there is something (or nothing, if EOF) to read on master pty: */ nread = read(master_pty_fd, buf, BUFFSIZE - 1); /* read it */ DPRINTF1(DEBUG_AD_HOC, "nread: %d", nread); if (nread <= 0) { if (command_is_dead || nread == 0) { /* we catched a SIGCHLD, or slave command has closed its stdout */ if (promptlen > 0) /* commands dying words were not terminated by \n ... */ my_putchar('\n'); /* provide the missing \n */ cleanup_rlwrap_and_exit(EXIT_SUCCESS); } else if (errno == EINTR) { /* interrupted by signal ...*/ continue; /* ... don't worry */ } else if (! seen_EOF) { /* maybe command has just died (and SIGCHLD, whose handler sets command_is_dead is not */ mymicrosleep(50); /* yet caught) Therefore we wait a bit, */ seen_EOF = TRUE; /* set a flag */ continue; /* and try one more time (hopefully catching the signal this time round */ } else { myerror(FATAL|USE_ERRNO, "read error on master pty"); } } remove_padding_and_terminate(buf, nread); completely_mirror_slaves_output_settings(); /* some programs (e.g. joe) need this. Gasp!! */ mirror_args(command_pid); check_cupcodes(buf); if (skip_rlwrap()) { /* Race condition here! The client may just have finished an emacs session and returned to cooked mode, while its ncurses-riddled output is stil waiting for us to be processed. */ if (advise_always_readline) { char *newlines = "\n\n"; assert(!always_readline); write_patiently(STDOUT_FILENO, newlines, strlen(newlines), "to stdout"); /* make the following warning stand out ... */ myerror(WARNING|NOERRNO, "rlwrap appears to do nothing for %s, which asks for\n" "single keypresses all the time. Don't you need --always-readline\n" "and possibly --no-children? (cf. the rlwrap manpage)\n", command_name); advise_always_readline = FALSE; } write_patiently(STDOUT_FILENO, buf, nread, "to stdout"); /* ... and print it before the clients output */ DPRINTF1(DEBUG_AD_HOC, "advise_always_readline = %d", advise_always_readline); DPRINTF2(DEBUG_TERMIO, "read from pty and wrote to stdout %d bytes in direct mode <%s>", nread, M(buf)); yield(); continue; } DPRINTF2(DEBUG_TERMIO, "read %d bytes from pty into buffer: %s", nread, M(buf)); write_logfile(buf); if (within_line_edit) /* client output arrives while we're editing keyboard input: */ save_rl_state(); /* temporarily disable readline and restore the screen state before readline was called */ assert(saved_rl_state.raw_prompt != NULL); /* We *always* compute the printable part and the new raw prompt, and *always* print the printable part There are four possibilities: 1. impatient before cooking. The raw prompt has been printed, write the new output after it 2. patient before cooking No raw prompt has been printed yet, don't print anything 3. impatient after cooking 3a no current prompt print the new output 3b some current prompt erase it, replace by current raw prompt and print new output 4. patient after cooking don't print anything */ /* sometimes we want to leave the prompt standing, e.g. after accepting a line, or when a signal arrived */ leave_prompt_alone = *saved_rl_state.raw_prompt == '\0' /* saved_rl_state.raw_prompt = "" in two distinct cases: when there is actually no prompt, or just after accepting a line, when the cursor is at the end of the prompt. In both cases, we dont't want to move the cursor */ || prompt_is_still_uncooked /* in this case no prompt has been displayed yet */ || command_is_dead || (we_just_got_a_signal_or_EOF && strrchr(buf, '\n')); /* a signal followed by output with a newline in it: treat it as response to user input, so leave the prompt alone */ DPRINTF3(DEBUG_READLINE, "leave_prompt_alone: %s (raw prompt: %s, prompt_is_still_uncooked: %d)", (leave_prompt_alone? "yes" : "no"), M(saved_rl_state.raw_prompt), prompt_is_still_uncooked); if (!leave_prompt_alone) /* && (!impatient_prompt || !saved_rl_state.cooked_prompt)) */ move_cursor_to_start_of_prompt(ERASE); else if (we_just_got_a_signal_or_EOF) { free (saved_rl_state.raw_prompt); saved_rl_state.raw_prompt = mysavestring(""); /* prevent reprinting the prompt */ } if (impatient_prompt && !leave_prompt_alone) old_raw_prompt = mysavestring(saved_rl_state.raw_prompt); new_output_minus_prompt = process_new_output(buf, &saved_rl_state); /* chop off the part after the last newline and put this in saved_rl_state.raw_prompt (or append buf if no newline found)*/ if (impatient_prompt) { /* in impatient mode, ALL command output is passed through the OUTPUT filter, including the prompt The prompt, however, is filtered separately at cooking time and then displayed */ char *filtered = pass_through_filter(TAG_OUTPUT, buf); if(!leave_prompt_alone) { my_putstr(old_raw_prompt); free(old_raw_prompt); } my_putstr(filtered); free (filtered); if (regexp_means_prompt && prompt_regexp && match_regexp(saved_rl_state.raw_prompt, prompt_regexp, FALSE)) { /* user specified -O!.... so any natching candidate prompt will be cooked and output immediately: */ move_cursor_to_start_of_prompt(ERASE); /* erase already printed raw prompt */ cook_prompt_if_necessary(); my_putstr(saved_rl_state.cooked_prompt); } rlwrap_already_prompted = TRUE; } else { my_putstr(new_output_minus_prompt); rlwrap_already_prompted = FALSE; } free(new_output_minus_prompt); prompt_is_still_uncooked = TRUE; if (within_line_edit) restore_rl_state(); yield(); /* wait for what client has to say .... */ continue; /* ... and don't attempt to process keyboard input as long as it is talking , in order to avoid re-printing the current prompt (i.e. unfinished output line) */ } /* ----------------------------- key pressed: read stdin -------------------------*/ if (FD_ISSET(STDIN_FILENO, &readfds)) { /* key pressed */ unsigned char byte_read; /* the readline function names and documentation talk about "characters" and "keys", but we're reading bytes (i.e. unsigned chars) here, and those may very well be part of a multi-byte character. Example: hebrew "aleph" in utf-8 is 0xd790; pressing this key will make us read 2 bytes 0x90 and then 0xd7, (or maybe the other way round depending on endianness??) The readline library hides all this complexity and allows one to just "pass the bytes around" */ nread = read(STDIN_FILENO, &byte_read, 1); /* read next byte of input */ assert(sizeof(unsigned char) == 1); /* gets optimised away */ if (nread <= 0) DPRINTF1(DEBUG_TERMIO, "read from stdin returned %d", nread); if (nread < 0) if (errno == EINTR) continue; else myerror(FATAL|USE_ERRNO, "Unexpected error"); else if (nread == 0) /* EOF on stdin */ cleanup_rlwrap_and_exit(EXIT_SUCCESS); else if (ignore_queued_input) continue; /* do nothing with it*/ assert(nread == 1); DPRINTF2(DEBUG_TERMIO, "read from stdin: byte 0x%02x (%s)", byte_read, mangle_char_for_debug_log(byte_read, TRUE)); if (skip_rlwrap()) { /* direct mode, just pass it on */ /* remote possibility of a race condition here: when the first half of a multi-byte char is read in direct mode and the second half in readline mode. Oh well... */ DPRINTF0(DEBUG_TERMIO, "passing it on (in transparent mode)"); if (!user_has_typed_first_NL && (byte_read == '\r' || byte_read == '\n')) { user_has_typed_first_NL = TRUE; advise_always_readline = TRUE; /* first NL is in direct mode: advise the user that she probably wants --always-readline */ } completely_mirror_slaves_terminal_settings(); /* this is of course 1 keypress too late: we should mirror the terminal settings *before* the user presses a key. (maybe using rl_event_hook??) @@@FIXME @@@ HOW?*/ write_patiently(master_pty_fd, &byte_read, 1, "to master pty"); } else { /* hand it over to readline */ if (!within_line_edit) { /* start a new line edit */ DPRINTF0(DEBUG_READLINE, "Starting line edit"); within_line_edit = TRUE; restore_rl_state(); } if (term_eof && byte_read == term_eof && strlen(rl_line_buffer) == 0) { /* hand a term_eof (usually CTRL-D) directly to command */ char *sent_EOF = mysavestring("?"); *sent_EOF = term_eof; put_in_output_queue(sent_EOF); we_just_got_a_signal_or_EOF = TRUE; free(sent_EOF); } else { rl_stuff_char(byte_read); /* stuff it back in readline's input queue */ DPRINTF0(DEBUG_TERMIO, "passing it to readline"); DPRINTF2(DEBUG_READLINE, "rl_callback_read_char() (_rl_eof_char=%d, term_eof=%d)", _rl_eof_char, term_eof); message_in_echo_area(NULL); rl_callback_read_char(); } } } /* -------------------------- write pty --------------------------------- */ if (FD_ISSET(master_pty_fd, &writefds)) { flush_output_queue(); if(output_queue) { /* there was more than one line in the queue - probably pasted input */ mymicrosleep(10); /* give slave some time to respond */ yield(); /* If we woudn't do this, nothing bad would happen, but the */ /* "dialogue" on screen will be out of order (which can still happen) */ } } } /* if (ndfs > 0) */ } /* while (1) */ } /* void main_loop() */ /* Read history and completion word lists */ static void init_rlwrap(char *command_line) { char *homedir, *histdir, *homedir_prefix, *hostname; struct stat statbuf; time_t now; hostname = getenv("HOSTNAME") ? getenv("HOSTNAME") : "?"; now = time(NULL); DPRINTF0(DEBUG_ALL, "-*- mode: grep -*-"); DPRINTF1(DEBUG_ALL, "command line: %s", command_line); DPRINTF3(DEBUG_ALL, "rlwrap version %s, host: %s, time: %s", VERSION, hostname, ctime(&now)); init_terminal(); /* Determine rlwrap home dir and prefix for default history and completion filenames */ homedir = (getenv("RLWRAP_HOME") ? getenv("RLWRAP_HOME") : getenv("HOME")); if (!homedir) { homedir = "."; myerror(WARNING | NOERRNO, "No HOME, using '%s'", homedir); } homedir_prefix = (getenv("RLWRAP_HOME") ? /* is RLWRAP_HOME set? */ add2strings(getenv("RLWRAP_HOME"), "/") : /* use $RLWRAP_HOME/_history */ add2strings(homedir, "/.")); /* if not, use ~/._history */ /* Determine history file name and check its existence and permissions */ if (history_filename) { histdir = mydirname(history_filename); } else { histdir = homedir; history_filename = add3strings(homedir_prefix, command_name, "_history"); } if (write_histfile) { if (access(history_filename, F_OK) == 0) { /* already exists, can we read/write it? */ if (access(history_filename, R_OK | W_OK) != 0) { myerror(FATAL|USE_ERRNO, "cannot read and write %s", history_filename); } /* OK, we can read and write it, but do we want to? Not if our effective UID doesn't match the owner of the history file (like after sudo rlwrap on Ubuntu) */ assert(!stat(history_filename, &statbuf)); if(statbuf.st_uid != geteuid()) { myerror(WARNING | NOERRNO, "Owner of %s and your effective UID don't match. History will be read-only", history_filename); write_histfile = FALSE; } } else { /* doesn't exist, can we create it? */ if(access(histdir, W_OK) != 0) { if (errno == ENOENT) { mode_t oldmask = umask(0); if (mkdir(histdir, 0700)) /* rwx------ */ myerror(FATAL|USE_ERRNO, "cannot create directory %s", histdir); umask(oldmask); } else { myerror(FATAL|USE_ERRNO, "cannot create history file %s", history_filename); } } } } /* Initialize history */ using_history(); stifle_history(histsize); read_history(history_filename); /* ignore errors here: history file may not yet exist, but will be created on exit */ if (feed_history_into_completion_list) feed_file_into_completion_list(history_filename); /* Determine completion file name (completion files are never written to, and ignored when unreadable or non-existent) */ completion_filename = add3strings(homedir_prefix, command_name, "_completions"); default_completion_filename = add3strings(DATADIR, "/rlwrap/completions/", command_name); rl_readline_name = command_name; DPRINTF1(DEBUG_READLINE, "Setting readline command name to '%s'", command_name); /* Initialise completion list (if is readable) */ if (access(completion_filename, R_OK) == 0) { feed_file_into_completion_list(completion_filename); } else if (access(default_completion_filename, R_OK) == 0) { feed_file_into_completion_list(default_completion_filename); } } /* * On systems where getopt doens't handle optional argments, warn the user whenever an * argument of the form - is seen, or whenever the argument is the last item on the command line * (e.g. 'rlwrap -a command', which will be parsed as 'rlwrap --always-readline=command') */ static char * check_optarg(char opt, int remaining, bool isdummy, const char *suggestion) { MAYBE_UNUSED(opt); MAYBE_UNUSED(remaining); MAYBE_UNUSED(isdummy); MAYBE_UNUSED(suggestion); if (!optarg) last_option_didnt_have_optional_argument = TRUE; /* if this variable is set, and if command is not found, suggest that it may have been meant as optional argument (e.g. 'rlwrap -a password sqlplus' will try to execute 'password sqlplus' ) */ #ifndef GETOPT_GROKS_OPTIONAL_ARGS if (optarg && /* is there an optional arg? have a look at it: */ ((optarg[0] == '-' && (optarg[1] == '-' || isalpha(optarg[1]))) || /* looks like next option */ remaining == 0)) /* or is last item on command line */ myerror(WARNING|NOERRNO, "on this system, the getopt() library function doesn't\n" "grok optional arguments, so '%s' is taken as an argument to the -%c option\n" "Is this what you meant? If not, please provide %s argument like '%s'", optarg, opt, isdummy ? "a dummy" : "an", suggestion); #endif return optarg; } /* find name of current option */ static const char * current_option(int opt, int longindex) { static char buf[BUFFSIZE]; #ifdef HAVE_GETOPT_LONG if (longindex >=0) { sprintf(buf, "--%s", longopts[longindex].name); return buf; } #endif sprintf(buf, "-%c", opt); return buf; } char * read_options_and_command_name(int argc, char **argv) { int c; char *opt_C = NULL; int option_count = 0; int opt_b = FALSE; int opt_f = FALSE; int remaining = -1; /* remaining number of arguments on command line */ int longindex = -1; /* index of current option in longopts[], set by getopt_long */ full_program_name = mysavestring(argv[0]); program_name = mybasename(full_program_name); /* normally "rlwrap"; needed by myerror() */ rl_basic_word_break_characters = " \t\n\r(){}[],'+-=&^%$#@\";|\\"; opterr = 0; /* we do our own error reporting */ while (1) { #ifdef HAVE_GETOPT_LONG c = getopt_long(argc, argv, optstring, longopts, &longindex); #else c = getopt(argc, argv, optstring); #endif if (c == EOF) break; option_count++; last_option_didnt_have_optional_argument = FALSE; remaining = argc - optind; last_opt = c; switch (c) { case 'a': always_readline = TRUE; if (check_optarg('a', remaining, TRUE, "brumbleslurgh")) password_prompt_search_string = mysavestring(optarg); break; case 'A': ansi_colour_aware = TRUE; if (check_optarg('A', remaining, TRUE, "") && strings_are_equal(optarg, "!")) bleach_the_prompt = TRUE; break; case 'b': rl_basic_word_break_characters = add3strings("\r\n \t", optarg, ""); opt_b = TRUE; break; case 'c': complete_filenames = TRUE; #ifndef CAN_FOLLOW_COMMANDS_CWD myerror(WARNING|NOERRNO, "On this system rlwrap cannot follow the rlwrapped command's working directory:\n" "filename completion will only be relative to rlwrap's own working directory "); #endif break; case 'C': opt_C = mysavestring(optarg); break; case 'd': #ifdef DEBUG if (option_count > 1) myerror(FATAL|NOERRNO, "-d or --debug option has to be the *first* rlwrap option\n" "in order to be able to follow the processing of all subsequent options"); debug = check_optarg('d', remaining, FALSE, "7") ? my_atoi(optarg) : DEBUG_DEFAULT; my_fopen(&debug_fp, DEBUG_FILENAME, "w+", "debug log"); /* w+, not w: both parent and child write to the same logfile, and need to fseek beyond where the other may have written stuff */ #else myerror(FATAL|NOERRNO, "To use -d( for debugging), configure %s with --enable-debug and rebuild",program_name); #endif break; case 'D': history_duplicate_avoidance_policy=my_atoi(optarg); if (history_duplicate_avoidance_policy < 0 || history_duplicate_avoidance_policy > 2) myerror(FATAL|NOERRNO, "%s option with illegal value %d, should be 0, 1 or 2", current_option('D', longindex), history_duplicate_avoidance_policy); break; case 'e': extra_char_after_completion = mysavestring(optarg); if (strlen(extra_char_after_completion) > 1) myerror(FATAL|NOERRNO, "-e (--extra-char-after-completion) argument should be at most one character"); break; case 'E': always_echo = TRUE; break; case 'f': if (strcmp(optarg, ".") == 0) feed_history_into_completion_list = TRUE; else feed_file_into_completion_list(optarg); opt_f = TRUE; break; case 'F': WONTRETURN(myerror(FATAL|NOERRNO, "The -F (--history-format) option is obsolete. Use -z \"history_format '%s'\" instead", optarg)); case 'g': forget_regexp = mysavestring(optarg); match_regexp("complain NOW if regexp is wrong", forget_regexp, 1); break; case 'h': WONTRETURN(usage(EXIT_SUCCESS)); case 'H': history_filename = mysavestring(optarg); break; case 'i': if (opt_f) myerror(FATAL|NOERRNO, "-i option has to precede -f options"); completion_is_case_sensitive = FALSE; break; case 'I': pass_on_sigINT_as_sigTERM = TRUE; break; case 'l': open_logfile(optarg); break; case 'n': nowarn = TRUE; break; case 'm': #ifndef HAVE_SYSTEM myerror(WARNING|NOERRNO, "the -m option doesn't work on this system"); #endif multiline_separator = /* \\\\ will be printed as \\ which is correct if we want ' \ ' to be the multiline separator */ (check_optarg('m', remaining, FALSE, " \\\\ ") ? mysavestring(optarg) : " \\ "); break; case 'M': multi_line_tmpfile_ext = mysavestring(optarg); break; case 'N': commands_children_not_wrapped = TRUE; break; case 'o': one_shot_rlwrap = TRUE; break; case 'O': prompt_regexp = mysavestring(optarg); if (*prompt_regexp == '!') { regexp_means_prompt = TRUE; prompt_regexp += 1; } match_regexp("complain NOW if regexp is wrong", prompt_regexp, 1); break; case 'p': colour_the_prompt = TRUE; initialise_colour_codes(check_optarg('p', remaining, FALSE, "Red") ? colour_name_to_ansi_code(optarg) : colour_name_to_ansi_code("Red")); break; case 'P': pre_given = mysavestring(optarg); always_readline = TRUE; /* pre_given does not work well with transparent mode */ break; case 'q': rl_basic_quote_characters = mysavestring(optarg); break; case 'r': remember_for_completion = TRUE; break; case 'R': renice = TRUE; break; case 's': histsize = my_atoi(optarg); if (histsize < 0 || *optarg == '-') { write_histfile = 0; histsize = -histsize; } break; case 'S': substitute_prompt = mysavestring(optarg);break; case 't': client_term_name=mysavestring(optarg);break; #ifdef DEBUG case 'T': test_terminal(); exit(EXIT_SUCCESS); #endif case 'U': mirror_arguments = TRUE; break; case 'v': printf("rlwrap %s\n", VERSION); exit(EXIT_SUCCESS); case 'w': wait_before_prompt = my_atoi(optarg); if (wait_before_prompt < 0) { wait_before_prompt *= -1; impatient_prompt = FALSE; } break; case 'W': polling = TRUE; break; case 'z': filter_command = mysavestring(optarg); break; case '?': assert(optind > 0); WONTRETURN(myerror(FATAL|NOERRNO, "unrecognised option %s\ntry '%s --help' for more information", argv[optind-1], full_program_name)); case ':': assert(optind > 0); WONTRETURN(myerror(FATAL|NOERRNO, "option %s requires an argument \ntry '%s --help' for more information", argv[optind-1], full_program_name)); default: usage(EXIT_FAILURE); } } if (!complete_filenames && !opt_b) { /* use / and . as default breaking characters whenever we don't complete filenames */ rl_basic_word_break_characters = add2strings(rl_basic_word_break_characters, "/."); } if (!complete_filenames && !opt_f && !remember_for_completion && !always_readline) { /* https://github.com/hanslub42/rlwrap/issues/147 */ rl_bind_key('\t', rl_insert); } if (optind >= argc) { /* rlwrap -a -b -c with no command specified */ if (filter_command) { /* rlwrap -z filter with no command specified */ mysignal(SIGALRM, HANDLER(handle_sigALRM)); /* needed for read_patiently2 */ spawn_filter(filter_command); pass_through_filter(TAG_OUTPUT,""); /* ignore result but allow TAG_OUTPUT_OUT_OF_BAND */ cleanup_rlwrap_and_exit(EXIT_SUCCESS); } else { usage(EXIT_FAILURE); } } if (opt_C) { int countback = 0; if (isnumeric(opt_C)) countback = my_atoi(opt_C); /* investigate whether -C option argument is numeric */ if (countback > 0) { /* e.g -C 1 or -C 12 */ if (argc - countback < optind) /* -C 666 */ myerror(FATAL|NOERRNO, "when using -C %d you need at least %d non-option arguments", countback, countback); else if (argv[argc - countback][0] == '-') /* -C 2 perl -d blah.pl */ myerror(FATAL|NOERRNO, "the last argument minus %d appears to be an option!", countback); else { /* -C 1 perl test.cgi */ command_name = mysavestring(mybasename(argv[argc - countback])); } } else if (countback == 0) { /* -C name1 name2 or -C 0 */ if (opt_C[0] == '0' && opt_C[1] == '\0') /* -C 0 */ myerror(FATAL|NOERRNO, "-C 0 makes no sense"); else if (strlen(mybasename(opt_C)) != strlen(opt_C)) /* -C dir/name */ myerror(FATAL|NOERRNO, "-C option argument should not contain directory components"); else if (opt_C[0] == '-') /* -C -d (?) */ myerror(FATAL|NOERRNO, "-C option needs argument"); else /* -C name */ command_name = opt_C; } else { /* -C -2 */ myerror (FATAL|NOERRNO, "-C option needs string or positive number as argument, perhaps you meant -C %d?", -countback); } } else { /* no -C option given, use command name */ command_name = mysavestring(mybasename(argv[optind])); } assert(command_name != NULL); return command_name; } int output_queue_is_nonempty(void) { return (output_queue ? TRUE : FALSE); } void put_in_output_queue(char *stuff) { output_queue = append_and_free_old(output_queue, stuff); DPRINTF3(DEBUG_TERMIO,"put %d bytes in output queue (which now has %d bytes): %s", (int) strlen(stuff), (int) strlen(output_queue), M(stuff)); } /* * flush the output queue, writing its contents to master_pty_fd * never write more than one line, or BUFFSIZE in one go */ void flush_output_queue(void) { int nwritten, queuelen, how_much; char *old_queue = output_queue; char *nl; if (!output_queue) return; queuelen = strlen(output_queue); nl = strchr(output_queue, '\n'); how_much = min(BUFFSIZE, nl ? 1+ nl - output_queue : queuelen); /* never write more than one line, and never more than BUFFSIZE in one go */ nwritten = write(master_pty_fd, output_queue, how_much); assert(nwritten <= (int) strlen(output_queue)); if (debug) { char scratch = output_queue[nwritten]; output_queue[nwritten] = '\0'; /* temporarily replace the last written byte + 1 by a '\0' */ DPRINTF3(DEBUG_TERMIO,"flushed %d of %d bytes from output queue to pty: %s", nwritten, queuelen, M(output_queue)); output_queue[nwritten] = scratch; } if (nwritten < 0) { switch (nwritten) { case EINTR: case EAGAIN: return; default: myerror(FATAL|USE_ERRNO, "write to master pty failed"); } } if (!output_queue[nwritten]) /* nothing left in queue */ output_queue = NULL; else output_queue = mysavestring(output_queue + nwritten); /* this much is left to be written */ free(old_queue); } void cleanup_rlwrap_and_exit(int status) { unblock_all_signals(); DPRINTF0(DEBUG_TERMIO, "Cleaning up"); if (write_histfile && (histsize==0 || history_total_bytes() > 0)) /* avoid creating empty .speling_eror_history file after typo */ write_history(history_filename); /* ignore errors */ close_logfile(); DPRINTF4(DEBUG_SIGNALS, "command_pid: %d, commands_exit_status: %x, filter_pid: %d, filters_exit_status: %x", command_pid, commands_exit_status, filter_pid, filters_exit_status); mymicrosleep(10); /* we may have got an EOF or EPIPE because the filter or command died, but this doesn't mean that SIGCHLD has been caught already. Taking a little nap now improves the chance that we will catch it (no grave problem if we miss it, but diagnostics, exit status and transparent signal handling depend on it) */ if (filter_pid) kill_filter(); else if (filter_is_dead) { int filters_killer = killed_by(filters_exit_status); myerror(WARNING|NOERRNO, (filters_killer ? "filter was killed by signal %d (%s)" : WEXITSTATUS(filters_exit_status) ? "filter died" : "filter exited"), filters_killer, signal_name(filters_killer)); } if (debug) debug_postmortem(); if (bracketed_paste_enabled) { int saved_nl_came_last = newline_came_last; DPRINTF0(DEBUG_READLINE, "disabling bracketed-paste"); my_putstr(term_disable_bracketed_paste); /* this will not output a newline, but also not add any visible output, so ... */ newline_came_last = saved_nl_came_last; /* preserve the value of newline_came_last */ } if (terminal_settings_saved) if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal_settings) < 0) /* ignore errors (almost dead anyway) */ { /* nothing ... */ } /* fprintf(stderr, "Arggh\n"); don't use myerror!!*/ if (!newline_came_last) /* print concluding newline, if necessary */ my_putstr("\n"); if (status != EXIT_SUCCESS) /* rlwrap itself has failed, rather than the wrapped command */ exit(status); else { int commands_killer = killed_by(commands_exit_status); if (commands_killer) suicide_by(commands_killer, commands_exit_status); /* command terminated by signal, make rlwrap's parent believe rlwrap was killed by it */ else exit(WEXITSTATUS(commands_exit_status)); /* propagate command's exit status */ } } rlwrap-0.46.1/src/malloc_debug.c000066400000000000000000000170161433170252700164720ustar00rootroot00000000000000/* malloc_debug.c: attempt at malloc/free replacement for debugging configure with --enable-debug to use it. Nowadays, valgrind will often be more useful. When configured with --enable-debug, and run with the DEBUG_MALLOC bit set this will log (in the debug log /tmp/rlwrap.debug) all malloc() and free() calls made by rlwrap (but not those made on behalf of rlwrap in library routines) At program exit this will log a list of all unfreed() memory blocks on the heap if DEBUG_WITH_TIMESTAMPS is set in debug , the timestamp of those blocks (i.e. of their allocation) will be listed as well. */ /* 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" #ifdef DEBUG # define USE_MALLOC_DEBUGGER # undef mymalloc # undef free #endif #ifdef USE_MALLOC_DEBUGGER #define SLAVERY_SLOGAN "rlwrap is boss!" #define FREEDOM_SLOGAN "free at last!" #define SLOGAN_MAXLEN 20 #define TIMESTAMP_MAXLEN 30 extern int debug; typedef void (*sighandler_t)(int); typedef struct freed_stamp { char magic[SLOGAN_MAXLEN]; /* magical string that tells us this something about this memory: malloced or freed? */ char *file; /* source file where we were malloced/freed */ int line; /* source line where we were malloced/freed */ int size; char timestamp[TIMESTAMP_MAXLEN]; void *previous; /* maintain a linked list of malloced/freed memory for post-mortem investigations*/ } *Freed_stamp; static void* blocklist = 0; /* start of linked list of allocated blocks. when freed, blocks stay on the list */ static int memory_usage = 0; static char *offending_sourcefile; static int offending_line; sighandler_t old_segfault_handler; /* local segfault handler, installed just before we dereference a pointer to test its writability */ static void handle_segfault(int UNUSED(sig)) { fprintf(stderr, "free() called on bad (unallocated) memory at %s:%d\n", offending_sourcefile, offending_line); exit(1); } /* allocates chunk of memory including a freed_stamp, in which we write line and file where we were alllocated, and a slogan to testify that we have been allocated and not yet freed. returns the address past the stamp If you think this memory will ever be freed by a normal free() use malloc_foreign instead */ void * debug_malloc(size_t size, char *file, int line) { void *chunk; Freed_stamp stamp; if (!(debug & DEBUG_MEMORY_MANAGEMENT)) return mymalloc(size); chunk = mymalloc(sizeof(struct freed_stamp) + size); stamp = (Freed_stamp) chunk; memory_usage += size; DPRINTF4(DEBUG_MEMORY_MANAGEMENT, "malloc size: %d at %s:%d: (total usage now: %d)", (int) size, file, line, memory_usage); strncpy(stamp->magic, SLAVERY_SLOGAN, SLOGAN_MAXLEN); stamp -> file = file; stamp -> line = line; stamp -> size = size; if (debug & DEBUG_WITH_TIMESTAMPS) timestamp(stamp -> timestamp, TIMESTAMP_MAXLEN); else stamp -> timestamp[0] = '\0'; stamp -> previous = blocklist; blocklist = chunk; return (char *) chunk + sizeof(struct freed_stamp); } /* Verifies that ptr indeed points to memory allocaded by debug_malloc, and has not yet been freed. Doesn't really free it, but marks it as freed so that we easily notice double frees */ void debug_free(void *ptr, char *file, int line) { Freed_stamp stamp; if (!(debug & DEBUG_MEMORY_MANAGEMENT)) { free(ptr); return; } stamp = ((Freed_stamp) ptr) - 1; offending_sourcefile = file; /* use static variables to communicate with signal handler */ offending_line = line; old_segfault_handler = signal(SIGSEGV, &handle_segfault); * (char *) ptr = 'x'; /* this, or the next statement will provoke a segfault when address is not writable, i.e. in read-only memory or not in a mamory-mapped area */ if (strcmp(FREEDOM_SLOGAN, stamp -> magic) == 0) { /* Argghh! this memory has been freed before! */ fprintf(stderr, "free() called twice, at %s:%d: (on memory already freed at %s:%d)\n", file, line, stamp->file, stamp->line); exit(1); } else if (strcmp(SLAVERY_SLOGAN, stamp -> magic) == 0) { DPRINTF4(DEBUG_MEMORY_MANAGEMENT, "free() (called at %s:%d) of memory malloced at %s:%d", file, line, stamp->file, stamp->line); strncpy(stamp->magic, FREEDOM_SLOGAN, SLOGAN_MAXLEN); stamp -> file = file; stamp -> line = line; memory_usage -= stamp -> size; signal(SIGSEGV, old_segfault_handler); /* don't really free ptr */ } else { fprintf(stderr, "free() called (at %s:%d) on unmalloced memory <%s>, or memory not malloced by debug_malloc()\n", file, line, mangle_string_for_debug_log(ptr, 30)); close_logfile(); exit(1); } } /* this function calls free() directly, and should be used on memory that was malloc'ed outside our own jurisdiction, i.e. not by debug_malloc(); */ void free_foreign(void *ptr) { free(ptr); } /* this function calls malloc() directly, and should be used on memory that could be freed outside our own jurisdiction, i.e. not by debug_free(); */ void *malloc_foreign(size_t size) { return malloc(size); } /* sometimes we put in one structure objects that were malloced elsewhere and our own mymalloced objects. we cannot free such a structure with free(), nor with free_foreign(). Solution: before using the "foreign" objects, copy them to mymalloced memory and free them immediately This function will be redefined to a NOP (i.e. just return its first argument) unless DEBUG is defined */ void *copy_and_free_for_malloc_debug(void *ptr, size_t size) { void *copy; if (ptr == NULL) return NULL; copy = debug_malloc(size,"foreign",0); memcpy(copy, ptr, size); free(ptr); return copy; } char *copy_and_free_string_for_malloc_debug(char* str) { if (str == NULL) return NULL; return copy_and_free_for_malloc_debug(str, strlen(str)+1); } /* this function logs all non-freed memory blocks (in order to hunt for memory leaks) */ /* blocklist = NULL, hence it is a no-op unless DEBUG_MALLOC has been defined */ void debug_postmortem(void) { Freed_stamp p; char *block; DPRINTF0(DEBUG_MEMORY_MANAGEMENT,"Postmortem list of unfree memory blocks (most recently allocated first): "); for (p = (Freed_stamp) blocklist; p; p = p ->previous) { if (strcmp(FREEDOM_SLOGAN, p -> magic) == 0) continue; else if (strcmp(SLAVERY_SLOGAN, p -> magic) == 0) { block = (char *) p + sizeof(struct freed_stamp); DPRINTF5(DEBUG_MEMORY_MANAGEMENT, "%d bytes malloced at %s %s:%d, contents: <%s>", p->size, p -> timestamp, p ->file, p ->line, M(block)); } else { DPRINTF0(DEBUG_MEMORY_MANAGEMENT, "Hmmm, unmalloced memory, or memory not malloced by debug_malloc()"); } } } #endif /* def USE_MALLOC_DEBUGGER */ rlwrap-0.46.1/src/malloc_debug.h000066400000000000000000000012771433170252700165010ustar00rootroot00000000000000 #ifdef DEBUG void *debug_malloc(size_t size, char *file, int line); void debug_free(void *ptr, char *file, int line); void debug_postmortem(void); void free_foreign(void *ptr); void *malloc_foreign(size_t size); void *copy_and_free_for_malloc_debug(void *ptr, size_t size); char *copy_and_free_string_for_malloc_debug(char* str); #define free(ptr) debug_free(ptr,__FILE__,__LINE__) #define mymalloc(size) debug_malloc(size, __FILE__, __LINE__) #else #define free_foreign free #define malloc_foreign malloc #define copy_and_free_for_malloc_debug(ptr,size) (ptr) #define copy_and_free_string_for_malloc_debug(str) (str) #define debug_postmortem() do_nothing(0) #endif rlwrap-0.46.1/src/multibyte.c000066400000000000000000000065371433170252700161010ustar00rootroot00000000000000#include "rlwrap.h" #include /* rlwrap was written without worrying about multibyte characters. the functions below "retrofit" multi-byte support by providing functions that can be used with minimal adaptations in the usual idioms like for (char *p = s; *p; p++) which can be re-written as: for(mbc_initstate(&st), p = s; *p; mbc_inc(&p, &st)) { c.f. https://kirste.userpage.fu-berlin.de/chemnet/use/info/libc/libc_18.html */ /* voor ascii chars is next_mbc(p) == p+1, maar voor een multibyte positie kan het ook bijv. p+3 of meer zijn */ #ifdef MULTIBYTE_AWARE MBSTATE * mbc_initstate(MBSTATE *st) { memset (st, 0, sizeof(MBSTATE)); return st; } MBSTATE * mbc_copystate(MBSTATE st, MBSTATE *stc) { *stc = st; return stc; } int mbc_is_valid(const char *mb_string, const MBSTATE *st) { MBSTATE scrap = *st; return (int) mbrlen(mb_string, MB_LEN_MAX, &scrap ) >= 0; } const char * mbc_next(const char *mb_string, MBSTATE *st) { if (!*mb_string || !mbc_is_valid(mb_string, st)) return mb_string + 1; else return mb_string + mbrlen(mb_string, MB_LEN_MAX, st); } const char * mbc_inc(const char **mbc, MBSTATE *st) { return (*mbc = mbc_next(*mbc, st)); } char * mbc_first(const char *mb_string, const MBSTATE *st) { MBSTATE scrap = *st; char buffer[MB_LEN_MAX+1]; int len = mbrlen(mb_string, MB_LEN_MAX, &scrap ); strncpy(buffer, mb_string, len); buffer[len] = '\0'; return mysavestring(buffer); } int mbc_charwidth(const char *p, MBSTATE *st) { int width = mbrlen(p, MB_LEN_MAX, st); if (width < 0) { DPRINTF1(DEBUG_READLINE, "invalid multi-byte charavter at stert of %s", M(p)); width = 1; /* if we don''n recognise it, interpret it as a byte */ } return width; } /* copy the first (wide) character in p to *q, incrementing q to one past the result. Does not NULL-terminate the copy */ void mbc_copy(const char *p, char **q, MBSTATE *st) { int i; for(i=0; i < mbc_charwidth(p, st); i++) *(*q)++ = p[i]; } int is_multibyte(const char *mb_char, const MBSTATE *st) { MBSTATE scrap = *st; return mbc_is_valid(mb_char, st) && mbrlen(mb_char, MB_LEN_MAX, &scrap) > 1; } size_t mbc_strnlen(const char *mb_string, size_t maxlen, MBSTATE *st) { size_t len; const char *p; for (len = 0, p = mb_string; *p && p < mb_string + maxlen; mbc_inc(&p, st)) len++; return len; } #else /* if not MULTIBYTE_AWARE: */ MBSTATE * mbc_initstate(MBSTATE *st) { return st; } MBSTATE * mbc_copystate(MBSTATE st, MBSTATE *stc) { *stc = st; return stc; } int mbc_is_valid(const char *UNUSED(string), const MBSTATE *UNUSED(st)) { return TRUE; } const char * mbc_next(const char *string, MBSTATE *UNUSED(st)) { return string + 1; } const char * mbc_inc(const char **p, MBSTATE *UNUSED(st)) { return (*p)++; } char * mbc_first(const char *string, const MBSTATE *UNUSED(st)) { char p[2] = " "; p[0] = string[0]; return mysavestring(p); } int mbc_charwidth(const char *UNUSED(p), MBSTATE *UNUSED(st)) { return 1; } /* copy the first byte in p to *q, incrementing q */ void mbc_copy(const char *p, char **q, MBSTATE *UNUSED(st)) { *(*q)++ = *p; } int is_multibyte(const char *UNUSED(p), const MBSTATE *UNUSED(st)) { return FALSE; } size_t mbc_strnlen(const char *string, size_t maxlen, MBSTATE *UNUSED(st)) { return strnlen(string, maxlen); } #endif rlwrap-0.46.1/src/pty.c000066400000000000000000000370631433170252700146750ustar00rootroot00000000000000/* pty.c: pty handling */ /* 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" /* global vars */ int slave_pty_sensing_fd = -1; /* slave pty: client uses this to communicate with rlwrap, * we keep it open after forking in order to keep track of * client's terminal settings */ static char *sensing_pty = "uninitialized"; pid_t my_pty_fork(int *ptr_master_fd, const struct termios *slave_termios, const struct winsize *slave_winsize) { int fdm, fds = -1; int ttyfd, timeout; pid_t pid; const char *slave_name; struct termios pterm; int only_sigchld[] = { SIGCHLD, 0 }; ptytty_openpty(&fdm, &fds, &slave_name); block_signals(only_sigchld); /* block SIGCHLD until we have had a chance to install a handler for it after the fork() */ fflush(NULL); if ((pid = fork()) < 0) { myerror(FATAL|USE_ERRNO, "Cannot fork"); return(42); /* the compiler may not know that myerror() won't return */ } else if (pid == 0) { /* child */ DEBUG_RANDOM_SLEEP; i_am_child = TRUE; /* remember who I am */ if (debug) my_fopen(&debug_fp, DEBUG_FILENAME, "a+", "debug log"); unblock_all_signals(); close(fdm); /* fdm not used in child */ ptytty_control_tty(fds, slave_name); if (dup2(fds, STDIN_FILENO) != STDIN_FILENO) /* extremely unlikely */ myerror(FATAL|USE_ERRNO, "dup2 to stdin failed"); if (isatty(STDOUT_FILENO) && dup2(fds, STDOUT_FILENO) != STDOUT_FILENO) myerror(FATAL|USE_ERRNO, "dup2 to stdout failed"); if (isatty(STDERR_FILENO) && dup2(fds, STDERR_FILENO) != STDERR_FILENO) myerror(FATAL|USE_ERRNO, "dup2 to stderr failed"); if (fds > STDERR_FILENO) close(fds); if (slave_termios != NULL) if (tcsetattr(STDIN_FILENO, TCSANOW, slave_termios) < 0) myerror(FATAL|USE_ERRNO, "tcsetattr failed on slave pty"); return (0); } else { /* parent */ srand(pid); DEBUG_RANDOM_SLEEP; command_pid = pid; /* the SIGCHLD signal handler needs this global variable */ *ptr_master_fd = fdm; /* Try to find a suitable file descriptor to sense the state of the slave pty */ /* At least on Solaris, we have to use the slave pty itself, but this works only after it has been set up in the child */ /* This may take some time, hence the (increasing) timeout below */ for(timeout = 10; timeout < 2000 && slave_pty_sensing_fd < 0; timeout *= 2) { if (tcgetattr(fdm, &pterm) == 0) { /* if we can do a tcgetattr on the master, assume that it reflects the slave's terminal settings (at least on Linux and FreeBSD, this works), and use it as slave_pty_sensing_fd, to keep tabs on slave terminal settings. In this case we can close fds (the slave), avoiding problems with lost output on FreeBSD when the slave dies */ slave_pty_sensing_fd = fdm; sensing_pty = "master"; close(fds); } else if (mymicrosleep(timeout), /* wait a little */ tcgetattr(fds, &pterm) == 0) { /* ... then try slave pty */ slave_pty_sensing_fd = fds; sensing_pty = "slave"; /* keep the slave pty open to get its terminal settings */ } } if (slave_pty_sensing_fd < 0) /* both master and slave unusable: give up */ myerror(FATAL|USE_ERRNO, "cannot determine terminal mode of slave command"); DPRINTF1(DEBUG_TERMIO, "Using %s pty to sense slave settings in parent", sensing_pty); if (!isatty(STDOUT_FILENO) || !isatty(STDERR_FILENO)) { /* stdout or stderr redirected? */ ttyfd = open("/dev/tty", O_WRONLY); /* open users terminal */ DPRINTF1(DEBUG_TERMIO, "stdout or stderr are not a terminal, opening /dev/tty with fd=%d", ttyfd); if (ttyfd <0) myerror(FATAL|USE_ERRNO, "Could not open /dev/tty"); if (dup2(ttyfd, STDOUT_FILENO) != STDOUT_FILENO) myerror(FATAL|USE_ERRNO, "dup2 of stdout to ttyfd failed"); if (dup2(ttyfd, STDERR_FILENO) != STDERR_FILENO) myerror(FATAL|USE_ERRNO, "dup2 of stderr to ttyfd failed"); close (ttyfd); } errno = 0; if (renice) { int retval = nice(1); if (retval < 0 && errno) myerror(FATAL|USE_ERRNO, "could not increase my own niceness"); } if (slave_winsize != NULL) if (ioctl(slave_pty_sensing_fd, TIOCSWINSZ, slave_winsize) < 0) myerror(FATAL|USE_ERRNO, "TIOCSWINSZ failed on %s pty", sensing_pty); /* This is done in parent and not in child as that would fail on Solaris (why?) */ return (pid); } } int slave_is_in_raw_mode(void) { struct termios *pterm_slave; static int been_warned = 0; int in_raw_mode; if (command_is_dead) return FALSE; /* filter last words too (even if ncurses-ridden) */ if (!(pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty"))) { if (been_warned++ == 1) /* only warn once, but not the first time (as this usually means that the rlwrapped command has just died) - this is still a race when signals get delivered very late*/ myerror(WARNING|USE_ERRNO, "tcgetattr error on slave pty (from parent process)"); return TRUE; } in_raw_mode = !(pterm_slave -> c_lflag & ICANON); free(pterm_slave); return in_raw_mode; } void mirror_slaves_echo_mode(void) { /* important e.g. when slave command asks for password */ struct termios *pterm_slave = NULL; int should_echo_anyway = always_echo || (always_readline && !dont_wrap_command_waits()); if ( !(pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty")) || command_is_dead ) /* race condition here: SIGCHLD may not yet have been caught */ return; assert (pterm_slave != NULL); if (tcsetattr(STDIN_FILENO, TCSANOW, pterm_slave) < 0 && errno != ENOTTY) /* @@@ */ myerror(FATAL|USE_ERRNO, "cannot prepare terminal (tcsetattr error on stdin)"); term_eof = pterm_slave -> c_cc[VEOF]; /* if the --always-readline option is set with argument "assword:", determine whether prompt ends with "assword:\s" */ if (should_echo_anyway && password_prompt_search_string) { char *p, *q; DPRINTF2(DEBUG_READLINE, "matching prompt <%s> and password search string <%s>..", saved_rl_state.raw_prompt, password_prompt_search_string); p = saved_rl_state.raw_prompt + strlen(saved_rl_state.raw_prompt) - 1; q = password_prompt_search_string + strlen(password_prompt_search_string) - 1; while (*p == ' ') /* skip trailing spaces in prompt */ p--; while (p >= saved_rl_state.raw_prompt && q >= password_prompt_search_string) if (*p-- != *q--) break; if (q < password_prompt_search_string) /* found "assword:" */ should_echo_anyway = FALSE; DPRINTF1(DEBUG_READLINE,".. result: should_echo_anyway = %d", should_echo_anyway); } if (!command_is_dead && (should_echo_anyway || pterm_slave->c_lflag & ECHO)) { redisplay = TRUE; } else { redisplay = FALSE; } if (pterm_slave) free(pterm_slave); set_echo(redisplay); /* This is a bit weird: we want echo off all the time, because readline takes care of echoing, but as readline uses the current ECHO mode to determine whether you want echo or not, we must set it even if we know that readline will switch it off immediately */ } void write_EOF_to_master_pty(void) { struct termios *pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty"); char *sent_EOF = mysavestring("?"); *sent_EOF = (pterm_slave && pterm_slave->c_cc[VEOF] ? pterm_slave->c_cc[VEOF] : 4) ; /*@@@ HL shouldn't we directly mysavestring(pterm_slave->c_cc[VEOF]) ??*/ DPRINTF1(DEBUG_TERMIO, "Sending %s", mangle_string_for_debug_log(sent_EOF, MANGLE_LENGTH)); put_in_output_queue(sent_EOF); free(pterm_slave); free(sent_EOF); } /* @@@ The next fuction is probably superfluous */ void write_EOL_to_master_pty(char *received_eol) { struct termios *pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty"); char *sent_eol = mysavestring("?"); *sent_eol = *received_eol; if (pterm_slave) { switch (*received_eol) { case '\n': if (pterm_slave->c_iflag & INLCR) *sent_eol = '\r'; break; case '\r': if (pterm_slave->c_iflag & IGNCR) return; if (pterm_slave->c_iflag & ICRNL) *sent_eol = '\n'; } } put_in_output_queue(sent_eol); free(pterm_slave); free(sent_eol); } void completely_mirror_slaves_terminal_settings(void) { struct termios *pterm_slave; DEBUG_RANDOM_SLEEP; pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty"); log_terminal_settings(pterm_slave); if (pterm_slave && tcsetattr(STDIN_FILENO, TCSANOW, pterm_slave) < 0 && errno != ENOTTY) { /* nothing ... */ } /* myerror(FATAL|USE_ERRNO, "cannot prepare terminal (tcsetattr error on stdin)"); */ free(pterm_slave); DEBUG_RANDOM_SLEEP; } void completely_mirror_slaves_output_settings(void) { struct termios *pterm_stdin, *pterm_slave; DEBUG_RANDOM_SLEEP; pterm_stdin = my_tcgetattr(STDIN_FILENO, "stdin"); pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty"); if (pterm_slave && pterm_stdin) { /* no error message - we can be called while slave is already dead */ pterm_stdin -> c_oflag = pterm_slave -> c_oflag; tcsetattr(STDIN_FILENO, TCSANOW, pterm_stdin); } free(pterm_slave); free(pterm_stdin); DEBUG_RANDOM_SLEEP; } /* rlwrap's transparency depends on knowledge of the slave commands terminal settings, which are kept in its termios structure. In many cases it is enough to inspect those settings after the user has pressed a key (e.g.: user presses a key - rlwrap inspects slaves ECHO flag - rlwrap echoes the keypress (or not) But some keypresses are directly handled by the terminal driver, e.g.: user presses CTRL-C - driver inspects rlwrap's ISIG flag and c_cc[VINTR] character - driver sends a sigINT to rlwrap. Rlwrap doesn't get a chance to intervene - except by unsetting ISIG beforehand and handling the interrupt key itself. We cannot, however, just bind the key to a readline action self-insert(key), as this wouldn't interrupt an ongoing line edit. Rlwrap solves this problem by copying the relevant flags and special characters from the slave pty to stdin/stdout. Of course, ideally this should happen *before* the user presses her key. This can be done by calling rlwrap with the -W (--polling) option. */ /* Many termios flags and characters are serial-specific and will never be used on a pty Possible exceptions (cf: http://www.lafn.org/~dave/linux/termios.txt) ISTRIP IGNCR (and related flags) and all the c_cc characters (although only VINTR seems to be changed in practice, e.g. by emacs to CTRL-G) */ void completely_mirror_slaves_special_characters(void) { struct termios *pterm_stdin, *pterm_slave; DEBUG_RANDOM_SLEEP; pterm_stdin = my_tcgetattr(STDIN_FILENO, "stdin"); pterm_slave = my_tcgetattr(slave_pty_sensing_fd, "slave pty"); if (pterm_slave && pterm_stdin) { /* no error message - we can be called while slave is already dead */ int isig_stdin = pterm_stdin -> c_lflag & ISIG; cc_t ic_stdin = pterm_stdin -> c_cc[VINTR]; int isig_slave = pterm_slave -> c_lflag & ISIG; cc_t ic_slave = pterm_slave -> c_cc[VINTR]; if ((isig_stdin == isig_slave) && (ic_stdin == ic_slave)) /* nothing to do */ return; DPRINTF4(DEBUG_TERMIO,"stdin interrupt handling copied from slave: ISIG: %0x->%0x, VINTR: %0x->%0x", isig_stdin, isig_slave, ic_stdin, ic_slave); pterm_stdin -> c_cc[VINTR] = ic_slave; pterm_stdin -> c_lflag ^= (isig_slave ^ ISIG); /* copy ISIG bit from slave */ tcsetattr(STDIN_FILENO, TCSANOW, pterm_stdin); } free(pterm_slave); free(pterm_stdin); DEBUG_RANDOM_SLEEP; } /* returns TRUE if the -N option has been specified, we can read /proc//wchan, (which happens only on linux, as far as I know) and what we read there contains one of the "wait_prhases"m meaning (presumably...) that command is waiting for one of its children if otherwise returns FALSE */ int dont_wrap_command_waits(void) { static char command_wchan[MAXPATHLEN+1]; static char *wait_phrases[] = {"wait4", "suspend", "do_wait", NULL}; static int initialised = FALSE; static int wchan_fd; static int been_warned = 0; char buffer[BUFFSIZE], **p; int nread, result = FALSE; DEBUG_RANDOM_SLEEP; if (!commands_children_not_wrapped) return FALSE; if (!initialised) { /* first time we're called after birth of child */ snprintf2(command_wchan, MAXPATHLEN , "%s/%d/wchan", PROC_MOUNTPOINT, command_pid); initialised = TRUE; } if (command_is_dead) return TRUE; /* This is lazy!! signal may not have been delivered @@@ */ if (screen_is_alternate) return TRUE; /* assume that programs that use the alternate screen never need rlwrap */ wchan_fd = open(command_wchan, O_RDONLY); if (wchan_fd < 0) { /* don't assume that programs that don't use the alternate screen always need rlwrap */ if (been_warned++ == 0 && !(term_rmcup && term_smcup)) { myerror(WARNING|NOERRNO, "you specified the -N (--no-children) option - but spying\n" "on %s's wait status doesn't work on your system, as rlwrap\n " "cannot sense the 'alternate screen',and cannot read %s", command_name, command_wchan); } return FALSE; } if (((nread = read(wchan_fd, buffer, BUFFSIZE -1)) > 0)) { buffer[nread] = '\0'; result = FALSE; for (p = wait_phrases; *p; p++) /* Not quite watertight: I don't have a complete (and unchanging) list of syscalls that make client wait for one of its children */ if (strstr(buffer, *p)) { result = TRUE; break; } DPRINTF3(DEBUG_READLINE, "read commands wchan %s: <%s>, waiting: %s", command_wchan, buffer, result ? "yes" : "no"); } close(wchan_fd); DEBUG_RANDOM_SLEEP; return result; } int skip_rlwrap(void) { /* this function is called from sigTSTP signal handler. Is it re-entrant? */ int retval = FALSE; DEBUG_RANDOM_SLEEP; if (dont_wrap_command_waits()) retval = TRUE; else if (always_readline) retval = FALSE; else if (slave_is_in_raw_mode()) retval= TRUE; DEBUG_RANDOM_SLEEP; return retval; } rlwrap-0.46.1/src/ptytty.c000066400000000000000000000227431433170252700154350ustar00rootroot00000000000000/* * Shamelessly ripped out of rxvt-2.7.10 (Copyright (c) 1999-2001 * Geoff Wing ) by Hans Lub * * All portions of code are copyright by their respective author/s. * * 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. *---------------------------------------------------------------------*/ #include "rlwrap.h" #include #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #if defined(HAVE_STRING_H) # include #endif #ifdef HAVE_FCNTL_H # include #endif #ifdef HAVE_SYS_IOCTL_H # include #endif #if defined(PTYS_ARE_PTMX) && !defined(__CYGWIN32__) # include /* for I_PUSH */ #endif /* ------------------------------------------------------------------------- * * GET PSEUDO TELETYPE - MASTER AND SLAVE * * ------------------------------------------------------------------------- */ /* * Returns pty file descriptor, or -1 on failure * If successful, ttydev is set to the name of the slave device. * fd_tty _may_ also be set to an open fd to the slave device */ #define O_RWNN O_RDWR | O_NDELAY | O_NOCTTY int ptytty_get_pty(int *fd_tty, const char **ttydev) { int pfd; #ifdef PTYS_ARE_OPENPTY char tty_name[sizeof "/dev/pts/????\0"]; if (openpty(&pfd, fd_tty, tty_name, NULL, NULL) != -1) { *ttydev = strdup(tty_name); return pfd; } #endif #ifdef PTYS_ARE__GETPTY *ttydev = _getpty(&pfd, O_RWNN, 0622, 0); if (*ttydev != NULL) return pfd; #endif #ifdef PTYS_ARE_GETPTY char *ptydev; while ((ptydev = getpty()) != NULL) if ((pfd = open(ptydev, O_RWNN, 0)) >= 0) { *ttydev = ptydev; return pfd; } #endif #if defined(HAVE_GRANTPT) && defined(HAVE_UNLOCKPT) # if defined(PTYS_ARE_GETPT) || defined(PTYS_ARE_PTMX) { extern char *ptsname(); # ifdef PTYS_ARE_GETPT pfd = getpt(); # else pfd = open("/dev/ptmx", O_RWNN, 0); # endif if (pfd >= 0) { if (grantpt(pfd) == 0 /* change slave permissions */ && unlockpt(pfd) == 0) { /* slave now unlocked */ *ttydev = ptsname(pfd); /* get slave's name */ return pfd; } close(pfd); } } # endif #endif #ifdef PTYS_ARE_PTC if ((pfd = open("/dev/ptc", O_RWNN, 0)) >= 0) { *ttydev = ttyname(pfd); return pfd; } #endif #ifdef PTYS_ARE_CLONE /* HP-UX */ if ((pfd = open("/dev/ptym/clone", O_RWNN, 0)) >= 0) { *ttydev = ptsname(pfd); return pfd; } #endif #ifdef PTYS_ARE_NUMERIC { int idx; char *c1, *c2; char pty_name[] = "/dev/ptyp???"; char tty_name[] = "/dev/ttyp???"; c1 = &(pty_name[sizeof(pty_name) - 4]); c2 = &(tty_name[sizeof(tty_name) - 4]); for (idx = 0; idx < 256; idx++) { snprintf1(c1, strlen(c1), "%d", idx); snprintf1(c2, strlen(c2), "%d", idx); if (access(tty_name, F_OK) < 0) { idx = 256; break; } if ((pfd = open(pty_name, O_RWNN, 0)) >= 0) { if (access(tty_name, R_OK | W_OK) == 0) { *ttydev = strdup(tty_name); return pfd; } close(pfd); } } } #endif #ifdef PTYS_ARE_SEARCHED { const char *c1, *c2; char pty_name[] = "/dev/pty??"; char tty_name[] = "/dev/tty??"; # ifndef PTYCHAR1 # define PTYCHAR1 "pqrstuvwxyz" # endif # ifndef PTYCHAR2 # define PTYCHAR2 "0123456789abcdef" # endif for (c1 = PTYCHAR1; *c1; c1++) { pty_name[(sizeof(pty_name) - 3)] = tty_name[(sizeof(pty_name) - 3)] = *c1; for (c2 = PTYCHAR2; *c2; c2++) { pty_name[(sizeof(pty_name) - 2)] = tty_name[(sizeof(pty_name) - 2)] = *c2; if ((pfd = open(pty_name, O_RWNN, 0)) >= 0) { if (access(tty_name, R_OK | W_OK) == 0) { *ttydev = strdup(tty_name); return pfd; } close(pfd); } } } } #endif return -1; } /*----------------------------------------------------------------------*/ /* * Returns tty file descriptor, or -1 on failure */ /* EXTPROTO */ int ptytty_get_tty(const char *ttydev) { return open(ttydev, O_RDWR | O_NOCTTY, 0); } /*----------------------------------------------------------------------*/ /* * Make our tty a controlling tty so that /dev/tty points to us */ /* EXTPROTO */ int ptytty_control_tty(int fd_tty, const char *ttydev) { DPRINTF3(DEBUG_TERMIO, "pid: %d, tty fd: %d, dev: %s", getpid(), fd_tty, ttydev); DPRINTF2(DEBUG_TERMIO, "tcgetpgrp(): %d getpgrp(): %d", tcgetpgrp(fd_tty), getpgrp()); /* ------------------- Become leader of our own session: --------------------- */ # ifdef HAVE_SETSID { pid_t ret = setsid(); DPRINTF2(DEBUG_TERMIO, "setsid() returned %d %s", (int)ret, ERRMSG(ret < 0)); } # endif /* ------------------- Get controlling tty --------------------- */ #ifndef __QNX__ /* in QNX, the only way to get a controlling tty is at process creation time, using qnx_spawn() instead of fork(). I'm too lazy to re-write my_pty_fork(), so I let rlwrap soldier on without a controlling tty */ { int fd; # ifdef TIOCNOTTY fd = open("/dev/tty", O_RDWR | O_NOCTTY); DPRINTF1(DEBUG_TERMIO, "Voiding tty associations: previous=%s", fd < 0 ? "no" : "yes"); if (fd >= 0) { int ret = ioctl(fd, TIOCNOTTY, NULL); /* void tty associations */ DPRINTF2(DEBUG_TERMIO, "ioctl(..., TIOCNOTTY): %d %s", ret, ERRMSG(ret < 0)); close(fd); } # endif /* ---------------------------------------- */ fd = open("/dev/tty", O_RDWR | O_NOCTTY); DPRINTF1(DEBUG_TERMIO, "ptytty_control_tty(): /dev/tty has controlling tty? %s", fd < 0 ? "no (good)" : "yes (bad)"); if (fd >= 0) close(fd); /* ouch: still have controlling tty */ /* ---------------------------------------- */ #ifdef HAVE_ISASTREAM if (isastream(fd_tty) == 1) { # if defined(I_SWROPT) ioctl(fd_tty, I_SWROPT, 0); # endif # if defined(PTYS_ARE_PTMX) && defined(I_PUSH) /* * Push STREAMS modules: * ptem: pseudo-terminal hardware emulation module. * ldterm: standard terminal line discipline. * ttcompat: V7, 4BSD and XENIX STREAMS compatibility module. * * After we push the STREAMS modules, the first open() on the slave side * (i.e. the next section between the dashes giving us "tty opened OK") * should make the "ptem" (or "ldterm" depending upon either which OS * version or which set of manual pages you have) module give us a * controlling terminal. We must already have close()d the master side * fd in this child process before we push STREAMS modules on because the * documentation is really unclear about whether it is any close() on * the master side or the last close() - i.e. a proper STREAMS dismantling * close() - on the master side which causes a hang up to be sent * through - Geoff Wing */ DPRINTF0(DEBUG_TERMIO, "Pushing STREAMS modules"); ioctl(fd_tty, I_PUSH, "ptem"); ioctl(fd_tty, I_PUSH, "ldterm"); ioctl(fd_tty, I_PUSH, "ttcompat"); # endif } #endif /* ---------------------------------------- */ # if defined(TIOCSCTTY) fd = ioctl(fd_tty, TIOCSCTTY, NULL); DPRINTF2(DEBUG_TERMIO, "ioctl(..,TIOCSCTTY): %d %s", fd, ERRMSG(fd < 0)); # elif defined(TIOCSETCTTY) fd = ioctl(fd_tty, TIOCSETCTTY, NULL); DPRINTF2(DEBUG_TERMIO, "ioctl(..,TIOCSETCTTY): %d %s", fd, ERRMSG(fd < 0)); # else fd = open(ttydev, O_RDWR); DPRINTF2(DEBUG_TERMIO, "tty open%s %s", (fd < 0 ? " failure" : "ed OK"), ERRMSG(fd < 0)); if (fd >= 0) close(fd); # endif /* ---------------------------------------- */ fd = open("/dev/tty", O_WRONLY); DPRINTF2(DEBUG_TERMIO, "do we have controlling tty now: %s %s", (fd < 0 ? "no (fatal)" : "yes (good)"), ERRMSG(fd < 0)); if (fd < 0) myerror(WARNING|USE_ERRNO, "Could not get controlling terminal for %s", program_name); /* mywarn called from child */ else close(fd); /* ---------------------------------------- */ DPRINTF2(DEBUG_TERMIO, "tcgetpgrp(): %d getpgrp(): %d", tcgetpgrp(fd_tty), getpgrp()); /* ---------------------------------------- */ } #endif /* ! __QNX__ */ return 0; } int ptytty_openpty(int *amaster, int *aslave, const char **name) { const char *scrap; *aslave = -1; *amaster = ptytty_get_pty(aslave, &scrap); if (*amaster < 0) myerror(FATAL|USE_ERRNO, "Could not open master pty"); if (*aslave < 0) *aslave = ptytty_get_tty(scrap); if (*aslave < 0) myerror(FATAL|USE_ERRNO, "Could not open slave pty %s", scrap); else if (name != NULL) *name = scrap; return 0; } rlwrap-0.46.1/src/readline.c000066400000000000000000001252221433170252700156370ustar00rootroot00000000000000/* readline.c: interacting with the GNU readline library (C) 2000-2007 Hans Lub 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" /* global vars */ int remember_for_completion = FALSE; /* whether we should put al words from in/output on the list */ char *multiline_separator = NULL; /* character sequence to use in lieu of newline when storing multi-line input in a single history line */ char *pre_given = NULL; /* pre-given user input when rlwrap starts up */ struct rl_state saved_rl_state = { "", "", 0, 0, 0 }; /* saved state of readline */ bool bracketed_paste_enabled = FALSE; static char return_key; /* Key pressed to enter line */ static int forget_line; static char *colour_start, *colour_end; /* colour codes */ int multiline_prompts = TRUE; /* forward declarations */ static void line_handler(char *); static void my_add_history(char *); static int my_accept_line(int, int); static int my_accept_line_and_forget(int, int); static void munge_file_in_editor(const char *filename, int lineno, int colno); static Keymap getmap(const char *name); static void bindkey(int key, rl_command_func_t *function, const char *function_name, const char *maplist); #define RL_COMMAND_FUN(f) f, #f /* readline bindable functions: */ static int munge_line_in_editor(int, int); static int direct_keypress(int, int); static int direct_prefix(int, int); static int handle_hotkey(int, int); static int handle_hotkey_without_history(int, int); /* only useful while debugging: */ static int debug_ad_hoc(int,int); static int dump_all_keybindings(int,int); void init_readline(char *UNUSED(prompt)) { DPRINTF1(DEBUG_READLINE, "Initialising readline version %x", rl_readline_version); rl_add_defun("rlwrap-accept-line", my_accept_line,-1); rl_add_defun("rlwrap-accept-line-and-forget", my_accept_line_and_forget,-1); rl_add_defun("rlwrap-call-editor", munge_line_in_editor, -1); rl_add_defun("rlwrap-direct-keypress", direct_keypress, -1); rl_add_defun("rlwrap-direct-prefix", direct_prefix, -1); rl_add_defun("rlwrap-hotkey", handle_hotkey, -1); rl_add_defun("rlwrap-hotkey-without-history", handle_hotkey_without_history, -1); /* only useful while debugging */ rl_add_defun("rlwrap-dump-all-keybindings", dump_all_keybindings,-1); rl_add_defun("rlwrap-debug-ad-hoc", debug_ad_hoc, -1); /* if someone's .inputrc binds a key to accept-line, make it use our own version in lieu of readline's */ rl_add_defun("accept-line", my_accept_line, -1); /* put the next variable binding(s) *before* rl_initialize(), so they can be overridden */ rl_variable_bind("blink-matching-paren","on"); bindkey('\n', RL_COMMAND_FUN(my_accept_line), "emacs-standard; vi-insert; vi-command"); bindkey('\r', RL_COMMAND_FUN(my_accept_line), "emacs-standard; vi-insert; vi-command"); bindkey(15, RL_COMMAND_FUN(my_accept_line_and_forget), "emacs-standard; vi-insert; vi-command"); /* ascii #15 (Control-O) is unused in readline's emacs and vi keymaps */ if (multiline_separator) bindkey(30, RL_COMMAND_FUN(munge_line_in_editor), "emacs-standard;vi-insert;vi-command"); /* CTRL-^: unused in vi-insert-mode, hardly used in emacs (doubles arrow-up) */ /* rl_variable_bind("gnah","gnerp"); It is not possible to create new readline variables (only functions) */ rl_catch_signals = 0; rl_initialize(); /* This has to happen *before* we set rl_redisplay_function, otherwise readline will not bother to call tgetent(), will be agnostic about terminal capabilities and hence not be able to honour e.g. a set horizontal-scroll-mode off in .inputrc */ /* put the next variable binding(s) *after* rl_initialize(), so they cannot be overridden */ if (rl_readline_version < 0x802) /* in bracketed_paste_mode rl_deprep_terminal() prints BRACK_PASTE_FINI ("\033[?2004l\r") which moves the cursor to the beginning of the line. In pre-8.2 readline, this doesn't set _rl_last_c_pos to 0, which makes rl_redisplay() think that it is already past the prompt, making it overwrite the prompt that rlwrap has already printed. cf. https://github.com/hanslub42/rlwrap/issues/168 */ rl_variable_bind("enable-bracketed-paste","off"); using_history(); rl_redisplay_function = my_redisplay; rl_completion_entry_function = (rl_compentry_func_t *) & my_completion_function; rl_catch_signals = FALSE; rl_catch_sigwinch = FALSE; saved_rl_state.input_buffer = mysavestring(pre_given ? pre_given : ""); /* Even though the pre-given input won't be displayed before the first cooking takes place, we still want it to be accepted when the user presses ENTER before that (e.g. because she already knows the pre-given input and wants to accept that) */ saved_rl_state.point = strlen(saved_rl_state.input_buffer); saved_rl_state.raw_prompt = mysavestring(""); saved_rl_state.cooked_prompt = NULL; bracketed_paste_enabled = term_enable_bracketed_paste != NULL && strings_are_equal(rl_variable_value("enable-bracketed-paste"),"on"); DPRINTF2(DEBUG_READLINE, "var: %s, enabled: %s", rl_variable_value("enable-bracketed-paste"), term_enable_bracketed_paste); if (bracketed_paste_enabled) { DPRINTF0(DEBUG_READLINE, "bracketed-paste is enabled"); my_putstr(term_enable_bracketed_paste); } } /* save readline internal state in rl_state, redisplay the prompt (so that client output gets printed at the right place) */ void save_rl_state(void) { free(saved_rl_state.input_buffer); /* free(saved_rl_state.raw_prompt) */; saved_rl_state.input_buffer = mysavestring(rl_line_buffer); /* saved_rl_state.raw_prompt = mysavestring(rl_prompt); */ saved_rl_state.point = rl_point; /* and point */ rl_line_buffer[0] = '\0'; if (saved_rl_state.already_saved) return; saved_rl_state.already_saved = 1; rl_delete_text(0, rl_end); /* clear line (after prompt) */ rl_point = 0; my_redisplay(); /* and redisplay (this time without user input, cf the comments for the line_handler() function below) */ rl_callback_handler_remove(); /* restore original terminal settings */ rl_deprep_terminal(); } /* Restore readline internal state from rl_state. */ void restore_rl_state(void) { char *newprompt; move_cursor_to_start_of_prompt(impatient_prompt ? ERASE : DONT_ERASE); cook_prompt_if_necessary(); newprompt = mark_invisible(saved_rl_state.cooked_prompt); /* bracket (colour) control sequences with \001 and \002 */ if (!strings_are_equal(newprompt, saved_rl_state.cooked_prompt)) DPRINTF2(DEBUG_READLINE, "newprompt: <%s>, cooked prompt: <%s>", M(newprompt), M(saved_rl_state.cooked_prompt)); rl_expand_prompt(newprompt); mirror_slaves_echo_mode(); /* don't show passwords etc */ DPRINTF1(DEBUG_READLINE,"newprompt now %s", M(newprompt)); rl_callback_handler_install(newprompt, &line_handler); DPRINTF0(DEBUG_AD_HOC, "freeing newprompt"); free(newprompt); /* readline docs don't say it, but we can free newprompt now (readline apparently uses its own copy) */ rl_insert_text(saved_rl_state.input_buffer); rl_point = saved_rl_state.point; saved_rl_state.already_saved = 0; DPRINTF0(DEBUG_AD_HOC, "Starting redisplay"); rl_redisplay(); rl_prep_terminal(1); prompt_is_still_uncooked = FALSE; /* has been done right now */ } /* display (or remove, if message == NULL) message in echo area, with the appropriate bookkeeping */ void message_in_echo_area(char *message) { static int message_in_echo_area = FALSE; DPRINTF1(DEBUG_READLINE, "message: %s", M(message)); if (message) { rl_save_prompt(); message_in_echo_area = TRUE; rl_message(message); } else { if (message_in_echo_area) rl_restore_prompt(); rl_clear_message(); message_in_echo_area = FALSE; } } #ifndef HAVE_RL_VARIABLE_VALUE /* very ancient readline? */ # define rl_variable_value(s) "off" #endif static void line_handler(char *line) { char *rewritten_line, *filtered_line; if (line == NULL) { /* EOF on input, forward it */ DPRINTF1(DEBUG_READLINE, "EOF detected, writing character %d", term_eof); /* colour_the_prompt = FALSE; don't mess with the cruft that may come out of dying command @@@ but command may not die!*/ write_EOF_to_master_pty(); } else { /* NB: with bracketed-paste, "line" may actually contain newline characters */ if (*line && /* forget empty lines */ redisplay && /* forget passwords */ !forget_line && /* forget lines entered by CTRL-O */ !match_regexp(line, forget_regexp, TRUE)) { /* forget lines (case-inseitively) matching -g option regexp */ my_add_history(line); /* if line consists of multiple lines, each line is added to history separately. Is this documented somewhere? */ } forget_line = FALSE; /* until CTRL-O is used again */ /* Time for a design decision: when we send the accepted line to the client command, it will most probably be echoed back. We have two choices: we leave the entered line on screen and suppress just enough of the clients output (I believe this is what rlfe does), or we remove the entered input (but not the prompt!) and let it be replaced by the echo. This is what we do; as a bonus, if the program doesn't echo, e.g. at a password prompt, the **** which has been put there by our homegrown_redisplay function will be removed (@@@is this what we want?) I think this method is more natural for multi-line input as well, (we will actually see our multi-line input as multiple lines) but not everyone will agree with that. O.K, we know for sure that cursor is at start of line (after the prompt, or at position 0, if bracketed paste is enabled)). When clients output arrives, it will be printed at just the right place - but first we 'll erase the user input (as it may be about to be changed by the filter) */ rl_delete_text(0, rl_end); /* clear line (after prompt) */ rl_point = 0; my_redisplay(); /* and redisplay (now without user input, which will be echoed back, cf. comments above) */ rewritten_line = (multiline_separator ? search_and_replace(multiline_separator, "\n", line, 0, NULL, NULL) : mysavestring(line)); if (redisplay) filtered_line = pass_through_filter(TAG_INPUT, rewritten_line); else { /* password, none of filters business */ pass_through_filter(TAG_INPUT, "***"); /* filter some input anyway, to keep filter in sync (result is discarded). */ filtered_line = mysavestring(rewritten_line); } free(rewritten_line); /* do we have to adapt clients winzsize now? */ if (deferred_adapt_commands_window_size) { adapt_tty_winsize(STDIN_FILENO, master_pty_fd); kill(-command_pid, SIGWINCH); deferred_adapt_commands_window_size = FALSE; } /*OK, now feed line to underlying command and wait for the echo to come back */ put_in_output_queue(filtered_line); DPRINTF2(DEBUG_READLINE, "putting %d bytes %s in output queue", (int) strlen(rewritten_line), M(rewritten_line)); write_EOL_to_master_pty(return_key ? &return_key : "\n"); accepted_lines++; free_foreign(line); /* free_foreign because line was malloc'ed by readline, not by rlwrap */ free(filtered_line); /* we're done with them */ return_key = 0; within_line_edit = FALSE; if(!RL_ISSTATE(RL_STATE_MACROINPUT)) /* when called during playback of a multi-line macro, line_handler() will be called more than once whithout re-entering main_loop(). If we'd remove it here, the second call would crash */ rl_callback_handler_remove(); set_echo(FALSE); free(saved_rl_state.input_buffer); free(saved_rl_state.raw_prompt); free(saved_rl_state.cooked_prompt); saved_rl_state.input_buffer = mysavestring(""); saved_rl_state.raw_prompt = mysavestring(""); saved_rl_state.cooked_prompt = NULL; saved_rl_state.point = 0; saved_rl_state.already_saved = TRUE; redisplay = TRUE; if (one_shot_rlwrap) write_EOF_to_master_pty(); /* readline only outputs term_enable_bracketed_paste when we call rl_prep_terminal(). That's too */ /* late for us, as we only call rl_prep_terminal *after* we have received user input */ if (bracketed_paste_enabled) my_putstr(term_enable_bracketed_paste); } } /* this function (drop-in replacement for readline's own accept-line()) will be bound to RETURN key: */ static int my_accept_line(int UNUSED(count), int key) { rl_point = 0; /* leave cursor on predictable place */ my_redisplay(); rl_done = 1; return_key = (char)key; return 0; } /* this function will be bound to rl_accept_key_and_forget key (normally CTRL-O) */ static int my_accept_line_and_forget(int count, int UNUSED(key)) { forget_line = 1; return my_accept_line(count, '\n'); } static int dump_all_keybindings(int count, int key) { rl_dump_functions(count,key); rl_variable_dumper(FALSE); rl_macro_dumper(FALSE); return 0; } /* format line and add it to history list, avoiding duplicates if necessary */ static void my_add_history(char *line) { int lookback, count, here; char *new_entry, *filtered_line, **lineptr, **list; /* with bracketed-paste mode, line may actually be multiple lines. We'll treat each of those lines */ /* as a separate history item (I believe that is what bash does as well). */ list = split_with(line,"\n"); for (lineptr = list; *lineptr; lineptr++) { filtered_line = pass_through_filter(TAG_HISTORY, *lineptr); switch (history_duplicate_avoidance_policy) { case KEEP_ALL_DOUBLES: lookback = 0; break; case ELIMINATE_SUCCESIVE_DOUBLES: lookback = 1; break; case ELIMINATE_ALL_DOUBLES: lookback = history_length; break; } new_entry = filtered_line; lookback = min(history_length, lookback); for (count = 0, here = history_length - 1; count < lookback ; count++, here--) { DPRINTF4(DEBUG_READLINE, "comparing <%s> and <%s> (count = %d, here = %d)", line , history_get(history_base + here)->line ,count, here); if (strncmp(new_entry, history_get(history_base + here) -> line, 10000) == 0) { /* history_get uses the logical offset history_base .. */ HIST_ENTRY *entry = remove_history (here); /* .. but remove_history doesn't! */ DPRINTF2(DEBUG_READLINE, "removing duplicate entry #%d (%s)", here, entry->line); free_foreign(entry->line); free_foreign(entry); } } add_history(new_entry); free(new_entry); } free_splitlist(list); } /* Homegrown redisplay function - erases current line and prints the new one. Used for passwords (where we want to show **** instead of user input) and whenever HOMEGROWN_REDISPLAY is defined (for systems where rl_redisplay() misbehaves, like sometimes on Solaris). Otherwise we use the much faster and smoother rl_redisplay() This function cannot display multiple lines: it will only scroll horizontally (even if horizontal-scroll-mode is off in .inputrc) */ static void my_homegrown_redisplay(int hide_passwords) { static int line_start = 0; /* at which position of prompt_plus_line does the printed line start? */ static int line_extends_right = 0; static int line_extends_left = 0; static char *previous_line = NULL; int width = winsize.ws_col; int skip = max(1, min(width / 5, 10)); /* jumpscroll this many positions when cursor reaches edge of terminal */ char *prompt_without_ignore_markers; int colourless_promptlen = colourless_strlen(rl_prompt, &prompt_without_ignore_markers, 0, 0, NULL); int promptlen = strlen(prompt_without_ignore_markers); int invisible_chars_in_prompt = promptlen - colourless_promptlen; char *prompt_plus_line = add2strings(prompt_without_ignore_markers, rl_line_buffer); char *new_line; int total_length = strlen(prompt_plus_line); int curpos = promptlen + rl_point; /* cursor position within prompt_plus_line */ int i, printed_length, new_curpos, /* cursor position on screen */ keep_old_line, vlinestart, printwidth, last_column; DPRINTF3(DEBUG_AD_HOC,"rl_prompt: <%s>, prompt_without_ignore_markers: <%s>, prompt_plus_line: <%s>", rl_prompt, prompt_without_ignore_markers, prompt_plus_line); /* In order to handle prompt with colour we either print the whole prompt, or start past it: starting in the middle is too difficult (i.e. I am too lazy) to get it right. We use a "virtual line start" vlinestart, which is the number of invisible chars in prompt in the former case, or linestart in the latter (which then must be >= strlen(prompt)) At all times (before redisplay and after) the following is true: - the cursor is at column (curpos - vlinestart) (may be < 0 or > width) - the character under the cursor is prompt_plus_line[curpos] - the character at column 0 is prompt_plus_line[linestart] - the last column is at - vlinestart the goal of this function is to display (part of) prompt_plus_line such that the cursor is visible again */ if (hide_passwords) for (i = promptlen; i < total_length; i++) prompt_plus_line[i] = '*'; /* hide a pasword by making user input unreadable */ if (rl_point == 0) /* (re)set at program start and after accept_line (where rl_point is zeroed) */ line_start = 0; assert(line_start == 0 || line_start >= promptlen); /* the line *never* starts in the middle of the prompt (too complicated to handle)*/ vlinestart = (line_start > promptlen ? line_start : invisible_chars_in_prompt); if (curpos - vlinestart > width - line_extends_right) /* cursor falls off right edge ? */ vlinestart = (curpos - width + line_extends_right) + skip; /* jumpscroll left */ else if (curpos < vlinestart + line_extends_left) { /* cursor falls off left edge ? */ if (curpos == total_length) /* .. but still at end of line? */ vlinestart = max(0, total_length - width); /* .. try to display entire line */ else /* in not at end of line .. */ vlinestart = curpos - line_extends_left - skip; /* ... jumpscroll right .. */ } if (vlinestart <= invisible_chars_in_prompt) { line_start = 0; /* ... but not past start of line! */ vlinestart = invisible_chars_in_prompt; } else if (vlinestart > invisible_chars_in_prompt && vlinestart <= promptlen) { line_start = vlinestart = promptlen; } else { line_start = vlinestart; } printwidth = (line_start > 0 ? width : width + invisible_chars_in_prompt); printed_length = min(printwidth, total_length - line_start); /* never print more than width */ last_column = printed_length - vlinestart; /* some invariants : 0 <= line_start <= curpos <= line_start + printed_length <= total_length */ /* these are interesting: ^ ^ */ assert(0 <= line_start); assert(line_start <= curpos); assert(curpos <= line_start + printed_length); /* <=, rather than <, as cursor may be past eol */ assert(line_start + printed_length <= total_length); new_line = prompt_plus_line + line_start; new_line[printed_length] = '\0'; new_curpos = curpos - vlinestart; /* indicate whether line extends past right or left edge (i.e. whether the "interesting inequalities marked ^ above are really unequal) */ line_extends_left = (line_start > 0 ? 1 : 0); line_extends_right = (total_length - vlinestart > width ? 1 : 0); if (line_extends_left) new_line[0] = '<'; if (line_extends_right) new_line[printwidth - 1] = '>'; keep_old_line = FALSE; if (term_cursor_hpos) { if (previous_line && strcmp(new_line, previous_line) == 0) { keep_old_line = TRUE; } else { if (previous_line) free(previous_line); previous_line = mysavestring(new_line); } } /* DPRINTF2(DEBUG_AD_HOC, "keep_old_line=%d, new_line=<%s>", keep_old_line, new_line); */ /* keep_old_line = TRUE; */ if (!keep_old_line) { clear_line(); cr(); write_patiently(STDOUT_FILENO, new_line, printed_length, "to stdout"); } assert(term_cursor_hpos || !keep_old_line); /* if we cannot position cursor, we must have reprinted ... */ if (term_cursor_hpos) cursor_hpos(new_curpos); else /* ... so we know we're 1 past last position on line */ backspace(last_column - new_curpos); free(prompt_plus_line); free(prompt_without_ignore_markers); } void my_redisplay(void) { int debug_force_homegrown_redisplay = 0; #ifdef DEBUG debug_force_homegrown_redisplay = debug & FORCE_HOMEGROWN_REDISPLAY; #endif #ifndef HOMEGROWN_REDISPLAY if (redisplay && !debug_force_homegrown_redisplay) { rl_redisplay(); } else #endif my_homegrown_redisplay(!redisplay); } static void munge_file_in_editor(const char *filename, int lineno, int colno) { int ret; char *editor_command1, *editor_command2, *editor_command3, *editor_command4, *line_number_as_string, *column_number_as_string, **possible_editor_commands; /* find out which editor command we have to use */ possible_editor_commands = list4(getenv("RLWRAP_EDITOR"), getenv("EDITOR"), getenv("VISUAL"), "vi +%L"); editor_command1 = first_of(possible_editor_commands); line_number_as_string = as_string(lineno); column_number_as_string = as_string(colno); editor_command2 = search_and_replace("%L", line_number_as_string, editor_command1, 0, NULL, NULL); editor_command3 = search_and_replace("%C", column_number_as_string, editor_command2, 0, NULL, NULL); editor_command4 = strstr(editor_command3, "%F") ? search_and_replace("%F", filename, editor_command3, 0, NULL, NULL) : add3strings(editor_command3, " ", filename); /* call editor, temporarily restoring terminal settings */ if (terminal_settings_saved && (tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal_settings) < 0)) /* reset terminal */ myerror(FATAL|USE_ERRNO, "tcsetattr error on stdin"); DPRINTF1(DEBUG_READLINE, "calling %s", editor_command4); if ((ret = system(editor_command4))) { if (WIFSIGNALED(ret)) { fprintf(stderr, "\n"); myerror(FATAL|NOERRNO, "editor killed by signal"); } else { myerror(FATAL|USE_ERRNO, "failed to invoke editor with '%s'", editor_command4); } } completely_mirror_slaves_terminal_settings(); ignore_queued_input = TRUE; free_multiple(possible_editor_commands, editor_command2, editor_command3, editor_command4, line_number_as_string, column_number_as_string, FMEND); } static int munge_line_in_editor(int UNUSED(count), int UNUSED(key)) { int line_number = 0, column_number = 0, tmpfile_fd, bytes_read; size_t tmpfilesize; char *p, *tmpfilename, *text_to_edit; char *input, *rewritten_input, *rewritten_input2; if (!multiline_separator) return 0; tmpfile_fd = open_unique_tempfile(multi_line_tmpfile_ext, &tmpfilename); text_to_edit = search_and_replace(multiline_separator, "\n", rl_line_buffer, rl_point, &line_number, &column_number); write_patiently(tmpfile_fd, text_to_edit, strlen(text_to_edit), "to temporary file"); if (close(tmpfile_fd) != 0) /* improbable */ myerror(FATAL|USE_ERRNO, "couldn't close temporary file %s", tmpfilename); munge_file_in_editor(tmpfilename, line_number, column_number); /* read back edited input, replacing real newline with substitute */ tmpfile_fd = open(tmpfilename, O_RDONLY); if (tmpfile_fd < 0) myerror(FATAL|USE_ERRNO, "could not read temp file %s", tmpfilename); tmpfilesize = filesize(tmpfilename); input = mymalloc(tmpfilesize + 1); bytes_read = read(tmpfile_fd, input, tmpfilesize); if (bytes_read < 0) myerror(FATAL|USE_ERRNO, "unreadable temp file %s", tmpfilename); input[bytes_read] = '\0'; rewritten_input = search_and_replace("\t", " ", input, 0, NULL, NULL); /* rlwrap cannot handle tabs in input lines */ rewritten_input2 = search_and_replace("\n", multiline_separator, rewritten_input, 0, NULL, NULL); for(p = rewritten_input2; *p ;p++) if(*p >= 0 && *p < ' ') /* @@@FIXME: works for UTF8, but not UTF16 or UTF32 (Mention this in manpage?)*/ *p = ' '; /* replace all control characters (like \r) by spaces */ rl_delete_text(0, strlen(rl_line_buffer)); rl_point = 0; clear_line(); cr(); my_putstr(saved_rl_state.cooked_prompt); rl_insert_text(rewritten_input2); rl_point = 0; /* leave cursor on predictable place */ rl_done = 1; /* accept line immediately */ /* wash those dishes */ if (unlink(tmpfilename)) myerror(FATAL|USE_ERRNO, "could not delete temporary file %s", tmpfilename); free(tmpfilename); free(text_to_edit); free(input); free(rewritten_input); free(rewritten_input2); return_key = (char)'\n'; return 0; } static int direct_keypress(int UNUSED(count), int key) { #ifdef HAVE_RL_EXECUTING_KEYSEQ /* i.e. if readline version is >= 6.3 */ MAYBE_UNUSED(key); DPRINTF1(DEBUG_READLINE,"direct keypress: %s", M(rl_executing_keyseq)); put_in_output_queue(rl_executing_keyseq); #else char *key_as_str = mysavestring("?"); /* ? is just a placeholder */ *key_as_str = key; DPRINTF1(DEBUG_READLINE,"direct keypress: %s", mangle_char_for_debug_log(key, TRUE)); put_in_output_queue(key_as_str); free(key_as_str); #endif return 0; } static int direct_prefix(int count, int key) { char *key_as_str = mysavestring("?"); /* ? is just a placeholder */ /* process the keypress used to invoke the function */ direct_keypress(count, key); /* read an extra key to pass on */ key = rl_read_key(); *key_as_str = key; DPRINTF1(DEBUG_READLINE,"read key: %s", mangle_char_for_debug_log(key, TRUE)); put_in_output_queue(key_as_str); free(key_as_str); return 0; } static char* entire_history_as_one_string(void) { HIST_ENTRY **the_list = history_list(), **entryp; char *big_string = mymalloc(history_total_bytes() + history_length + 1); char *stringp = big_string; if (!the_list) /* i.e. if there is no history */ return mysavestring(""); for (entryp = the_list; *entryp; entryp++) { int length = strlen((*entryp)->line); strcpy(stringp, (*entryp)->line); stringp +=length; *stringp++ = '\n'; } *stringp = '\0'; DPRINTF1(DEBUG_READLINE, "stringified %d bytes of history", (int) strlen(big_string)); return big_string; } static int debug_ad_hoc(int UNUSED(count), int UNUSED(hotkey)) { printf("\n%s", entire_history_as_one_string()); cleanup_rlwrap_and_exit(EXIT_SUCCESS); return 42; } static int handle_hotkey2(int UNUSED(count), int hotkey, int without_history) { char *prefix, *postfix, *history, *histpos_as_string, *executing_keyseq; char *new_prefix, *new_postfix, *new_history, *new_histpos_as_string, *message; char *filter_food, *filtered, **fragments, *new_rl_line_buffer; int nfilter_fields, length, new_histpos; unsigned long int hash; static const unsigned int MAX_HISTPOS_DIGITS = 6; /* one million history items should suffice */ #ifdef HAVE_RL_EXECUTING_KEYSEQ /* i.e. if readline version is >= 6.3 */ executing_keyseq = mysavestring(rl_executing_keyseq); #else executing_keyseq = mysavestring("?"); *executing_keyseq = hotkey; /* The filter will only get the *last* byte of the key sequence that triggered rl_handle_hotkey */ #endif DPRINTF3(DEBUG_READLINE, "hotkey press (without_history == %d): %x (%s)", without_history, hotkey, M(executing_keyseq)); prefix = mysavestring(rl_line_buffer); prefix[rl_point] = '\0'; /* chop off just before cursor */ postfix = mysavestring(rl_line_buffer + rl_point); if (without_history) { histpos_as_string = mysavestring("0"); history = mysavestring(""); } else { histpos_as_string = as_string(where_history()); assert(strlen(histpos_as_string) <= MAX_HISTPOS_DIGITS); history = entire_history_as_one_string(); hash = hash_multiple(2, history, histpos_as_string); } /* filter_food = + '\0' */ filter_food = merge_fields(executing_keyseq, prefix, postfix, history, histpos_as_string, END_FIELD); /* this is the format that the filter expects */ /* let the filter filter ...! */ filtered= pass_through_filter(TAG_HOTKEY, filter_food); /* OK, we now have to read back everything. After splitting the message, here should be exactly 5 components*/ fragments = split_filter_message(filtered, &nfilter_fields); assert(nfilter_fields == 5); message = fragments[0]; new_prefix = fragments[1]; new_postfix = fragments[2]; new_history = fragments[3]; new_histpos_as_string = fragments[4]; if (!without_history && hash_multiple(2, new_history, new_histpos_as_string) != hash) { /* history has been rewritten */ char **linep, **history_lines = split_on_single_char(new_history, '\n', 0); DPRINTF3(DEBUG_READLINE, "hash=%lx, new_history is %d bytes long, histpos <%s>", hash, (int) strlen(new_history), new_histpos_as_string); clear_history(); for (linep = history_lines; *linep; linep++) add_history(*linep); new_histpos = my_atoi(new_histpos_as_string); history_set_pos(new_histpos); free_splitlist(history_lines); } new_rl_line_buffer = add2strings(new_prefix, new_postfix); if ( (length = strlen(new_rl_line_buffer)) > 0 && new_rl_line_buffer[length - 1] == '\n') { new_rl_line_buffer[length - 1] = '\0'; rl_done = TRUE; return_key = (char) '\n'; assert(strchr(new_rl_line_buffer, '\n') == NULL); } rl_delete_text(0, strlen(rl_line_buffer)); rl_point = 0; rl_insert_text(new_rl_line_buffer); rl_point = strlen(new_rl_line_buffer); if (*message && *message != hotkey) { /* if message has been set (i.e. != hotkey) , and isn't empty: */ message = append_and_free_old(mysavestring(message), " "); /* put space (for readability) between the message and the input line .. */ message_in_echo_area(message); /* .. then write it to echo area */ } clear_line(); rl_on_new_line(); rl_redisplay(); free_splitlist(fragments); /* this will free all the fragments (and the list itself) in one go */ free_multiple(prefix, postfix, filter_food, executing_keyseq, filtered, new_rl_line_buffer, history, histpos_as_string, FMEND); return 0; } static int handle_hotkey(int count, int hotkey) { return handle_hotkey2(count, hotkey, FALSE); } static int handle_hotkey_without_history(int count, int hotkey) { return handle_hotkey2(count, hotkey, TRUE); } void initialise_colour_codes(char *colour) { int attributes, foreground, background; DPRINTF1(DEBUG_READLINE, "initialise_colour_codes(\"%s\")", colour); attributes = foreground = -1; background = 40; /* don't need to specify background; 40 passes the test automatically */ sscanf(colour, "%d;%d;%d", &attributes, &foreground, &background); #define OUTSIDE(lo,hi,val) (val < lo || val > hi) if (OUTSIDE(0,8,attributes) || OUTSIDE(30,37,foreground) || OUTSIDE(40,47,background)) myerror(FATAL|NOERRNO, "\n" " prompt colour spec should be ;[;]\n" " where ranges over [0...8], over [30...37] and over [40...47]\n" " example: 0;33 for yellow on current background, 1;31;40 for bold red on black "); colour_start= add3strings("\033[", colour,"m"); colour_end = "\033[0m"; } /* returns a colourised copy of prompt, trailing space is not colourised */ char* colourise (const char *prompt) { char *prompt_copy, *trailing_space, *colour_end_with_space, *result, *p; prompt_copy = mysavestring(prompt); /* if (strchr(prompt_copy, '\033') || strchr(prompt_copy, RL_PROMPT_START_IGNORE) ) { /\* prompt contains escape codes? *\/ */ /* DPRINTF1(DEBUG_READLINE, "colourise %s: left as-is", prompt); */ /* return prompt_copy; /\* if so, leave prompt alone *\/ */ /* } */ for (p = prompt_copy + strlen(prompt_copy); p > prompt_copy && *(p-1) == ' '; p--) ; /* skip back over trailing space */ trailing_space = mysavestring(p); /* p now points at first trailing space, or else the final NULL */ *p = '\0'; colour_end_with_space = add2strings(colour_end, trailing_space); result = add3strings(colour_start, prompt_copy, colour_end_with_space); free (prompt_copy); free(trailing_space); free(colour_end_with_space); DPRINTF1(DEBUG_READLINE, "colourise %s: colour added ", prompt); return result; } void move_cursor_to_start_of_prompt(int erase) { int termwidth = winsize.ws_col; int promptlen_on_screen, number_of_lines_in_prompt, curpos, count; int cooked = (saved_rl_state.cooked_prompt != NULL); DPRINTF2(DEBUG_READLINE,"prompt_is_still_uncooked: %d, impatient_prompt: %d", prompt_is_still_uncooked, impatient_prompt); if (prompt_is_still_uncooked && ! impatient_prompt) return; /* @@@ is this necessary ?*/ promptlen_on_screen = colourless_strlen_unmarked(saved_rl_state.cooked_prompt ? saved_rl_state.cooked_prompt : saved_rl_state.raw_prompt, termwidth); curpos = (within_line_edit ? 1 : 0); /* if the user has pressed a key the cursor will be 1 past the current prompt */ assert(termwidth > 0); number_of_lines_in_prompt = 1 + ((promptlen_on_screen + curpos -1) / termwidth); /* integer arithmetic! (e.g. 171/80 = 2) */ cr(); for (count = 0; count < number_of_lines_in_prompt -1; count++) { if (erase) clear_line(); curs_up(); } clear_line(); DPRINTF4(DEBUG_READLINE,"moved cursor up %d lines (erase = %d, len=%d, cooked=%d)", number_of_lines_in_prompt - 1, erase, promptlen_on_screen, cooked); } int prompt_is_single_line(void) { int homegrown_redisplay= FALSE; int force_homegrown_redisplay = FALSE; int retval; #ifndef SPY_ON_READLINE # define _rl_horizontal_scroll_mode FALSE # define rl_variable_value(s) "off" #endif #ifdef HOMEGROWN_REDISPLAY homegrown_redisplay=TRUE; #endif #ifdef DEBUG force_homegrown_redisplay = debug & FORCE_HOMEGROWN_REDISPLAY; #endif retval = _rl_horizontal_scroll_mode || strncmp(rl_variable_value("horizontal-scroll-mode"),"on",3) == 0 || homegrown_redisplay || force_homegrown_redisplay; DPRINTF1(DEBUG_READLINE, "prompt is %s-line", (retval ? "single" : "multi")); return retval; } char *process_new_output(const char* buffer, struct rl_state* UNUSED(state)) { char*last_nl, *old_prompt_plus_new_output, *new_prompt, *result; old_prompt_plus_new_output = append_and_free_old(saved_rl_state.raw_prompt, buffer); last_nl = strrchr(old_prompt_plus_new_output, '\n'); if (last_nl != NULL) { /* newline seen, will get new prompt: */ new_prompt = mysavestring(last_nl +1); /* chop off the part after the last newline - this will be the new prompt */ *last_nl = '\0'; old_prompt_plus_new_output = append_and_free_old (old_prompt_plus_new_output, "\n"); result = (impatient_prompt ? mysavestring (old_prompt_plus_new_output): pass_through_filter(TAG_OUTPUT, old_prompt_plus_new_output)); if (remember_for_completion) { feed_line_into_completion_list(result); /* feed output into completion list *after* filtering */ } } else { new_prompt = mysavestring(old_prompt_plus_new_output); result = mysavestring(""); } free(old_prompt_plus_new_output); saved_rl_state.raw_prompt = new_prompt; if (saved_rl_state.cooked_prompt) { free (saved_rl_state.cooked_prompt); saved_rl_state.cooked_prompt = NULL; } return result; } int cook_prompt_if_necessary (void) { char *pre_cooked, *slightly_cooked, *rubbish_from_alternate_screen, *filtered, *uncoloured, *cooked, *p, *non_rubbish = NULL; static char **term_ctrl_seqs[] = {&term_rmcup, &term_rmkx, NULL}; /* (NULL-terminated) list of (pointers to) term control sequences that may be used by clients to return from an 'alternate screen'. If we spot one of those, assume that it, and anything before it, is rubbish and better left untouched */ char ***tcptr; filtered = NULL; DPRINTF2(DEBUG_READLINE, "Prompt <%s>: %s", saved_rl_state.raw_prompt, prompt_is_still_uncooked ? "still raw" : "cooked already"); if (saved_rl_state.cooked_prompt) /* if (!prompt_is_still_uncooked) bombs with multi-line paste. Apparently prompt_is_still_uncooked can be FALSE while saved_rl_state.cooked_prompt = NULL. Ouch!@@@! */ return FALSE; /* cooked already */ pre_cooked = mysavestring(saved_rl_state.raw_prompt); for (tcptr = term_ctrl_seqs; *tcptr; tcptr++) { /* find last occurence of one of term_ctrl_seq */ if (**tcptr && (p = mystrstr(pre_cooked, **tcptr))) { p += strlen(**tcptr); /* p now points 1 char past term control sequence */ if (p > non_rubbish) non_rubbish = p; } } /* non_rubbish now points 1 past the last 'alternate screen terminating' control char in prompt */ if (non_rubbish) { rubbish_from_alternate_screen = pre_cooked; pre_cooked = mysavestring(non_rubbish); *non_rubbish = '\0'; /* 0-terminate rubbish_from_alternate_screen */ } else { rubbish_from_alternate_screen = mysavestring(""); } slightly_cooked = ansi_colour_aware ? protect_or_cleanup(pre_cooked, FALSE) : mysavestring(pre_cooked); free(pre_cooked); unbackspace(slightly_cooked); /* programs that display a running counter would otherwise make rlwrap keep prompts like " 1%\r 2%\r 3%\ ......" */ if ( /* raw prompt doesn't match '--only-cook' regexp */ (prompt_regexp && ! match_regexp(slightly_cooked, prompt_regexp, FALSE)) || /* now filter it, but filter may "refuse" the prompt */ (strcmp((filtered = pass_through_filter(TAG_PROMPT, slightly_cooked)), "_THIS_CANNOT_BE_A_PROMPT_")== 0)) { /* don't cook, eat raw (and eat nothing if patient) */ saved_rl_state.cooked_prompt = (impatient_prompt ? mysavestring(slightly_cooked) : mysavestring("")); /* NB: if impatient, the rubbish_from_alternate_screen has been output already, no need to send it again */ free(slightly_cooked); free(filtered); /* free(NULL) is never an error */ return FALSE; } free(slightly_cooked); if(substitute_prompt) { uncoloured = mysavestring(substitute_prompt); free(filtered); } else { uncoloured = filtered; } if (colour_the_prompt) { cooked = colourise(uncoloured); free(uncoloured); } else { cooked = uncoloured; } if (! impatient_prompt) /* in this case our rubbish hasn't been output yet. Output it now, but don't store it in the prompt, as this may be re-printed e.g. after resuming a suspended rlwrap */ write_patiently(STDOUT_FILENO,rubbish_from_alternate_screen, strlen(rubbish_from_alternate_screen), "to stdout"); free(rubbish_from_alternate_screen); saved_rl_state.cooked_prompt = cooked; return TRUE; } /* From the readline documentation: Acceptable keymap names are emacs, emacs-standard, emacs-meta, emacs-ctlx, vi, vi-move, vi-command, and vi-insert. vi is equivalent to vi-command; emacs is equivalent to emacs-standard. The default value is emacs. The value of the editing-mode variable also affects the default keymap. */ static Keymap getmap(const char *name) { Keymap km = rl_get_keymap_by_name(name); if (!km) myerror(FATAL|NOERRNO, "Could not get keymap '%s'", name); return km; } static void bindkey(int key, rl_command_func_t *function, const char *function_name, const char *maplist) { char *mapnames[] = {"emacs-standard","emacs-ctlx","emacs-meta","vi-insert","vi-move","vi-command",NULL}; char **mapname; for (mapname = mapnames; *mapname; mapname++) if(strstr(maplist, *mapname)) { Keymap kmap = getmap(*mapname); DPRINTF4(DEBUG_READLINE,"Binding key %d (%s) in keymap '%s' to %s()", key, mangle_char_for_debug_log(key,TRUE), *mapname, function_name); if (rl_bind_key_in_map(key, function, kmap)) myerror(FATAL|NOERRNO, "Could not bind key %d (%s) in keymap '%s'", key, mangle_char_for_debug_log(key,TRUE), *mapname); } } #ifdef UNIT_TEST void my_line_handler(char *line) { /* This function (callback) gets called by readline whenever rl_callback_read_char sees an ENTER */ printf("\nYou typed: '%s'\n", line); if (!line) exit(0); } TESTFUNC(simple_callback, argc, argv, stage) { char byte_read; ONLY_AT_STAGE(TEST_AFTER_OPTION_PARSING); init_readline("Weh! "); set_echo(FALSE); rl_callback_handler_install("Type anything: ", &my_line_handler); while (TRUE) { if (!read(STDIN_FILENO, &byte_read, 1)) exit(0); DPRINTF1(DEBUG_READLINE, "Byte read: %s", mangle_char_for_debug_log(byte_read, TRUE)); rl_stuff_char(byte_read); rl_callback_read_char(); } } TESTFUNC(standard_readline, argc, argv, stage) { ONLY_AT_STAGE(TEST_AFTER_OPTION_PARSING); while(TRUE) { char *line_read = readline ("go ahead: "); printf("\nYou typed: '%s'\n", line_read); if (!line_read || !*line_read) break; free(line_read); } exit(0); } #endif /* UNIT_TEST */ rlwrap-0.46.1/src/redblack.h000066400000000000000000000140421433170252700156250ustar00rootroot00000000000000/* * RCS $Id: redblack.h,v 1.9 2003/10/24 01:31:21 damo Exp $ */ /* Redblack balanced tree algorithm Copyright (C) Damian Ivereigh 2000 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the file COPYING for details. 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 Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* Header file for redblack.c, should be included by any code that ** uses redblack.c since it defines the functions */ /* Stop multiple includes */ #ifndef _REDBLACK_H #ifndef RB_CUSTOMIZE /* * Without customization, the data member in the tree nodes is a void * pointer, and you need to pass in a comparison function to be * applied at runtime. With customization, you specify the data type * as the macro RB_ENTRY(data_t) (has to be a macro because compilers * gag on typdef void) and the name of the compare function as the * value of the macro RB_CMP. Because the comparison function is * compiled in, RB_CMP only needs to take two arguments. If your * content type is not a pointer, define INLINE to get direct access. */ #define rbdata_t void #define RB_CMP(s, t, e) (*rbinfo->rb_cmp)(s, t, e) #undef RB_INLINE #define RB_ENTRY(name) rb##name #endif /* RB_CUSTOMIZE */ #ifndef RB_STATIC #define RB_STATIC #endif /* Modes for rblookup */ #define RB_NONE -1 /* None of those below */ #define RB_LUEQUAL 0 /* Only exact match */ #define RB_LUGTEQ 1 /* Exact match or greater */ #define RB_LULTEQ 2 /* Exact match or less */ #define RB_LULESS 3 /* Less than key (not equal to) */ #define RB_LUGREAT 4 /* Greater than key (not equal to) */ #define RB_LUNEXT 5 /* Next key after current */ #define RB_LUPREV 6 /* Prev key before current */ #define RB_LUFIRST 7 /* First key in index */ #define RB_LULAST 8 /* Last key in index */ /* For rbwalk - pinched from search.h */ typedef enum { preorder, postorder, endorder, leaf } VISIT; struct RB_ENTRY (lists) { const struct RB_ENTRY (node) * rootp; const struct RB_ENTRY (node) * nextp; }; #define RBLIST struct RB_ENTRY(lists) struct RB_ENTRY (tree) { #ifndef RB_CUSTOMIZE /* comparison routine */ int (*rb_cmp) (const void *, const void *, const void *); /* config data to be passed to rb_cmp */ const void *rb_config; /* root of tree */ #endif /* RB_CUSTOMIZE */ struct RB_ENTRY (node) * rb_root; }; #ifndef RB_CUSTOMIZE RB_STATIC struct RB_ENTRY (tree) * rbinit(int (*)(const void *, const void *, const void *), const void *); #else RB_STATIC struct RB_ENTRY (tree) * RB_ENTRY(init) (void); #endif /* RB_CUSTOMIZE */ #ifndef no_delete RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(delete) (const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_find RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(find) (const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_lookup RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(lookup) (int, const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_search RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(search) (const RB_ENTRY(data_t) *, struct RB_ENTRY(tree) *); #endif #ifndef no_destroy RB_STATIC void RB_ENTRY(destroy) (struct RB_ENTRY(tree) *); #endif #ifndef no_walk RB_STATIC void RB_ENTRY(walk) (const struct RB_ENTRY(tree) *, void (*)(const RB_ENTRY(data_t) *, const VISIT, const int, void *), void *); #endif #ifndef no_readlist RB_STATIC RBLIST *RB_ENTRY(openlist) (const struct RB_ENTRY(tree) *); RB_STATIC const RB_ENTRY(data_t) * RB_ENTRY(readlist) (RBLIST *); RB_STATIC void RB_ENTRY(closelist) (RBLIST *); #endif /* Some useful macros */ #define rbmin(rbinfo) RB_ENTRY(lookup)(RB_LUFIRST, NULL, (rbinfo)) #define rbmax(rbinfo) RB_ENTRY(lookup)(RB_LULAST, NULL, (rbinfo)) #define _REDBLACK_H #endif /* _REDBLACK_H */ /* * * $Log: redblack.h,v $ * Revision 1.9 2003/10/24 01:31:21 damo * Patches from Eric Raymond: %prefix is implemented.  Various other small * changes avoid stepping on global namespaces and improve the documentation. * * Revision 1.8 2003/10/23 04:18:47 damo * Fixed up the rbgen stuff ready for the 1.3 release * * Revision 1.7 2002/08/26 03:11:40 damo * Fixed up a bunch of compiler warnings when compiling example4 * * Tidies up the Makefile.am & Specfile. * * Renamed redblack to rbgen * * Revision 1.6 2002/08/26 01:03:35 damo * Patch from Eric Raymond to change the way the library is used:- * * Eric's idea is to convert libredblack into a piece of in-line code * generated by another program. This should be faster, smaller and easier * to use. * * This is the first check-in of his code before I start futzing with it! * * Revision 1.5 2002/01/30 07:54:53 damo * Fixed up the libtool versioning stuff (finally) * Fixed bug 500600 (not detecting a NULL return from malloc) * Fixed bug 509485 (no longer needs search.h) * Cleaned up debugging section * Allow multiple inclusions of redblack.h * Thanks to Matthias Andree for reporting (and fixing) these * * Revision 1.4 2000/06/06 14:43:43 damo * Added all the rbwalk & rbopenlist stuff. Fixed up malloc instead of sbrk. * Added two new examples * * Revision 1.3 2000/05/24 06:45:27 damo * Converted everything over to using const * Added a new example1.c file to demonstrate the worst case scenario * Minor fixups of the spec file * * Revision 1.2 2000/05/24 06:17:10 damo * Fixed up the License (now the LGPL) * * Revision 1.1 2000/05/24 04:15:53 damo * Initial import of files. Versions are now all over the place. Oh well * */ rlwrap-0.46.1/src/rlwrap.h000066400000000000000000000565661433170252700154060ustar00rootroot00000000000000 /* rlwrap.h: includes, definitions, declarations */ /* 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L typedef int bool; #define true 1 #define false 0 #endif #include "../config.h" #include #if HAVE_SYS_WAIT_H # include #endif #ifndef WEXITSTATUS # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) #endif #include #include #include #include #include #include #include #include #include /* stdint.h is not on AIX, inttypes.h is in ISO C 1999 */ #include #include /* #define __USE_XOPEN #define __USE_GNU */ #include #include #include #if HAVE_SYS_TIME_H # include #else # include #endif #if HAVE_SYS_FILE_H #include #endif #ifdef HAVE_GETOPT_H # include #endif #ifdef HAVE_STROPTS_H # include #endif #ifdef HAVE_LIBGEN_H # include #endif #ifdef HAVE_CURSES_H # include # ifdef HAVE_TERM_H # include # else # ifdef HAVE_NCURSES_TERM_H /* cygwin? AIX? */ # include # endif # endif #else # ifdef HAVE_TERMCAP_H # include # endif #endif #include #ifdef HAVE_REGEX_H # include #endif #if STDC_HEADERS # include #else # ifndef HAVE_STRRCHR # define strrchr rindex # endif char *strchr(), *strrchr(); # ifndef HAVE_MEMMOVE # define memmove(d, s, n) bcopy ((s), (d), (n)) # endif #endif #ifdef HAVE_PTY_H /* glibc (even if BSD) */ # include #elif HAVE_LIBUTIL_H /* BSD, non-glibc */ # include #elif HAVE_UTIL_H /* BSD, other varriants */ # include #endif #if HAVE_DECL_PROC_PIDVNODEPATHINFO # include #endif #if HAVE_FREEBSD_LIBPROCSTAT # include # include # include # include # include #endif #if HAVE_PROC_PID_CWD || HAVE_DECL_PROC_PIDVNODEPATHINFO || HAVE_FREEBSD_LIBPROCSTAT # define CAN_FOLLOW_COMMANDS_CWD 1 #endif #define BUFFSIZE 2048 #ifndef MAXPATHLEN #define MAXPATHLEN 512 #endif #ifdef HAVE_SNPRINTF /* don't rely on the compiler understanding variadic macros */ # define snprintf0(buf,bufsize,format) snprintf(buf,bufsize,format) # define snprintf1(buf,bufsize,format,arg1) snprintf(buf,bufsize,format,arg1) # define snprintf2(buf,bufsize,format,arg1,arg2) snprintf(buf,bufsize,format,arg1,arg2) # define snprintf3(buf,bufsize,format,arg1,arg2,arg3) snprintf(buf,bufsize,format,arg1,arg2,arg3) # define snprintf4(buf,bufsize,format,arg1,arg2,arg3,arg4) snprintf(buf,bufsize,format,arg1,arg2,arg3,arg4) # define snprintf5(buf,bufsize,format,arg1,arg2,arg3,arg4,arg5) snprintf(buf,bufsize,format,arg1,arg2,arg3,arg4,arg5) # define snprintf6(buf,bufsize,format,arg1,arg2,arg3,arg4,arg5,arg6) snprintf(buf,bufsize,format,arg1,arg2,arg3,arg4,arg5,arg6) #else # define snprintf0(buf,bufsize,format) sprintf(buf,format) # define snprintf1(buf,bufsize,format,arg1) sprintf(buf,format,arg1) # define snprintf2(buf,bufsize,format,arg1,arg2) sprintf(buf,format,arg1,arg2) # define snprintf3(buf,bufsize,format,arg1,arg2,arg3) sprintf(buf,format,arg1,arg2,arg3) # define snprintf4(buf,bufsize,format,arg1,arg2,arg3,arg4) sprintf(buf,format,arg1,arg2,arg3,arg4) # define snprintf5(buf,bufsize,format,arg1,arg2,arg3,arg4,arg5) sprintf(buf,format,arg1,arg2,arg3,arg4,arg5) # define snprintf6(buf,bufsize,format,arg1,arg2,arg3,arg4,arg5,arg6) sprintf(buf,format,arg1,arg2,arg3,arg4,arg5,arg6) # define vsnprintf(buf,bufsize,format,ap) vsprintf(buf,format,ap) #endif #ifndef HAVE_STRNLEN # define strnlen(s,l) strlen(s) #endif #include #include #ifndef HAVE_RL_VARIABLE_VALUE # define rl_variable_value(s) "off" #endif #ifndef HAVE_RL_READLINE_VERSION # define rl_readline_version 0xbaddef #endif #if defined(SPY_ON_READLINE) extern int _rl_eof_char; /* Spying on readline's private life .... */ extern int _rl_horizontal_scroll_mode; # if !defined(HOMEGROWN_REDISPLAY) # define MAYBE_MULTILINE 1 # endif #else # define _rl_eof_char 0 #endif #ifdef MAYBE_MULTILINE # define redisplay_multiple_lines (!_rl_horizontal_scroll_mode) #else # define redisplay_multiple_lines (strncmp(rl_variable_value("horizontal-scroll-mode"),"off",3) == 0) #endif #define DEF_UNIT_TEST(name) void name(int argc, char **argv, test_stage s) #define STAGE(stage) s == stage extern void test_main(void); /* in main.c: */ extern int master_pty_fd; extern int slave_pty_sensing_fd; extern FILE *debug_fp; extern char *program_name, *command_name; extern int always_readline; extern int always_echo; extern int complete_filenames; extern int within_line_edit; extern int screen_is_alternate; extern pid_t command_pid; extern char *rlwrap_command_line; extern int unit_test_argc; extern char **unit_test_argv; extern char *command_line; extern char *extra_char_after_completion; extern int i_am_child; extern int i_am_filter; extern int nowarn; extern int debug; extern char *password_prompt_search_string; extern int one_shot_rlwrap; extern char *substitute_prompt; extern char *history_format; extern char *forget_regexp; extern char *multi_line_tmpfile_ext; extern char *prompt_regexp; extern int renice; extern int ignore_queued_input; extern int history_duplicate_avoidance_policy; extern int pass_on_sigINT_as_sigTERM; /* now follow the possible values for history_duplicate_avoidance_policy: */ #define KEEP_ALL_DOUBLES 0 #define ELIMINATE_SUCCESIVE_DOUBLES 1 #define ELIMINATE_ALL_DOUBLES 2 extern int one_shot_rlwrap; extern int ansi_colour_aware; extern int bleach_the_prompt; extern int colour_the_prompt; extern int received_WINCH; extern int prompt_is_still_uncooked; extern int mirror_arguments; extern int impatient_prompt; extern int we_just_got_a_signal_or_EOF; extern int remember_for_completion; extern int commands_children_not_wrapped; extern int accepted_lines; extern char *filter_command; extern int polling; void cleanup_rlwrap_and_exit(int status); void put_in_output_queue(char *stuff); int output_queue_is_nonempty(void); void flush_output_queue(void); /* in readline.c: */ extern struct rl_state { /* struct to save readline state while we're processing output from slave command*/ char *input_buffer; /* current input buffer */ char *raw_prompt; /* current prompt */ char *cooked_prompt; /* ditto redefined by user, or with colour added */ int point; /* cursor position within input buffer */ int already_saved; /* flag set when saved, cleared when restored */ } saved_rl_state; void save_rl_state(void); void restore_rl_state(void); void message_in_echo_area(char *message); void init_readline(char *); void my_redisplay(void); void initialise_colour_codes(char *colour); void reprint_prompt(int coloured); char *colourise (const char *prompt); void move_cursor_to_start_of_prompt(int erase); #define ERASE 1 #define DONT_ERASE 0 int prompt_is_single_line(void); char *process_new_output(const char* buffer, struct rl_state* state); int cook_prompt_if_necessary (void); extern int transparent; extern char *multiline_separator; extern char *pre_given; extern int leave_prompt_alone; extern bool bracketed_paste_enabled; /* in signals.c */ extern volatile int command_is_dead; extern int commands_exit_status; extern int filter_is_dead; extern int filters_exit_status; extern int sigterm_received; extern int deferred_adapt_commands_window_size; extern int signal_handlers_were_installed; extern int received_sigALRM; typedef void (*sighandler_type)(int); /* we'll install signal handlers with mysignal(SIGBLAH, HANDLER(handler)), to improve debug log readabilty */ void mysignal(int sig, sighandler_type handler, const char *handler_name); #define HANDLER(f) &f, #f void install_signal_handlers(void); void block_signals(int *sigs); void unblock_signals(int *sigs); void block_all_passed_on_signals(void); void block_all_signals(void); void unblock_all_signals(void); void ignore_sigchld(void); void suicide_by(int sig, int status); int adapt_tty_winsize(int from_fd, int to_fd); void myalarm(int msec); void handle_sigALRM(int signo); char *signal_name(int signal); /* in utils.c */ void yield(void); void zero_select_timeout(void); int my_pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *ptimeout_ts, const sigset_t *sigmask); struct termios *my_tcgetattr(int fd, char *which); int read_patiently(int fd, void *buffer, int count, char *whence); int write_patiently(int fd, const void *buffer, int count, const char *whither); void read_patiently2(int fd, void *buffer, int count, int uninterruptible_msec, const char *whence); void write_patiently2(int fd, const void *buffer, int count, int uninterruptible_msec, const char *whither); void mysetenv(const char *name, const char *value); void set_ulimit(int resource, long value); void usage(int status); int open_unique_tempfile(const char *suffix, char **tmpfile_name); void mirror_args_init(char **argv); void mirror_args(pid_t command_pid); /* flags to use for the error_flags argument to myerror */ #define FATAL 2 #define WARNING 0 #define USE_ERRNO 1 #define NOERRNO 0 /* constant to signify the end of a free_multiple() argument list (NULL would't work) */ #define FMEND ((void *) -1) void myerror(int error_flags, const char *message, ...); void *mymalloc(size_t size); void free_multiple(void *ptr, ...); void mysetsid(void); void close_open_files_without_writing_buffers(void); size_t filesize(const char *filename); void my_fopen(FILE **pfp, const char *path, const char *mode, const char *description); void open_logfile(const char *filename); void write_logfile(const char *str); void close_logfile(void); void timestamp(char *buf, int size); unsigned long hash_multiple(int n, ...); int killed_by(int status); void change_working_directory(void); void log_terminal_settings(struct termios *terminal_settings); void log_fd_info(int fd); void last_minute_checks(void); void mymicrosleep(int msec); void do_nothing(int unused); /* in string_utils.c */ char *mybasename(const char *filename); char *mydirname(const char *filename); void mystrlcpy(char *dst, const char *src, size_t size); void mystrlcat(char *dst, const char *src, size_t size); bool strings_are_equal(const char *s1, const char *s2); char *mystrstr(const char *haystack, const char *needle); char *mysavestring(const char *string); char *strifnull(char *string); char *add3strings(const char *str1, const char *str2, const char *str3); #define add2strings(a,b) add3strings(a,b,"") int my_atoi(const char *nptr); char *mystrtok(const char *s, const char *delim); char **split_with(const char *string, const char *delim); char *unsplit_with(int n, char ** strings, const char *delim); char **split_on_single_char(const char *string, char c, int expected_count); int scan_metacharacters(const char* string, const char *metacharacters); char **list4 (char *el0, char *el1, char *el2, char *el3); void free_splitlist (char **list); char *append_and_free_old(char *str1, const char *str2); char *mangle_char_for_debug_log(char c, int quote_me); char *mangle_string_for_debug_log(const char *string, int maxlen); char *mangle_buffer_for_debug_log(const char *buffer, int length); char *mem2str(const char *mem, int size); char *search_and_replace(char *patt, const char *repl, const char *string, int cursorpos, int *line, int *col); char *first_of(char **strings); char *as_string(int i); char *append_and_expand_history_format(char *line); void remove_padding_and_terminate(char *buf, int length); void unbackspace(char* buf); char *mark_invisible(const char *buf); char *copy_and_unbackspace(const char *original); int colourless_strlen(const char *str, char **pcopy_without_ignore_markers, int termwidth, int stop_at, char **stopptr); int colourless_strlen_unmarked (const char *str, int termwidth); char *get_last_screenline(char *long_line, int termwidth); char *lowercase(const char *str); char *colour_name_to_ansi_code(const char *colour_name); int match_regexp(const char *string, const char *regexp, int case_insensitive); int isnumeric(char *string); #define END_FIELD (char*)NULL /* marker object to terminate vargs */ char *append_field_and_free_old(char *message, const char *field); char *merge_fields(char *field, ...); char **split_filter_message(char *message, int *count); char *protect_or_cleanup(char *prompt, bool free_prompt); void check_cupcodes(const char *client_output); /* in pty.c: */ pid_t my_pty_fork(int *, const struct termios *, const struct winsize *); int slave_is_in_raw_mode(void); struct termios *get_pterm_slave(void); void mirror_slaves_echo_mode(void); void completely_mirror_slaves_terminal_settings(void); void completely_mirror_slaves_output_settings(void); void completely_mirror_slaves_special_characters(void); void write_EOF_to_master_pty(void); void write_EOL_to_master_pty(char *); int dont_wrap_command_waits(void); int skip_rlwrap(void); /* in ptytty.c: */ int ptytty_get_pty(int *fd_tty, const char **ttydev); int ptytty_get_tty(const char *ttydev); int ptytty_control_tty(int fd_tty, const char *ttydev); int ptytty_openpty(int *amaster, int *aslave, const char **name); /* in completion.rb: */ void init_completer(void); void feed_file_into_completion_list(const char *completions_file); void feed_line_into_completion_list(const char *line); void add_word_to_completions(const char *word); void remove_word_from_completions(const char *word); char *my_completion_function(char *prefix, int state); extern int completion_is_case_sensitive; /* in term.c: */ extern int redisplay; /* TRUE when user input should be readable (instead of *******) */ void init_terminal(void); void set_echo(int); void prepare_terminal(void); void cr(void); void backspace(int); void clear_line(void); void clear_the_screen(void); void curs_up(void); void curs_down(void); void curs_left(void); void test_terminal(void); int my_putchar(TPUTS_PUTC_ARGTYPE c); void my_putstr(const char *string); int cursor_hpos(int col); extern struct termios saved_terminal_settings; extern int terminal_settings_saved; extern struct winsize winsize; extern char *term_name; extern char *term_backspace, term_eof, term_stop, *term_cursor_hpos, *term_cursor_up, *term_cursor_down, *term_cursor_left, *term_smcup, *term_rmcup, *term_rmkx, *term_enable_bracketed_paste, *term_disable_bracketed_paste; extern int term_has_colours; extern int newline_came_last; /* in filter.c */ #define MAX_TAG 255 #define TAG_INPUT 0 #define TAG_OUTPUT 1 #define TAG_HISTORY 2 #define TAG_COMPLETION 3 #define TAG_PROMPT 4 #define TAG_HOTKEY 5 #define TAG_SIGNAL 6 #define MAX_INTERESTING_TAG 6 /* max tag for which the filter can have a handler */ #define TAG_WHAT_ARE_YOUR_INTERESTS 127 #define TAG_IGNORE 251 #define TAG_ADD_TO_COMPLETION_LIST 252 #define TAG_REMOVE_FROM_COMPLETION_LIST 253 #define TAG_OUTPUT_OUT_OF_BAND 254 #define TAG_ERROR 255 #define out_of_band(tag) (tag & 128) extern pid_t filter_pid; extern int filter_is_dead; void spawn_filter(const char *filter_command); void kill_filter(void); int filter_is_interested_in(int tag); char *pass_through_filter(int tag, const char *buffer); char *filters_last_words(void); void filter_test(void); /* in multibyte.c: */ #ifdef MULTIBYTE_AWARE /* i.e. if configured with --enable-multibyte-aware */ #include #define MBSTATE mbstate_t #else #define MBSTATE int #endif MBSTATE * mbc_initstate(MBSTATE *st); MBSTATE *mbc_copystate(MBSTATE st, MBSTATE *stc); int mbc_is_valid(const char *mb_string, const MBSTATE *st); const char * mbc_next(const char *mb_string, MBSTATE *st); int mbc_charwidth(const char *p, MBSTATE *st); char *mbc_first(const char *mb_string, const MBSTATE *st); int is_multibyte(const char *mb_char, const MBSTATE *st); const char *mbc_inc(const char **mbc, MBSTATE *st); void mbc_copy(const char *p, char **q, MBSTATE *st); size_t mbc_strnlen(const char *mb_string, size_t maxlen, MBSTATE *st); /* some handy macros */ #ifndef TRUE # define TRUE 1 #endif #ifndef FALSE # define FALSE 0 #endif #ifndef min # define min(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef max # define max(a,b) ((a) < (b) ? (b) : (a)) #endif /* macros for writing unit tests */ typedef enum {TEST_AT_PROGRAM_START, TEST_AFTER_OPTION_PARSING, TEST_AFTER_SPAWNING_SLAVE_COMMAND,TEST_AFTER_READLINE_INIT} test_stage; #define TESTFUNC(f, argc, argv, stage) void f(int argc, char **argv, test_stage stage) #define ONLY_AT_STAGE(s) if(stage != s) return #include "malloc_debug.h" /* malloc_debug.{c,h} not ready for prime time */ #define DEBUG_FILENAME "/tmp/rlwrap.debug" #define KA_BOOM {char *p = (char *) 1; *p = 'c';} /* dump core right here */ #define KA_SCRUNCH {volatile int x=1, y=0; x = x/y;} /* force a SIGFPE */ #define KA_SCREECH kill(getpid(),SIGTRAP); /* enter the debugger - use it to set (conditional) breakpoints from within C code: if (condition) KA_SCREECH; */ /* DPRINTF0 and its ilk doesn't produce any output except when DEBUG is #defined (via --enable-debug configure option) */ /* Use a superfluous "break" (withn a switch) to prevent "this statement may fall through" warnings when we know that a statement will end the program anyway */ #define WONTRETURN(statement) statement;break # ifdef __GNUC__ # define __MYFUNCTION__ __extension__ __FUNCTION__ # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) # define UNUSED_FUNCTION(f) __attribute__((__unused__)) UNUSED_ ## f # else # define __MYFUNCTION__ "" # define UNUSED(x) UNUSED_ ## x # define UNUSED_FUNCTION(f) UNUSED_ ## f # endif #define MAYBE_UNUSED(x) (void) (x) #ifdef DEBUG # define DEBUG_TERMIO 1 # define DEBUG_SIGNALS 2 # define DEBUG_READLINE 4 # define DEBUG_MEMORY_MANAGEMENT 8 /* used with malloc_debug.c */ # define DEBUG_FILTERING 16 # define DEBUG_COMPLETION 32 # define DEBUG_AD_HOC 64 /* only used during rlwrap development */ # define DEBUG_WITH_TIMESTAMPS 128 /* add timestamps to every line in debug log */ # define FORCE_HOMEGROWN_REDISPLAY 256 /* force use of my_homegrown_redisplay() */ # define DEBUG_LONG_STRINGS 512 /* log all strings completely, however long they are */ # define DEBUG_RACES 1024 /* introduce random delays */ # define DEBUG_RANDOM_FAIL 2048 /* fail tests randomly */ # define DEBUG_SHOWCURSOR 4096 /* run test_main and exit */ # define DEBUG_MAX DEBUG_SHOWCURSOR # define MANGLE_LENGTH ((debug_saved & DEBUG_LONG_STRINGS) ? 0 : 20) /* debug_saved is defined within DPRINTF macro */ # define DEBUG_DEFAULT (DEBUG_TERMIO | DEBUG_SIGNALS | DEBUG_READLINE) # define DEBUG_ALL (2*DEBUG_MAX-1) # define M(x) mangle_string_for_debug_log(x, MANGLE_LENGTH) # define ONLY_USED_FOR_DEBUGGING(x) x # ifndef HAVE_FLOCK # define flock(x,y) # endif # define WHERE_AND_WHEN \ int debug_saved = debug; char file_line[100], when[100]; \ if(debug & DEBUG_WITH_TIMESTAMPS) timestamp(when, sizeof(when)); else *when='\0'; \ debug = 0; /* don't debug while evaluating the DPRINTF arguments */ \ snprintf2(file_line, sizeof(file_line), "%.15s:%d:", __FILE__, __LINE__); \ flock(fileno(debug_fp), LOCK_EX); \ fseek(debug_fp, 0, SEEK_END); \ fprintf(debug_fp, "%-20s %s %-7s %-25.25s ", file_line, when, (i_am_child ? "child" : i_am_filter? "filter" :"parent"), __MYFUNCTION__); # define NL_AND_FLUSH fputc('\n', debug_fp) ; fflush(debug_fp); flock(fileno(debug_fp), LOCK_UN); debug = debug_saved # define DPRINTF0(mask, format) \ do {if ((debug & mask) && debug_fp) {WHERE_AND_WHEN; fprintf(debug_fp, format); NL_AND_FLUSH; }} while(FALSE) # define DPRINTF1(mask, format,arg) \ do {if ((debug & mask) && debug_fp) {WHERE_AND_WHEN; fprintf(debug_fp, format, arg); NL_AND_FLUSH; }} while(FALSE) # define DPRINTF2(mask, format,arg1, arg2) \ do {if ((debug & mask) && debug_fp) {WHERE_AND_WHEN; fprintf(debug_fp, format, arg1, arg2); NL_AND_FLUSH; }} while (FALSE) # define DPRINTF3(mask, format,arg1, arg2, arg3) \ do {if ((debug & mask) && debug_fp) {WHERE_AND_WHEN; fprintf(debug_fp, format, arg1, arg2, arg3); NL_AND_FLUSH; }} while (FALSE) # define DPRINTF4(mask, format,arg1, arg2, arg3, arg4) \ do {if ((debug & mask) && debug_fp) {WHERE_AND_WHEN; fprintf(debug_fp, format, arg1, arg2, arg3, arg4); NL_AND_FLUSH; }} while (FALSE) # define DPRINTF5(mask, format,arg1, arg2, arg3, arg4, arg5) \ do {if ((debug & mask) && debug_fp) {WHERE_AND_WHEN; fprintf(debug_fp, format, arg1, arg2, arg3, arg4, arg5); NL_AND_FLUSH; }} while (FALSE) # define ERRMSG(b) (b && (errno != 0) ? add3strings("(", strerror(errno), ")") : "" ) # define SHOWCURSOR(c) if (debug & DEBUG_SHOWCURSOR) {my_putchar(c); mymicrosleep(1000); curs_left();} /* (may work incorrectly at last column!)*/ # define DEBUG_RANDOM_SLEEP if (debug & DEBUG_RACES) {int sleeptime=rand()&31; DPRINTF1(DEBUG_RACES,"sleeping for %d msecs", sleeptime); mymicrosleep(sleeptime);} #else # define ONLY_USED_FOR_DEBUGGING(x) UNUSED(x) # define MANGLE_LENGTH 0 # define DPRINTF0(mask, format) do {;} while(FALSE) # define DPRINTF1(mask, format,arg) do {;} while(FALSE) # define DPRINTF2(mask, format,arg1, arg2) do {;} while(FALSE) # define DPRINTF3(mask, format,arg1, arg2, arg3) do {;} while(FALSE) # define DPRINTF4(mask, format,arg1, arg2, arg3, arg4) do {;} while(FALSE) # define DPRINTF5(mask, format,arg1, arg2, arg3, arg4, arg5) do {;} while(FALSE) # define ERRMSG(b) # define SHOWCURSOR # define DEBUG_RANDOM_SLEEP /* Don't #define NDEBUG! There are assertions that cannot be skipped, as in assert(important_function_call()) */ /* Todo (maybe) #define myassert(x) if(DEBUG){assert(x)} for the other (skippable) assertions */ #endif #include rlwrap-0.46.1/src/signals.c000066400000000000000000000437341433170252700155230ustar00rootroot00000000000000/* signals.c: signal handling */ /* 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" volatile int command_is_dead = FALSE; int commands_exit_status = 0; int filter_is_dead = FALSE; int filters_exit_status = 0; int deferred_adapt_commands_window_size = FALSE; /* whether we have to adapt clients winsize when accepting a line */ int signal_handlers_were_installed = FALSE; int received_sigALRM = FALSE; static void change_signalmask(int, int *); static void child_died(int); static void pass_on_signal(int); static void handle_sigTSTP(int); int adapt_tty_winsize(int, int); static void wipe_textarea(struct winsize *old_size); static int signals_program_error(int signal); static int signals_to_be_passed_on[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGCONT, SIGUSR1, SIGUSR2, SIGWINCH, 0 }; #ifndef MAX_SIG /* is this POSIX? couldn't find it, so: */ #define MAX_SIG 100 #endif #ifdef DEBUG static void log_named_signal(int); #else static void handle_program_error_signal(int); #endif int sigterm_received = FALSE; void mysignal(int sig, sighandler_type handler, const char *handler_name) { #ifdef HAVE_SIGACTION struct sigaction action; if (handler == SIG_DFL) DPRINTF2(DEBUG_SIGNALS,"Re-setting handler for signal %d (%s) to its default", sig, signal_name(sig)); else if (handler == SIG_IGN) DPRINTF2(DEBUG_SIGNALS,"Ignoring signal %d (%s)", sig, signal_name(sig)); else DPRINTF3(DEBUG_SIGNALS,"Setting handler for signal %d (%s) to %s()", sig, signal_name(sig), handler_name); action.sa_handler = handler; sigfillset(&action.sa_mask); /* don't bother making our signal handlers re-entrant (they aren't) */ action.sa_flags = (sig == SIGCHLD ? SA_NOCLDSTOP : 0); /* no SA_RESTART */ if (sigaction(sig, &action, NULL) != 0) # else /* rlwrap running in Ye Olde Computer Museum?? */ if (signal(sig, handler) == SIG_ERR) # endif if(handler != SIG_DFL) /* allow e.g. KILL to be set to its default */ myerror(FATAL|USE_ERRNO, "Failed setting handler for signal %d (%s)", sig, signal_name(sig)); } void install_signal_handlers(void) { int i; mysignal(SIGCHLD, HANDLER(child_died)); mysignal(SIGTSTP, HANDLER(handle_sigTSTP)); #ifndef DEBUG /* we want core dumps when debugging, no polite excuses! */ for (i = 1; i %d, ws.row: %d -> %d", old_winsize.ws_col, winsize.ws_col, old_winsize.ws_row, winsize.ws_row); if (always_readline &&!dont_wrap_command_waits()) /* if --always_readline option is set, the client will probably spew a */ deferred_adapt_commands_window_size = TRUE; /* volley of control chars at us when its terminal is resized. Hence we don't do it now */ else { ret = ioctl(to_fd, TIOCSWINSZ, &winsize); DPRINTF1(DEBUG_SIGNALS, "ioctl (..., TIOCSWINSZ) = %d", ret); } DPRINTF2(DEBUG_READLINE, "rl_prompt: %s, line_buffer: %s", mangle_string_for_debug_log(rl_prompt, 100), rl_line_buffer); rl_set_screen_size(winsize.ws_row, winsize.ws_col); /* this is safe: we know that right now rlwrap is not calling the readline library because we keep SIGWINCH blocked all the time */ if (!within_line_edit && !skip_rlwrap()) { wipe_textarea(&old_winsize); received_WINCH = TRUE; /* we can't start line edit in signal handler, so we only set a flag */ } else if (within_line_edit) { /* try to keep displayed line tidy */ wipe_textarea(&old_winsize); rl_on_new_line(); rl_redisplay(); } return (!always_readline || dont_wrap_command_waits()); /* pass the signal on (except when always_readline is set and command is not waiting) */ } else { /* window size has not changed */ return FALSE; } } /* After a resize, clear all the lines that were occupied by prompt + line buffer before the resize */ static void wipe_textarea(struct winsize *old_winsize) { int point, lineheight, linelength, cursor_height, i, promptlength; if (!prompt_is_single_line()) { /* Don't need to do anything in horizontal_scroll_mode */ promptlength = colourless_strlen((saved_rl_state.cooked_prompt ? saved_rl_state.cooked_prompt: saved_rl_state.raw_prompt), NULL, old_winsize -> ws_col, 0, NULL); linelength = (within_line_edit ? strlen(rl_line_buffer) : 0) + promptlength; point = (within_line_edit ? rl_point : 0) + promptlength; assert(old_winsize -> ws_col > 0); lineheight = (linelength == 0 ? 0 : (1 + (max(point, (linelength - 1)) / old_winsize -> ws_col))); if (lineheight > 1 && term_cursor_up != NULL && term_cursor_down != NULL) { /* i.e. if we have multiple rows displayed, and we can clean them up first */ cr(); cursor_height = point / old_winsize -> ws_col; /* cursor is still on old line */ DPRINTF2(DEBUG_SIGNALS, "lineheight: %d, cursor_height: %d", lineheight, cursor_height); for (i = 1 + cursor_height; i < lineheight; i++) curs_down(); /* ...move it down to last line */ for (i = lineheight; i > 1; i--) { /* go up again, erasing every line */ clear_line(); curs_up(); } } clear_line(); cr(); } } static void child_died(int UNUSED(signo)) { int saved_errno; DEBUG_RANDOM_SLEEP; zero_select_timeout(); saved_errno = errno; DPRINTF0(DEBUG_SIGNALS, "Caught SIGCHLD"); if(command_pid && waitpid(command_pid, &commands_exit_status, WNOHANG)) { DPRINTF2(DEBUG_SIGNALS, "child (pid %d) has died, exit status: %x", command_pid, commands_exit_status); command_is_dead = TRUE; command_pid = 0; /* thus we know that there is no child anymore to pass signals to */ } else if (filter_pid && waitpid(filter_pid, &filters_exit_status, WNOHANG)) { DPRINTF2(DEBUG_SIGNALS, "filter (pid %d) has died, exit status: %x", filter_pid, filters_exit_status); filter_is_dead = TRUE; filter_pid = 0; } else { DPRINTF0(DEBUG_ALL, "Whoa, got a SIGCHLD, but not from slave command or filter! I must have children I don't know about (blush...)!"); /* ignore */ } errno = saved_errno; return; /* allow remaining output from child to be processed in main loop */ /* (so that we will see childs good-bye talk) */ /* this will then clean up and terminate */ } #ifdef DEBUG static void log_named_signal(int signo) { if (debug) DPRINTF1(DEBUG_SIGNALS, "got %s", signal_name(signo)); } #else static void handle_program_error_signal(int sig) { /* Even after sudden and unexpected death, leave the terminal in a tidy state */ int res; printf("\n%s: Oops, crashed (caught %s) - this should not have happened!\n" "If you need a core dump, re-configure with --enable-debug and rebuild\n" "Resetting terminal and cleaning up...\n", program_name, signal_name(sig)); if (colour_the_prompt || filter_pid) res = write(STDOUT_FILENO,"\033[0m",4); /* reset terminal colours */ if (terminal_settings_saved) tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal_settings); exit(EXIT_FAILURE); } #endif static int coredump(int status) { #ifdef WCOREDUMP return WCOREDUMP(status); #else return 0; #endif } void suicide_by(int signal, int status) { /* Some signals suggest a program error. When rlwrap kills itself with one of those, the shell may tell the user that rlwrap itself has failed. Make clear that it didn't. @@@ We could also try argv[0] = command_name just before dying ? */ if (signals_program_error(signal)) { myerror(WARNING|NOERRNO, "%s crashed, killed by %s%s.\n%s itself has not crashed, but for transparency,\n" "it will now kill itself %swith the same signal\n", command_name, signal_name(signal), (coredump(status) ? " (core dumped)" : ""), program_name, (coredump(status) ? "" : "(without dumping core) ") ); } uninstall_signal_handlers(); unblock_all_signals(); set_ulimit(RLIMIT_CORE,0); /* prevent our own useless core dump from clobbering the interesting one created by command */ DPRINTF1(DEBUG_SIGNALS, "suicide by signal %s", signal_name(signal)); kill(getpid(), signal); /* still alive? */ sleep(1); exit(0); } static int myalarm_was_set = FALSE; /* drop-in replacement for alarm (*but* with arg in msecs, not secs). Also sets global flag myalarm_was_set */ void myalarm(int msecs) { #ifdef HAVE_SETITIMER int retval; struct itimerval awhile = {{0,0},{0,0}}; int secs = msecs/1000; awhile.it_value.tv_sec = secs; awhile.it_value.tv_usec = (msecs - secs * 1000) * 1000; received_sigALRM = FALSE; retval = setitimer(ITIMER_REAL, &awhile, NULL); DPRINTF3(DEBUG_AD_HOC, "setitimer() = %d (tv_sec = %d, tv_usec=%ld)", retval, secs, awhile.it_value.tv_usec); #else received_sigALRM = FALSE; alarm(msecs == 0 ? 0 : 1 + msecs/1000)); #endif DPRINTF1(DEBUG_AD_HOC, "set alarm (%d msecs)", msecs); if (msecs == 0) return; myalarm_was_set = TRUE; } void handle_sigALRM(int UNUSED(signo)) { received_sigALRM = TRUE; assert(myalarm_was_set); /* cry wolf if sigALRM is caught when none was requested by myalarm */ myalarm_was_set= FALSE; DPRINTF0(DEBUG_SIGNALS, "got sigALRM"); } /* Give name of signal. A case() construct is not appropriate here as on some architectures signal values may coincide */ char *signal_name(int signal) { return signal == SIGHUP ? "SIGHUP" : signal == SIGINT ? "SIGINT" : signal == SIGQUIT ? "SIGQUIT" : signal == SIGILL ? "SIGILL" : signal == SIGABRT ? "SIGABRT" : signal == SIGTRAP ? "SIGTRAP" : #ifdef SIGIOT /* 4.2 BSD (IOT trap ) */ signal == SIGIOT ? "SIGIOT" : #endif #ifdef SIGEMT /* 4.2 BSD (EMT trap ) */ signal == SIGEMT ? "SIGEMT" : #endif signal == SIGFPE ? "SIGFPE" : signal == SIGKILL ? "SIGKILL" : #ifdef SIGBUS /* 4.2 BSD (Bus error ) */ signal == SIGBUS ? "SIGBUS" : #endif signal == SIGSEGV ? "SIGSEGV" : #ifdef SIGSYS /* 4.2 BSD (Bad argument to system call ) */ signal == SIGSYS ? "SIGSYS" : #endif signal == SIGPIPE ? "SIGPIPE" : signal == SIGALRM ? "SIGALRM" : signal == SIGTERM ? "SIGTERM" : signal == SIGUSR1 ? "SIGUSR1" : signal == SIGUSR2 ? "SIGUSR2" : signal == SIGCHLD ? "SIGCHLD" : signal == SIGSTOP ? "SIGSTOP" : signal == SIGTSTP ? "SIGTSTP" : signal == SIGCONT ? "SIGCONT" : #ifdef SIGCLD /* System V (Same as SIGCHLD ) */ signal == SIGCLD ? "SIGCLD" : #endif #ifdef SIGPWR /* System V (Power failure restart ) */ signal == SIGPWR ? "SIGPWR" : #endif signal == SIGXCPU ? "SIGXCPU" : signal == SIGXFSZ ? "SIGXFSZ" : signal == SIGWINCH ? "SIGWINCH" : /* non-POSIX, but present on most systems */ as_string(signal); } static int signals_program_error(int signal) { return signal == SIGILL || signal == SIGABRT || signal == SIGTRAP || #ifdef SIGIOT /* 4.2 BSD (IOT trap ) */ signal == SIGIOT || #endif #ifdef SIGEMT /* 4.2 BSD (EMT trap ) */ signal == SIGEMT || #endif signal == SIGFPE || #ifdef SIGBUS /* 4.2 BSD (Bus error ) */ signal == SIGBUS || #endif signal == SIGSEGV || #ifdef SIGSYS /* 4.2 BSD (Bad argument to system call ) */ signal == SIGSYS || #endif signal == SIGXCPU || signal == SIGXFSZ || FALSE ? TRUE : FALSE; } rlwrap-0.46.1/src/string_utils.c000066400000000000000000001344401433170252700166040ustar00rootroot00000000000000/* rlwrap - a readline wrapper (C) 2000-2007 Hans Lub This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, ************************************************************** string_utils.c: rlwrap uses a fair number of custom string-handling functions. A few of those are replacements for (or default to) GNU or POSIX standard funcions (with names like mydirname, mystrldup). Others are special purpose string manglers for debugging, removing colour codes and the construction of history entries) All of these functions work on basic types as char * and int, and none of them refer to any of rlwraps global variables (except debug) */ #include "rlwrap.h" /* mystrlcpy and mystrlcat: wrappers around strlcat and strlcpy, if available, otherwise emulations of them. Both versions *assure* 0-termination, but don't check for truncation: return type is void */ void mystrlcpy(char *dst, const char *src, size_t size) { #ifdef HAVE_STRLCPY strlcpy(dst, src, size); #else strncpy(dst, src, size - 1); dst[size - 1] = '\0'; #endif } void mystrlcat(char *dst, const char *src, size_t size) { #ifdef HAVE_STRLCAT strlcat(dst, src, size); dst[size - 1] = '\0'; /* we don't check for truncation, just assure '\0'-termination. */ #else strncat(dst, src, size - strnlen(dst, size) - 1); dst[size - 1] = '\0'; #endif } /* mystrndup: strndup replacement that uses the safer mymalloc instead of malloc*/ static char * mystrndup(const char *string, int len) { /* allocate copy of string on the heap */ char *buf; assert(string != NULL); buf = (char *)mymalloc(len + 1); mystrlcpy(buf, string, len + 1); return buf; } /* compare strings for equality. fail if either of them is NULL */ bool strings_are_equal(const char *s1, const char *s2) { return s1 && s2 && strcmp(s1,s2)== 0; } /* allocate a copy of a string on the heap */ char * mysavestring(const char *string) { assert(string != NULL); return mystrndup(string, strlen(string)); } /* return "" if arg == NULL, else arg */ char * strifnull(char *string) { return string ? string : ""; } /* add3strings: allocate a sufficently long buffer on the heap and successively copy the three arguments into it */ char * add3strings(const char *str1, const char *str2, const char *str3) { int size; char *buf; assert(str1!= NULL); assert(str2!= NULL); assert(str3!= NULL); size = strlen(str1) + strlen(str2) + strlen(str3) + 1; /* total length plus 0 byte */ buf = (char *) mymalloc(size); /* DPRINTF3(DEBUG_TERMIO,"size1: %d, size2: %d, size3: %d", (int) strlen(str1), (int) strlen(str2), (int) strlen(str3)); */ mystrlcpy(buf, str1, size); mystrlcat(buf, str2, size); mystrlcat(buf, str3, size); return buf; } /* append_and_free_old(str1, str2): return add2strings(str1, str2), freeing str1 append_and_free_old(NULL, str) just returns a copy of str */ char * append_and_free_old(char *str1, const char *str2) { if (!str1) return mysavestring(str2); /* if str1 == NULL there is no need to "free the old str1" */ else { char *result = add2strings(str1,str2); free (str1); return result; } } /* mybasename and mydirname: wrappers around basename and dirname, if available, otherwise emulations of them */ char * mybasename(const char *filename) { /* determine basename of "filename" */ #if defined(HAVE_BASENAME) && defined(_GNU_SOURCE) /* we want only the GNU version - but this doesn't guarantee that */ char *filename_copy = mysavestring(filename); char *result = mysavestring(basename(filename_copy)); free(filename_copy); return result; /* basename on HP-UX is toxic: the result will be overwritten by subsequent invocations! */ #else char *p; /* find last '/' in name (if any) */ for (p = filename + strlen(filename) - 1; p > filename; p--) if (*(p - 1) == '/') break; return p; #endif } char * mydirname(const char *filename) { /* determine directory component of "name" */ #ifdef HAVE_DIRNAME char *filename_copy = mysavestring(filename); char *result = dirname(filename_copy); return result; #else char *p; /* find last '/' in name (if any) */ for (p = filename + strlen(filename) - 1; p > filename; p--) if (*(p - 1) == '/') break; return (p == filename ? "." : mystrndup(filename, p - filename)); #endif } /* Better atoi() with error checking */ int my_atoi(const char *nptr) { int result; char *endptr; errno = 0; result = (int) strtol(nptr, &endptr, 10); if (errno || endptr == nptr || *endptr) myerror(FATAL|USE_ERRNO, "Could not make sense of <%s> as an integer", mangle_string_for_debug_log(nptr, 20)); return result; } /* TODO: clean up the following mess. strtok() is cute, but madness. Write one function char *tokenize(const char *string, const char *delimiters, bool allow_empty_strings), and make both split_with functions a special case of it. Drop mystrtok, count_str_occurrences and count_char_occurrences */ /* mystrtok: saner version of strtok that doesn't overwrite its first argument */ /* Scary strtok: "The strtok() function breaks a string into a sequence of zero or more nonempty tokens. On the first call to strtok(), the string to be parsed should be specified in str. In each subsequent call that should parse the same string, str must be NULL. */ char * mystrtok(const char *s, const char *delimiters) { static char *scratchpad = NULL; if (s) { /* first call */ if (scratchpad) free(scratchpad); /* old news */ scratchpad = mysavestring(s); } return strtok(s ? scratchpad : NULL, delimiters); } static int count_str_occurrences(const char *haystack, const char* needle) { int count = 0, needle_length = strlen(needle); const char *p = haystack; assert(needle_length > 0); while ((p = strstr(p, needle))) { count++; p += needle_length; } return count; } static int count_char_occurrences(const char *haystack, char c) { int count; char *needle = mysavestring(" "); *needle = c; count = count_str_occurrences(haystack, needle); free(needle); return count; } void test_haystack(const char *haystack, const char* needle) { printf("<%s> contains <%s> %d times\n", haystack, needle, count_str_occurrences(haystack, needle)); } /* split_with("a bee cee"," ") returns a pointer to an array {"a", "bee", "cee", NULL} on the heap */ char ** split_with(const char *string, const char *delimiters) { const char *s; char *token, **pword; char **list = mymalloc((1 + strlen(string)) * sizeof(char **)); /* worst case: only delimiters */ for (s = string, pword = list; (token = mystrtok(s, delimiters)); s = NULL) *pword++ = mysavestring(token); *pword = NULL; return list; } /* unsplit_with(3, ["a", "bee", "cee"], "; ") returns a pointer to "a; bee; cee" on the heap */ /* takes n elements from strings (or all of them if n < 0) */ char * unsplit_with(int n, char **strings, const char *delim) { int i; char *result = mysavestring(n != 0 && strings[0] ? strings[0]: ""); for (i = 1; (n<0 && strings[i]) || i < n; i++) { result = append_and_free_old(result, delim); result = append_and_free_old(result, strings[i]); } return result; } /* split_with("a\t\tbla", '\t') returns {"a" "bla", NULL}, but we want {"a", "", "bla", NULL} for filter completion. We write a special version (can be freed with free_splitlist), that optionally checks the number of components (if expected_count > 0) */ char ** split_on_single_char(const char *string, char c, int expected_count) { /* the 1st +1 for the last element ("bla"), the 2nd +1 for the marker element (NULL) */ char **list = mymalloc((count_char_occurrences(string,c) + 1 + 1) * sizeof(char **)); char *stringcopy = mysavestring(string); char *p, **pword, *current_word; for (pword = list, p = current_word = stringcopy; *p; p++) { if (*p == c) { *p = '\0'; *pword++ = mysavestring(current_word); current_word = p+1; } } *pword++ = mysavestring(current_word); if (expected_count && pword-list != expected_count) myerror(FATAL|NOERRNO, "splitting <%s> on single %s yields %d components, expected %d", mangle_string_for_debug_log(string, 50), mangle_char_for_debug_log(c, 1), pword - list, expected_count); *pword = NULL; free(stringcopy); return list; } /* free_splitlist(list) frees lists components and then list itself. list must be NULL-terminated */ void free_splitlist (char **list) { char **p = list; while(*p) free(*p++); free (list); } /* search_and_replace() is a utilty for handling multi-line input (-m option), keeping track of the cursor position on rlwraps prompt in order to put the cursor on the very same spot in the external editor For example, when using NL as a newline substitute (rlwrap -m NL ): search_and_replace("NL", "\n", "To be NL ... or not to be", 11, &line, &col) will return "To be \n ... or not to be", put 2 in line and 3 in col because a cursor position of 11 in "To be NL ..." corresponds to the 3rd column on the 2nd line in "To be \n ..." cursorpos, col and line only make sense if repl == "\n", otherwise they may be 0/NULL (search_and_replace works for any repl). The first position on the string corresponds to cursorpos = col = 0 and line = 1. */ char * search_and_replace(char *patt, const char *repl, const char *string, int cursorpos, int *line, int *col) { int i, j, k; int pattlen, replen, stringlen; int cursor_found = FALSE; int current_line = 1; int current_column = 0; size_t scratchsize; char *scratchpad, *result; assert(patt); pattlen = strlen(patt); assert(repl); replen = strlen(repl); assert(string); stringlen = strlen(string); DPRINTF2(DEBUG_READLINE, "string=%s, cursorpos=%d", M(string), cursorpos); scratchsize = max(stringlen, (stringlen * replen) / pattlen) + 1; /* worst case : repleng > pattlen and string consists of only */ DPRINTF1(DEBUG_READLINE, "Allocating %d bytes for scratchpad", (int) scratchsize); scratchpad = mymalloc(scratchsize); for (i = j = 0; i < stringlen; ) { if (line && col && /* if col and line are BOTH non-NULL, and .. */ i >= cursorpos && !cursor_found) { /* ... for the first time, i >= cursorpos: */ cursor_found = TRUE; /* flag that we're done here */ *line = current_line; /* update *line */ *col = current_column; /* update *column */ } if (strncmp(patt, string + i, pattlen) == 0) { /* found match */ i += pattlen; /* update i ("jump over" patt (and, maybe, cursorpos)) */ for (k = 0; k < replen; k++) /* append repl to scratchpad */ scratchpad[j++] = repl[k]; current_line++; /* update line # (assuming that repl = "\n") */ current_column = 0; /* reset column */ } else { scratchpad[j++] = string[i++]; /* or else just copy things */ current_column++; } } if (line && col) DPRINTF2(DEBUG_READLINE, "line=%d, col=%d", *line, *col); scratchpad[j] = '\0'; result = mysavestring(scratchpad); free(scratchpad); return (result); } /* first_of(&string_array) returns the first non-NULL element of string_array */ char * first_of(char **strings) { char **p; for (p = strings; *p == NULL; p++); return *p; } /* allocate string representation of an integer on the heap */ char * as_string(int i) { #define MAXDIGITS 10 /* let's pray no-one edits multi-line input more than 10000000000 lines long :-) */ char *newstring = mymalloc(MAXDIGITS+1); snprintf1(newstring, MAXDIGITS, "%d", i); return (newstring); } char * mangle_char_for_debug_log(char c, int quote_me) { char *special = NULL; char scrap[10], code, *format; char *remainder = "\\]^_"; switch (c) { case 0: special = ""; break; case 8: special = ""; break; case 9: special = ""; break; case 10: special = ""; break; case 13: special = ""; break; case 27: special = ""; break; case 127: special = ""; break; } if (!special) { if (c > 0 && c < 27 ) { format = ""; code = c + 64; } else if (c > 27 && c < 32) { format = ""; code = remainder[c-28]; } else { format = (quote_me ? "\"%c\"" : "%c"); code = c; } snprintf1(scrap, sizeof(scrap), format, code); } return mysavestring (special ? special : scrap); } /* mangle_string_for_debug_log(string, len) returns a printable representation of string for the debug log. It will truncate a resulting string longer than len, appending three dots ... */ char * mangle_string_for_debug_log(const char *string, int maxlen) { int total_length; char *mangled_char, *result; const char *p; /* good old K&R-style *p. I have become a fossil... */ MBSTATE st; if (!string) return mysavestring("(null)"); result = mysavestring(""); for(mbc_initstate(&st), p = string, total_length = 0; *p; mbc_inc(&p, &st)) { if (is_multibyte(p, &st)) { mangled_char = mbc_first(p, &st); total_length += 1; } else { mangled_char = mangle_char_for_debug_log(*p, FALSE); total_length += strlen(mangled_char); /* can be more than 1 ("CTRL-A") */ } if (maxlen && (total_length > maxlen)) { result = append_and_free_old(result, "..."); break; /* break *before* we append the latest char and exceed maxlen */ } result = append_and_free_old(result, mangled_char); free(mangled_char); } return result; } char *mangle_buffer_for_debug_log(const char *buffer, int length) { char *string = mymalloc(length+1); int debug_saved = debug; /* needed by macro MANGLE_LENGTH */ memcpy(string, buffer, length); string[length] = '\0'; return mangle_string_for_debug_log(string, MANGLE_LENGTH); } /* mem2str(mem, size) returns a fresh string representation of mem where al 0 bytes have been replaced by "\\0" */ char *mem2str(const char *mem, int size) { const char *p_mem; char *p_str; char *str = mymalloc(2*size + 1); /* worst case: "\0\0\0\0.." */ for(p_mem = mem, p_str = str; p_mem < mem + size; p_mem++) { if (*p_mem) *p_str++ = *p_mem; else { *p_str++ = '\\'; *p_str++ = '0'; } } *p_str = '\0'; return str; } char * mystrstr(const char *haystack, const char *needle) { return strstr(haystack, needle); } int scan_metacharacters(const char* string, const char *metacharacters) { const char *c; for (c = metacharacters; *c; c++) if (strchr(string, *c)) return TRUE; return FALSE; } /* allocate and init array of 4 strings (helper for munge_line_in_editor() and filter) */ char ** list4 (char *el0, char *el1, char *el2, char *el3) { char **list = mymalloc(4*sizeof(char*)); list[0] = el0; list[1] = el1; list[2] = el2; list[3] = el3; DPRINTF4(DEBUG_AD_HOC, "el0: <%s> el1: <%s> el2: <%s> el3: <%s>", el0, el1, el2, el3); return list; } /* remove_padding_and_terminate(buf, N) overwrites buf with a copy of its first N bytes, omitting any zero bytes, and then terminates the result with a final zero byte. Example: if buf="a\0b\0\0cde@#!" then, after calling remove_padding_and_terminate(buf, 8) buf will contain "abcde\0de@#!" We need to call this function on everything we get from the inferior command because (out of sheer programmer laziness) rlwrap uses C strings internally. Zero bytes are only ever used as padding (@@@ I have never seen this happen, by the way), and padding is not used anymore on modern terminals. (except maybe for things like the visual bell) */ void remove_padding_and_terminate(char *buf, int length) { char *readptr, *copyptr; for (readptr = copyptr = buf; readptr < buf + length; readptr++) { if (*readptr != '\0') *copyptr++ = *readptr; } *copyptr = '\0'; if (debug && strlen(buf) != (unsigned int) length) DPRINTF2(DEBUG_TERMIO, "removed %d zero bytes (padding?) from %s", length - (int) strlen(buf), M(buf)); } #define ESCAPE '\033' #define BACKSPACE '\010' #define CARRIAGE_RETURN '\015' /* unbackspace(&buf) will overwrite buf (up to and including the first '\0') with a copy of itself. Backspaces will move the "copy pointer" one backwards, carriage returns will re-set it to the begining of buf. Because the re-written string is always shorter than the original, we need not worry about writing outside buf Example: if buf="abc\bd\r\e" then, after calling unbackspace(buf), buf will contain "ebd" @@@ Should be just "e" We need this function because many commands emit "status lines" using backspaces and carriage returns to re-write parts of the line in-place. Rlwrap will consider such lines as "prompts" (@@@myabe it shouldn't?) but mayhem results if we feed the \b and \r characters to readline */ void unbackspace_old(char* buf) { char *readptr, *copyptr, *endptr; int seen_bs_or_cr; DPRINTF1(DEBUG_TERMIO,"unbackspace: %s", M(buf)); seen_bs_or_cr = FALSE; for (readptr = copyptr = endptr = buf; *readptr; readptr++) { assert(endptr <= readptr); assert(copyptr <= endptr); switch (*readptr) { case BACKSPACE: copyptr = (copyptr > buf ? copyptr - 1 : buf); /* cannot backspace past beginning of buf */ seen_bs_or_cr = TRUE; break; case CARRIAGE_RETURN: copyptr = buf; seen_bs_or_cr = TRUE; break; default: *copyptr++ = *readptr; break; } if (copyptr > endptr) endptr = copyptr; } *endptr = '\0'; if (seen_bs_or_cr) DPRINTF1(DEBUG_TERMIO,"unbackspace result: %s", M(buf)); } void unbackspace(char* buf) { char *readptr, *copyptr; int seen_bs_or_cr; DPRINTF1(DEBUG_TERMIO,"unbackspace: %s", M(buf)); seen_bs_or_cr = FALSE; for (readptr = copyptr = buf; *readptr; readptr++) { switch (*readptr) { case BACKSPACE: copyptr = (copyptr > buf ? copyptr - 1 : buf); /* cannot backspace past beginning of buf */ while (*copyptr == RL_PROMPT_END_IGNORE && copyptr > buf) { /* skip control codes ... */ while (*copyptr != RL_PROMPT_START_IGNORE && copyptr > buf) /* but never past buffer start */ copyptr--; /* e.g. with pathological "x\002\b" */ if (copyptr > buf) copyptr--; } seen_bs_or_cr = TRUE; break; case CARRIAGE_RETURN: copyptr = buf; seen_bs_or_cr = TRUE; break; default: *copyptr++ = *readptr; break; } } *copyptr = '\0'; if (seen_bs_or_cr) DPRINTF1(DEBUG_TERMIO,"unbackspace result: %s", M(buf)); } #ifdef UNIT_TEST static void test_unbackspace (const char *input, const char *expected_result) { char *scrap = mysavestring(input); unbackspace(scrap); if (strcmp(scrap, expected_result) != 0) myerror(FATAL|NOERRNO, "unbackspace '%s' yielded '%s', expected '%s'", mangle_string_for_debug_log(input,0), mangle_string_for_debug_log(scrap,0), expected_result); } /* run with: make clean ; make CFLAGS='-g -DUNIT_TEST=test'; ./rlwrap */ TESTFUNC(test, argc, argv, stage) { ONLY_AT_STAGE(TEST_AT_PROGRAM_START ); test_unbackspace("zx\001a\002\bq","sssssssss"); exit(0); } #endif /* Readline allows to single out character sequences that take up no physical screen space when displayed by bracketing them with the special markers `RL_PROMPT_START_IGNORE' and `RL_PROMPT_END_IGNORE' (declared in `readline.h'). mark_invisible(buf) returns a new copy of buf with sequences of the form ESC[;0-9]*m? marked in this way. */ /* (Re-)definitions for testing #undef RL_PROMPT_START_IGNORE #undef RL_PROMPT_END_IGNORE #undef isprint #define isprint(c) (c != 'x') #define RL_PROMPT_START_IGNORE '(' #define RL_PROMPT_END_IGNORE ')' #define ESCAPE 'E' */ /* TODO @@@ replace the following obscure and unsafe functions using the regex library */ static void copy_ordinary_char_or_ESC_sequence(const char **original, char **copy); static void match_and_copy(const char *charlist, const char **original, char **copy); static int matches (const char *charlist, char c) ; static void copy_next(int n, const char **original, char **copy); char * mark_invisible(const char *buf) { int padsize = (assert(buf != NULL), (3 * strlen(buf) + 1)); /* worst case: every char in buf gets surrounded by RL_PROMPT_{START,END}_IGNORE */ char *scratchpad = mymalloc (padsize); char *result = scratchpad; const char **original = &buf; char **copy = &scratchpad; DPRINTF1(DEBUG_AD_HOC, "mark_invisible(%s) ...", M(buf)); if (strchr(buf, RL_PROMPT_START_IGNORE)) return mysavestring(buf); /* "invisible" parts already marked */ while (**original) { copy_ordinary_char_or_ESC_sequence(original, copy); assert(*copy - scratchpad < padsize); } **copy = '\0'; DPRINTF1(DEBUG_AD_HOC, "mark_invisible(...) = <%s>", M(result)); return(result); } static void copy_ordinary_char_or_ESC_sequence (const char **original, char **copy) { if (**original != ESCAPE || ! matches ("[]", *(*original + 1))) { copy_next(1, original, copy); return; /* not an ESC[ sequence */ } *(*copy)++ = RL_PROMPT_START_IGNORE; copy_next(2, original, copy); match_and_copy(";0123456789", original, copy); match_and_copy("m", original, copy); *(*copy)++ = RL_PROMPT_END_IGNORE; } static void match_and_copy(const char *charlist, const char **original, char **copy) { while (matches(charlist, **original)) *(*copy)++ = *(*original)++; } static int matches (const char *charlist, char c) { const char *p; for (p = charlist; *p; p++) if (*p == c) return TRUE; return FALSE; } static void copy_next(int n, const char **original, char **copy) { int i; for (i = 0; **original && (i < n); i++) *(*copy)++ = *(*original)++; } /* helper function: returns the number of displayed characters (the "colourless length") of str (which has to have its unprintable sequences marked with RL_PROMPT_*_IGNORE). It works internally by building a list of visible character cells (struct mbchar_cell, see below) and returning its length. Flattens this list into bytes and points copy_without_ignore_markers (if != NULL) to the result if stop_at != 0 then the list building will end as soon as stop_at cells have been seen, and point stopptr (if != NULL) at a copy of str beginning at the first (visible, multibyte) character past the stop position. This can be used to determine where a (possibly multibyte, coloured) prompt moves to a new line on a narrow terminal. */ int colourless_strlen(const char *str, char ** pcopy_without_ignore_markers, int UNUSED(termwidth), int stop_at, char **stopptr) { int visible = TRUE; int length = strlen(str); int i, colourless_length, colourless_bytes; const char *str_ptr, *p; char *copy_ptr, *copy_without_ignore_markers; MBSTATE st; typedef struct { char *bytes; MBSTATE state; } mbchar_cell; mbchar_cell *cellptr, *copied_cells = mymalloc((length + 1) * sizeof(mbchar_cell)); /* The next loop scans str, one multi-byte character at a time, constructing a colourless copy */ /* cellptr always points at the next available free cell */ for(mbc_initstate(&st), colourless_length = 0, str_ptr = str, colourless_bytes = 0, cellptr = copied_cells; *str_ptr; mbc_inc(&str_ptr, &st)) { assert (cellptr < copied_cells + length); if (stop_at && stopptr && (colourless_length >= stop_at)) { *stopptr = mysavestring(str_ptr); break; } switch (*str_ptr) { case RL_PROMPT_START_IGNORE: visible = FALSE; break; case RL_PROMPT_END_IGNORE: visible = TRUE; break; case '\r': if (visible) { /* only ever interpret CR (and Backspace) when visible (i.e. outside control sequences) */ for ( ; cellptr > copied_cells; cellptr--) free((cellptr-1)->bytes); /* free all cells */ mbc_initstate(&st); /* restart with virgin state */ colourless_bytes = 0; } break; case '\b': if ((visible && cellptr > copied_cells)) { /* except when invisible, or at beginning of copy ... */ cellptr -= 1; /* ... reset cellptr to previous (multibyte) char */ colourless_bytes -= strlen(cellptr->bytes); free(cellptr->bytes); if (cellptr > copied_cells) st = (cellptr -1) -> state; /* restore corresponding shift state */ else mbc_initstate(&st); /* or initial state, if at start of line */ } break; default: if (visible) { MBSTATE st_copy = st; int nbytes = mbc_charwidth(str_ptr, &st_copy); char *q = cellptr -> bytes = mymalloc(1 + nbytes); colourless_bytes += nbytes; mbc_copy(str_ptr,&q, &st); /* copy the possibly multi-byte character at str_ptr to cellptr -> bytes , incrementing q to just past the copy */ *q = '\0'; cellptr -> state = st; /* remember shift state after reading str_ptr, just in case a backspace comes along later */ cellptr += 1; } break; } colourless_length = cellptr - copied_cells; } /* end of for loop */ copy_without_ignore_markers = mymalloc(colourless_bytes + 1); for (cellptr = copied_cells, copy_ptr = copy_without_ignore_markers, i = 0; i < colourless_length; i++, cellptr++) { for(p = cellptr->bytes; *p; p++, copy_ptr++) *copy_ptr = *p; free(cellptr->bytes); } *copy_ptr = '\0'; free(copied_cells); DPRINTF4(DEBUG_READLINE, "colourless_strlen(\"%s\", \"%s\") = %d chars, %d bytes", M(str), copy_without_ignore_markers, colourless_length, colourless_bytes); if (pcopy_without_ignore_markers) *pcopy_without_ignore_markers = copy_without_ignore_markers; else free(copy_without_ignore_markers); return colourless_length; } DEF_UNIT_TEST(test_colourless_strlen) { if (STAGE(TEST_AT_PROGRAM_START)) { char test[] = "\033[0;31mblא\033[0m bla \033[0;33mblא\033[0m"; char *result, *copy; int len = colourless_strlen(mark_invisible(test), ©, 0, 2, &result); printf("origineel = '%s', len = %d, copy = '%s', result = '%s'\n", test, len, copy, result); exit(0); } } /* helper function: returns the number of displayed characters (the "colourless length") of str (which has its unprintable sequences marked with RL_PROMPT_*_IGNORE). Until rlwrap 0.44, this function didn't take wide characters into consideration, causing problems with long prompts containing wide characters. */ int colourless_strlen_unmarked (const char *str, int termwidth) { char *marked_str = mark_invisible(str); int colourless_length = colourless_strlen(marked_str, NULL, termwidth, 0, NULL); free(marked_str); return colourless_length; } /* skip a maximal number (possibly zero) of termwidth-wide initial segments of long_line and return the remainder (i.e. the last line of long_line on screen) if long_line contains an ESC character, return "" (signaling "don't touch") */ char * get_last_screenline(char *long_line, int termwidth) { int line_length, removed; char *line_copy, *last_screenline; line_copy = mysavestring(long_line); line_length = strlen(line_copy); if (termwidth == 0 || /* this may be the case on some weird systems */ line_length <= termwidth) { /* line doesn't extend beyond right margin @@@ are there terminals that put the cursor on the next line if line_length == termwidth?? */ return line_copy; } else if (strchr(long_line, '\033')) { /* found, give up */ free (line_copy); return mysavestring("Ehhmm..? > "); } else { removed = (line_length / termwidth) * termwidth; /* integer arithmetic: 33/10 = 3 */ last_screenline = mysavestring(line_copy + removed); free(line_copy); return last_screenline; } } /* lowercase(str) returns lowercased copy of str */ char * lowercase(const char *str) { char *result, *p; result = mysavestring(str); for (p=result; *p; p++) *p = tolower(*p); return result; } char * colour_name_to_ansi_code(const char *colour_name) { if (colour_name && *colour_name && isalpha(*colour_name)) { char *lc_colour_name = mysavestring(lowercase(colour_name)); char *bold_code = (isupper(*colour_name) ? "1" : "0"); #define isit(c) (strcmp(c,lc_colour_name)==0) char *colour_code = isit("black") ? "30" : isit("red") ? "31" : isit("green") ? "32" : isit("yellow") ? "33" : isit("blue") ? "34" : isit("magenta") ? "35" : isit("purple") ? "35" : isit("cyan") ? "36" : isit("white") ? "37" : NULL ; #undef isit if (colour_code) return add3strings(bold_code,";",colour_code); else myerror(FATAL|NOERRNO, "unrecognised colour name '%s'. Use e.g. 'yellow' or 'Blue'.", colour_name); } return mysavestring(colour_name); } /* returns TRUE if string is numeric (i.e. positive or negative integer), otherwise FALSE */ int isnumeric(char *string){ char *pstr = string; if (*pstr == '-') /* allow negative numbers */ pstr++; while (*pstr != '\0') if (!isdigit(*pstr++)) return FALSE; return TRUE; } #define DIGITS_NUMBER 8 /* number of (hex) digits of length of field. 6 digits -> max 16MB per message field, should suffice */ #define MAX_FIELD_LENGTH ((1UL << (DIGITS_NUMBER * 4 - 1)) -1) /* max integer that can be written with DIGITS_NUMBER (hex)digits */ #define MY_HEX_FORMAT(n) ("%0" MY_ITOA(n) "x") #define MY_ITOA(n) #n /* fussy strtol with error checking */ long mystrtol(const char *nptr, int base) { char *endptr; long result; errno = 0; result = strtol(nptr, &endptr, base); if (*endptr != '\0') myerror(FATAL|NOERRNO, "invalid representation %s", nptr); if (errno != 0) myerror(FATAL|USE_ERRNO, "strtol error"); return result; } /* Encode a length as a string on the heap */ static char * encode_field_length(int length) { char *encoded_length = mymalloc(DIGITS_NUMBER+1); sprintf(encoded_length, MY_HEX_FORMAT(DIGITS_NUMBER), length); return encoded_length; } /* decode first length field in a message of form " ...." and advance pointer *ppmessage to the start of */ static int decode_field_length(char** ppmessage) { char hex_string[DIGITS_NUMBER+1]; long length; mystrlcpy(hex_string, *ppmessage, DIGITS_NUMBER+1); length = mystrtol(hex_string, 16); *ppmessage += DIGITS_NUMBER; return length; } /* Test an invariant: */ void test_field_length_encoding(void) { int testval = 1423722; char *encoded = encode_field_length(testval); assert( decode_field_length(&encoded) == testval && (*encoded == '\0')); } /* Append to message and return the result (after freeing the original message) is a string representation of s length message can be empty, or, equivalently, NULL. */ char * append_field_and_free_old(char *message, const char *field) { long unsigned int length = strlen(field); char *encoded_length; if (length > MAX_FIELD_LENGTH) myerror(FATAL|NOERRNO, "message field\"%s...\" has length %ld, it should be less than %ld", mangle_string_for_debug_log(field, 10), length, MAX_FIELD_LENGTH); encoded_length = encode_field_length(length); message = append_and_free_old(message, encoded_length); message = append_and_free_old(message, field); free(encoded_length); return message; } char * merge_fields(char *field, ...) { char *varg = field; char *message = NULL; va_list vargs; va_start(vargs, field); while (varg != END_FIELD) { message = append_field_and_free_old(message, varg); varg = va_arg(vargs, char*); } va_end(vargs); return message; } /* split a message of a string: ... into: [, , ...] */ char ** split_filter_message(char *message, int *counter) { char *pmessage = message; int message_length = strlen(message); char **list, **plist; int nfields = 0; static int smallest_message_size = 0; if (smallest_message_size == 0) smallest_message_size = strlen(append_field_and_free_old(NULL, "")); /* this assumes that the empty message is the smallest possible */ assert(smallest_message_size > 0); plist = list = mymalloc(sizeof(char*) * (1 + strlen(message)/smallest_message_size )); /* worst case: "0000000000000000000000" */ nfields = 0; while(!(*pmessage == '\0')) { long length = decode_field_length(&pmessage); /* cut out a field from the head of the message: */ char *field = mymalloc(sizeof(char) * (length+1)); mystrlcpy(field, pmessage, length+1); *plist++ = field; pmessage += length; nfields++; if (pmessage > message + message_length) myerror(FATAL|NOERRNO, "malformed message; %s", mangle_string_for_debug_log(message, 256)); } if (counter) *counter = nfields; *plist = 0; return list; } #ifndef HAVE_REGEX_H char *protect_or_cleanup(const char *prompt) { return mysavestring(prompt); /*essentially a NOP */ } #else /* regcomp with error checking (and simpler signature) */ static regex_t *my_regcomp(const char*regex, int flags) { regex_t *compiled_regexp; int compile_error; if (!*regex) return NULL; compiled_regexp = mymalloc(sizeof(regex_t)); compile_error = regcomp(compiled_regexp, regex, flags); if (compile_error) { int size = regerror(compile_error, compiled_regexp, NULL, 0); char *error_message = mymalloc(size); regerror(compile_error, compiled_regexp, error_message, size); myerror(FATAL|NOERRNO, "(Internal error:) in regexp \"%s\": %s", mangle_string_for_debug_log(regex,256), error_message); } return compiled_regexp; } #define TOKEN '@' #define MAXGROUPS 2 /* protect_pattern("foo", 'a', 'b) = "(a[^b]*b)|(foo)" */ char *protect(const char *pattern, char protect_start, char protect_end) { char *alternative_pattern = mymalloc(strlen(pattern) + 14); sprintf(alternative_pattern,"(%c[^%c]*%c)|(%s)", protect_start, protect_end, protect_end, pattern); return alternative_pattern; } /* Substitute all occurences of the second group in a "(..)|(..)" within with (any TOKENs within the replacement will be replaced by the match) , skipping everything that matches the first group (which can be said to "protect" against replacement) E.g: replace_special("a zzz b zzz", "(a[^a]*b)|(z+)", "(@)") = "a zzz b (zzz)" The role of a and b above will be played by the RL_PROMPT_{START,END}_IGNORE characters */ char *replace_special(const char *source, regex_t *compiled_pattern, const char*replacement) { const char *source_cursor; char *copy_with_replacements, *copy_cursor; int max_copylen; assert(source != NULL); max_copylen = 1 + max(1, strlen(replacement)) * strlen(source); if (!compiled_pattern) /* pattern == NULL: just return a copy */ return mysavestring(source); copy_with_replacements = mymalloc(max_copylen); /* worst case: replace every char in source by replacement (+ 1 final zero byte) */ source_cursor = source; copy_cursor = copy_with_replacements; while(TRUE) { regmatch_t matches[MAXGROUPS + 1]; /* whole match + MAXGROUPS groups */ if ((regexec(compiled_pattern, source_cursor, MAXGROUPS + 1, matches , 0) == REG_NOMATCH)) { /* no (more) matches ... */ strcpy(copy_cursor,source_cursor); /* .. copy remainder of source (may be empty), and we're done */ break; /* 0-terminates copy, even if last match consumed source copletely */ } else { int i; const char *p; int protected_start = matches[1].rm_so; int protected_end = matches[1].rm_eo; int protected_length = protected_end - protected_start; int match_start = matches[2].rm_so; int match_end = matches[2].rm_eo; int match_length = match_end - match_start; /* Either the first ("protected") group in alternative_pattern matches, */ assert(!(protected_end > -1 && match_end > -1)); /* ... or the second (never both - that is the assertion here) */ assert(protected_length > 0 || match_length > 0); /* ... and that match cannot be empty */ if (protected_end > -1) /* If it is the first ... */ for (i = 0; i< protected_end; i++) /* copy until protected match ends */ *copy_cursor++ = *source_cursor++; else { /* if it is the second (the original pattern) ... */ for (i = 0; i< match_start; i++) /* ... copy until match starts */ *copy_cursor++ = *source_cursor++; for(p = replacement; *p; p++) /* splice replacement */ if (*p == TOKEN) { /* where there is a TOKEN (may be more than one) ... */ assert(copy_cursor + match_length - copy_with_replacements < max_copylen); for(i=0; i< match_length ; i++) /* ... splice matched group */ *copy_cursor++ = *(source_cursor + i); } else /* otherwise, just copy replacement */ *copy_cursor++ = *p; source_cursor += match_length; /* finished replacing this match, try again */ } } *copy_cursor = '\0'; } return copy_with_replacements; } /* All the codes we want to preserve (i.e. keep and put between RL_PROMPT_{START,END}_IGNORE ) */ static char *protected_codes[] = { "\x1B\x1B", NULL}; /* As we don't protect (and hence will get rid of) ANSI colour codes when "bleaching" the prompt, */ /* specify separately: */ static char *ansi_colour_code_regexp = "(\x1B\\[[0-9;]*m)"; /* colour codes */ /* All the codes we want to get rid of. cf. */ /* https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python */ /* We only cover 7-bit C1 ANSI sequences; the 8-bit are not much used as they interfere with UTF-8, and we */ /* don't need to absolutely catch everything anyway */ static char *unwanted_codes[] = { "(\x1B[ -/]*[0-Z\\\\-~])" /* ANSI X3.41: ESC + I-pattern + F-pattern (except [, which is covered below) */ ,"(\x1B[@-Z\\]-_])" /* C1_Fe */ ,"(\x1B\\[[0-9:;<>=?]*[-/]*[@-~])" /* CSI (overlaps with "protected" codes!) (is the meaning of [@-~] locale-dependent?) */ ,"(\x1B\\[?1h\x1B=)" /* smkx keypad */ ,"(\x1B\\]0;[[:print:]]*\x07)" /* tsl fsl */ ,NULL}; /* mark protected codes between RL_PROMPT_{START,END}_IGNORE and erase unwanted codes */ char *protect_or_cleanup(char *prompt, bool free_prompt) { char *result1, *result; static char *protected_codes_regexp ; static regex_t *compiled_and_protected_protected_codes_regexp; static char *protected_token; static char *unwanted_codes_regexp; static regex_t *compiled_and_protected_unwanted_codes_regexp; /* protect stuff we want to keep: */ /* (once)construct the regexp for the protected codes: */ if (!protected_codes_regexp) { protected_codes_regexp = unsplit_with(-1, &protected_codes[0], "|"); if (!bleach_the_prompt) protected_codes_regexp = add3strings(protected_codes_regexp, "|", ansi_colour_code_regexp); compiled_and_protected_protected_codes_regexp = my_regcomp(protect(protected_codes_regexp, RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE), REG_EXTENDED); } /* (once) construct the replacement pattern */ if (!protected_token) { protected_token = mymalloc(4); /* "\x01@\x02" */ sprintf(protected_token,"%c%c%c",RL_PROMPT_START_IGNORE, TOKEN, RL_PROMPT_END_IGNORE); } result1 = replace_special(prompt, compiled_and_protected_protected_codes_regexp, protected_token); if (!unwanted_codes_regexp) { unwanted_codes_regexp = unsplit_with(-1, &unwanted_codes[0], "|"); DPRINTF1(DEBUG_AD_HOC, "unwanted_codes_regexp: %s", M(unwanted_codes_regexp)); compiled_and_protected_unwanted_codes_regexp = my_regcomp(protect(unwanted_codes_regexp, RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE), REG_EXTENDED); } result = replace_special(result1, compiled_and_protected_unwanted_codes_regexp, ""); DPRINTF2(DEBUG_READLINE, "protect_or_cleanup(%s) = %s", M(prompt), M(result)); free(result1); if (free_prompt) free(prompt); return result; } /* returns TRUE if 'string' matches the 'regexp' (or is a superstring of it, when we don't HAVE_REGEX_H). The regexp is recompiled with every call, which doesn't really hurt as this function is not called often: at most twice for every prompt. 'string' and 'regexp' may be NULL (in which case FALSE is returned) Only used for the --forget-regexp and the --prompt-regexp options */ int match_regexp (const char *string, const char *regexp, int case_insensitive) { int result = FALSE; if (!regexp || !string) return FALSE; #ifndef HAVE_REGEX_H { static int been_warned = 0; char *metachars = "*()+?"; char *lc_string = (case_insensitive ? lowercase(string) : mysavestring(string)); char *lc_regexp = (case_insensitive ? lowercase(regexp) : mysavestring(regexp)); if (scan_metacharacters(regexp, metachars) && !been_warned++) /* warn only once if the user specifies a metacharacter */ myerror(WARNING|NOERRNO, "one of the regexp metacharacters \"%s\" occurs in regexp(?) \"%s\"\n" " ...but on your platform, regexp matching is not supported!", metachars, regexp); result = mystrstr(lc_string, lc_regexp); free(lc_string); free(lc_regexp); } #else { regex_t *compiled_regexp = my_regcomp(regexp, REG_EXTENDED|REG_NOSUB|(case_insensitive ? REG_ICASE : 0)); result = !regexec(compiled_regexp, string, 0, NULL, 0); regfree(compiled_regexp); } #endif return result; } #ifdef UNIT_TEST TESTFUNC(test_subst, argc, argv, stage) { ONLY_AT_STAGE(TEST_AFTER_OPTION_PARSING); while(TRUE) { regmatch_t matches[MAXGROUPS + 1]; /* whole match + MAXGROUPS groups */ char *line_read = readline ("go ahead (string pattern): "); if (strings_are_equal(line_read, "stop")) break; char**components = split_with(line_read," "); if (!(components[0] && components[1])) continue; regex_t *re = my_regcomp(protect(components[1],'a','b'), REG_EXTENDED); bool does_match = regexec(re, components[0], MAXGROUPS + 1, matches , 0) != REG_NOMATCH; printf("%s\n", does_match ? "JA" : "NEE"); free_foreign(line_read); } cleanup_rlwrap_and_exit(EXIT_SUCCESS); } #endif #endif /* def HAVE_REGEX_H */ /* scan blocks of client output for "cupcodes" that enter and exit the "alternate screen" and set the global variable screen_is_alternate accordingly */ void check_cupcodes(const char *client_output) { static char *still_unchecked; /* to avoid missing a cupcode that spans more than 1 read buffer, we keep the last few bytes in a static buffer (still_unchecked) , that we always prepend to the next incoming block */ static int rmcup_len, smcup_len, max_cuplen; char *output_copy, *outputptr; int cut_here; if (!(commands_children_not_wrapped && term_smcup && term_rmcup)) return; /* check is impossible or unnecessary */ if (still_unchecked == NULL) { /* init static vars once */ still_unchecked = mysavestring(""); rmcup_len = strlen(term_rmcup); smcup_len = strlen(term_smcup); max_cuplen = max(rmcup_len, smcup_len); } outputptr = output_copy = append_and_free_old(still_unchecked, client_output); /* keep the very end for next time, as it might contain a partial cupcode */ /* in the worst case we will notice the shortest of the two cupcodes twice */ cut_here = max(0, strlen(output_copy) - max_cuplen); still_unchecked = mysavestring (&output_copy[cut_here]); while (TRUE) { char *rmptr = strstr(outputptr, term_rmcup); char *smptr = strstr(outputptr, term_smcup); if (rmptr == smptr) { assert(rmptr == NULL); /* can only fail if term_smcup is prefix of term_rmcup, or vice versa, which never happens as far as I know */ break; } else if (rmptr > smptr) { /* rmcup and smcup may occur in the same block of output */ DPRINTF0(DEBUG_READLINE, "Saw rmcup"); screen_is_alternate = FALSE; outputptr = rmptr + rmcup_len; /* 1 past match */ } else { DPRINTF0(DEBUG_READLINE, "Saw smcup"); screen_is_alternate = TRUE; outputptr = smptr + smcup_len; } } free(output_copy); } rlwrap-0.46.1/src/term.c000066400000000000000000000334531433170252700150270ustar00rootroot00000000000000/* term.c: terminal handling, cursor movement etc. (C) 2000-2007 Hans Lub 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" /* rlwrap needs a little bit of cursor acrobatics to cope with multi-line prompts, recovering from CTRL-Z and editors that may have used the alternate screen. All such cursor movements are done via the functions in this file. In the past, rlwrap relied on only the termcap library (or an emulation of it), and would fall back on reasonable defaults ("\r", "\b" etc) if this was not found. In the future it will also use terminfo or even ncurses. But the fall-back to older libraries is still important, as rlwrap seems to be especially needed on older machines */ /*global vars */ char term_eof; /* end_of_file char */ char term_stop; /* stop (suspend) key */ char *term_backspace; /* backspace control seq (or 0, if none defined in terminfo) */ char *term_cursor_hpos; /* control seq to position cursor st given position on current line */ char *term_clear_screen; char *term_cursor_up; char *term_cursor_down; char *term_cursor_left; /* only used for debugging (the SHOWCURSOR macro) */ char *term_cursor_right; /* only used to emulate a missing term_cursor_hpos */ char *term_smcup; /* smcup - char sequence to switch to alternate screen */ char *term_rmcup; /* rmcup - char sequence to return from alternate screen */ char *term_rmkx; /* rmkx - char sequence to return from keyboard application mode */ char *term_enable_bracketed_paste; /* If we write this to some terminals (xterm etc.) they will */ /* "bracket" pasted input with \e[200 and \e201 */ char *term_disable_bracketed_paste; int term_has_colours; int redisplay = 1; int newline_came_last = TRUE; /* used to determine whether rlwrap needs to ouptut a newline at the very end */ struct termios saved_terminal_settings; /* original terminal settings */ int terminal_settings_saved = FALSE; /* saved_terminal_settings is valid */ struct winsize winsize; /* current window size */ static char *term_cr; /* carriage return (or 0, if none defined in terminfo) */ static char *term_clear_line; char *term_name; static char *my_tgetstr (char *id, const char *capability_name) { #ifdef HAVE_TERMCAP_H char *term_string_buf = (char *)mymalloc(2048), *tb = term_string_buf; char *stringcap = tgetstr(id, &tb); /* rl_get_termcap(id) only gets capabilities used by readline */ char *retval = stringcap ? mysavestring(stringcap) : NULL; DPRINTF3(DEBUG_TERMIO, "tgetstr(\"%s\") = %s (%s)", id, (stringcap ? M(stringcap) : "NULL"), capability_name); free(term_string_buf); return retval; #else return NULL; #endif } static char *my_tigetstr (char *tid, const char *capability_name) { #ifdef HAVE_CURSES_H static int term_has_been_setup = FALSE; char *stringcap, *retval; if (!term_has_been_setup) { setupterm(term_name, STDIN_FILENO, (int *)0); /* like tgetent() before tgetstr(), we have to call setupterm() befor tigetstr() */ term_has_been_setup = TRUE; } stringcap = tigetstr(tid); retval = stringcap ? mysavestring(stringcap) : NULL; DPRINTF3(DEBUG_TERMIO, "tigetstr(\"%s\") = %s (%s)", tid, (stringcap ? M(stringcap) : "NULL"), capability_name); return retval; #else return NULL; #endif } /* Get (copies of) escape codes ("string capabilities") by name. First try terminfo name, then termcap name */ static char * tigetstr_or_else_tgetstr(char *capname, char *tcap_code, const char *capability_name) { char *retval; retval = my_tigetstr(capname, capability_name); if (!retval) retval = my_tgetstr(tcap_code, capability_name); return retval; } #define FALLBACK_TERMINAL "vt100" /* hard-coded terminal name to use when we don't know which terminal we're on */ void init_terminal(void) { /* save term settings and determine term type */ char *term_buf = mymalloc(2048); int we_have_stringcaps; int we_are_on_hp_ux11; if (!isatty(STDIN_FILENO)) myerror(FATAL|NOERRNO, "stdin is not a tty"); if (tcgetattr(STDIN_FILENO, &saved_terminal_settings) < 0) myerror(FATAL|USE_ERRNO, "tcgetattr error on stdin"); else terminal_settings_saved = TRUE; if (ioctl(STDIN_FILENO, TIOCGWINSZ, &winsize) < 0) myerror(FATAL|USE_ERRNO, "Could not get terminal size"); DPRINTF2(DEBUG_TERMIO, "winsize.ws_col (term width): %d; winsize.ws_row (term height): %d", winsize.ws_col, winsize.ws_row); if (winsize.ws_col == 0) myerror(FATAL|NOERRNO, "My terminal reports width=0 (is it emacs?) I can't handle this, sorry!"); /* init some variables: */ term_name = getenv("TERM"); DPRINTF1(DEBUG_TERMIO, "found TERM = %s", term_name); /* On weird and scary HP-UX 11 succesful tgetent() returns 0, so we need the followig to portably catch errors: */ we_are_on_hp_ux11 = tgetent(term_buf, "vt100") == 0 && tgetent(term_buf,"QzBgt57gr6xwxw") == -1; /* assuming there is no terminal called Qz... */ #define T_OK(retval) ((retval) > 0 || ((retval)== 0 && we_are_on_hp_ux11)) /* if TERM is not set, use FALLBACK_TERMINAL */ if (!term_name || strlen(term_name)==0) { myerror(WARNING|USE_ERRNO, "environment variable TERM not set, assuming %s", FALLBACK_TERMINAL); term_name = FALLBACK_TERMINAL; } /* If we cannot find stringcaps, use FALLBACK_TERMINAL */ if (! (we_have_stringcaps = T_OK(tgetent(term_buf, term_name))) && strcmp(term_name, FALLBACK_TERMINAL)) { myerror(WARNING|NOERRNO, "your $TERM is '%s' but %s couldn't find it in the terminfo database. We'll use '%s'", term_name, program_name, FALLBACK_TERMINAL); term_name = FALLBACK_TERMINAL; } /* If we cannot find stringcaps, even for FALLBACK_TERMINAL, complain */ if (!we_have_stringcaps && ! (we_have_stringcaps = T_OK(tgetent(term_buf, FALLBACK_TERMINAL)))) myerror(WARNING|NOERRNO, "Even %s is not found in the terminfo database. Expect some problems.", FALLBACK_TERMINAL); DPRINTF1(DEBUG_TERMIO, "using TERM = %s", term_name); mysetenv("TERM", term_name); if (we_have_stringcaps) { term_backspace = tigetstr_or_else_tgetstr("cub1", "le", "backspace"); term_cr = tigetstr_or_else_tgetstr("cr", "cr", "carriage return"); term_clear_line = tigetstr_or_else_tgetstr("el", "ce", "clear line"); term_clear_screen = tigetstr_or_else_tgetstr("clear", "cl", "clear screen"); term_cursor_hpos = tigetstr_or_else_tgetstr("hpa", "ch", "set cursor horizontal position"); term_cursor_left = tigetstr_or_else_tgetstr("cub1", "le", "move cursor left"); term_cursor_right = tigetstr_or_else_tgetstr("cuf1", "nd", "move cursor right"); term_cursor_up = tigetstr_or_else_tgetstr("cuu1", "up", "move cursor up"); term_cursor_down = tigetstr_or_else_tgetstr("cud1", "do", "move cursor down"); term_has_colours = tigetstr_or_else_tgetstr("initc", "Ic", "initialise colour") ? TRUE : FALSE; /* the following codes are never output by rlwrap, but used to recognize the use of the the alternate screen by the client, and to filter out "garbage" that is coming from commands that use them */ term_smcup = tigetstr_or_else_tgetstr("smcup", "ti", "switch to alternate screen"); term_rmcup = tigetstr_or_else_tgetstr("rmcup", "te", "exit alternate screen"); term_rmkx = tigetstr_or_else_tgetstr("rmkx", "ke", "leave keypad-transmit mode"); /* there is no way (yet) to determine whether a terminal knows about bracketed_paste_enabled paste - we cannot use tigetstr(). "Dumb" terminals do not, of course */ term_enable_bracketed_paste = strings_are_equal(term_name, "dumb") ? NULL : "\033[?2004h"; term_disable_bracketed_paste = strings_are_equal(term_name, "dumb") ? NULL : "\033[?2004l"; if (!term_cursor_right) /* probably only on 'dumb' terminal */ term_cursor_right = " "; /* term_colors = tigetnum("colors"); DPRINTF1(DEBUG_TERMIO, "terminal colors: %d", term_colors); */ } if (!(term_cursor_hpos || term_cursor_right) || !term_cursor_up || !term_cursor_down) myerror(WARNING|NOERRNO, "Your terminal '%s' is not fully functional, expect some problems.", term_name); term_eof = saved_terminal_settings.c_cc[VEOF]; term_stop = saved_terminal_settings.c_cc[VSTOP]; DPRINTF1(DEBUG_TERMIO, "term_eof=%d", term_eof); free(term_buf); } void set_echo(int yes) { struct termios *pterm = my_tcgetattr(slave_pty_sensing_fd, "slave pty"); /* mimic terminal settings of client */ if (!pterm) /* child has probably died */ return; pterm->c_lflag &= ~ICANON; /* except a few details... */ pterm->c_cc[VMIN] = 1; pterm->c_cc[VTIME] = 0; if (yes) pterm->c_lflag |= ECHO; else /* no */ pterm->c_lflag &= ~ECHO; log_terminal_settings(pterm); if (tcsetattr(STDIN_FILENO, TCSANOW, pterm) < 0 && errno != ENOTTY) { /* nothing */ } /* myerror(FATAL|USE_ERRNO, "cannot prepare terminal (tcsetattr error on stdin)"); */ free(pterm); } int cursor_hpos(int col) { char *instantiated; if (term_cursor_hpos) { instantiated = tgoto(term_cursor_hpos, 0, col); /* tgoto with a command that takes one parameter: parameter goes to 2nd arg ("vertical position"). */ assert(instantiated); DPRINTF2(DEBUG_TERMIO, "tgoto(term_cursor_hpos, 0, %d) = %s", col, M(instantiated)); tputs(instantiated, 1, my_putchar); } else { int i; cr(); for (i = 0; i < col; i++) tputs(term_cursor_right, 1, my_putchar); } return TRUE; } void cr(void) { if (term_cr) tputs(term_cr, 1, my_putchar); else my_putchar('\r'); } void clear_the_screen(void) { /* clear_screen is a macro in term.h */ int i; if (term_clear_screen) tputs(term_clear_screen, 1, my_putchar); else for (i = 0; i < winsize.ws_row; i++) my_putchar('\n'); /* poor man's clear screen */ } void clear_line(void) { int i; int width = winsize.ws_col; char *p, *spaces; cr(); if (term_clear_line) tputs(term_clear_line, 1, my_putchar); else { spaces = (char *) mymalloc(width +1); for (i = 0, p = spaces; i < width; i++, p++) *p = ' '; *p = '\0'; my_putstr(spaces); /* poor man's clear line */ free((void *)spaces); } cr(); } void backspace(int count) { int i; if (term_backspace) for (i = 0; i < count; i++) tputs(term_backspace, 1, my_putchar); else for (i = 0; i < count; i++) my_putchar('\b'); } void curs_left(void) { if (term_cursor_left) tputs(term_cursor_left, 1, my_putchar); else backspace(1); } void curs_up(void) { if (term_cursor_up) tputs(term_cursor_up, 1, my_putchar); } void curs_down(void) { if (term_cursor_down) tputs(term_cursor_down, 1, my_putchar); } int my_putchar(TPUTS_PUTC_ARGTYPE c) { char ch = c; ssize_t nwritten = write(STDOUT_FILENO, &ch, 1); return (nwritten == -1 ? -1 : c); } void my_putstr(const char *string) { int string_length = strlen(string); DPRINTF2(DEBUG_TERMIO,"wrote %d bytes to stdout: %s", string_length, M(string)); if (string_length == 0) return; write_patiently(STDOUT_FILENO, string, string_length, "to stdout"); newline_came_last = (string[string_length - 1] == '\n'); /* remember whether newline came last */ } static void test_termfunc(char *control_string, char *control_string_name, char* start, void (* termfunc)(void), char *end) { char *mangled_control_string = (control_string ? add3strings("\"", mangle_string_for_debug_log(control_string,20),"\"") : mysavestring("NULL")); printf("\n%s = %s\n", control_string_name, mangled_control_string); if (!control_string) printf("trying without suitable control string, fasten seatbelts and brace for impact... \n"); my_putstr(start); sleep(1); termfunc(); my_putstr(end); sleep(1); free(mangled_control_string); } static void backspace1 (void) { backspace(1); } static void cursor_hpos4 (void) { cursor_hpos(4);} void test_terminal(void) { if (debug) { debug_fp = fopen(DEBUG_FILENAME, "w"); setbuf(debug_fp, NULL); } init_terminal(); printf("\nTerminal is \"%s\"\n", term_name); test_termfunc(term_backspace, "term_backspace", "This should print \"grape\": \ngras", &backspace1, "pe\n"); test_termfunc(term_cr, "term_cr", "This should print \"lemon juice\": \napple", &cr, "lemon juice\n"); test_termfunc(term_clear_line, "term_clear_line","This should print \"apple\": \npomegranate", &clear_line, "apple\n"); test_termfunc(term_cursor_hpos, "term_cursor_hpos", "This should print \"pomegranate\": \npomelo", &cursor_hpos4, "granate\n"); test_termfunc(term_cursor_up, "term_cursor_up", "This should print \"blackberry\": \n blueberry\n", &curs_up,"black\n"); printf("\n"); } rlwrap-0.46.1/src/utils.c000066400000000000000000000674541433170252700152300ustar00rootroot00000000000000/* 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; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. You may contact the author by: e-mail: hanslub42@gmail.com */ #include "rlwrap.h" static FILE *log_fp; /* Give up the processor so that other processes (like command) can have their say. Especially useful with multi-line input: if we, after writing a line to the inferior command, would return to the main select() loop immediately, we would find more output to write, and keep blathering away without giving the poor command time to say something back */ void yield(void) { DPRINTF0(DEBUG_TERMIO, "yield..."); #if 0 /* If command writes some output and immediately starts a long computation, sched_yield() will not return for a long time,and rlwrap will not see the output for all that time. The "poor mans sched_yield" with select actually works better! */ sched_yield(); #else { struct timeval a_few_millisecs = { 0, 1000 }; select(0, NULL, NULL, NULL, &a_few_millisecs); /* poor man's sched_yield() */ } #endif } static volatile int signal_handled = FALSE; #ifdef HAVE_REAL_PSELECT void zero_select_timeout (void) { signal_handled = TRUE; } #else #define until_hell_freezes NULL ; static struct timeval * volatile pmy_select_timeout_tv; /* The SIGCHLD handler sets this variable (see zero_select_timeout() below) to make select() return immediately when a child has died. gcc (4.8 and higher) may optimize it into a register, which won't work: hence the "volatile" keyword */ static struct timeval my_select_timeout_tv; void zero_select_timeout (void) { my_select_timeout_tv.tv_sec = 0; my_select_timeout_tv.tv_usec = 0; pmy_select_timeout_tv = &my_select_timeout_tv; signal_handled = TRUE; } #endif /* Even though even older linux systems HAVE PSELECT, is is non-atomic: signal handlers may (and generally will) run between the unblocking of the signals and the select call (which will then wait untill hell freezes over) Therefore we always convert the contents op ptimeout_ts into to a static struct timeval my_select_timeout_tv, and use the function zero_select_timeout to set this to {0,0} from within a signal handler, which will then make select return immediately. (Linus Torvalds mentions this trick in http://lkml.indiana.edu/hypermail/linux/kernel/0506.1/1191.html), As I know of no reliable way to distiguish real pselect from fake pselect at configure time HAVE_REAL_PSELECT will be undefined on all systems, even those that do have a genuine pselect. */ int my_pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *ptimeout_ts, const sigset_t *sigmask) { #ifdef HAVE_REAL_PSELECT return pselect(n, readfds, writefds, exceptfds, ptimeout_ts, sigmask); #else int retval; sigset_t oldmask; if (ptimeout_ts) { /* convert timespec e.g {0,40000000} to timeval {0, 40000} */ pmy_select_timeout_tv = &my_select_timeout_tv; pmy_select_timeout_tv -> tv_sec = ptimeout_ts -> tv_sec; pmy_select_timeout_tv -> tv_usec = ptimeout_ts -> tv_nsec / 1000; } else pmy_select_timeout_tv = until_hell_freezes; signal_handled = FALSE; sigprocmask(SIG_SETMASK, sigmask, &oldmask); /* most signals will be catched HERE (and their handlers will set my_select_timeout_tv to {0,0}) */ retval = select(n, readfds, writefds, exceptfds, pmy_select_timeout_tv); /* but even if they are slow off the mark and get catched HERE the code 3 lines below will notice */ sigprocmask(SIG_SETMASK, &oldmask, NULL); if (signal_handled && retval >= 0) { errno = EINTR; return -1; } else { return retval; } #endif } struct termios * my_tcgetattr(int fd, char *UNUSED(which)) { struct termios *pterm = (struct termios *)mymalloc(sizeof(struct termios)); return tcgetattr(fd, pterm) < 0 ? NULL : pterm; } /* empty signal handler */ void do_nothing(int UNUSED(signal)) { /* yawn.... */ } /* When using read (2) or write (2), one always has to be prepared to handle incomplete reads or writes. The {read,write}_patiently* routines below do just that: they repeat the read or write until the whole buffer has been filled, or an error occurs. The {read,write}_patiently2 functions are specialised for reading/writing the input/output pipe of a filter */ int write_patiently(int fd, const void *buffer, int count, const char *whither) { int nwritten = 0; int total_written = 0; assert(count >= 0); if (count == 0) return TRUE; while(1) { if((nwritten = write(fd, (char *)buffer + total_written, count - total_written)) <= 0) { if (errno == EINTR) continue; else if (errno == EPIPE || nwritten == 0) { return FALSE; } else myerror(FATAL|USE_ERRNO, "write error %s", whither); } total_written += nwritten; if (total_written == count) /* done */ break; } return TRUE; } /* keep reading from fd and write into buffer until count bytes have been read. for uninterruptible_msec, restart when interrupted by a signal, after this, bail out with an error. if count < 0, returns a 0-terminated copy of buffer */ void read_patiently2 (int fd, void *buffer, int count, int uninterruptible_msec, const char *whence) { int nread = 0; int total_read = 0; int interruptible = FALSE; DPRINTF2(DEBUG_AD_HOC, "read_patiently2 tries to read %d bytes (uninterruptible: %d msec)", count, uninterruptible_msec); assert (count >= 0); if (count > 0) { myalarm(uninterruptible_msec); while (1) { assert(count > total_read); if((nread = read(fd, (char *) buffer + total_read, count - total_read)) <= 0) { if (nread < 0 && errno == EINTR) { if (interruptible) myerror(FATAL|NOERRNO, "(user) interrupt reading %s, filter_hangs?", whence); if (received_sigALRM) { received_sigALRM = FALSE; interruptible = TRUE; } continue; } else if (nread == 0) myerror(FATAL|NOERRNO, "EOF reading %s", whence); else /* nread < 0 */ myerror(FATAL|USE_ERRNO, "error reading %s", whence); } total_read += nread; if (total_read == count) /* done */ break; } } myalarm(0); /* reset alarm */ block_all_signals(); DPRINTF2(DEBUG_AD_HOC, "read_patiently2 read %d bytes: %s", total_read, mangle_buffer_for_debug_log(buffer, total_read)); return; } void write_patiently2(int fd, const void *buffer, int count, int uninterruptible_msec, const char* whither) { int nwritten = 0; int total_written = 0; int interruptible = FALSE; assert(count >= 0); if (count == 0) return; /* no-op */ myalarm(uninterruptible_msec); while(1) { if((nwritten = write(fd, (char *)buffer + total_written, count - total_written)) <= 0) { if (nwritten < 0 && errno == EINTR) { if (interruptible) myerror(FATAL|NOERRNO, "(user) interrupt - filter hangs?"); if (received_sigALRM) { received_sigALRM = FALSE; interruptible = TRUE; } continue; } else /* nwritten== 0 or < 0 with error other than EINTR */ myerror(FATAL|USE_ERRNO, "error writing %s", whither); } total_written += nwritten; if (total_written == count) /* done */ break; } myalarm(0); DPRINTF2(DEBUG_AD_HOC, "write_patiently2 wrote %d bytes: %s", total_written, mangle_buffer_for_debug_log(buffer, total_written)); return; } void mysetenv(const char *name, const char *value) { int return_value = 0; #ifdef HAVE_SETENV return_value = setenv(name, value, TRUE); #elif defined(HAVE_PUTENV) char *name_is_value = add3strings (name, "=", value); return_value = putenv (name_is_value); #else /* won't happen, but anyway: */ myerror(WARNING|NOERRNO, "setting environment variable %s=%s failed, as this system has neither setenv() nor putenv()", name, value); #endif if (return_value != 0) myerror(WARNING|USE_ERRNO, "setting environment variable %s=%s failed%s", name, value, (errno ? "" : " (insufficient environment space?)")); /* will setenv(...) = -1 set errno? */ } void set_ulimit(int resource, long value) { #ifdef HAVE_SETRLIMIT struct rlimit limit; int result; limit.rlim_cur = value; result = setrlimit(resource, &limit); DPRINTF4(DEBUG_ALL, "setrlim() used to set resource #%d to value %ld, result = %d (%s)", resource, value, result, (result == 0 ? "success" : "failure")); #endif } int open_unique_tempfile(const char *suffix, char **tmpfile_name) { char **tmpdirs = list4(getenv("TMPDIR"), getenv("TMP"), getenv("TEMP"), "/tmp"); char *tmpdir = first_of(tmpdirs); int tmpfile_fd; if (!suffix) suffix = ""; *tmpfile_name = mymalloc(MAXPATHLEN+1); #ifdef HAVE_MKSTEMPS snprintf4(*tmpfile_name, MAXPATHLEN, "%s/%s_%s_XXXXXX%s", tmpdir, program_name, command_name, suffix); tmpfile_fd = mkstemps(*tmpfile_name, strlen(suffix)); /* this will write into *tmpfile_name */ #else { static int tmpfile_counter = 0; snprintf6(*tmpfile_name, MAXPATHLEN, "%s/%s_%s_%d_%d%s", tmpdir, program_name, command_name, command_pid, tmpfile_counter++, suffix); tmpfile_fd = open(*tmpfile_name, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); } #endif if (tmpfile_fd < 0) myerror(FATAL|USE_ERRNO, "could not create readable/writable temporary file %s", tmpfile_name); free(tmpdirs); return tmpfile_fd; } static char* markup(const char* colour_name, const char*str) { char *result, *colour_code; if (colour_name && isatty(STDOUT_FILENO) && (ansi_colour_aware || term_has_colours || colour_the_prompt)) { colour_code = add3strings("\033[", colour_name_to_ansi_code(colour_name), "m"); result = add3strings(colour_code, str,"\033[0m"); free(colour_code); } else { result = mysavestring(str); } return result; } /* print error or warning message. There are two error flags, defined in rlwrap.h FATAL: exit(EXIT_FAILURE) after printing the message) USE_ERRNO: print perror(errno) after the message */ void myerror(int error_flags, const char *message_format, ...) { int saved_errno = errno; char contents[BUFFSIZE]; int is_warning = !(error_flags & FATAL); char *warning_or_error = is_warning ? "warning: " : "error: "; char *coloured_warning_or_error = markup(is_warning? "Magenta" : "Red", warning_or_error); char *message_for_debug_log; static int warnings_given = 0; char *message = add2strings(program_name, ": "); va_list ap; va_start(ap, message_format); vsnprintf(contents, sizeof(contents) - 1, message_format, ap); va_end(ap); message = append_and_free_old(message, coloured_warning_or_error); free(coloured_warning_or_error); message = append_and_free_old(message, contents); if ((error_flags & USE_ERRNO) && saved_errno) { message = append_and_free_old(message, ": "); message = append_and_free_old(message, strerror(saved_errno)); } message = append_and_free_old(message,"\n"); fflush(stdout); message_for_debug_log = search_and_replace("\n", "; ", contents, 0, NULL, NULL); DPRINTF2(DEBUG_ALL, "%s %s", warning_or_error, message_for_debug_log); free(message_for_debug_log); if (! (is_warning && nowarn)) fputs(message, stderr); /* @@@ error reporting (still) uses buffered I/O */ if (is_warning && !warnings_given++ && !nowarn) fputs("warnings can be silenced by the --no-warnings (-n) option\n", stderr); fflush(stderr); free(message); errno = saved_errno; if (error_flags & FATAL) { #ifdef DUMP_CORE_ON_ERROR KA_BOOM; #endif if (!i_am_child) cleanup_rlwrap_and_exit(EXIT_FAILURE); else /* child: die and let parent clean up */ exit(EXIT_FAILURE); } } /* fopen with error handling. Will close and re-open if *fp != NULL; */ void my_fopen(FILE **pfp, const char *path, const char *mode, const char *description ) { char *what = "open"; char *how = mode[0] == 'w' ? "writing" : mode[0] == 'a' ? "appending" : "reading"; if (*pfp) { fclose(*pfp); what = "re-open"; } *pfp = fopen(path, mode); if (!*pfp) myerror(FATAL|USE_ERRNO, "Cannot %s %s %s for %s", what, description, path, how); } void open_logfile(const char *filename) { time_t now; my_fopen(&log_fp, filename, "a", "logfile"); now = time(NULL); fprintf(log_fp, "\n\n[rlwrap] %s\n", ctime(&now)); } void write_logfile(const char *str) { if (log_fp) fputs(str, log_fp); } size_t filesize(const char *filename) { struct stat buf; if (stat(filename, &buf)) myerror(FATAL|USE_ERRNO, "couldn't stat file %s", filename); return (size_t) buf.st_size; } void close_logfile(void) { if (log_fp) fclose(log_fp); } void close_open_files_without_writing_buffers(void) /* called from child just before exec(command) */ { if(log_fp) close(fileno(log_fp)); /* don't flush buffers to avoid avoid double double output output */ if (debug) close(fileno(debug_fp)); } void timestamp(char *buf, int size) { struct timeval now; static struct timeval firsttime; /* remember when first called */ static int never_called = 1; long diff_usec; float diff_sec; gettimeofday(&now, NULL); if (never_called) { firsttime = now; never_called = 0; } diff_usec = 1000000 * (now.tv_sec -firsttime.tv_sec) + (now.tv_usec - firsttime.tv_usec); diff_sec = diff_usec / 1000000.0; snprintf1(buf, size, "%f ", diff_sec); } /* Dan Bernsteins djb2, hashing n strings in one go */ unsigned long hash_multiple(int n, ...) { unsigned long hash = 5381; int i, c; char *str; va_list ap; va_start(ap, n); for(i = 0; i < n; i++) { str = va_arg(ap, char *); assert(str != NULL); while ((c = (unsigned char) *str++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ } va_end(ap); return hash; } int killed_by(int status) { #ifdef WTERMSIG if (WIFSIGNALED(status)) return WTERMSIG(status); #endif return 0; } /* get_new_slave_cwd(&cwd) tries to find the rlwrapped command's current working directory. If this differs from cwd, free(*cwd) and set *cwd to (a copy of) the new working directory. Return value: 0, or 1 if rlwrap needs to do a chdir(cwd) to again have the same working dir as the rlwrapped command. */ static int get_new_slave_cwd(char **cwd) { char *possibly_new_cwd = NULL; int return_value = 0; #if defined(HAVE_PROC_PID_CWD) /* Linux, Solaris, and FreeBSD with the proc filesystem */ static char *proc_pid_cwd = NULL; char readlink_buffer[MAXPATHLEN+1]; if (!proc_pid_cwd) proc_pid_cwd = add3strings(PROC_MOUNTPOINT, "/", add2strings(as_string(command_pid), "/cwd")); # ifdef HAVE_READLINK if (readlink(proc_pid_cwd, readlink_buffer, MAXPATHLEN) > 0) possibly_new_cwd = mysavestring(readlink_buffer); # else /* readlink unavailable, use /proc/nnn/cwd ... */ possibly_new_cwd = mysavestring(proc_pid_cwd); return_value = 1; /* always do a chdir(): the symlink /proc/nnn/cwd may point somewhere else now... */ # endif /* HAVE_READLINK */ #elif HAVE_DECL_PROC_PIDVNODEPATHINFO /* OS X */ struct proc_vnodepathinfo vpi; if (proc_pidinfo(command_pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)) <= 0) DPRINTF2(DEBUG_COMPLETION, "proc_pidinfo(%d, .. failed: %s", command_pid, strerror(errno)); possibly_new_cwd = mysavestring(vpi.pvi_cdir.vip_path); #elif HAVE_FREEBSD_LIBPROCSTAT /* FreeBSD without the proc filesystem */ unsigned int count = 0; struct procstat *procstat = procstat_open_sysctl(); struct kinfo_proc *kip = procstat_getprocs(procstat, KERN_PROC_PID, command_pid, &count); struct filestat_list *head; struct filestat *fst; if (count == 1) { head = procstat_getfiles(procstat, kip, 0); STAILQ_FOREACH(fst, head, next) if (fst->fs_uflags & PS_FST_UFLAG_CDIR) { possibly_new_cwd = mysavestring(fst->fs_path); break; } procstat_freefiles(procstat, head); procstat_freeprocs(procstat, kip); procstat_close(procstat); } #else /* no HAVE_PROC_PID_CWD, HAVE_DECL_PROC_PIDVNODEPATHINFO or HAVE_FREEBSD_LIBPROCSTAT: do nothing */ #endif if (possibly_new_cwd && strcmp(*cwd, possibly_new_cwd)) { return_value = 1; free(*cwd); *cwd = mysavestring(possibly_new_cwd); } free(possibly_new_cwd); return return_value; } /* change_working_directory() tries to change rlwrap's working directory to the rlwrapped command's current working directory */ void change_working_directory(void) { static char *slaves_working_directory = NULL; if (!slaves_working_directory) slaves_working_directory = mysavestring("."); if(command_pid > 0 && get_new_slave_cwd(&slaves_working_directory)) { if (chdir(slaves_working_directory)) { DPRINTF2(DEBUG_COMPLETION, "chdir(%s) failed: %s", slaves_working_directory, strerror(errno)); } else { DPRINTF1(DEBUG_COMPLETION, "chdir(%s): success", slaves_working_directory); } } } #undef isset #define isset(flag) ((flag) ? "set" : "unset") /* print info about terminal settings */ void log_terminal_settings(struct termios *terminal_settings) { if (!terminal_settings) return; DPRINTF3(DEBUG_TERMIO, "terminal settings: clflag.ISIG: %s, cc_c[VINTR]=%d, cc_c[VEOF]=%d", isset(terminal_settings->c_lflag | ISIG), terminal_settings->c_cc[VINTR], terminal_settings->c_cc[VEOF]); } void log_fd_info(int fd) { struct termios terminal_settings; if (isatty(fd)) { if (tcgetattr(fd, &terminal_settings) < 0) { DPRINTF1(DEBUG_TERMIO, "could not get terminal settings for fd %d", fd); } else { DPRINTF1(DEBUG_TERMIO, "terminal settings for fd %d:", fd); } log_terminal_settings(&terminal_settings); } } /* some last-minute checks before we can start */ void last_minute_checks(void) { /* flag unhealthy option combinations */ if (multiline_separator && filter_command) myerror(WARNING|NOERRNO, "Filters don't work very well with multi-line rlwrap!"); } /* sleep a little (for debugging cursor movement with the SHOWCURSOR macro) */ void mymicrosleep(int msec) { int sec = msec / 1000; struct timeval timeout; msec -= (1000*sec); timeout.tv_sec = sec; timeout.tv_usec = 1000 * msec; select (0,NULL,NULL,NULL,&timeout); } /* print info about option, considering whether we HAVE_GETOPT_LONG and whether GETOPT_GROKS_OPTIONAL_ARGS */ static void print_option(char shortopt, char *longopt, char*argument, int optional, char *comment) { int long_opts, optional_args; char *format; char *maybe_optional = ""; char *longoptional = ""; #ifdef HAVE_GETOPT_LONG long_opts = TRUE; #else long_opts = FALSE; #endif #ifdef GETOPT_GROKS_OPTIONAL_ARGS optional_args = TRUE; #else optional_args = FALSE; #endif if (argument) { maybe_optional = (optional_args && optional ? add3strings("[", argument,"]") : add3strings(" <", argument,">")); longoptional = (optional ? add3strings("[=", argument,"]") : add3strings("=<", argument, ">")); } /* if we cannot use long options, use the long option as a reminder (no warnings) instead of "--no-warnings" */ if (!long_opts) longopt = search_and_replace("-"," ", longopt, 0, NULL,NULL); format = add2strings (" -%c%-24.24s", (long_opts ? " --%s%s" : "(%s)")); fprintf(stderr, format, shortopt, maybe_optional, longopt, longoptional); if (comment) fprintf(stderr, " %s", comment); fprintf(stderr, "\n"); /* don't free allocated strings: we'll exit() soon */ } static void print_debug_flag(int flag, char *explanation) { fprintf(stderr, " %4d %s\n", flag, explanation); } void usage(int status) { fprintf(stderr, "Usage: %s [options] command ...\n" "\n" "Options:\n", program_name); print_option('a', "always-readline", "password prompt", TRUE, NULL); print_option('A', "ansi-colour-aware", NULL, FALSE, NULL); print_option('b', "break-chars", "chars", FALSE, NULL); print_option('c', "complete-filenames", NULL, FALSE, NULL); print_option('C', "command-name", "name|N", FALSE, NULL); print_option('D', "history-no-dupes", "0|1|2", FALSE, NULL); print_option('e', "extra-char-after-completion", "char|''", FALSE, NULL); print_option('f', "file", "completion list", FALSE,NULL); print_option('g', "forget-matching", "regexp", FALSE,NULL); print_option('h', "help", NULL, FALSE, NULL); print_option('H', "history-filename", "file", FALSE, NULL); print_option('i', "case-insensitive", NULL, FALSE, NULL); print_option('I', "pass-sigint-as-sigterm", NULL, FALSE, NULL); print_option('l', "logfile", "file", FALSE, NULL); print_option('m', "multi-line", "newline substitute", TRUE, NULL); print_option('M', "multi-line-ext", ".ext", FALSE, NULL); print_option('n', "no-warnings", NULL, FALSE, NULL); print_option('N', "no-children", NULL, FALSE, NULL); print_option('o', "one-shot", NULL, FALSE, NULL); print_option('O', "only-cook", "regexp", FALSE, NULL); print_option('p', "prompt-colour", "colour", TRUE, NULL); print_option('P', "pre-given","input", FALSE, NULL); print_option('q', "quote-characters", "chars", FALSE, NULL); print_option('r', "remember", NULL, FALSE, NULL); print_option('R', "renice", NULL, FALSE, NULL); print_option('s', "histsize", "N", FALSE,"(negative: readonly)"); print_option('S', "substitute-prompt", "prompt", FALSE, NULL); print_option('t', "set-term-name", "name", FALSE, NULL); print_option('U', "mirror-arguments", NULL, FALSE, NULL); print_option('v', "version", NULL, FALSE, NULL); print_option('w', "wait-before-prompt", "N", FALSE, "(msec, <0 : patient mode)"); print_option('W', "polling", NULL, FALSE, NULL); print_option('z', "filter", "filter command", FALSE, "('rlwrap -z listing' writes a list of installed filters)"); #ifdef DEBUG fprintf(stderr, "\n"); print_option('T', "test-terminal", NULL, FALSE, NULL); print_option('d', "debug", "mask", TRUE, add3strings("(output sent to ", DEBUG_FILENAME,")")); fprintf(stderr, " \n" "The -d or --debug option *must* come first\n" "The debugging mask is a bitmask obtained by adding:\n"); print_debug_flag (DEBUG_TERMIO, "to debug termio,"); print_debug_flag (DEBUG_SIGNALS, "signal handling,"); print_debug_flag (DEBUG_READLINE, "readline,"); print_debug_flag (DEBUG_MEMORY_MANAGEMENT, "memory management,"); print_debug_flag (DEBUG_FILTERING, "filtering,"); print_debug_flag (DEBUG_COMPLETION, "and completion."); print_debug_flag (DEBUG_AD_HOC, "to see your own DEBUG_AD_HOC results"); print_debug_flag (DEBUG_WITH_TIMESTAMPS, "to add (relative) timestamps,"); print_debug_flag (FORCE_HOMEGROWN_REDISPLAY, "to force the use of my_homegrown_redisplay(),"); print_debug_flag (DEBUG_LONG_STRINGS, "to not limit the length of strings in debug log (sloooow!)"); print_debug_flag (DEBUG_RACES, "add random delays to expose race conditions."); fprintf(stderr, " default debug mask = %d (debug termio, signals and readline handling)\n" " use the shell construct $[ ] to calculate the mask, e.g. -d$[%d+%d+%d]\n", DEBUG_DEFAULT, DEBUG_DEFAULT, DEBUG_WITH_TIMESTAMPS, DEBUG_RACES); #endif fprintf(stderr, "\n" "bug reports, suggestions, updates:\n" "https://github.com/hanslub42/rlwrap\n"); exit(status); } #ifdef DEBUG #undef mymalloc #endif /* malloc with simplistic error handling: just bail out when out of memory */ void * mymalloc(size_t size) { void *ptr; ptr = malloc(size); if (ptr == NULL) { /* don't call myerror(), as this calls mymalloc() again */ #ifdef DUMP_CORE_ON_ERROR KA_BOOM; #endif fprintf(stderr, "Out of memory: tried in vain to allocate %d bytes\n", (int) size); exit(EXIT_FAILURE); } return ptr; } /* free() with variable number of arguments. To show where the argumets end, the last argument should be special, (and never a legitimate pointer) but we cannot use NULL (as the to-be-freed pointers may legitimately be NULL) We now use FMEND (#defined as ((void *) -1) in rlwrap.h, but @@@ is this fool-proof? */ void free_multiple(void *ptr, ...) { void *p; va_list ap; free(ptr); va_start(ap, ptr); while((p = va_arg(ap, void *)) != FMEND) { free(p); } va_end(ap); } void mysetsid(void) { # ifdef HAVE_SETSID /* c'mon, this is POSIX! */ pid_t ret = setsid(); DPRINTF2(DEBUG_TERMIO, "setsid() returned %d %s", (int)ret, ERRMSG(ret < 0)); # endif } /* mirror_args(): look up command's command line and copy it to our own important for commands that re-write their command lines e.g. to hide passwords. */ static char ** rlwrap_command_argv; /* The slice of rlwrap's argv after all rlwrap options */ static char *argv_buffer; static int argv_len; static char *stored_cmdline_filename; void mirror_args_init(char**argv) { #ifdef ENABLE_MIRROR_ARGS int i; rlwrap_command_argv = argv; stored_cmdline_filename = mymalloc(MAXPATHLEN); *stored_cmdline_filename = '\0'; for (i = 0; argv[i]; i++) { argv_len += strlen(argv[i]) + 1; } argv_buffer = mymalloc(argv_len * sizeof(char) + 1); #else stored_cmdline_filename = NULL; myerror(WARNING|NOERRNO, "On this system, the -U (--mirror-arguments) option doesn't work"); #endif } /* C standard: "The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination. This doesn't guarantee that those changed values will be visible to e.g. the ps (1) command */ void mirror_args(pid_t command_pid) { int cmdline_fd; long cmdline_length; static int been_warned = 0; if (!stored_cmdline_filename || !command_pid) /* uninitialized, unborn or dead command */ return; if (!*stored_cmdline_filename) snprintf2(stored_cmdline_filename, MAXPATHLEN , "%s/%d/cmdline", PROC_MOUNTPOINT, command_pid); if((cmdline_fd = open(stored_cmdline_filename, O_RDONLY)) < 1) { stored_cmdline_filename = NULL; if (been_warned++ == 0) myerror(WARNING|USE_ERRNO, "cannot mirror command's command line, as %s is unreadable", stored_cmdline_filename); return; } cmdline_length = read(cmdline_fd, argv_buffer,argv_len); /* argv_buffer[cmdline_length] = '\0'; */ DPRINTF2(DEBUG_TERMIO,"read %d bytes from %s", (int) cmdline_length, stored_cmdline_filename); if (memcmp(*rlwrap_command_argv, argv_buffer, cmdline_length)) { char *rlwrap_argstr = mem2str(*rlwrap_command_argv, cmdline_length); char *command_argstr = mem2str(argv_buffer, cmdline_length); DPRINTF2(DEBUG_TERMIO, "discrepancy: rlwarp_args: %s, command_args %s", rlwrap_argstr, command_argstr); free(rlwrap_argstr); free(command_argstr); memcpy(*rlwrap_command_argv, argv_buffer, cmdline_length); } } rlwrap-0.46.1/test/000077500000000000000000000000001433170252700140745ustar00rootroot00000000000000rlwrap-0.46.1/test/testclient000077500000000000000000000246101433170252700162030ustar00rootroot00000000000000#!/usr/bin/env perl # You can use this test program to grill rlwrap - I wrote it in perl because I'm lazy # TODO: use Term::ReadKey if available use utf8; # so literals and identifiers can be in UTF-8 eval "require 5.012_001"; # or later to get "unicode_strings" feature $@ and warn "Your perl version is rather antique; expect some problems.\n"; use strict; # quote strings, declare variables use warnings; # on by default use warnings qw(FATAL utf8); # fatalize encoding glitches use open qw(:std :utf8); # undeclared streams in UTF-8 use charnames qw(:full :short); # unneeded in v5.16 use Getopt::Std; eval "use Term::ReadKey"; my $have_ReadKey = not $@; my $interrupt_char = "^C"; my $opt_d; getopts('d:'); my $debug_file = $opt_d; if ($debug_file) { open DEBUG, ">$debug_file" or die "Couldn't not open $debug_file: $!\n"; } use vars qw($prompting $prompt); $|=1; use POSIX qw(:termios_h :signal_h setsid); my ($term, $oterm, $fd_stdin, $errorcode, $sigset_blocked); my $verbose = 1; $prompt = (my $original_prompt = "pid %p, type :h for help > "); sub lprint($); init(); lprint "\n\n"; help(); while(1) { local $prompting = 1; prompt(); $_ = <>; defined $_ or exit 0; /^:a(\s+(.*))/ and change_argv($2); /^:b/ and run_bash(); /^:B/ and progress_bar(); /^:c\b/ and change_prompt(); /^:C/ and countdown(); /^:cd(\s+(.*))?/ and chdir($2 ? $2 : $ENV{HOME}); /^:d/ and die_eloquently(); (/^:e\s*([+-]?\d+)?/) and exit ($1 || 0); /^:f/ and do_fork(); /^:h/ and help(); /^:H/ and hanky_panky(); /^:i/ and toggle_interrupt_char(); /^:l/ and long_and_difficult_string(); /^:p/ and pass(); /^:P/ and print_chunky(scalar `cat $0`); /^:r/ and reset_prompt(); /^:R/ and raw(); /^:s/ and segfault(); /^:t/ and trickle(); /^:S/ and trickle2(); /^:u/ and utf8(); /^:T/ and test_controlling_terminal(); /^:v/ and toggle_verbosity(); /^!!(.*)/ and perl($1); /^!([^!].*)/ and shell($1); /^:w/ and ridiculously_wide_prompt(); /^(:|!)/ or show_input($_); } ########################### subs ################################################ sub init { my (@signals_to_block); $fd_stdin = fileno(STDIN); # system ("reset"); $sigset_blocked = POSIX::SigSet->new; $sigset_blocked -> fillset() or die "Could not fill \$sigset_blocked: $!\n"; $term = POSIX::Termios->new(); $term->getattr($fd_stdin); $oterm = $term->getlflag(); install_signal_handlers(); } sub help { print < run in shell !! evaluate Perl expression :a change commandline :b run ./bash or bash with current prompt (to compare readline behaviour with weird prompts) :B show a progress bar using Unicode block elements :c change prompt :cd [] chdir to (or \$HOME) :C countdown in prompt :d die eloquently :e [N] exit (with error code N) :f fork and let child take over (parent waits) :h help :H hanky-panky with backspace and carriage return :i toggle interrupt char between CTRL C and CTRL G :l print a long and difficult text :p ask "passsword" :P print chunky :r reset prompt :R raw mode (char-at-a-time) :s and sefgault :S and trickle slowly :t trickle output (10 chars) :T test controlling terminal :u try some utf-8 :v toggle verbosity (e.g. when receiving a signal) :w ridiculously wide prompt EOF } sub prompt { my $sprompt = shift || $prompt; return unless $prompt; my $pid = $$; $sprompt =~ s/%p/$pid/g; $sprompt =~ s/%t/`tty`/eg; chomp(my $pwd = `pwd`); $pwd =~ s/^$ENV{HOME}/~/; $sprompt =~ s/%d/$pwd/eg; #$sprompt =~ s/\n//g; lprint $sprompt; } sub change_argv { my ($cmdline) = @_; $0=$cmdline; } sub run_bash { my $bashprompt = $prompt; $bashprompt =~ s/\e(\[[\d;]*m)/\\[\\e$1\\]/g; my $rcfile = "/tmp/bashprompt.$$"; open OUT, ">$rcfile"; print OUT "PS1=\"$bashprompt\"\n"; close OUT; system "bash --rcfile $rcfile"; unlink $rcfile; } sub progress_bar { for (my $i=0; $i < 200; $i++) { #print "\x{1F642}"; # smiley - example of wide (multi-cell) character that will confuse rlwrap when in a prompt; print "\x{2588}"; # "\N{FULL BLOCK}"; select(undef, undef, undef, 0.02); } print "\n"; } sub pass { noecho(); prompt (local $prompt = "Password: "); my $input = <>; show_input($input); cooked(); } sub do_fork { my $pid; return unless ($pid = fork); waitpid($pid,0); exit 0; } sub shell { local $prompting; my($command) = @_; system($command); cooked(); } sub perl { local $prompting; my($exp) = @_; my $result = eval $exp; if ($@) { print "error: $@\n"; } else { print "OK, result = $result\n"; } cooked(); } sub trickle { local $prompting; my $i; foreach my $c (split " ", ("trickle, trackle, trockle, " x 4) . ">") { print "$c "; print "\n" if ++$i % 2 == 0; sleep 1; } my $input = <>; show_input($input); } sub trickle2 { local $prompting; my $i; foreach my $c (split //, "trickle, trackle > ") { print $c; sleep 1; } my $input = <>; show_input($input); } sub hanky_panky { for (my $i = 99; $i > 95 ; $i--) { print "$i bottles of beer on the wall, $i bottles of beef"; sleep 1; print "\br"; sleep 1; print "\r"; } print "\nYawn!\n" } sub toggle_interrupt_char { $interrupt_char = $interrupt_char eq "^C" ? "^G" : "^C"; print "interrupt char is now '$interrupt_char'\n"; system "stty intr '$interrupt_char'"; } sub countdown { local $prompting; for (my $i = 9; $i >= 0; $i--) { print "\r countdown: $i >"; sleep 1; } my $input = <>; show_input($input); } sub test_controlling_terminal { if (not open DEVTTY, ">/dev/tty") { print "I could not open /dev/tty, so there's no controlling terminal ($!)\n"; } else { print DEVTTY "found controlling terminal: /dev/tty speaking here!\n"; } } sub show_input { my ($input) = @_; defined $input or exit; $input =~ s/\r?\n$//; my $comment = ""; length $input or $comment = "(nothing)"; lprint "\nYou typed '$input' $comment\n"; } sub change_prompt { my $input; my ($termwidth) = eval "GetTerminalSize"; { local $prompt = "New prompt here > "; my $redblah = red("blah"); lprint "\%p -> pid, \%t -> tty, %d -> pwd, red{blah} -> $redblah, \\n -newline, 4*x -> xxxx" .($have_ReadKey ? ", %w -> termwidth\n" : "\n"); prompt(); $input = <>; $input =~ s/\r?\n$//; $input =~ s/\\n/\n/g; $input =~ s/\%w/$termwidth/ge; $input =~ s/\((\d.*?)\)/eval($1)/ge; $input =~ s/(\d+)\*([^ {}]+)/$2 x $1/ge; $input =~ s/red\{(.*?)\}/red($1)/eg; } $prompt = $input; } sub segfault { kill 'SEGV', $$; } sub red { my ($text) = @_; return colour($text,31); } sub blue { my ($text) = @_; return colour($text,34); } sub colour { my ($text, $colourcode) = @_; $text = "\e[1;${colourcode}m$text\e[0m" if $ENV{TERM} =~ /ansi|xterm|rxvt|cygwin|linux|screen|tmux/; return $text; } sub long_and_difficult_string { my $text = (red("hot") . " and ". blue("cold"). ", ") x 3000; print "$text\n"; } sub reset_prompt { $prompt = $original_prompt; } sub ridiculously_wide_prompt { $prompt = "Supercalifragilistic, " x 10; # 220 $prompt .= "Expialidocious > "; # + 17 = 237 } sub utf8 { $prompt = "Íslenska: "; printf "Ég get etið gler án þess að meiða mig\n"; } sub raw { binmode(STDIN, ":raw"); cbreak(); my $key; prompt (local $prompt = "Press Any Key >"); sysread(STDIN, $key, 1); my $c = ord $key; cooked(); lprint "\nYou typed a '$key' (ASCII $c)\n"; binmode(STDIN, ":utf8"); } sub toggle_verbosity { $verbose = not $verbose; print ($verbose ? "verbose now\n" : "not verbose now\n"); } sub die_eloquently { my $last_words = <setlflag($oterm & ~(ECHO|ECHOK|ICANON)); $term->setcc(VTIME, 1); $term->setattr($fd_stdin, TCSANOW); } sub cooked { return unless defined $fd_stdin; $term->setlflag($oterm); $term->setcc(VTIME, 0); $term->setattr($fd_stdin, TCSANOW); } sub noecho { $term->setlflag($oterm & ~(ECHO)); $term->setcc(VTIME, 0); $term->setattr($fd_stdin, TCSANOW); } sub END { local $?; # because POSIX::Termios functions call system() and may thus reset $? cooked(); lprint "\n"; } sub lprint($) { my ($text) = @_; eval '"a" =~ /[[:^print:]]/'; # check whether this perl knows (negated) POSIX character class syntax (not before perl 5.6.0?) if (! $@) { $text =~ s/([^[:print:]\s\e])/sprintf("\\x%02x", (unpack "c", $1))/eg; # show unprintable characters in hex; } if ($debug_file) { syswrite DEBUG, $text; } print $text; } # Local variables: # mode:cperl # End: rlwrap-0.46.1/test/testit000077500000000000000000000015411433170252700153370ustar00rootroot00000000000000#!/bin/sh # This script should be called from the rlwrap-x.xx top directory (normally by 'make check') # if perl -e 1 # then # src/rlwrap -dr -f README perl test/testclient # exit $? # else # echo "Perl is needed for this test" # exit 77 # fi echo echo echo '*****************************************************************************' echo '* *' echo '* Testing rlwrap from within "make" causes problems with signal handling *' echo '* therefore, you should do it by hand: *' echo '* $ src/rlwrap perl test/testclient *' echo '* *' echo '*****************************************************************************' echo echo