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