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