1136644Sache/*
2136644SacheDate: Tue, 16 Mar 2004 19:38:40 -0800
3136644SacheFrom: Harold Levy <Harold.Levy@synopsys.com>
4136644SacheSubject: fgets(stdin) --> readline() redirector
5136644SacheTo: chet@po.cwru.edu
6136644Sache
7136644SacheHi Chet,
8136644Sache
9136644SacheHere is something you may find useful enough to include in the readline
10136644Sachedistribution.  It is a shared library that redirects calls to fgets(stdin)
11136644Sacheto readline() via LD_PRELOAD, and it supports a custom prompt and list of
12136644Sachecommand names.  Many people have asked me for this file, so I thought I'd
13136644Sachepass it your way in hope of just including it with readline to begin with.
14136644Sache
15136644SacheBest Regards,
16136644Sache
17136644Sache-Harold
18136644Sache*/
19136644Sache
20136644Sache/******************************************************************************
21136644Sache*******************************************************************************
22136644Sache
23136644Sache  FILE NAME:    fgets.c                  TARGET:   libfgets.so
24136644Sache  AUTHOR:       Harold Levy              VERSION:  1.0
25136644Sache                hlevy@synopsys.com
26136644Sache
27136644Sache  ABSTRACT:  Customize fgets() behavior via LD_PRELOAD in the following ways:
28136644Sache
29136644Sache    -- If fgets(stdin) is called, redirect to GNU readline() to obtain
30136644Sache       command-line editing, file-name completion, history, etc.
31136644Sache
32136644Sache    -- A list of commands for command-name completion can be configured by
33136644Sache       setting the environment-variable FGETS_COMMAND_FILE to a file containing
34136644Sache       the list of commands to be used.
35136644Sache
36136644Sache    -- Command-line editing with readline() works best when the prompt string
37136644Sache       is known; you can set this with the FGETS_PROMPT environment variable.
38136644Sache
39136644Sache    -- There special strings that libfgets will interpret as internal commands:
40136644Sache
41136644Sache           _fgets_reset_    reset the command list
42136644Sache
43136644Sache           _fgets_dump_     dump status
44136644Sache
45136644Sache           _fgets_debug_    toggle debug messages
46136644Sache
47136644Sache  HOW TO BUILD:  Here are examples of how to build libfgets.so on various
48136644Sache  platforms; you will have to add -I and -L flags to configure access to
49136644Sache  the readline header and library files.
50136644Sache
51136644Sache  (32-bit builds with gcc)
52136644Sache  AIX:   gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lreadline -ltermcap
53136644Sache  HP-UX: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldld -lreadline
54136644Sache  Linux: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lreadline
55136644Sache  SunOS: gcc -fPIC fgets.c -shared -o libfgets.so -lc -ldl -lgen -lreadline
56136644Sache
57136644Sache  (64-bit builds without gcc)
58136644Sache  SunOS: SUNWspro/bin/cc -D_LARGEFILE64_SOURCE=1 -xtarget=ultra -xarch=v9 \
59136644Sache           -KPIC fgets.c -Bdynamic -lc -ldl -lgen -ltermcap -lreadline
60136644Sache
61136644Sache  HOW TO USE:  Different operating systems have different levels of support
62136644Sache  for the LD_PRELOAD concept.  The generic method for 32-bit platforms is to
63136644Sache  put libtermcap.so, libfgets.so, and libreadline.so (with absolute paths)
64136644Sache  in the LD_PRELOAD environment variable, and to put their parent directories
65136644Sache  in the LD_LIBRARY_PATH environment variable.  Unfortunately there is no
66136644Sache  generic method for 64-bit platforms; e.g. for 64-bit SunOS, you would have
67136644Sache  to build both 32-bit and 64-bit libfgets and libreadline libraries, and
68136644Sache  use the LD_FLAGS_32 and LD_FLAGS_64 environment variables with preload and
69136644Sache  library_path configurations (a mix of 32-bit and 64-bit calls are made under
70136644Sache  64-bit SunOS).
71136644Sache
72136644Sache  EXAMPLE WRAPPER:  Here is an example shell script wrapper around the
73136644Sache  program "foo" that uses fgets() for command-line input:
74136644Sache
75136644Sache      #!/bin/csh
76136644Sache      #### replace this with the libtermcap.so directory:
77136644Sache      set dir1 = "/usr/lib"
78136644Sache      #### replace this with the libfgets.so directory:
79136644Sache      set dir2 = "/usr/fgets"
80136644Sache      #### replace this with the libreadline.so directory:
81136644Sache      set dir3 = "/usr/local/lib"
82136644Sache      set lib1 = "${dir1}/libtermcap.so"
83136644Sache      set lib2 = "${dir2}/libfgets.so"
84136644Sache      set lib3 = "${dir3}/libreadline.so"
85136644Sache      if ( "${?LD_PRELOAD}" ) then
86136644Sache        setenv LD_PRELOAD "${lib1}:${lib2}:${lib3}:${LD_PRELOAD}"
87136644Sache      else
88136644Sache        setenv LD_PRELOAD "${lib1}:${lib2}:${lib3}"
89136644Sache      endif
90136644Sache      if ( "${?LD_LIBRARY_PATH}" ) then
91136644Sache        setenv LD_LIBRARY_PATH "${dir1}:${dir2}:${dir3}:${LD_LIBRARY_PATH}"
92136644Sache      else
93136644Sache        setenv LD_LIBRARY_PATH "${dir1}:${dir2}:${dir3}"
94136644Sache      endif
95136644Sache      setenv FGETS_COMMAND_FILE "${dir2}/foo.commands"
96136644Sache      setenv FGETS_PROMPT       "foo> "
97136644Sache      exec "foo" $*
98136644Sache
99136644Sache  Copyright (C)�2003-2004 Harold Levy.
100136644Sache
101136644Sache  This code links to the GNU readline library, and as such is bound by the
102136644Sache  terms of the GNU General Public License as published by the Free Software
103136644Sache  Foundation, either version 2 or (at your option) any later version.
104136644Sache
105136644Sache  The GNU General Public License is often shipped with GNU software, and is
106136644Sache  generally kept in a file called COPYING or LICENSE.  If you do not have a
107136644Sache  copy of the license, write to the Free Software Foundation, 59 Temple Place,
108136644Sache  Suite 330, Boston, MA 02111 USA.
109136644Sache
110136644Sache  This program is distributed in the hope that it will be useful, but WITHOUT
111136644Sache  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
112136644Sache  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
113136644Sache  details.
114136644Sache
115136644Sache*******************************************************************************
116136644Sache******************************************************************************/
117136644Sache
118136644Sache
119136644Sache
120136644Sache#include <dlfcn.h>
121136644Sache#include <stdio.h>
122136644Sache#include <strings.h>
123136644Sache#include <stdlib.h>
124136644Sache#include <unistd.h>
125136644Sache
126136644Sache#include <readline/readline.h>
127136644Sache#include <readline/history.h>
128136644Sache
129136644Sache
130136644Sache
131136644Sache/* for dynamically connecting to the native fgets() */
132136644Sache#if defined(RTLD_NEXT)
133136644Sache#define REAL_LIBC RTLD_NEXT
134136644Sache#else
135136644Sache#define REAL_LIBC ((void *) -1L)
136136644Sache#endif
137136644Sachetypedef char * ( * fgets_t ) ( char * s, int n, FILE * stream ) ;
138136644Sache
139136644Sache
140136644Sache
141136644Sache/* private data */
142136644Sache/* -- writeable data is stored in the shared library's data segment
143136644Sache   -- every process that uses the shared library gets a private memory copy of
144136644Sache      its entire data segment
145136644Sache   -- static data in the shared library is not copied to the application
146136644Sache   -- only read-only (i.e. 'const') data is stored in the shared library's
147136644Sache      text segment
148136644Sache*/
149136644Sachestatic char ** my_fgets_names           = NULL ;
150136644Sachestatic int     my_fgets_number_of_names = 0    ;
151136644Sachestatic int     my_fgets_debug_flag      = 0    ;
152136644Sache
153136644Sache
154136644Sache
155136644Sache/* invoked with _fgets_reset_ */
156136644Sachestatic void
157136644Sachemy_fgets_reset (
158136644Sache  void
159136644Sache) {
160136644Sache  if ( my_fgets_names && (my_fgets_number_of_names > 0) ) {
161136644Sache    int i ;
162136644Sache    if ( my_fgets_debug_flag ) {
163136644Sache      printf ( "libfgets:  removing command list\n" ) ;
164136644Sache    }
165136644Sache    for ( i = 0 ; i < my_fgets_number_of_names ; i ++ ) {
166136644Sache      if ( my_fgets_names[i] ) free ( my_fgets_names[i] ) ;
167136644Sache    }
168136644Sache    free ( my_fgets_names ) ;
169136644Sache  }
170136644Sache  my_fgets_names = NULL ;
171136644Sache  my_fgets_number_of_names = 0 ;
172136644Sache}
173136644Sache
174136644Sache
175136644Sache
176136644Sache/* invoked with _fgets_dump_ */
177136644Sachestatic void
178136644Sachemy_fgets_dump (
179136644Sache  void
180136644Sache) {
181136644Sache  char * s ;
182136644Sache  printf ( "\n" ) ;
183136644Sache  s = getenv ( "FGETS_PROMPT" ) ;
184136644Sache  printf ( "FGETS_PROMPT       = %s\n", s ? s : "" ) ;
185136644Sache  s = getenv ( "FGETS_COMMAND_FILE" ) ;
186136644Sache  printf ( "FGETS_COMMAND_FILE = %s\n", s ? s : "" ) ;
187136644Sache  printf ( "debug flag         = %d\n", my_fgets_debug_flag ) ;
188136644Sache  printf ( "#commands          = %d\n", my_fgets_number_of_names ) ;
189136644Sache  if ( my_fgets_debug_flag ) {
190136644Sache    if ( my_fgets_names && (my_fgets_number_of_names > 0) ) {
191136644Sache      int i ;
192136644Sache      for ( i = 0 ; i < my_fgets_number_of_names ; i ++ ) {
193136644Sache        printf ( "%s\n", my_fgets_names[i] ) ;
194136644Sache      }
195136644Sache    }
196136644Sache  }
197136644Sache  printf ( "\n" ) ;
198136644Sache}
199136644Sache
200136644Sache
201136644Sache
202136644Sache/* invoked with _fgets_debug_ */
203136644Sachestatic void
204136644Sachemy_fgets_debug_toggle (
205136644Sache  void
206136644Sache) {
207136644Sache  my_fgets_debug_flag = my_fgets_debug_flag ? 0 : 1 ;
208136644Sache  if ( my_fgets_debug_flag ) {
209136644Sache    printf ( "libfgets:  debug flag = %d\n", my_fgets_debug_flag ) ;
210136644Sache  }
211136644Sache}
212136644Sache
213136644Sache
214136644Sache
215136644Sache/* read the command list if needed, return the i-th name */
216136644Sachestatic char *
217136644Sachemy_fgets_lookup (
218136644Sache  int index
219136644Sache) {
220136644Sache  if ( (! my_fgets_names) || (! my_fgets_number_of_names) ) {
221136644Sache    char * fname ;
222136644Sache    FILE * fp ;
223136644Sache    fgets_t _fgets ;
224136644Sache    int i ;
225136644Sache    char buf1[256], buf2[256] ;
226136644Sache    fname = getenv ( "FGETS_COMMAND_FILE" ) ;
227136644Sache    if ( ! fname ) {
228136644Sache      if ( my_fgets_debug_flag ) {
229136644Sache        printf ( "libfgets:  empty or unset FGETS_COMMAND_FILE\n" ) ;
230136644Sache      }
231136644Sache      return NULL ;
232136644Sache    }
233136644Sache    fp = fopen ( fname, "r" ) ;
234136644Sache    if ( ! fp ) {
235136644Sache      if ( my_fgets_debug_flag ) {
236136644Sache        printf ( "libfgets:  cannot open '%s' for reading\n", fname ) ;
237136644Sache      }
238136644Sache      return NULL ;
239136644Sache    }
240136644Sache    _fgets = (fgets_t) dlsym ( REAL_LIBC, "fgets" ) ;
241136644Sache    if ( ! _fgets ) {
242136644Sache      fprintf ( stderr,
243136644Sache        "libfgets:  failed to dynamically link to native fgets()\n"
244136644Sache      ) ;
245136644Sache      return NULL ;
246136644Sache    }
247136644Sache    for ( i = 0 ; _fgets(buf1,255,fp) ; i ++ ) ;
248136644Sache    if ( ! i ) { fclose(fp) ; return NULL ; }
249136644Sache    my_fgets_names = (char**) calloc ( i, sizeof(char*) ) ;
250136644Sache    rewind ( fp ) ;
251136644Sache    i = 0 ;
252136644Sache    while ( _fgets(buf1,255,fp) ) {
253136644Sache      buf1[255] = 0 ;
254136644Sache      if ( 1 == sscanf(buf1,"%s",buf2) ) {
255136644Sache        my_fgets_names[i] = strdup(buf2) ;
256136644Sache        i ++ ;
257136644Sache      }
258136644Sache    }
259136644Sache    fclose ( fp ) ;
260136644Sache    my_fgets_number_of_names = i ;
261136644Sache    if ( my_fgets_debug_flag ) {
262136644Sache      printf ( "libfgets:  successfully read %d commands\n", i ) ;
263136644Sache    }
264136644Sache  }
265136644Sache  if ( index < my_fgets_number_of_names ) {
266136644Sache    return my_fgets_names[index] ;
267136644Sache  } else {
268136644Sache    return NULL ;
269136644Sache  }
270136644Sache}
271136644Sache
272136644Sache
273136644Sache
274136644Sache/* generate a list of partial name matches for readline() */
275136644Sachestatic char *
276136644Sachemy_fgets_generator (
277136644Sache  const char * text,
278136644Sache  int          state
279136644Sache)
280136644Sache{
281136644Sache  static int list_index, len ;
282136644Sache  char *     name ;
283136644Sache  if ( ! state ) {
284136644Sache    list_index = 0 ;
285136644Sache    len = strlen ( text ) ;
286136644Sache  }
287136644Sache  while ( ( name = my_fgets_lookup(list_index) ) ) {
288136644Sache    list_index ++ ;
289136644Sache    if ( ! strncmp ( name, text, len ) ) {
290136644Sache      return ( strdup ( name ) ) ;
291136644Sache    }
292136644Sache  }
293136644Sache  return ( NULL ) ;
294136644Sache}
295136644Sache
296136644Sache
297136644Sache
298136644Sache/* partial name completion callback for readline() */
299136644Sachestatic char **
300136644Sachemy_fgets_completion (
301136644Sache  const char * text,
302136644Sache  int          start,
303136644Sache  int          end
304136644Sache)
305136644Sache{
306136644Sache  char ** matches ;
307136644Sache  matches = NULL ;
308136644Sache  if ( ! start ) {
309136644Sache    matches = rl_completion_matches ( text, my_fgets_generator ) ;
310136644Sache  }
311136644Sache  return ( matches ) ;
312136644Sache}
313136644Sache
314136644Sache
315136644Sache
316136644Sache/* fgets() intercept */
317136644Sachechar *
318136644Sachefgets (
319136644Sache  char * s,
320136644Sache  int    n,
321136644Sache  FILE * stream
322136644Sache)
323136644Sache{
324136644Sache  if ( ! s ) return NULL ;
325136644Sache  if ( stream == stdin ) {
326136644Sache    char * prompt ;
327136644Sache    char * my_fgets_line ;
328136644Sache    rl_already_prompted = 1 ;
329136644Sache    rl_attempted_completion_function = my_fgets_completion ;
330136644Sache    rl_catch_signals = 1 ;
331136644Sache    rl_catch_sigwinch = 1 ;
332136644Sache    rl_set_signals () ;
333136644Sache    prompt = getenv ( "FGETS_PROMPT" ) ;
334136644Sache    for (
335136644Sache      my_fgets_line = 0 ; ! my_fgets_line ; my_fgets_line=readline(prompt)
336136644Sache    ) ;
337136644Sache    if ( ! strncmp(my_fgets_line, "_fgets_reset_", 13) ) {
338136644Sache      my_fgets_reset () ;
339136644Sache      free ( my_fgets_line ) ;
340136644Sache      strcpy ( s, "\n" ) ;
341136644Sache      return ( s ) ;
342136644Sache    }
343136644Sache    if ( ! strncmp(my_fgets_line, "_fgets_dump_", 12) ) {
344136644Sache      my_fgets_dump () ;
345136644Sache      free ( my_fgets_line ) ;
346136644Sache      strcpy ( s, "\n" ) ;
347136644Sache      return ( s ) ;
348136644Sache    }
349136644Sache    if ( ! strncmp(my_fgets_line, "_fgets_debug_", 13) ) {
350136644Sache      my_fgets_debug_toggle () ;
351136644Sache      free ( my_fgets_line ) ;
352136644Sache      strcpy ( s, "\n" ) ;
353136644Sache      return ( s ) ;
354136644Sache    }
355136644Sache    (void) strncpy ( s, my_fgets_line, n-1 ) ;
356136644Sache    (void) strcat ( s, "\n" ) ;
357136644Sache    if ( *my_fgets_line ) add_history ( my_fgets_line ) ;
358136644Sache    free ( my_fgets_line ) ;
359136644Sache    return ( s ) ;
360136644Sache  } else {
361136644Sache    static fgets_t _fgets ;
362136644Sache    _fgets = (fgets_t) dlsym ( REAL_LIBC, "fgets" ) ;
363136644Sache    if ( ! _fgets ) {
364136644Sache      fprintf ( stderr,
365136644Sache        "libfgets:  failed to dynamically link to native fgets()\n"
366136644Sache      ) ;
367136644Sache      strcpy ( s, "\n" ) ;
368136644Sache      return ( s ) ;
369136644Sache    }
370136644Sache    return (
371136644Sache      _fgets ( s, n, stream )
372136644Sache    ) ;
373136644Sache  }
374136644Sache}
375