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) 51static void *xmalloc (), *xrealloc (); 52#else 53# include "xmalloc.h" 54#endif /* TEST || STATIC_MALLOC */ 55 56#if !defined (HAVE_GETPW_DECLS) 57extern struct passwd *getpwuid PARAMS((uid_t)); 58extern struct passwd *getpwnam PARAMS((const char *)); 59#endif /* !HAVE_GETPW_DECLS */ 60 61#if !defined (savestring) 62#include <stdio.h> 63static char * 64xstrdup(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. */ 88extern char *sh_get_home_dir PARAMS((void)); 89extern 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. */ 94static 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. */ 100static 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. */ 107tilde_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. */ 113tilde_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 `:~'. */ 118char **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 `=~'. */ 123char **tilde_additional_suffixes = (char **)default_suffixes; 124 125static int tilde_find_prefix PARAMS((const char *, int *)); 126static int tilde_find_suffix PARAMS((const char *)); 127static char *isolate_tilde_prefix PARAMS((const char *, int *)); 128static 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. */ 133static int 134tilde_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. */ 168static int 169tilde_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. */ 197char * 198tilde_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. */ 272static char * 273isolate_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. */ 295static char * 296glue_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. */ 316char * 317tilde_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 398main (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 429static void memory_error_and_abort (); 430 431static void * 432xmalloc (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 442static void * 443xrealloc (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 460static void 461memory_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