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