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