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