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 */
_new_CompleteFile(void)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 */
_del_CompleteFile(CompleteFile * cf)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 */
_cf_complete_file(WordCompletion * cpl,CompleteFile * cf,const char * line,int word_start,int word_end,int escaped,CplCheckFn * check_fn,void * check_data)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 */
_cf_last_error(CompleteFile * cf)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 */
cf_expand_home_dir(CompleteFile * cf,const char * user)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 */
cf_complete_username(CompleteFile * cf,WordCompletion * cpl,const char * prefix,const char * line,int word_start,int word_end,int escaped)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 */
HOME_DIR_FN(cf_homedir_callback)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 */
cf_complete_entry(CompleteFile * cf,WordCompletion * cpl,const char * line,int word_start,int word_end,int escaped,CplCheckFn * check_fn,void * check_data)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 */
cf_read_name(CompleteFile * cf,const char * type,const char * string,int slen,char * nambuf,int nammax)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 */
cf_prepare_suffix(CompleteFile * cf,const char * suffix,int add_escapes)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