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