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