1/* 2Date: Tue, 16 Mar 2004 19:38:40 -0800 3From: Harold Levy <Harold.Levy@synopsys.com> 4Subject: fgets(stdin) --> readline() redirector 5To: chet@po.cwru.edu 6 7Hi Chet, 8 9Here is something you may find useful enough to include in the readline 10distribution. It is a shared library that redirects calls to fgets(stdin) 11to readline() via LD_PRELOAD, and it supports a custom prompt and list of 12command names. Many people have asked me for this file, so I thought I'd 13pass it your way in hope of just including it with readline to begin with. 14 15Best Regards, 16 17-Harold 18*/ 19 20/****************************************************************************** 21******************************************************************************* 22 23 FILE NAME: fgets.c TARGET: libfgets.so 24 AUTHOR: Harold Levy VERSION: 1.0 25 hlevy@synopsys.com 26 27 ABSTRACT: Customize fgets() behavior via LD_PRELOAD in the following ways: 28 29 -- If fgets(stdin) is called, redirect to GNU readline() to obtain 30 command-line editing, file-name completion, history, etc. 31 32 -- A list of commands for command-name completion can be configured by 33 setting the environment-variable FGETS_COMMAND_FILE to a file containing 34 the list of commands to be used. 35 36 -- Command-line editing with readline() works best when the prompt string 37 is known; you can set this with the FGETS_PROMPT environment variable. 38 39 -- There special strings that libfgets will interpret as internal commands: 40 41 _fgets_reset_ reset the command list 42 43 _fgets_dump_ dump status 44 45 _fgets_debug_ toggle debug messages 46 47 HOW TO BUILD: Here are examples of how to build libfgets.so on various 48 platforms; you will have to add -I and -L flags to configure access to 49 the readline header and library files. 50 51 (32-bit builds with gcc) 52 AIX: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lreadline -ltermcap 53 HP-UX: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldld -lreadline 54 Linux: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lreadline 55 SunOS: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lgen -lreadline 56 57 (64-bit builds without gcc) 58 SunOS: SUNWspro/bin/cc -D_LARGEFILE64_SOURCE=1 -xtarget=ultra -xarch=v9 \ 59 -KPIC fgets.c -Bdynamic -lc -ldl -lgen -ltermcap -lreadline 60 61 HOW TO USE: Different operating systems have different levels of support 62 for the LD_PRELOAD concept. The generic method for 32-bit platforms is to 63 put libtermcap.so, libfgets.so, and libreadline.so (with absolute paths) 64 in the LD_PRELOAD environment variable, and to put their parent directories 65 in the LD_LIBRARY_PATH environment variable. Unfortunately there is no 66 generic method for 64-bit platforms; e.g. for 64-bit SunOS, you would have 67 to build both 32-bit and 64-bit libfgets and libreadline libraries, and 68 use the LD_FLAGS_32 and LD_FLAGS_64 environment variables with preload and 69 library_path configurations (a mix of 32-bit and 64-bit calls are made under 70 64-bit SunOS). 71 72 EXAMPLE WRAPPER: Here is an example shell script wrapper around the 73 program "foo" that uses fgets() for command-line input: 74 75 #!/bin/csh 76 #### replace this with the libtermcap.so directory: 77 set dir1 = "/usr/lib" 78 #### replace this with the libfgets.so directory: 79 set dir2 = "/usr/fgets" 80 #### replace this with the libreadline.so directory: 81 set dir3 = "/usr/local/lib" 82 set lib1 = "${dir1}/libtermcap.so" 83 set lib2 = "${dir2}/libfgets.so" 84 set lib3 = "${dir3}/libreadline.so" 85 if ( "${?LD_PRELOAD}" ) then 86 setenv LD_PRELOAD "${lib1}:${lib2}:${lib3}:${LD_PRELOAD}" 87 else 88 setenv LD_PRELOAD "${lib1}:${lib2}:${lib3}" 89 endif 90 if ( "${?LD_LIBRARY_PATH}" ) then 91 setenv LD_LIBRARY_PATH "${dir1}:${dir2}:${dir3}:${LD_LIBRARY_PATH}" 92 else 93 setenv LD_LIBRARY_PATH "${dir1}:${dir2}:${dir3}" 94 endif 95 setenv FGETS_COMMAND_FILE "${dir2}/foo.commands" 96 setenv FGETS_PROMPT "foo> " 97 exec "foo" $* 98 99 Copyright (C)�2003-2004 Harold Levy. 100 101 This code links to the GNU readline library, and as such is bound by the 102 terms of the GNU General Public License as published by the Free Software 103 Foundation, either version 2 or (at your option) any later version. 104 105 The GNU General Public License is often shipped with GNU software, and is 106 generally kept in a file called COPYING or LICENSE. If you do not have a 107 copy of the license, write to the Free Software Foundation, 59 Temple Place, 108 Suite 330, Boston, MA 02111 USA. 109 110 This program is distributed in the hope that it will be useful, but WITHOUT 111 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 112 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 113 details. 114 115******************************************************************************* 116******************************************************************************/ 117 118 119 120#include <dlfcn.h> 121#include <stdio.h> 122#include <strings.h> 123#include <stdlib.h> 124#include <unistd.h> 125 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