1 /* 2 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd. 3 * 4 * All rights reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, and/or sell copies of the Software, and to permit persons 11 * to whom the Software is furnished to do so, provided that the above 12 * copyright notice(s) and this permission notice appear in all copies of 13 * the Software and that both the above copyright notice(s) and this 14 * permission notice appear in supporting documentation. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 19 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 20 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL 21 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING 22 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 23 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 24 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25 * 26 * Except as contained in this notice, the name of a copyright holder 27 * shall not be used in advertising or otherwise to promote the sale, use 28 * or other dealings in this Software without prior written authorization 29 * of the copyright holder. 30 */ 31 32 /* 33 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 34 * Use is subject to license terms. 35 */ 36 37 #pragma ident "%Z%%M% %I% %E% SMI" 38 39 /* 40 * If file-system access is to be excluded, this module has no function, 41 * so all of its code should be excluded. 42 */ 43 #ifndef WITHOUT_FILE_SYSTEM 44 45 /* 46 * Standard includes. 47 */ 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <limits.h> 51 #include <errno.h> 52 #include <string.h> 53 #include <ctype.h> 54 55 /* 56 * Local includes. 57 */ 58 #include "libtecla.h" 59 #include "direader.h" 60 #include "homedir.h" 61 #include "pathutil.h" 62 #include "cplfile.h" 63 #include "errmsg.h" 64 65 /* 66 * Set the maximum length allowed for usernames. 67 * names. 68 */ 69 #define USR_LEN 100 70 71 /* 72 * Set the maximum length allowed for environment variable names. 73 */ 74 #define ENV_LEN 100 75 76 /* 77 * The resources needed to complete a filename are maintained in objects 78 * of the following type. 79 */ 80 struct CompleteFile { 81 ErrMsg *err; /* The error reporting buffer */ 82 DirReader *dr; /* A directory reader */ 83 HomeDir *home; /* A home directory expander */ 84 PathName *path; /* The buffer in which to accumulate the path */ 85 PathName *buff; /* A pathname work buffer */ 86 char usrnam[USR_LEN+1]; /* The buffer used when reading the names of */ 87 /* users. */ 88 char envnam[ENV_LEN+1]; /* The buffer used when reading the names of */ 89 /* environment variables. */ 90 }; 91 92 static int cf_expand_home_dir(CompleteFile *cf, const char *user); 93 static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl, 94 const char *prefix, const char *line, 95 int word_start, int word_end, int escaped); 96 static HOME_DIR_FN(cf_homedir_callback); 97 static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl, 98 const char *line, int word_start, int word_end, 99 int escaped, CplCheckFn *check_fn, 100 void *check_data); 101 static char *cf_read_name(CompleteFile *cf, const char *type, 102 const char *string, int slen, 103 char *nambuf, int nammax); 104 static int cf_prepare_suffix(CompleteFile *cf, const char *suffix, 105 int add_escapes); 106 107 /* 108 * A stack based object of the following type is used to pass data to the 109 * cf_homedir_callback() function. 110 */ 111 typedef struct { 112 CompleteFile *cf; /* The file-completion resource object */ 113 WordCompletion *cpl; /* The string-completion rsource object */ 114 size_t prefix_len; /* The length of the prefix being completed */ 115 const char *line; /* The line from which the prefix was extracted */ 116 int word_start; /* The index in line[] of the start of the username */ 117 int word_end; /* The index in line[] following the end of the prefix */ 118 int escaped; /* If true, add escapes to the completion suffixes */ 119 } CfHomeArgs; 120 121 /*....................................................................... 122 * Create a new file-completion object. 123 * 124 * Output: 125 * return CompleteFile * The new object, or NULL on error. 126 */ 127 CompleteFile *_new_CompleteFile(void) 128 { 129 CompleteFile *cf; /* The object to be returned */ 130 /* 131 * Allocate the container. 132 */ 133 cf = (CompleteFile *) malloc(sizeof(CompleteFile)); 134 if(!cf) { 135 errno = ENOMEM; 136 return NULL; 137 }; 138 /* 139 * Before attempting any operation that might fail, initialize the 140 * container at least up to the point at which it can safely be passed 141 * to _del_CompleteFile(). 142 */ 143 cf->err = NULL; 144 cf->dr = NULL; 145 cf->home = NULL; 146 cf->path = NULL; 147 cf->buff = NULL; 148 cf->usrnam[0] = '\0'; 149 cf->envnam[0] = '\0'; 150 /* 151 * Allocate a place to record error messages. 152 */ 153 cf->err = _new_ErrMsg(); 154 if(!cf->err) 155 return _del_CompleteFile(cf); 156 /* 157 * Create the object that is used for reading directories. 158 */ 159 cf->dr = _new_DirReader(); 160 if(!cf->dr) 161 return _del_CompleteFile(cf); 162 /* 163 * Create the object that is used to lookup home directories. 164 */ 165 cf->home = _new_HomeDir(); 166 if(!cf->home) 167 return _del_CompleteFile(cf); 168 /* 169 * Create the buffer in which the completed pathname is accumulated. 170 */ 171 cf->path = _new_PathName(); 172 if(!cf->path) 173 return _del_CompleteFile(cf); 174 /* 175 * Create a pathname work buffer. 176 */ 177 cf->buff = _new_PathName(); 178 if(!cf->buff) 179 return _del_CompleteFile(cf); 180 return cf; 181 } 182 183 /*....................................................................... 184 * Delete a file-completion object. 185 * 186 * Input: 187 * cf CompleteFile * The object to be deleted. 188 * Output: 189 * return CompleteFile * The deleted object (always NULL). 190 */ 191 CompleteFile *_del_CompleteFile(CompleteFile *cf) 192 { 193 if(cf) { 194 cf->err = _del_ErrMsg(cf->err); 195 cf->dr = _del_DirReader(cf->dr); 196 cf->home = _del_HomeDir(cf->home); 197 cf->path = _del_PathName(cf->path); 198 cf->buff = _del_PathName(cf->buff); 199 free(cf); 200 }; 201 return NULL; 202 } 203 204 /*....................................................................... 205 * Look up the possible completions of the incomplete filename that 206 * lies between specified indexes of a given command-line string. 207 * 208 * Input: 209 * cpl WordCompletion * The object in which to record the completions. 210 * cf CompleteFile * The filename-completion resource object. 211 * line const char * The string containing the incomplete filename. 212 * word_start int The index of the first character in line[] 213 * of the incomplete filename. 214 * word_end int The index of the character in line[] that 215 * follows the last character of the incomplete 216 * filename. 217 * escaped int If true, backslashes in line[] are 218 * interpreted as escaping the characters 219 * that follow them, and any spaces, tabs, 220 * backslashes, or wildcard characters in the 221 * returned suffixes will be similarly escaped. 222 * If false, backslashes will be interpreted as 223 * literal parts of the file name, and no 224 * backslashes will be added to the returned 225 * suffixes. 226 * check_fn CplCheckFn * If not zero, this argument specifies a 227 * function to call to ask whether a given 228 * file should be included in the list 229 * of completions. 230 * check_data void * Anonymous data to be passed to check_fn(). 231 * Output: 232 * return int 0 - OK. 233 * 1 - Error. A description of the error can be 234 * acquired by calling _cf_last_error(cf). 235 */ 236 int _cf_complete_file(WordCompletion *cpl, CompleteFile *cf, 237 const char *line, int word_start, int word_end, 238 int escaped, CplCheckFn *check_fn, void *check_data) 239 { 240 const char *lptr; /* A pointer into line[] */ 241 int nleft; /* The number of characters still to be processed */ 242 /* in line[]. */ 243 /* 244 * Check the arguments. 245 */ 246 if(!cpl || !cf || !line || word_end < word_start) { 247 if(cf) { 248 _err_record_msg(cf->err, "_cf_complete_file: Invalid arguments", 249 END_ERR_MSG); 250 }; 251 return 1; 252 }; 253 /* 254 * Clear the buffer in which the filename will be constructed. 255 */ 256 _pn_clear_path(cf->path); 257 /* 258 * How many characters are to be processed? 259 */ 260 nleft = word_end - word_start; 261 /* 262 * Get a pointer to the start of the incomplete filename. 263 */ 264 lptr = line + word_start; 265 /* 266 * If the first character is a tilde, then perform home-directory 267 * interpolation. 268 */ 269 if(nleft > 0 && *lptr == '~') { 270 int slen; 271 if(!cf_read_name(cf, "User", ++lptr, --nleft, cf->usrnam, USR_LEN)) 272 return 1; 273 /* 274 * Advance over the username in the input line. 275 */ 276 slen = strlen(cf->usrnam); 277 lptr += slen; 278 nleft -= slen; 279 /* 280 * If we haven't hit the end of the input string then we have a complete 281 * username to translate to the corresponding home directory. 282 */ 283 if(nleft > 0) { 284 if(cf_expand_home_dir(cf, cf->usrnam)) 285 return 1; 286 /* 287 * ~user and ~ are usually followed by a directory separator to 288 * separate them from the file contained in the home directory. 289 * If the home directory is the root directory, then we don't want 290 * to follow the home directory by a directory separator, so we should 291 * skip over it so that it doesn't get copied into the filename. 292 */ 293 if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 && 294 strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { 295 lptr += FS_DIR_SEP_LEN; 296 nleft -= FS_DIR_SEP_LEN; 297 }; 298 /* 299 * If we have reached the end of the input string, then the username 300 * may be incomplete, and we should attempt to complete it. 301 */ 302 } else { 303 /* 304 * Look up the possible completions of the username. 305 */ 306 return cf_complete_username(cf, cpl, cf->usrnam, line, word_start+1, 307 word_end, escaped); 308 }; 309 }; 310 /* 311 * Copy the rest of the path, stopping to expand $envvar expressions 312 * where encountered. 313 */ 314 while(nleft > 0) { 315 int seglen; /* The length of the next segment to be copied */ 316 /* 317 * Find the length of the next segment to be copied, stopping if an 318 * unescaped '$' is seen, or the end of the path is reached. 319 */ 320 for(seglen=0; seglen < nleft; seglen++) { 321 int c = lptr[seglen]; 322 if(escaped && c == '\\') 323 seglen++; 324 else if(c == '$') 325 break; 326 /* 327 * We will be completing the last component of the file name, 328 * so whenever a directory separator is seen, assume that it 329 * might be the start of the last component, and mark the character 330 * that follows it as the start of the name that is to be completed. 331 */ 332 if(nleft >= FS_DIR_SEP_LEN && 333 strncmp(lptr + seglen, FS_DIR_SEP, FS_DIR_SEP_LEN)==0) { 334 word_start = (lptr + seglen) - line + FS_DIR_SEP_LEN; 335 }; 336 }; 337 /* 338 * We have reached either the end of the filename or the start of 339 * $environment_variable expression. Record the newly checked 340 * segment of the filename in the output filename, removing 341 * backslash-escapes where needed. 342 */ 343 if(_pn_append_to_path(cf->path, lptr, seglen, escaped) == NULL) { 344 _err_record_msg(cf->err, "Insufficient memory to complete filename", 345 END_ERR_MSG); 346 return 1; 347 }; 348 lptr += seglen; 349 nleft -= seglen; 350 /* 351 * If the above loop finished before we hit the end of the filename, 352 * then this was because an unescaped $ was seen. In this case, interpolate 353 * the value of the environment variable that follows it into the output 354 * filename. 355 */ 356 if(nleft > 0) { 357 char *value; /* The value of the environment variable */ 358 int vlen; /* The length of the value string */ 359 int nlen; /* The length of the environment variable name */ 360 /* 361 * Read the name of the environment variable. 362 */ 363 if(!cf_read_name(cf, "Environment", ++lptr, --nleft, cf->envnam, ENV_LEN)) 364 return 1; 365 /* 366 * Advance over the environment variable name in the input line. 367 */ 368 nlen = strlen(cf->envnam); 369 lptr += nlen; 370 nleft -= nlen; 371 /* 372 * Get the value of the environment variable. 373 */ 374 value = getenv(cf->envnam); 375 if(!value) { 376 _err_record_msg(cf->err, "Unknown environment variable: ", cf->envnam, 377 END_ERR_MSG); 378 return 1; 379 }; 380 vlen = strlen(value); 381 /* 382 * If we are at the start of the filename and the first character of the 383 * environment variable value is a '~', attempt home-directory 384 * interpolation. 385 */ 386 if(cf->path->name[0] == '\0' && value[0] == '~') { 387 if(!cf_read_name(cf, "User", value+1, vlen-1, cf->usrnam, USR_LEN) || 388 cf_expand_home_dir(cf, cf->usrnam)) 389 return 1; 390 /* 391 * If the home directory is the root directory, and the ~usrname expression 392 * was followed by a directory separator, prevent the directory separator 393 * from being appended to the root directory by skipping it in the 394 * input line. 395 */ 396 if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 && 397 strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { 398 lptr += FS_DIR_SEP_LEN; 399 nleft -= FS_DIR_SEP_LEN; 400 }; 401 } else { 402 /* 403 * Append the value of the environment variable to the output path. 404 */ 405 if(_pn_append_to_path(cf->path, value, strlen(value), escaped)==NULL) { 406 _err_record_msg(cf->err, "Insufficient memory to complete filename", 407 END_ERR_MSG); 408 return 1; 409 }; 410 /* 411 * Prevent extra directory separators from being added. 412 */ 413 if(nleft >= FS_DIR_SEP_LEN && 414 strcmp(cf->path->name, FS_ROOT_DIR) == 0 && 415 strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { 416 lptr += FS_DIR_SEP_LEN; 417 nleft -= FS_DIR_SEP_LEN; 418 } else if(vlen > FS_DIR_SEP_LEN && 419 strcmp(value + vlen - FS_DIR_SEP_LEN, FS_DIR_SEP)==0) { 420 cf->path->name[vlen-FS_DIR_SEP_LEN] = '\0'; 421 }; 422 }; 423 /* 424 * If adding the environment variable didn't form a valid directory, 425 * we can't complete the line, since there is no way to separate append 426 * a partial filename to an environment variable reference without 427 * that appended part of the name being seen later as part of the 428 * environment variable name. Thus if the currently constructed path 429 * isn't a directory, quite now with no completions having been 430 * registered. 431 */ 432 if(!_pu_path_is_dir(cf->path->name)) 433 return 0; 434 /* 435 * For the reasons given above, if we have reached the end of the filename 436 * with the expansion of an environment variable, the only allowed 437 * completion involves the addition of a directory separator. 438 */ 439 if(nleft == 0) { 440 if(cpl_add_completion(cpl, line, lptr-line, word_end, FS_DIR_SEP, 441 "", "")) { 442 _err_record_msg(cf->err, cpl_last_error(cpl), END_ERR_MSG); 443 return 1; 444 }; 445 return 0; 446 }; 447 }; 448 }; 449 /* 450 * Complete the filename if possible. 451 */ 452 return cf_complete_entry(cf, cpl, line, word_start, word_end, escaped, 453 check_fn, check_data); 454 } 455 456 /*....................................................................... 457 * Return a description of the last path-completion error that occurred. 458 * 459 * Input: 460 * cf CompleteFile * The path-completion resource object. 461 * Output: 462 * return const char * The description of the last error. 463 */ 464 const char *_cf_last_error(CompleteFile *cf) 465 { 466 return cf ? _err_get_msg(cf->err) : "NULL CompleteFile argument"; 467 } 468 469 /*....................................................................... 470 * Lookup the home directory of the specified user, or the current user 471 * if no name is specified, appending it to output pathname. 472 * 473 * Input: 474 * cf CompleteFile * The pathname completion resource object. 475 * user const char * The username to lookup, or "" to lookup the 476 * current user. 477 * Output: 478 * return int 0 - OK. 479 * 1 - Error. 480 */ 481 static int cf_expand_home_dir(CompleteFile *cf, const char *user) 482 { 483 /* 484 * Attempt to lookup the home directory. 485 */ 486 const char *home_dir = _hd_lookup_home_dir(cf->home, user); 487 /* 488 * Failed? 489 */ 490 if(!home_dir) { 491 _err_record_msg(cf->err, _hd_last_home_dir_error(cf->home), END_ERR_MSG); 492 return 1; 493 }; 494 /* 495 * Append the home directory to the pathname string. 496 */ 497 if(_pn_append_to_path(cf->path, home_dir, -1, 0) == NULL) { 498 _err_record_msg(cf->err, "Insufficient memory for home directory expansion", 499 END_ERR_MSG); 500 return 1; 501 }; 502 return 0; 503 } 504 505 /*....................................................................... 506 * Lookup and report all completions of a given username prefix. 507 * 508 * Input: 509 * cf CompleteFile * The filename-completion resource object. 510 * cpl WordCompletion * The object in which to record the completions. 511 * prefix const char * The prefix of the usernames to lookup. 512 * line const char * The command-line in which the username appears. 513 * word_start int The index within line[] of the start of the 514 * username that is being completed. 515 * word_end int The index within line[] of the character which 516 * follows the incomplete username. 517 * escaped int True if the completions need to have special 518 * characters escaped. 519 * Output: 520 * return int 0 - OK. 521 * 1 - Error. 522 */ 523 static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl, 524 const char *prefix, const char *line, 525 int word_start, int word_end, int escaped) 526 { 527 /* 528 * Set up a container of anonymous arguments to be sent to the 529 * username-lookup iterator. 530 */ 531 CfHomeArgs args; 532 args.cf = cf; 533 args.cpl = cpl; 534 args.prefix_len = strlen(prefix); 535 args.line = line; 536 args.word_start = word_start; 537 args.word_end = word_end; 538 args.escaped = escaped; 539 /* 540 * Iterate through the list of users, recording those which start 541 * with the specified prefix. 542 */ 543 if(_hd_scan_user_home_dirs(cf->home, prefix, &args, cf_homedir_callback)) { 544 _err_record_msg(cf->err, _hd_last_home_dir_error(cf->home), END_ERR_MSG); 545 return 1; 546 }; 547 return 0; 548 } 549 550 /*....................................................................... 551 * The user/home-directory scanner callback function (see homedir.h) 552 * used by cf_complete_username(). 553 */ 554 static HOME_DIR_FN(cf_homedir_callback) 555 { 556 /* 557 * Get the file-completion resources from the anonymous data argument. 558 */ 559 CfHomeArgs *args = (CfHomeArgs *) data; 560 WordCompletion *cpl = args->cpl; 561 CompleteFile *cf = args->cf; 562 /* 563 * Copy the username into the pathname work buffer, adding backslash 564 * escapes where needed. 565 */ 566 if(cf_prepare_suffix(cf, usrnam+args->prefix_len, args->escaped)) { 567 strncpy(errmsg, _err_get_msg(cf->err), maxerr); 568 errmsg[maxerr] = '\0'; 569 return 1; 570 }; 571 /* 572 * Report the completion suffix that was copied above. 573 */ 574 if(cpl_add_completion(cpl, args->line, args->word_start, args->word_end, 575 cf->buff->name, FS_DIR_SEP, FS_DIR_SEP)) { 576 strncpy(errmsg, cpl_last_error(cpl), maxerr); 577 errmsg[maxerr] = '\0'; 578 return 1; 579 }; 580 return 0; 581 } 582 583 /*....................................................................... 584 * Report possible completions of the filename in cf->path->name[]. 585 * 586 * Input: 587 * cf CompleteFile * The file-completion resource object. 588 * cpl WordCompletion * The object in which to record the completions. 589 * line const char * The input line, as received by the callback 590 * function. 591 * word_start int The index within line[] of the start of the 592 * last component of the filename that is being 593 * completed. 594 * word_end int The index within line[] of the character which 595 * follows the incomplete filename. 596 * escaped int If true, escape special characters in the 597 * completion suffixes. 598 * check_fn CplCheckFn * If not zero, this argument specifies a 599 * function to call to ask whether a given 600 * file should be included in the list 601 * of completions. 602 * check_data void * Anonymous data to be passed to check_fn(). 603 * Output: 604 * return int 0 - OK. 605 * 1 - Error. 606 */ 607 static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl, 608 const char *line, int word_start, int word_end, 609 int escaped, CplCheckFn *check_fn, 610 void *check_data) 611 { 612 const char *dirpath; /* The name of the parent directory */ 613 int start; /* The index of the start of the last filename */ 614 /* component in the transcribed filename. */ 615 const char *prefix; /* The filename prefix to be completed */ 616 int prefix_len; /* The length of the filename prefix */ 617 const char *file_name; /* The lastest filename being compared */ 618 int waserr = 0; /* True after errors */ 619 int terminated=0; /* True if the directory part had to be terminated */ 620 /* 621 * Get the pathname string and its current length. 622 */ 623 char *pathname = cf->path->name; 624 int pathlen = strlen(pathname); 625 /* 626 * Locate the start of the final component of the pathname. 627 */ 628 for(start=pathlen - 1; start >= 0 && 629 strncmp(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; start--) 630 ; 631 /* 632 * Is the parent directory the root directory? 633 */ 634 if(start==0 || 635 (start < 0 && strncmp(pathname, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0)) { 636 dirpath = FS_ROOT_DIR; 637 start += FS_ROOT_DIR_LEN; 638 /* 639 * If we found a directory separator then the part which precedes the 640 * last component is the name of the directory to be opened. 641 */ 642 } else if(start > 0) { 643 /* 644 * The _dr_open_dir() function requires the directory name to be '\0' 645 * terminated, so temporarily do this by overwriting the first character 646 * of the directory separator. 647 */ 648 pathname[start] = '\0'; 649 dirpath = pathname; 650 terminated = 1; 651 /* 652 * We reached the start of the pathname before finding a directory 653 * separator, so arrange to open the current working directory. 654 */ 655 } else { 656 start = 0; 657 dirpath = FS_PWD; 658 }; 659 /* 660 * Attempt to open the directory. 661 */ 662 if(_dr_open_dir(cf->dr, dirpath, NULL)) { 663 _err_record_msg(cf->err, "Can't open directory: ", dirpath, END_ERR_MSG); 664 return 1; 665 }; 666 /* 667 * If removed above, restore the directory separator and skip over it 668 * to the start of the filename. 669 */ 670 if(terminated) { 671 memcpy(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN); 672 start += FS_DIR_SEP_LEN; 673 }; 674 /* 675 * Get the filename prefix and its length. 676 */ 677 prefix = pathname + start; 678 prefix_len = strlen(prefix); 679 /* 680 * Traverse the directory, looking for files who's prefixes match the 681 * last component of the pathname. 682 */ 683 while((file_name = _dr_next_file(cf->dr)) != NULL && !waserr) { 684 int name_len = strlen(file_name); 685 /* 686 * Is the latest filename a possible completion of the filename prefix? 687 */ 688 if(name_len >= prefix_len && strncmp(prefix, file_name, prefix_len)==0) { 689 /* 690 * When listing all files in a directory, don't list files that start 691 * with '.'. This is how hidden files are denoted in UNIX. 692 */ 693 if(prefix_len > 0 || file_name[0] != '.') { 694 /* 695 * Copy the completion suffix into the work pathname cf->buff->name, 696 * adding backslash escapes if needed. 697 */ 698 if(cf_prepare_suffix(cf, file_name + prefix_len, escaped)) { 699 waserr = 1; 700 } else { 701 /* 702 * We want directories to be displayed with directory suffixes, 703 * and other fully completed filenames to be followed by spaces. 704 * To check the type of the file, append the current suffix 705 * to the path being completed, check the filetype, then restore 706 * the path to its original form. 707 */ 708 const char *cont_suffix = ""; /* The suffix to add if fully */ 709 /* completed. */ 710 const char *type_suffix = ""; /* The suffix to add when listing */ 711 if(_pn_append_to_path(cf->path, file_name + prefix_len, 712 -1, escaped) == NULL) { 713 _err_record_msg(cf->err, 714 "Insufficient memory to complete filename.", 715 END_ERR_MSG); 716 return 1; 717 }; 718 /* 719 * Specify suffixes according to the file type. 720 */ 721 if(_pu_path_is_dir(cf->path->name)) { 722 cont_suffix = FS_DIR_SEP; 723 type_suffix = FS_DIR_SEP; 724 } else if(!check_fn || check_fn(check_data, cf->path->name)) { 725 cont_suffix = " "; 726 } else { 727 cf->path->name[pathlen] = '\0'; 728 continue; 729 }; 730 /* 731 * Remove the temporarily added suffix. 732 */ 733 cf->path->name[pathlen] = '\0'; 734 /* 735 * Record the latest completion. 736 */ 737 if(cpl_add_completion(cpl, line, word_start, word_end, cf->buff->name, 738 type_suffix, cont_suffix)) 739 waserr = 1; 740 }; 741 }; 742 }; 743 }; 744 /* 745 * Close the directory. 746 */ 747 _dr_close_dir(cf->dr); 748 return waserr; 749 } 750 751 /*....................................................................... 752 * Read a username or environment variable name, stopping when a directory 753 * separator is seen, when the end of the string is reached, or the 754 * output buffer overflows. 755 * 756 * Input: 757 * cf CompleteFile * The file-completion resource object. 758 * type char * The capitalized name of the type of name being read. 759 * string char * The string who's prefix contains the name. 760 * slen int The number of characters in string[]. 761 * nambuf char * The output name buffer. 762 * nammax int The longest string that will fit in nambuf[], excluding 763 * the '\0' terminator. 764 * Output: 765 * return char * A pointer to nambuf on success. On error NULL is 766 * returned and a description of the error is recorded 767 * in cf->err. 768 */ 769 static char *cf_read_name(CompleteFile *cf, const char *type, 770 const char *string, int slen, 771 char *nambuf, int nammax) 772 { 773 int namlen; /* The number of characters in nambuf[] */ 774 const char *sptr; /* A pointer into string[] */ 775 /* 776 * Work out the max number of characters that should be copied. 777 */ 778 int nmax = nammax < slen ? nammax : slen; 779 /* 780 * Get the environment variable name that follows the dollar. 781 */ 782 for(sptr=string,namlen=0; 783 namlen < nmax && (slen-namlen < FS_DIR_SEP_LEN || 784 strncmp(sptr, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0); 785 namlen++) { 786 nambuf[namlen] = *sptr++; 787 }; 788 /* 789 * Did the name overflow the buffer? 790 */ 791 if(namlen >= nammax) { 792 _err_record_msg(cf->err, type, " name too long", END_ERR_MSG); 793 return NULL; 794 }; 795 /* 796 * Terminate the string. 797 */ 798 nambuf[namlen] = '\0'; 799 return nambuf; 800 } 801 802 /*....................................................................... 803 * Using the work buffer cf->buff, make a suitably escaped copy of a 804 * given completion suffix, ready to be passed to cpl_add_completion(). 805 * 806 * Input: 807 * cf CompleteFile * The file-completion resource object. 808 * suffix char * The suffix to be copied. 809 * add_escapes int If true, escape special characters. 810 * Output: 811 * return int 0 - OK. 812 * 1 - Error. 813 */ 814 static int cf_prepare_suffix(CompleteFile *cf, const char *suffix, 815 int add_escapes) 816 { 817 const char *sptr; /* A pointer into suffix[] */ 818 int nbsl; /* The number of backslashes to add to the suffix */ 819 int i; 820 /* 821 * How long is the suffix? 822 */ 823 int suffix_len = strlen(suffix); 824 /* 825 * Clear the work buffer. 826 */ 827 _pn_clear_path(cf->buff); 828 /* 829 * Count the number of backslashes that will have to be added to 830 * escape spaces, tabs, backslashes and wildcard characters. 831 */ 832 nbsl = 0; 833 if(add_escapes) { 834 for(sptr = suffix; *sptr; sptr++) { 835 switch(*sptr) { 836 case ' ': case '\t': case '\\': case '*': case '?': case '[': 837 nbsl++; 838 break; 839 }; 840 }; 841 }; 842 /* 843 * Arrange for the output path buffer to have sufficient room for the 844 * both the suffix and any backslashes that have to be inserted. 845 */ 846 if(_pn_resize_path(cf->buff, suffix_len + nbsl) == NULL) { 847 _err_record_msg(cf->err, "Insufficient memory to complete filename", 848 END_ERR_MSG); 849 return 1; 850 }; 851 /* 852 * If the suffix doesn't need any escapes, copy it directly into the 853 * work buffer. 854 */ 855 if(nbsl==0) { 856 strlcpy(cf->buff->name, suffix, cf->buff->dim); 857 } else { 858 /* 859 * Make a copy with special characters escaped? 860 */ 861 if(nbsl > 0) { 862 const char *src = suffix; 863 char *dst = cf->buff->name; 864 for(i=0; i<suffix_len; i++) { 865 switch(*src) { 866 case ' ': case '\t': case '\\': case '*': case '?': case '[': 867 *dst++ = '\\'; 868 }; 869 *dst++ = *src++; 870 }; 871 *dst = '\0'; 872 }; 873 }; 874 return 0; 875 } 876 877 #endif /* ifndef WITHOUT_FILE_SYSTEM */ 878