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 */
80struct 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
92static int cf_expand_home_dir(CompleteFile *cf, const char *user);
93static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl,
94				const char *prefix, const char *line,
95				int word_start, int word_end, int escaped);
96static HOME_DIR_FN(cf_homedir_callback);
97static 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);
101static char *cf_read_name(CompleteFile *cf, const char *type,
102			  const char *string, int slen,
103			  char *nambuf, int nammax);
104static 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 */
111typedef 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 */
127CompleteFile *_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 */
191CompleteFile *_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 */
236int _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 */
464const 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 */
481static 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 */
523static 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 */
554static 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 */
607static 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 */
769static 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 */
814static 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