rl-fgets.c revision 256281
11558Srgrimes/* 21558SrgrimesDate: Tue, 16 Mar 2004 19:38:40 -0800 31558SrgrimesFrom: Harold Levy <Harold.Levy@synopsys.com> 41558SrgrimesSubject: fgets(stdin) --> readline() redirector 51558SrgrimesTo: chet@po.cwru.edu 61558Srgrimes 71558SrgrimesHi Chet, 81558Srgrimes 91558SrgrimesHere is something you may find useful enough to include in the readline 101558Srgrimesdistribution. It is a shared library that redirects calls to fgets(stdin) 111558Srgrimesto readline() via LD_PRELOAD, and it supports a custom prompt and list of 121558Srgrimescommand names. Many people have asked me for this file, so I thought I'd 131558Srgrimespass it your way in hope of just including it with readline to begin with. 141558Srgrimes 151558SrgrimesBest Regards, 161558Srgrimes 171558Srgrimes-Harold 181558Srgrimes*/ 191558Srgrimes 201558Srgrimes/****************************************************************************** 211558Srgrimes******************************************************************************* 221558Srgrimes 231558Srgrimes FILE NAME: fgets.c TARGET: libfgets.so 241558Srgrimes AUTHOR: Harold Levy VERSION: 1.0 251558Srgrimes hlevy@synopsys.com 261558Srgrimes 271558Srgrimes ABSTRACT: Customize fgets() behavior via LD_PRELOAD in the following ways: 281558Srgrimes 291558Srgrimes -- If fgets(stdin) is called, redirect to GNU readline() to obtain 301558Srgrimes command-line editing, file-name completion, history, etc. 311558Srgrimes 321558Srgrimes -- A list of commands for command-name completion can be configured by 331558Srgrimes setting the environment-variable FGETS_COMMAND_FILE to a file containing 341558Srgrimes the list of commands to be used. 3541477Sjulian 3623675Speter -- Command-line editing with readline() works best when the prompt string 3741477Sjulian is known; you can set this with the FGETS_PROMPT environment variable. 3841477Sjulian 3950476Speter -- There special strings that libfgets will interpret as internal commands: 401558Srgrimes 411558Srgrimes _fgets_reset_ reset the command list 421558Srgrimes 4323675Speter _fgets_dump_ dump status 441558Srgrimes 4541474Sjulian _fgets_debug_ toggle debug messages 4641474Sjulian 4723675Speter HOW TO BUILD: Here are examples of how to build libfgets.so on various 4841474Sjulian platforms; you will have to add -I and -L flags to configure access to 4941474Sjulian the readline header and library files. 501558Srgrimes 511558Srgrimes (32-bit builds with gcc) 527585Sbde AIX: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lreadline -ltermcap 531558Srgrimes HP-UX: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldld -lreadline 541558Srgrimes Linux: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lreadline 5541474Sjulian SunOS: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lgen -lreadline 5641474Sjulian 571558Srgrimes (64-bit builds without gcc) 5841474Sjulian SunOS: SUNWspro/bin/cc -D_LARGEFILE64_SOURCE=1 -xtarget=ultra -xarch=v9 \ 5941474Sjulian -KPIC fgets.c -Bdynamic -lc -ldl -lgen -ltermcap -lreadline 601558Srgrimes 6141474Sjulian HOW TO USE: Different operating systems have different levels of support 6270050Siedowse for the LD_PRELOAD concept. The generic method for 32-bit platforms is to 6370050Siedowse put libtermcap.so, libfgets.so, and libreadline.so (with absolute paths) 6470050Siedowse in the LD_PRELOAD environment variable, and to put their parent directories 6570050Siedowse in the LD_LIBRARY_PATH environment variable. Unfortunately there is no 6670050Siedowse generic method for 64-bit platforms; e.g. for 64-bit SunOS, you would have 6770050Siedowse to build both 32-bit and 64-bit libfgets and libreadline libraries, and 6841474Sjulian use the LD_FLAGS_32 and LD_FLAGS_64 environment variables with preload and 6941474Sjulian library_path configurations (a mix of 32-bit and 64-bit calls are made under 701558Srgrimes 64-bit SunOS). 7141474Sjulian 721558Srgrimes EXAMPLE WRAPPER: Here is an example shell script wrapper around the 7341474Sjulian program "foo" that uses fgets() for command-line input: 741558Srgrimes 7541474Sjulian #!/bin/csh 7641474Sjulian #### replace this with the libtermcap.so directory: 7741474Sjulian set dir1 = "/usr/lib" 7841474Sjulian #### replace this with the libfgets.so directory: 7941474Sjulian set dir2 = "/usr/fgets" 8041474Sjulian #### replace this with the libreadline.so directory: 8174556Smckusick set dir3 = "/usr/local/lib" 8274556Smckusick set lib1 = "${dir1}/libtermcap.so" 8341474Sjulian set lib2 = "${dir2}/libfgets.so" 8441474Sjulian set lib3 = "${dir3}/libreadline.so" 8541474Sjulian if ( "${?LD_PRELOAD}" ) then 8641474Sjulian setenv LD_PRELOAD "${lib1}:${lib2}:${lib3}:${LD_PRELOAD}" 871558Srgrimes else 881558Srgrimes setenv LD_PRELOAD "${lib1}:${lib2}:${lib3}" 891558Srgrimes endif 9041474Sjulian if ( "${?LD_LIBRARY_PATH}" ) then 9141474Sjulian setenv LD_LIBRARY_PATH "${dir1}:${dir2}:${dir3}:${LD_LIBRARY_PATH}" 921558Srgrimes else 931558Srgrimes setenv LD_LIBRARY_PATH "${dir1}:${dir2}:${dir3}" 941558Srgrimes endif 9541474Sjulian setenv FGETS_COMMAND_FILE "${dir2}/foo.commands" 9641474Sjulian setenv FGETS_PROMPT "foo> " 9741474Sjulian exec "foo" $* 9841474Sjulian 9941474Sjulian Copyright (C)�2003-2004 Harold Levy. 10041474Sjulian 10141474Sjulian This code links to the GNU readline library, and as such is bound by the 10241474Sjulian terms of the GNU General Public License as published by the Free Software 10341474Sjulian Foundation, either version 2 or (at your option) any later version. 10441474Sjulian 10541474Sjulian The GNU General Public License is often shipped with GNU software, and is 10641474Sjulian generally kept in a file called COPYING or LICENSE. If you do not have a 10741474Sjulian copy of the license, write to the Free Software Foundation, 59 Temple Place, 10841474Sjulian Suite 330, Boston, MA 02111 USA. 10941474Sjulian 11041474Sjulian This program is distributed in the hope that it will be useful, but WITHOUT 11141474Sjulian ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11241474Sjulian FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 11341474Sjulian details. 11441474Sjulian 11541474Sjulian******************************************************************************* 11641474Sjulian******************************************************************************/ 11741474Sjulian 11841474Sjulian 11941474Sjulian 12041474Sjulian#include <dlfcn.h> 12141474Sjulian#include <stdio.h> 12241474Sjulian#include <strings.h> 1231558Srgrimes#include <stdlib.h> 1241558Srgrimes#include <unistd.h> 1251558Srgrimes 126#include <readline/readline.h> 127#include <readline/history.h> 128 129 130 131/* for dynamically connecting to the native fgets() */ 132#if defined(RTLD_NEXT) 133#define REAL_LIBC RTLD_NEXT 134#else 135#define REAL_LIBC ((void *) -1L) 136#endif 137typedef char * ( * fgets_t ) ( char * s, int n, FILE * stream ) ; 138 139 140 141/* private data */ 142/* -- writeable data is stored in the shared library's data segment 143 -- every process that uses the shared library gets a private memory copy of 144 its entire data segment 145 -- static data in the shared library is not copied to the application 146 -- only read-only (i.e. 'const') data is stored in the shared library's 147 text segment 148*/ 149static char ** my_fgets_names = NULL ; 150static int my_fgets_number_of_names = 0 ; 151static int my_fgets_debug_flag = 0 ; 152 153 154 155/* invoked with _fgets_reset_ */ 156static void 157my_fgets_reset ( 158 void 159) { 160 if ( my_fgets_names && (my_fgets_number_of_names > 0) ) { 161 int i ; 162 if ( my_fgets_debug_flag ) { 163 printf ( "libfgets: removing command list\n" ) ; 164 } 165 for ( i = 0 ; i < my_fgets_number_of_names ; i ++ ) { 166 if ( my_fgets_names[i] ) free ( my_fgets_names[i] ) ; 167 } 168 free ( my_fgets_names ) ; 169 } 170 my_fgets_names = NULL ; 171 my_fgets_number_of_names = 0 ; 172} 173 174 175 176/* invoked with _fgets_dump_ */ 177static void 178my_fgets_dump ( 179 void 180) { 181 char * s ; 182 printf ( "\n" ) ; 183 s = getenv ( "FGETS_PROMPT" ) ; 184 printf ( "FGETS_PROMPT = %s\n", s ? s : "" ) ; 185 s = getenv ( "FGETS_COMMAND_FILE" ) ; 186 printf ( "FGETS_COMMAND_FILE = %s\n", s ? s : "" ) ; 187 printf ( "debug flag = %d\n", my_fgets_debug_flag ) ; 188 printf ( "#commands = %d\n", my_fgets_number_of_names ) ; 189 if ( my_fgets_debug_flag ) { 190 if ( my_fgets_names && (my_fgets_number_of_names > 0) ) { 191 int i ; 192 for ( i = 0 ; i < my_fgets_number_of_names ; i ++ ) { 193 printf ( "%s\n", my_fgets_names[i] ) ; 194 } 195 } 196 } 197 printf ( "\n" ) ; 198} 199 200 201 202/* invoked with _fgets_debug_ */ 203static void 204my_fgets_debug_toggle ( 205 void 206) { 207 my_fgets_debug_flag = my_fgets_debug_flag ? 0 : 1 ; 208 if ( my_fgets_debug_flag ) { 209 printf ( "libfgets: debug flag = %d\n", my_fgets_debug_flag ) ; 210 } 211} 212 213 214 215/* read the command list if needed, return the i-th name */ 216static char * 217my_fgets_lookup ( 218 int index 219) { 220 if ( (! my_fgets_names) || (! my_fgets_number_of_names) ) { 221 char * fname ; 222 FILE * fp ; 223 fgets_t _fgets ; 224 int i ; 225 char buf1[256], buf2[256] ; 226 fname = getenv ( "FGETS_COMMAND_FILE" ) ; 227 if ( ! fname ) { 228 if ( my_fgets_debug_flag ) { 229 printf ( "libfgets: empty or unset FGETS_COMMAND_FILE\n" ) ; 230 } 231 return NULL ; 232 } 233 fp = fopen ( fname, "r" ) ; 234 if ( ! fp ) { 235 if ( my_fgets_debug_flag ) { 236 printf ( "libfgets: cannot open '%s' for reading\n", fname ) ; 237 } 238 return NULL ; 239 } 240 _fgets = (fgets_t) dlsym ( REAL_LIBC, "fgets" ) ; 241 if ( ! _fgets ) { 242 fprintf ( stderr, 243 "libfgets: failed to dynamically link to native fgets()\n" 244 ) ; 245 return NULL ; 246 } 247 for ( i = 0 ; _fgets(buf1,255,fp) ; i ++ ) ; 248 if ( ! i ) { fclose(fp) ; return NULL ; } 249 my_fgets_names = (char**) calloc ( i, sizeof(char*) ) ; 250 rewind ( fp ) ; 251 i = 0 ; 252 while ( _fgets(buf1,255,fp) ) { 253 buf1[255] = 0 ; 254 if ( 1 == sscanf(buf1,"%s",buf2) ) { 255 my_fgets_names[i] = strdup(buf2) ; 256 i ++ ; 257 } 258 } 259 fclose ( fp ) ; 260 my_fgets_number_of_names = i ; 261 if ( my_fgets_debug_flag ) { 262 printf ( "libfgets: successfully read %d commands\n", i ) ; 263 } 264 } 265 if ( index < my_fgets_number_of_names ) { 266 return my_fgets_names[index] ; 267 } else { 268 return NULL ; 269 } 270} 271 272 273 274/* generate a list of partial name matches for readline() */ 275static char * 276my_fgets_generator ( 277 const char * text, 278 int state 279) 280{ 281 static int list_index, len ; 282 char * name ; 283 if ( ! state ) { 284 list_index = 0 ; 285 len = strlen ( text ) ; 286 } 287 while ( ( name = my_fgets_lookup(list_index) ) ) { 288 list_index ++ ; 289 if ( ! strncmp ( name, text, len ) ) { 290 return ( strdup ( name ) ) ; 291 } 292 } 293 return ( NULL ) ; 294} 295 296 297 298/* partial name completion callback for readline() */ 299static char ** 300my_fgets_completion ( 301 const char * text, 302 int start, 303 int end 304) 305{ 306 char ** matches ; 307 matches = NULL ; 308 if ( ! start ) { 309 matches = rl_completion_matches ( text, my_fgets_generator ) ; 310 } 311 return ( matches ) ; 312} 313 314 315 316/* fgets() intercept */ 317char * 318fgets ( 319 char * s, 320 int n, 321 FILE * stream 322) 323{ 324 if ( ! s ) return NULL ; 325 if ( stream == stdin ) { 326 char * prompt ; 327 char * my_fgets_line ; 328 rl_already_prompted = 1 ; 329 rl_attempted_completion_function = my_fgets_completion ; 330 rl_catch_signals = 1 ; 331 rl_catch_sigwinch = 1 ; 332 rl_set_signals () ; 333 prompt = getenv ( "FGETS_PROMPT" ) ; 334 for ( 335 my_fgets_line = 0 ; ! my_fgets_line ; my_fgets_line=readline(prompt) 336 ) ; 337 if ( ! strncmp(my_fgets_line, "_fgets_reset_", 13) ) { 338 my_fgets_reset () ; 339 free ( my_fgets_line ) ; 340 strcpy ( s, "\n" ) ; 341 return ( s ) ; 342 } 343 if ( ! strncmp(my_fgets_line, "_fgets_dump_", 12) ) { 344 my_fgets_dump () ; 345 free ( my_fgets_line ) ; 346 strcpy ( s, "\n" ) ; 347 return ( s ) ; 348 } 349 if ( ! strncmp(my_fgets_line, "_fgets_debug_", 13) ) { 350 my_fgets_debug_toggle () ; 351 free ( my_fgets_line ) ; 352 strcpy ( s, "\n" ) ; 353 return ( s ) ; 354 } 355 (void) strncpy ( s, my_fgets_line, n-1 ) ; 356 (void) strcat ( s, "\n" ) ; 357 if ( *my_fgets_line ) add_history ( my_fgets_line ) ; 358 free ( my_fgets_line ) ; 359 return ( s ) ; 360 } else { 361 static fgets_t _fgets ; 362 _fgets = (fgets_t) dlsym ( REAL_LIBC, "fgets" ) ; 363 if ( ! _fgets ) { 364 fprintf ( stderr, 365 "libfgets: failed to dynamically link to native fgets()\n" 366 ) ; 367 strcpy ( s, "\n" ) ; 368 return ( s ) ; 369 } 370 return ( 371 _fgets ( s, n, stream ) 372 ) ; 373 } 374} 375