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