xref: /titanic_52/usr/src/lib/libtecla/common/cplfile.c (revision c2580b931007758eab8cb5ae8726ebe1588e259b)
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