xref: /openbsd/gnu/usr.bin/texinfo/info/tilde.c (revision 7b36286a)
1 /* tilde.c -- tilde expansion code (~/foo := $HOME/foo).
2    $Id: tilde.c,v 1.4 2006/07/17 16:12:36 espie Exp $
3 
4    Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1996, 1998, 1999,
5    2002, 2004 Free Software Foundation, Inc.
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2, or (at your option)
10    any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 
21    Written by Brian Fox (bfox@ai.mit.edu). */
22 
23 /* Include config.h before doing alloca.  */
24 #include "info.h"
25 #include "tilde.h"
26 
27 #if defined (TEST) || defined (STATIC_MALLOC)
28 static void *xmalloc (), *xrealloc ();
29 #endif /* TEST || STATIC_MALLOC */
30 
31 /* The default value of tilde_additional_prefixes.  This is set to
32    whitespace preceding a tilde so that simple programs which do not
33    perform any word separation get desired behaviour. */
34 static char *default_prefixes[] =
35   { " ~", "\t~", (char *)NULL };
36 
37 /* The default value of tilde_additional_suffixes.  This is set to
38    whitespace or newline so that simple programs which do not
39    perform any word separation get desired behaviour. */
40 static char *default_suffixes[] =
41   { " ", "\n", (char *)NULL };
42 
43 /* If non-null, this contains the address of a function to call if the
44    standard meaning for expanding a tilde fails.  The function is called
45    with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
46    which is the expansion, or a NULL pointer if there is no expansion. */
47 CFunction *tilde_expansion_failure_hook = (CFunction *)NULL;
48 
49 /* When non-null, this is a NULL terminated array of strings which
50    are duplicates for a tilde prefix.  Bash uses this to expand
51    `=~' and `:~'. */
52 char **tilde_additional_prefixes = default_prefixes;
53 
54 /* When non-null, this is a NULL terminated array of strings which match
55    the end of a username, instead of just "/".  Bash sets this to
56    `:' and `=~'. */
57 char **tilde_additional_suffixes = default_suffixes;
58 
59 /* Find the start of a tilde expansion in STRING, and return the index of
60    the tilde which starts the expansion.  Place the length of the text
61    which identified this tilde starter in LEN, excluding the tilde itself. */
62 static int
63 tilde_find_prefix (char *string, int *len)
64 {
65   register int i, j, string_len;
66   register char **prefixes = tilde_additional_prefixes;
67 
68   string_len = strlen (string);
69   *len = 0;
70 
71   if (!*string || *string == '~')
72     return (0);
73 
74   if (prefixes)
75     {
76       for (i = 0; i < string_len; i++)
77         {
78           for (j = 0; prefixes[j]; j++)
79             {
80               if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0)
81                 {
82                   *len = strlen (prefixes[j]) - 1;
83                   return (i + *len);
84                 }
85             }
86         }
87     }
88   return (string_len);
89 }
90 
91 /* Find the end of a tilde expansion in STRING, and return the index of
92    the character which ends the tilde definition.  */
93 static int
94 tilde_find_suffix (char *string)
95 {
96   register int i, j, string_len;
97   register char **suffixes = tilde_additional_suffixes;
98 
99   string_len = strlen (string);
100 
101   for (i = 0; i < string_len; i++)
102     {
103       if (IS_SLASH (string[i]) || !string[i])
104         break;
105 
106       for (j = 0; suffixes && suffixes[j]; j++)
107         {
108           if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0)
109             return (i);
110         }
111     }
112   return (i);
113 }
114 
115 /* Return a new string which is the result of tilde expanding STRING. */
116 char *
117 tilde_expand (char *string)
118 {
119   char *result;
120   int result_size, result_index;
121 
122   result_size = result_index = 0;
123   result = (char *)NULL;
124 
125   /* Scan through STRING expanding tildes as we come to them. */
126   while (1)
127     {
128       register int start, end;
129       char *tilde_word, *expansion;
130       int len;
131 
132       /* Make START point to the tilde which starts the expansion. */
133       start = tilde_find_prefix (string, &len);
134 
135       /* Copy the skipped text into the result. */
136       if ((result_index + start + 1) > result_size)
137         result = (char *)xrealloc (result, 1 + (result_size += (start + 20)));
138 
139       strncpy (result + result_index, string, start);
140       result_index += start;
141 
142       /* Advance STRING to the starting tilde. */
143       string += start;
144 
145       /* Make END be the index of one after the last character of the
146          username. */
147       end = tilde_find_suffix (string);
148 
149       /* If both START and END are zero, we are all done. */
150       if (!start && !end)
151         break;
152 
153       /* Expand the entire tilde word, and copy it into RESULT. */
154       tilde_word = (char *)xmalloc (1 + end);
155       strncpy (tilde_word, string, end);
156       tilde_word[end] = '\0';
157       string += end;
158 
159       expansion = tilde_expand_word (tilde_word);
160       free (tilde_word);
161 
162       len = strlen (expansion);
163       if ((result_index + len + 1) > result_size)
164         result = (char *)xrealloc (result, 1 + (result_size += (len + 20)));
165 
166       strcpy (result + result_index, expansion);
167       result_index += len;
168       free (expansion);
169     }
170 
171   result[result_index] = '\0';
172 
173   return (result);
174 }
175 
176 /* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
177    tilde.  If there is no expansion, call tilde_expansion_failure_hook. */
178 char *
179 tilde_expand_word (char *filename)
180 {
181   char *dirname = filename ? xstrdup (filename) : NULL;
182 
183   if (dirname && *dirname == '~')
184     {
185       char *temp_name;
186       if (!dirname[1] || IS_SLASH (dirname[1]))
187         {
188           /* Prepend $HOME to the rest of the string. */
189           char *temp_home = getenv ("HOME");
190 
191           /* If there is no HOME variable, look up the directory in
192              the password database. */
193           if (!temp_home)
194             {
195               struct passwd *entry;
196 
197               entry = (struct passwd *) getpwuid (getuid ());
198               if (entry)
199                 temp_home = entry->pw_dir;
200             }
201 
202           temp_name = xmalloc (1 + strlen (&dirname[1])
203                                + (temp_home ? strlen (temp_home) : 0));
204           if (temp_home)
205             strcpy (temp_name, temp_home);
206           else
207             temp_name[0] = 0;
208           strcat (temp_name, &dirname[1]);
209           free (dirname);
210           dirname = xstrdup (temp_name);
211           free (temp_name);
212         }
213       else
214         {
215           struct passwd *user_entry;
216           char *username = xmalloc (257);
217           int i, c;
218 
219           for (i = 1; (c = dirname[i]); i++)
220             {
221               if (IS_SLASH (c))
222                 break;
223               else
224                 username[i - 1] = c;
225             }
226           username[i - 1] = 0;
227 
228           if (!(user_entry = (struct passwd *) getpwnam (username)))
229             {
230               /* If the calling program has a special syntax for
231                  expanding tildes, and we couldn't find a standard
232                  expansion, then let them try. */
233               if (tilde_expansion_failure_hook)
234                 {
235                   char *expansion = (*tilde_expansion_failure_hook) (username);
236 
237                   if (expansion)
238                     {
239                       temp_name = xmalloc (1 + strlen (expansion)
240                                            + strlen (&dirname[i]));
241                       strcpy (temp_name, expansion);
242                       strcat (temp_name, &dirname[i]);
243                       free (expansion);
244                       goto return_name;
245                     }
246                 }
247               /* We shouldn't report errors. */
248             }
249           else
250             {
251               temp_name = xmalloc (1 + strlen (user_entry->pw_dir)
252                                    + strlen (&dirname[i]));
253               strcpy (temp_name, user_entry->pw_dir);
254               strcat (temp_name, &dirname[i]);
255 
256             return_name:
257               free (dirname);
258               dirname = xstrdup (temp_name);
259               free (temp_name);
260             }
261 
262           endpwent ();
263           free (username);
264         }
265     }
266   return dirname;
267 }
268 
269 
270 #if defined (TEST)
271 #undef NULL
272 #include <stdio.h>
273 
274 main (argc, argv)
275      int argc;
276      char **argv;
277 {
278   char *result, line[512];
279   int done = 0;
280 
281   while (!done)
282     {
283       printf ("~expand: ");
284       fflush (stdout);
285 
286       if (!gets (line))
287         strcpy (line, "done");
288 
289       if ((strcmp (line, "done") == 0) ||
290           (strcmp (line, "quit") == 0) ||
291           (strcmp (line, "exit") == 0))
292         {
293           done = 1;
294           break;
295         }
296 
297       result = tilde_expand (line);
298       printf ("  --> %s\n", result);
299       free (result);
300     }
301   xexit (0);
302 }
303 
304 static void memory_error_and_abort ();
305 
306 static void *
307 xmalloc (bytes)
308      int bytes;
309 {
310   void *temp = (void *)malloc (bytes);
311 
312   if (!temp)
313     memory_error_and_abort ();
314   return (temp);
315 }
316 
317 static void *
318 xrealloc (pointer, bytes)
319      void *pointer;
320      int bytes;
321 {
322   void *temp;
323 
324   if (!pointer)
325     temp = (char *)malloc (bytes);
326   else
327     temp = (char *)realloc (pointer, bytes);
328 
329   if (!temp)
330     memory_error_and_abort ();
331 
332   return (temp);
333 }
334 
335 static void
336 memory_error_and_abort ()
337 {
338   fprintf (stderr, _("readline: Out of virtual memory!\n"));
339   abort ();
340 }
341 #endif /* TEST */
342 
343