1/* $NetBSD: filecomplete.c,v 1.23 2010/12/06 00:05:38 dholland Exp $ */ 2 3/*- 4 * Copyright (c) 1997 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jaromir Dolecek. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32/* AIX requires this to be the first thing in the file. */ 33#if defined (_AIX) && !defined (__GNUC__) 34 #pragma alloca 35#endif 36 37#include "config.h" 38 39#ifdef __GNUC__ 40# undef alloca 41# define alloca(n) __builtin_alloca (n) 42#else 43# ifdef HAVE_ALLOCA_H 44# include <alloca.h> 45# else 46# ifndef _AIX 47extern char *alloca (); 48# endif 49# endif 50#endif 51 52#if !defined(lint) && !defined(SCCSID) 53__RCSID("$NetBSD: filecomplete.c,v 1.23 2010/12/06 00:05:38 dholland Exp $"); 54#endif /* not lint && not SCCSID */ 55 56#include <sys/types.h> 57#include <sys/stat.h> 58#include <stdio.h> 59#include <dirent.h> 60#include <string.h> 61#include <pwd.h> 62#include <ctype.h> 63#include <stdlib.h> 64#include <unistd.h> 65#include <limits.h> 66#include <errno.h> 67#include <fcntl.h> 68#include <vis.h> 69 70#include "el.h" 71#include "fcns.h" /* for EL_NUM_FCNS */ 72#include "histedit.h" 73#include "filecomplete.h" 74 75static const Char break_chars[] = { ' ', '\t', '\n', '"', '\\', '\'', '`', '@', 76 '$', '>', '<', '=', ';', '|', '&', '{', '(', '\0' }; 77 78 79/********************************/ 80/* completion functions */ 81 82/* 83 * does tilde expansion of strings of type ``~user/foo'' 84 * if ``user'' isn't valid user name or ``txt'' doesn't start 85 * w/ '~', returns pointer to strdup()ed copy of ``txt'' 86 * 87 * it's callers's responsibility to free() returned string 88 */ 89char * 90fn_tilde_expand(const char *txt) 91{ 92 struct passwd pwres, *pass; 93 char *temp; 94 size_t len = 0; 95 char pwbuf[1024]; 96 97 if (txt[0] != '~') 98 return (strdup(txt)); 99 100 temp = strchr(txt + 1, '/'); 101 if (temp == NULL) { 102 temp = strdup(txt + 1); 103 if (temp == NULL) 104 return NULL; 105 } else { 106 len = temp - txt + 1; /* text until string after slash */ 107 temp = malloc(len); 108 if (temp == NULL) 109 return NULL; 110 (void)strncpy(temp, txt + 1, len - 2); 111 temp[len - 2] = '\0'; 112 } 113 if (temp[0] == 0) { 114#ifdef HAVE_GETPW_R_POSIX 115 if (getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf), &pass) != 0) 116 pass = NULL; 117#elif HAVE_GETPW_R_DRAFT 118 pass = getpwuid_r(getuid(), &pwres, pwbuf, sizeof(pwbuf)); 119#else 120 pass = getpwuid(getuid()); 121#endif 122 } else { 123#ifdef HAVE_GETPW_R_POSIX 124 if (getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf), &pass) != 0) 125 pass = NULL; 126#elif HAVE_GETPW_R_DRAFT 127 pass = getpwnam_r(temp, &pwres, pwbuf, sizeof(pwbuf)); 128#else 129 pass = getpwnam(temp); 130#endif 131 } 132 free(temp); /* value no more needed */ 133 if (pass == NULL) 134 return (strdup(txt)); 135 136 /* update pointer txt to point at string immedially following */ 137 /* first slash */ 138 txt += len; 139 140 temp = malloc(strlen(pass->pw_dir) + 1 + strlen(txt) + 1); 141 if (temp == NULL) 142 return NULL; 143 (void)sprintf(temp, "%s/%s", pass->pw_dir, txt); 144 145 return (temp); 146} 147 148 149/* 150 * return first found file name starting by the ``text'' or NULL if no 151 * such file can be found 152 * value of ``state'' is ignored 153 * 154 * it's caller's responsibility to free returned string 155 */ 156char * 157fn_filename_completion_function(const char *text, int state) 158{ 159 static DIR *dir = NULL; 160 static char *filename = NULL, *dirname = NULL, *dirpath = NULL; 161 static size_t filename_len = 0; 162 struct dirent *entry; 163 char *temp; 164 size_t len; 165 166 if (state == 0 || dir == NULL) { 167 temp = strrchr(text, '/'); 168 if (temp) { 169 char *nptr; 170 temp++; 171 nptr = realloc(filename, strlen(temp) + 1); 172 if (nptr == NULL) { 173 free(filename); 174 filename = NULL; 175 return NULL; 176 } 177 filename = nptr; 178 (void)strcpy(filename, temp); 179 len = temp - text; /* including last slash */ 180 181 nptr = realloc(dirname, len + 1); 182 if (nptr == NULL) { 183 free(dirname); 184 dirname = NULL; 185 return NULL; 186 } 187 dirname = nptr; 188 (void)strncpy(dirname, text, len); 189 dirname[len] = '\0'; 190 } else { 191 free(filename); 192 if (*text == 0) 193 filename = NULL; 194 else { 195 filename = strdup(text); 196 if (filename == NULL) 197 return NULL; 198 } 199 free(dirname); 200 dirname = NULL; 201 } 202 203 if (dir != NULL) { 204 (void)closedir(dir); 205 dir = NULL; 206 } 207 208 /* support for ``~user'' syntax */ 209 210 free(dirpath); 211 dirpath = NULL; 212 if (dirname == NULL) { 213 if ((dirname = strdup("")) == NULL) 214 return NULL; 215 dirpath = strdup("./"); 216 } else if (*dirname == '~') 217 dirpath = fn_tilde_expand(dirname); 218 else 219 dirpath = strdup(dirname); 220 221 if (dirpath == NULL) 222 return NULL; 223 224 dir = opendir(dirpath); 225 if (!dir) 226 return (NULL); /* cannot open the directory */ 227 228 /* will be used in cycle */ 229 filename_len = filename ? strlen(filename) : 0; 230 } 231 232 /* find the match */ 233 while ((entry = readdir(dir)) != NULL) { 234 /* skip . and .. */ 235 if (entry->d_name[0] == '.' && (!entry->d_name[1] 236 || (entry->d_name[1] == '.' && !entry->d_name[2]))) 237 continue; 238 if (filename_len == 0) 239 break; 240 /* otherwise, get first entry where first */ 241 /* filename_len characters are equal */ 242 if (entry->d_name[0] == filename[0] 243 /* Some dirents have d_namlen, but it is not portable. */ 244 && strlen(entry->d_name) >= filename_len 245 && strncmp(entry->d_name, filename, 246 filename_len) == 0) 247 break; 248 } 249 250 if (entry) { /* match found */ 251 252 /* Some dirents have d_namlen, but it is not portable. */ 253 len = strlen(entry->d_name); 254 255 temp = malloc(strlen(dirname) + len + 1); 256 if (temp == NULL) 257 return NULL; 258 (void)sprintf(temp, "%s%s", dirname, entry->d_name); 259 } else { 260 (void)closedir(dir); 261 dir = NULL; 262 temp = NULL; 263 } 264 265 return (temp); 266} 267 268 269static const char * 270append_char_function(const char *name) 271{ 272 struct stat stbuf; 273 char *expname = *name == '~' ? fn_tilde_expand(name) : NULL; 274 const char *rs = " "; 275 276 if (stat(expname ? expname : name, &stbuf) == -1) 277 goto out; 278 if (S_ISDIR(stbuf.st_mode)) 279 rs = "/"; 280out: 281 if (expname) 282 free(expname); 283 return rs; 284} 285/* 286 * returns list of completions for text given 287 * non-static for readline. 288 */ 289char ** completion_matches(const char *, char *(*)(const char *, int)); 290char ** 291completion_matches(const char *text, char *(*genfunc)(const char *, int)) 292{ 293 char **match_list = NULL, *retstr, *prevstr; 294 size_t match_list_len, max_equal, which, i; 295 size_t matches; 296 297 matches = 0; 298 match_list_len = 1; 299 while ((retstr = (*genfunc) (text, (int)matches)) != NULL) { 300 /* allow for list terminator here */ 301 if (matches + 3 >= match_list_len) { 302 char **nmatch_list; 303 while (matches + 3 >= match_list_len) 304 match_list_len <<= 1; 305 nmatch_list = realloc(match_list, 306 match_list_len * sizeof(char *)); 307 if (nmatch_list == NULL) { 308 free(match_list); 309 return NULL; 310 } 311 match_list = nmatch_list; 312 313 } 314 match_list[++matches] = retstr; 315 } 316 317 if (!match_list) 318 return NULL; /* nothing found */ 319 320 /* find least denominator and insert it to match_list[0] */ 321 which = 2; 322 prevstr = match_list[1]; 323 max_equal = strlen(prevstr); 324 for (; which <= matches; which++) { 325 for (i = 0; i < max_equal && 326 prevstr[i] == match_list[which][i]; i++) 327 continue; 328 max_equal = i; 329 } 330 331 retstr = malloc(max_equal + 1); 332 if (retstr == NULL) { 333 free(match_list); 334 return NULL; 335 } 336 (void)strncpy(retstr, match_list[1], max_equal); 337 retstr[max_equal] = '\0'; 338 match_list[0] = retstr; 339 340 /* add NULL as last pointer to the array */ 341 match_list[matches + 1] = (char *) NULL; 342 343 return (match_list); 344} 345 346/* 347 * Sort function for qsort(). Just wrapper around strcasecmp(). 348 */ 349static int 350_fn_qsort_string_compare(const void *i1, const void *i2) 351{ 352 const char *s1 = ((const char * const *)i1)[0]; 353 const char *s2 = ((const char * const *)i2)[0]; 354 355 return strcasecmp(s1, s2); 356} 357 358/* 359 * Display list of strings in columnar format on readline's output stream. 360 * 'matches' is list of strings, 'num' is number of strings in 'matches', 361 * 'width' is maximum length of string in 'matches'. 362 * 363 * matches[0] is not one of the match strings, but it is counted in 364 * num, so the strings are matches[1] *through* matches[num-1]. 365 */ 366void 367fn_display_match_list (EditLine *el, char **matches, size_t num, size_t width) 368{ 369 size_t line, lines, col, cols, thisguy; 370 int screenwidth = el->el_term.t_size.h; 371 372 /* Ignore matches[0]. Avoid 1-based array logic below. */ 373 matches++; 374 num--; 375 376 /* 377 * Find out how many entries can be put on one line; count 378 * with one space between strings the same way it's printed. 379 */ 380 cols = screenwidth / (width + 1); 381 if (cols == 0) 382 cols = 1; 383 384 /* how many lines of output, rounded up */ 385 lines = (num + cols - 1) / cols; 386 387 /* Sort the items. */ 388 qsort(matches, num, sizeof(char *), _fn_qsort_string_compare); 389 390 /* 391 * On the ith line print elements i, i+lines, i+lines*2, etc. 392 */ 393 for (line = 0; line < lines; line++) { 394 for (col = 0; col < cols; col++) { 395 thisguy = line + col * lines; 396 if (thisguy >= num) 397 break; 398 (void)fprintf(el->el_outfile, "%s%-*s", 399 col == 0 ? "" : " ", (int)width, matches[thisguy]); 400 } 401 (void)fprintf(el->el_outfile, "\n"); 402 } 403} 404 405/* 406 * Complete the word at or before point, 407 * 'what_to_do' says what to do with the completion. 408 * \t means do standard completion. 409 * `?' means list the possible completions. 410 * `*' means insert all of the possible completions. 411 * `!' means to do standard completion, and list all possible completions if 412 * there is more than one. 413 * 414 * Note: '*' support is not implemented 415 * '!' could never be invoked 416 */ 417int 418fn_complete(EditLine *el, 419 char *(*complet_func)(const char *, int), 420 char **(*attempted_completion_function)(const char *, int, int), 421 const Char *word_break, const Char *special_prefixes, 422 const char *(*app_func)(const char *), size_t query_items, 423 int *completion_type, int *over, int *point, int *end) 424{ 425 const TYPE(LineInfo) *li; 426 Char *temp; 427 char **matches; 428 const Char *ctemp; 429 size_t len; 430 int what_to_do = '\t'; 431 int retval = CC_NORM; 432 433 if (el->el_state.lastcmd == el->el_state.thiscmd) 434 what_to_do = '?'; 435 436 /* readline's rl_complete() has to be told what we did... */ 437 if (completion_type != NULL) 438 *completion_type = what_to_do; 439 440 if (!complet_func) 441 complet_func = fn_filename_completion_function; 442 if (!app_func) 443 app_func = append_char_function; 444 445 /* We now look backwards for the start of a filename/variable word */ 446 li = FUN(el,line)(el); 447 ctemp = li->cursor; 448 while (ctemp > li->buffer 449 && !Strchr(word_break, ctemp[-1]) 450 && (!special_prefixes || !Strchr(special_prefixes, ctemp[-1]) ) ) 451 ctemp--; 452 453 len = li->cursor - ctemp; 454#if defined(__SSP__) || defined(__SSP_ALL__) 455 temp = malloc(sizeof(*temp) * (len + 1)); 456#else 457 temp = alloca(sizeof(*temp) * (len + 1)); 458#endif 459 (void)Strncpy(temp, ctemp, len); 460 temp[len] = '\0'; 461 462 /* these can be used by function called in completion_matches() */ 463 /* or (*attempted_completion_function)() */ 464 if (point != 0) 465 *point = (int)(li->cursor - li->buffer); 466 if (end != NULL) 467 *end = (int)(li->lastchar - li->buffer); 468 469 if (attempted_completion_function) { 470 int cur_off = (int)(li->cursor - li->buffer); 471 matches = (*attempted_completion_function) (ct_encode_string(temp, &el->el_scratch), 472 (int)(cur_off - len), cur_off); 473 } else 474 matches = 0; 475 if (!attempted_completion_function || 476 (over != NULL && !*over && !matches)) 477 matches = completion_matches(ct_encode_string(temp, &el->el_scratch), complet_func); 478 479 if (over != NULL) 480 *over = 0; 481 482 if (matches) { 483 int i; 484 size_t matches_num, maxlen, match_len, match_display=1; 485 486 retval = CC_REFRESH; 487 /* 488 * Only replace the completed string with common part of 489 * possible matches if there is possible completion. 490 */ 491 if (matches[0][0] != '\0') { 492 el_deletestr(el, (int) len); 493 FUN(el,insertstr)(el, 494 ct_decode_string(matches[0], &el->el_scratch)); 495 } 496 497 if (what_to_do == '?') 498 goto display_matches; 499 500 if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) { 501 /* 502 * We found exact match. Add a space after 503 * it, unless we do filename completion and the 504 * object is a directory. 505 */ 506 FUN(el,insertstr)(el, 507 ct_decode_string((*app_func)(matches[0]), 508 &el->el_scratch)); 509 } else if (what_to_do == '!') { 510 display_matches: 511 /* 512 * More than one match and requested to list possible 513 * matches. 514 */ 515 516 for(i = 1, maxlen = 0; matches[i]; i++) { 517 match_len = strlen(matches[i]); 518 if (match_len > maxlen) 519 maxlen = match_len; 520 } 521 /* matches[1] through matches[i-1] are available */ 522 matches_num = i - 1; 523 524 /* newline to get on next line from command line */ 525 (void)fprintf(el->el_outfile, "\n"); 526 527 /* 528 * If there are too many items, ask user for display 529 * confirmation. 530 */ 531 if (matches_num > query_items) { 532 (void)fprintf(el->el_outfile, 533 "Display all %zu possibilities? (y or n) ", 534 matches_num); 535 (void)fflush(el->el_outfile); 536 if (getc(stdin) != 'y') 537 match_display = 0; 538 (void)fprintf(el->el_outfile, "\n"); 539 } 540 541 if (match_display) { 542 /* 543 * Interface of this function requires the 544 * strings be matches[1..num-1] for compat. 545 * We have matches_num strings not counting 546 * the prefix in matches[0], so we need to 547 * add 1 to matches_num for the call. 548 */ 549 fn_display_match_list(el, matches, 550 matches_num+1, maxlen); 551 } 552 retval = CC_REDISPLAY; 553 } else if (matches[0][0]) { 554 /* 555 * There was some common match, but the name was 556 * not complete enough. Next tab will print possible 557 * completions. 558 */ 559 el_beep(el); 560 } else { 561 /* lcd is not a valid object - further specification */ 562 /* is needed */ 563 el_beep(el); 564 retval = CC_NORM; 565 } 566 567 /* free elements of array and the array itself */ 568 for (i = 0; matches[i]; i++) 569 free(matches[i]); 570 free(matches); 571 matches = NULL; 572 } 573#if defined(__SSP__) || defined(__SSP_ALL__) 574 free(temp); 575#endif 576 return retval; 577} 578 579/* 580 * el-compatible wrapper around rl_complete; needed for key binding 581 */ 582/* ARGSUSED */ 583unsigned char 584_el_fn_complete(EditLine *el, int ch __attribute__((__unused__))) 585{ 586 return (unsigned char)fn_complete(el, NULL, NULL, 587 break_chars, NULL, NULL, 100, 588 NULL, NULL, NULL, NULL); 589} 590