xref: /openbsd-src/gnu/lib/libreadline/tilde.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */
2 
3 /* Copyright (C) 1988,1989 Free Software Foundation, Inc.
4 
5    This file is part of GNU Readline, a library for reading lines
6    of text with interactive input and history editing.
7 
8    Readline is free software; you can redistribute it and/or modify it
9    under the terms of the GNU General Public License as published by the
10    Free Software Foundation; either version 2, or (at your option) any
11    later version.
12 
13    Readline is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    General Public License for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with Readline; see the file COPYING.  If not, write to the Free
20    Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
21 
22 #if defined (HAVE_CONFIG_H)
23 #  include <config.h>
24 #endif
25 
26 #if defined (HAVE_UNISTD_H)
27 #  ifdef _MINIX
28 #    include <sys/types.h>
29 #  endif
30 #  include <unistd.h>
31 #endif
32 
33 #if defined (HAVE_STRING_H)
34 #  include <string.h>
35 #else /* !HAVE_STRING_H */
36 #  include <strings.h>
37 #endif /* !HAVE_STRING_H */
38 
39 #if defined (HAVE_STDLIB_H)
40 #  include <stdlib.h>
41 #else
42 #  include "ansi_stdlib.h"
43 #endif /* HAVE_STDLIB_H */
44 
45 #include <sys/types.h>
46 #include <pwd.h>
47 
48 #include "tilde.h"
49 
50 #if defined (TEST) || defined (STATIC_MALLOC)
51 static char *xmalloc (), *xrealloc ();
52 #else
53 #  if defined __STDC__
54 extern char *xmalloc (int);
55 extern char *xrealloc (void *, int);
56 #  else
57 extern char *xmalloc (), *xrealloc ();
58 #  endif /* !__STDC__ */
59 #endif /* TEST || STATIC_MALLOC */
60 
61 #if !defined (HAVE_GETPW_DECLS)
62 extern struct passwd *getpwuid (), *getpwnam ();
63 #endif /* !HAVE_GETPW_DECLS */
64 
65 #if !defined (savestring)
66 #  ifndef strcpy
67 extern char *strcpy ();
68 #  endif
69 #define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x))
70 #endif /* !savestring */
71 
72 #if !defined (NULL)
73 #  if defined (__STDC__)
74 #    define NULL ((void *) 0)
75 #  else
76 #    define NULL 0x0
77 #  endif /* !__STDC__ */
78 #endif /* !NULL */
79 
80 /* If being compiled as part of bash, these will be satisfied from
81    variables.o.  If being compiled as part of readline, they will
82    be satisfied from shell.o. */
83 extern char *get_home_dir __P((void));
84 extern char *get_env_value __P((char *));
85 
86 /* The default value of tilde_additional_prefixes.  This is set to
87    whitespace preceding a tilde so that simple programs which do not
88    perform any word separation get desired behaviour. */
89 static char *default_prefixes[] =
90   { " ~", "\t~", (char *)NULL };
91 
92 /* The default value of tilde_additional_suffixes.  This is set to
93    whitespace or newline so that simple programs which do not
94    perform any word separation get desired behaviour. */
95 static char *default_suffixes[] =
96   { " ", "\n", (char *)NULL };
97 
98 /* If non-null, this contains the address of a function that the application
99    wants called before trying the standard tilde expansions.  The function
100    is called with the text sans tilde, and returns a malloc()'ed string
101    which is the expansion, or a NULL pointer if the expansion fails. */
102 CPFunction *tilde_expansion_preexpansion_hook = (CPFunction *)NULL;
103 
104 /* If non-null, this contains the address of a function to call if the
105    standard meaning for expanding a tilde fails.  The function is called
106    with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
107    which is the expansion, or a NULL pointer if there is no expansion. */
108 CPFunction *tilde_expansion_failure_hook = (CPFunction *)NULL;
109 
110 /* When non-null, this is a NULL terminated array of strings which
111    are duplicates for a tilde prefix.  Bash uses this to expand
112    `=~' and `:~'. */
113 char **tilde_additional_prefixes = default_prefixes;
114 
115 /* When non-null, this is a NULL terminated array of strings which match
116    the end of a username, instead of just "/".  Bash sets this to
117    `:' and `=~'. */
118 char **tilde_additional_suffixes = default_suffixes;
119 
120 /* Find the start of a tilde expansion in STRING, and return the index of
121    the tilde which starts the expansion.  Place the length of the text
122    which identified this tilde starter in LEN, excluding the tilde itself. */
123 static int
124 tilde_find_prefix (string, len)
125      char *string;
126      int *len;
127 {
128   register int i, j, string_len;
129   register char **prefixes;
130 
131   prefixes = tilde_additional_prefixes;
132 
133   string_len = strlen (string);
134   *len = 0;
135 
136   if (*string == '\0' || *string == '~')
137     return (0);
138 
139   if (prefixes)
140     {
141       for (i = 0; i < string_len; i++)
142 	{
143 	  for (j = 0; prefixes[j]; j++)
144 	    {
145 	      if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0)
146 		{
147 		  *len = strlen (prefixes[j]) - 1;
148 		  return (i + *len);
149 		}
150 	    }
151 	}
152     }
153   return (string_len);
154 }
155 
156 /* Find the end of a tilde expansion in STRING, and return the index of
157    the character which ends the tilde definition.  */
158 static int
159 tilde_find_suffix (string)
160      char *string;
161 {
162   register int i, j, string_len;
163   register char **suffixes;
164 
165   suffixes = tilde_additional_suffixes;
166   string_len = strlen (string);
167 
168   for (i = 0; i < string_len; i++)
169     {
170 #if defined (__MSDOS__)
171       if (string[i] == '/' || string[i] == '\\' /* || !string[i] */)
172 #else
173       if (string[i] == '/' /* || !string[i] */)
174 #endif
175 	break;
176 
177       for (j = 0; suffixes && suffixes[j]; j++)
178 	{
179 	  if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0)
180 	    return (i);
181 	}
182     }
183   return (i);
184 }
185 
186 /* Return a new string which is the result of tilde expanding STRING. */
187 char *
188 tilde_expand (string)
189      char *string;
190 {
191   char *result;
192   int result_size, result_index;
193 
194   result_index = result_size = 0;
195   if (result = strchr (string, '~'))
196     result = xmalloc (result_size = (strlen (string) + 16));
197   else
198     result = xmalloc (result_size = (strlen (string) + 1));
199 
200   /* Scan through STRING expanding tildes as we come to them. */
201   while (1)
202     {
203       register int start, end;
204       char *tilde_word, *expansion;
205       int len;
206 
207       /* Make START point to the tilde which starts the expansion. */
208       start = tilde_find_prefix (string, &len);
209 
210       /* Copy the skipped text into the result. */
211       if ((result_index + start + 1) > result_size)
212 	result = xrealloc (result, 1 + (result_size += (start + 20)));
213 
214       strncpy (result + result_index, string, start);
215       result_index += start;
216 
217       /* Advance STRING to the starting tilde. */
218       string += start;
219 
220       /* Make END be the index of one after the last character of the
221 	 username. */
222       end = tilde_find_suffix (string);
223 
224       /* If both START and END are zero, we are all done. */
225       if (!start && !end)
226 	break;
227 
228       /* Expand the entire tilde word, and copy it into RESULT. */
229       tilde_word = xmalloc (1 + end);
230       strncpy (tilde_word, string, end);
231       tilde_word[end] = '\0';
232       string += end;
233 
234       expansion = tilde_expand_word (tilde_word);
235       free (tilde_word);
236 
237       len = strlen (expansion);
238 #ifdef __CYGWIN32__
239       /* Fix for Cygwin to prevent ~user/xxx from expanding to //xxx when
240          $HOME for `user' is /.  On cygwin, // denotes a network drive. */
241       if (len > 1 || *expansion != '/' || *string != '/')
242 #endif
243 	{
244 	  if ((result_index + len + 1) > result_size)
245 	    result = xrealloc (result, 1 + (result_size += (len + 20)));
246 
247 	  strcpy (result + result_index, expansion);
248 	  result_index += len;
249 	}
250       free (expansion);
251     }
252 
253   result[result_index] = '\0';
254 
255   return (result);
256 }
257 
258 /* Take FNAME and return the tilde prefix we want expanded.  If LENP is
259    non-null, the index of the end of the prefix into FNAME is returned in
260    the location it points to. */
261 static char *
262 isolate_tilde_prefix (fname, lenp)
263      char *fname;
264      int *lenp;
265 {
266   char *ret;
267   int i;
268 
269   ret = xmalloc (strlen (fname));
270 #if defined (__MSDOS__)
271   for (i = 1; fname[i] && fname[i] != '/' && fname[i] != '\\'; i++)
272 #else
273   for (i = 1; fname[i] && fname[i] != '/'; i++)
274 #endif
275     ret[i - 1] = fname[i];
276   ret[i - 1] = '\0';
277   if (lenp)
278     *lenp = i;
279   return ret;
280 }
281 
282 /* Return a string that is PREFIX concatenated with SUFFIX starting at
283    SUFFIND. */
284 static char *
285 glue_prefix_and_suffix (prefix, suffix, suffind)
286      char *prefix, *suffix;
287      int suffind;
288 {
289   char *ret;
290   int plen, slen;
291 
292   plen = (prefix && *prefix) ? strlen (prefix) : 0;
293   slen = strlen (suffix + suffind);
294   ret = xmalloc (plen + slen + 1);
295   if (plen)
296     strcpy (ret, prefix);
297   strcpy (ret + plen, suffix + suffind);
298   return ret;
299 }
300 
301 /* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
302    tilde.  If there is no expansion, call tilde_expansion_failure_hook.
303    This always returns a newly-allocated string, never static storage. */
304 char *
305 tilde_expand_word (filename)
306      char *filename;
307 {
308   char *dirname, *expansion, *username;
309   int user_len;
310   struct passwd *user_entry;
311 
312   if (filename == 0)
313     return ((char *)NULL);
314 
315   if (*filename != '~')
316     return (savestring (filename));
317 
318   /* A leading `~/' or a bare `~' is *always* translated to the value of
319      $HOME or the home directory of the current user, regardless of any
320      preexpansion hook. */
321   if (filename[1] == '\0' || filename[1] == '/')
322     {
323       /* Prefix $HOME to the rest of the string. */
324       expansion = get_env_value ("HOME");
325 
326       /* If there is no HOME variable, look up the directory in
327 	 the password database. */
328       if (expansion == 0 || *expansion == '\0')
329 	expansion = get_home_dir ();
330 
331       return (glue_prefix_and_suffix (expansion, filename, 1));
332     }
333 
334   username = isolate_tilde_prefix (filename, &user_len);
335 
336   if (tilde_expansion_preexpansion_hook)
337     {
338       expansion = (*tilde_expansion_preexpansion_hook) (username);
339       if (expansion)
340 	{
341 	  dirname = glue_prefix_and_suffix (expansion, filename, user_len);
342 	  free (username);
343 	  free (expansion);
344 	  return (dirname);
345 	}
346     }
347 
348   /* No preexpansion hook, or the preexpansion hook failed.  Look in the
349      password database. */
350   dirname = (char *)NULL;
351   user_entry = getpwnam (username);
352   if (user_entry == 0)
353     {
354       /* If the calling program has a special syntax for expanding tildes,
355 	 and we couldn't find a standard expansion, then let them try. */
356       if (tilde_expansion_failure_hook)
357 	{
358 	  expansion = (*tilde_expansion_failure_hook) (username);
359 	  if (expansion)
360 	    {
361 	      dirname = glue_prefix_and_suffix (expansion, filename, user_len);
362 	      free (expansion);
363 	    }
364 	}
365       free (username);
366       /* If we don't have a failure hook, or if the failure hook did not
367 	 expand the tilde, return a copy of what we were passed. */
368       if (dirname == 0)
369 	dirname = savestring (filename);
370     }
371   else
372     {
373       free (username);
374       dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len);
375     }
376 
377   endpwent ();
378   return (dirname);
379 }
380 
381 
382 #if defined (TEST)
383 #undef NULL
384 #include <stdio.h>
385 
386 main (argc, argv)
387      int argc;
388      char **argv;
389 {
390   char *result, line[512];
391   int done = 0;
392 
393   while (!done)
394     {
395       printf ("~expand: ");
396       fflush (stdout);
397 
398       if (!gets (line))
399 	strcpy (line, "done");
400 
401       if ((strcmp (line, "done") == 0) ||
402 	  (strcmp (line, "quit") == 0) ||
403 	  (strcmp (line, "exit") == 0))
404 	{
405 	  done = 1;
406 	  break;
407 	}
408 
409       result = tilde_expand (line);
410       printf ("  --> %s\n", result);
411       free (result);
412     }
413   exit (0);
414 }
415 
416 static void memory_error_and_abort ();
417 
418 static char *
419 xmalloc (bytes)
420      int bytes;
421 {
422   char *temp = (char *)malloc (bytes);
423 
424   if (!temp)
425     memory_error_and_abort ();
426   return (temp);
427 }
428 
429 static char *
430 xrealloc (pointer, bytes)
431      char *pointer;
432      int bytes;
433 {
434   char *temp;
435 
436   if (!pointer)
437     temp = (char *)malloc (bytes);
438   else
439     temp = (char *)realloc (pointer, bytes);
440 
441   if (!temp)
442     memory_error_and_abort ();
443 
444   return (temp);
445 }
446 
447 static void
448 memory_error_and_abort ()
449 {
450   fprintf (stderr, "readline: out of virtual memory\n");
451   abort ();
452 }
453 
454 /*
455  * Local variables:
456  * compile-command: "gcc -g -DTEST -o tilde tilde.c"
457  * end:
458  */
459 #endif /* TEST */
460