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