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