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