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