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