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