17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3*1da57d55SToomas Soome  *
47c478bd9Sstevel@tonic-gate  * All rights reserved.
5*1da57d55SToomas Soome  *
67c478bd9Sstevel@tonic-gate  * Permission is hereby granted, free of charge, to any person obtaining a
77c478bd9Sstevel@tonic-gate  * copy of this software and associated documentation files (the
87c478bd9Sstevel@tonic-gate  * "Software"), to deal in the Software without restriction, including
97c478bd9Sstevel@tonic-gate  * without limitation the rights to use, copy, modify, merge, publish,
107c478bd9Sstevel@tonic-gate  * distribute, and/or sell copies of the Software, and to permit persons
117c478bd9Sstevel@tonic-gate  * to whom the Software is furnished to do so, provided that the above
127c478bd9Sstevel@tonic-gate  * copyright notice(s) and this permission notice appear in all copies of
137c478bd9Sstevel@tonic-gate  * the Software and that both the above copyright notice(s) and this
147c478bd9Sstevel@tonic-gate  * permission notice appear in supporting documentation.
15*1da57d55SToomas Soome  *
167c478bd9Sstevel@tonic-gate  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
177c478bd9Sstevel@tonic-gate  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
187c478bd9Sstevel@tonic-gate  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
197c478bd9Sstevel@tonic-gate  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
207c478bd9Sstevel@tonic-gate  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
217c478bd9Sstevel@tonic-gate  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
227c478bd9Sstevel@tonic-gate  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
237c478bd9Sstevel@tonic-gate  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
247c478bd9Sstevel@tonic-gate  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25*1da57d55SToomas Soome  *
267c478bd9Sstevel@tonic-gate  * Except as contained in this notice, the name of a copyright holder
277c478bd9Sstevel@tonic-gate  * shall not be used in advertising or otherwise to promote the sale, use
287c478bd9Sstevel@tonic-gate  * or other dealings in this Software without prior written authorization
297c478bd9Sstevel@tonic-gate  * of the copyright holder.
307c478bd9Sstevel@tonic-gate  */
317c478bd9Sstevel@tonic-gate 
327c478bd9Sstevel@tonic-gate /*
337c478bd9Sstevel@tonic-gate  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
347c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
357c478bd9Sstevel@tonic-gate  */
367c478bd9Sstevel@tonic-gate 
377c478bd9Sstevel@tonic-gate /*
387c478bd9Sstevel@tonic-gate  * If file-system access is to be excluded, this module has no function,
397c478bd9Sstevel@tonic-gate  * so all of its code should be excluded.
407c478bd9Sstevel@tonic-gate  */
417c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
427c478bd9Sstevel@tonic-gate 
437c478bd9Sstevel@tonic-gate /*
447c478bd9Sstevel@tonic-gate  * Standard includes.
457c478bd9Sstevel@tonic-gate  */
467c478bd9Sstevel@tonic-gate #include <stdio.h>
477c478bd9Sstevel@tonic-gate #include <stdlib.h>
487c478bd9Sstevel@tonic-gate #include <limits.h>
497c478bd9Sstevel@tonic-gate #include <errno.h>
507c478bd9Sstevel@tonic-gate #include <string.h>
517c478bd9Sstevel@tonic-gate #include <ctype.h>
527c478bd9Sstevel@tonic-gate 
537c478bd9Sstevel@tonic-gate /*
547c478bd9Sstevel@tonic-gate  * Local includes.
557c478bd9Sstevel@tonic-gate  */
567c478bd9Sstevel@tonic-gate #include "libtecla.h"
577c478bd9Sstevel@tonic-gate #include "direader.h"
587c478bd9Sstevel@tonic-gate #include "homedir.h"
597c478bd9Sstevel@tonic-gate #include "pathutil.h"
607c478bd9Sstevel@tonic-gate #include "cplfile.h"
617c478bd9Sstevel@tonic-gate #include "errmsg.h"
627c478bd9Sstevel@tonic-gate 
637c478bd9Sstevel@tonic-gate /*
647c478bd9Sstevel@tonic-gate  * Set the maximum length allowed for usernames.
657c478bd9Sstevel@tonic-gate  * names.
667c478bd9Sstevel@tonic-gate  */
677c478bd9Sstevel@tonic-gate #define USR_LEN 100
687c478bd9Sstevel@tonic-gate 
697c478bd9Sstevel@tonic-gate /*
707c478bd9Sstevel@tonic-gate  * Set the maximum length allowed for environment variable names.
717c478bd9Sstevel@tonic-gate  */
727c478bd9Sstevel@tonic-gate #define ENV_LEN 100
737c478bd9Sstevel@tonic-gate 
747c478bd9Sstevel@tonic-gate /*
757c478bd9Sstevel@tonic-gate  * The resources needed to complete a filename are maintained in objects
767c478bd9Sstevel@tonic-gate  * of the following type.
777c478bd9Sstevel@tonic-gate  */
787c478bd9Sstevel@tonic-gate struct CompleteFile {
797c478bd9Sstevel@tonic-gate   ErrMsg *err;                 /* The error reporting buffer */
807c478bd9Sstevel@tonic-gate   DirReader *dr;               /* A directory reader */
817c478bd9Sstevel@tonic-gate   HomeDir *home;               /* A home directory expander */
827c478bd9Sstevel@tonic-gate   PathName *path;              /* The buffer in which to accumulate the path */
837c478bd9Sstevel@tonic-gate   PathName *buff;              /* A pathname work buffer */
847c478bd9Sstevel@tonic-gate   char usrnam[USR_LEN+1];      /* The buffer used when reading the names of */
857c478bd9Sstevel@tonic-gate                                /*  users. */
867c478bd9Sstevel@tonic-gate   char envnam[ENV_LEN+1];      /* The buffer used when reading the names of */
877c478bd9Sstevel@tonic-gate                                /*  environment variables. */
887c478bd9Sstevel@tonic-gate };
897c478bd9Sstevel@tonic-gate 
907c478bd9Sstevel@tonic-gate static int cf_expand_home_dir(CompleteFile *cf, const char *user);
917c478bd9Sstevel@tonic-gate static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl,
927c478bd9Sstevel@tonic-gate 				const char *prefix, const char *line,
937c478bd9Sstevel@tonic-gate 				int word_start, int word_end, int escaped);
947c478bd9Sstevel@tonic-gate static HOME_DIR_FN(cf_homedir_callback);
957c478bd9Sstevel@tonic-gate static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl,
967c478bd9Sstevel@tonic-gate 			     const char *line, int word_start, int word_end,
977c478bd9Sstevel@tonic-gate 			     int escaped, CplCheckFn *check_fn,
987c478bd9Sstevel@tonic-gate 			     void *check_data);
997c478bd9Sstevel@tonic-gate static char *cf_read_name(CompleteFile *cf, const char *type,
1007c478bd9Sstevel@tonic-gate 			  const char *string, int slen,
1017c478bd9Sstevel@tonic-gate 			  char *nambuf, int nammax);
1027c478bd9Sstevel@tonic-gate static int cf_prepare_suffix(CompleteFile *cf, const char *suffix,
1037c478bd9Sstevel@tonic-gate 			     int add_escapes);
1047c478bd9Sstevel@tonic-gate 
1057c478bd9Sstevel@tonic-gate /*
1067c478bd9Sstevel@tonic-gate  * A stack based object of the following type is used to pass data to the
1077c478bd9Sstevel@tonic-gate  * cf_homedir_callback() function.
1087c478bd9Sstevel@tonic-gate  */
1097c478bd9Sstevel@tonic-gate typedef struct {
1107c478bd9Sstevel@tonic-gate   CompleteFile *cf;    /* The file-completion resource object */
1117c478bd9Sstevel@tonic-gate   WordCompletion *cpl; /* The string-completion rsource object */
1127c478bd9Sstevel@tonic-gate   size_t prefix_len;   /* The length of the prefix being completed */
1137c478bd9Sstevel@tonic-gate   const char *line;    /* The line from which the prefix was extracted */
1147c478bd9Sstevel@tonic-gate   int word_start;      /* The index in line[] of the start of the username */
1157c478bd9Sstevel@tonic-gate   int word_end;        /* The index in line[] following the end of the prefix */
1167c478bd9Sstevel@tonic-gate   int escaped;         /* If true, add escapes to the completion suffixes */
1177c478bd9Sstevel@tonic-gate } CfHomeArgs;
1187c478bd9Sstevel@tonic-gate 
1197c478bd9Sstevel@tonic-gate /*.......................................................................
1207c478bd9Sstevel@tonic-gate  * Create a new file-completion object.
1217c478bd9Sstevel@tonic-gate  *
1227c478bd9Sstevel@tonic-gate  * Output:
1237c478bd9Sstevel@tonic-gate  *  return  CompleteFile *  The new object, or NULL on error.
1247c478bd9Sstevel@tonic-gate  */
_new_CompleteFile(void)1257c478bd9Sstevel@tonic-gate CompleteFile *_new_CompleteFile(void)
1267c478bd9Sstevel@tonic-gate {
1277c478bd9Sstevel@tonic-gate   CompleteFile *cf;  /* The object to be returned */
1287c478bd9Sstevel@tonic-gate /*
1297c478bd9Sstevel@tonic-gate  * Allocate the container.
1307c478bd9Sstevel@tonic-gate  */
1317c478bd9Sstevel@tonic-gate   cf = (CompleteFile *) malloc(sizeof(CompleteFile));
1327c478bd9Sstevel@tonic-gate   if(!cf) {
1337c478bd9Sstevel@tonic-gate     errno = ENOMEM;
1347c478bd9Sstevel@tonic-gate     return NULL;
1357c478bd9Sstevel@tonic-gate   };
1367c478bd9Sstevel@tonic-gate /*
1377c478bd9Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
1387c478bd9Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
1397c478bd9Sstevel@tonic-gate  * to _del_CompleteFile().
1407c478bd9Sstevel@tonic-gate  */
1417c478bd9Sstevel@tonic-gate   cf->err = NULL;
1427c478bd9Sstevel@tonic-gate   cf->dr = NULL;
1437c478bd9Sstevel@tonic-gate   cf->home = NULL;
1447c478bd9Sstevel@tonic-gate   cf->path = NULL;
1457c478bd9Sstevel@tonic-gate   cf->buff = NULL;
1467c478bd9Sstevel@tonic-gate   cf->usrnam[0] = '\0';
1477c478bd9Sstevel@tonic-gate   cf->envnam[0] = '\0';
1487c478bd9Sstevel@tonic-gate /*
1497c478bd9Sstevel@tonic-gate  * Allocate a place to record error messages.
1507c478bd9Sstevel@tonic-gate  */
1517c478bd9Sstevel@tonic-gate   cf->err = _new_ErrMsg();
1527c478bd9Sstevel@tonic-gate   if(!cf->err)
1537c478bd9Sstevel@tonic-gate     return _del_CompleteFile(cf);
1547c478bd9Sstevel@tonic-gate /*
1557c478bd9Sstevel@tonic-gate  * Create the object that is used for reading directories.
1567c478bd9Sstevel@tonic-gate  */
1577c478bd9Sstevel@tonic-gate   cf->dr = _new_DirReader();
1587c478bd9Sstevel@tonic-gate   if(!cf->dr)
1597c478bd9Sstevel@tonic-gate     return _del_CompleteFile(cf);
1607c478bd9Sstevel@tonic-gate /*
1617c478bd9Sstevel@tonic-gate  * Create the object that is used to lookup home directories.
1627c478bd9Sstevel@tonic-gate  */
1637c478bd9Sstevel@tonic-gate   cf->home = _new_HomeDir();
1647c478bd9Sstevel@tonic-gate   if(!cf->home)
1657c478bd9Sstevel@tonic-gate     return _del_CompleteFile(cf);
1667c478bd9Sstevel@tonic-gate /*
1677c478bd9Sstevel@tonic-gate  * Create the buffer in which the completed pathname is accumulated.
1687c478bd9Sstevel@tonic-gate  */
1697c478bd9Sstevel@tonic-gate   cf->path = _new_PathName();
1707c478bd9Sstevel@tonic-gate   if(!cf->path)
1717c478bd9Sstevel@tonic-gate     return _del_CompleteFile(cf);
1727c478bd9Sstevel@tonic-gate /*
1737c478bd9Sstevel@tonic-gate  * Create a pathname work buffer.
1747c478bd9Sstevel@tonic-gate  */
1757c478bd9Sstevel@tonic-gate   cf->buff = _new_PathName();
1767c478bd9Sstevel@tonic-gate   if(!cf->buff)
1777c478bd9Sstevel@tonic-gate     return _del_CompleteFile(cf);
1787c478bd9Sstevel@tonic-gate   return cf;
1797c478bd9Sstevel@tonic-gate }
1807c478bd9Sstevel@tonic-gate 
1817c478bd9Sstevel@tonic-gate /*.......................................................................
1827c478bd9Sstevel@tonic-gate  * Delete a file-completion object.
1837c478bd9Sstevel@tonic-gate  *
1847c478bd9Sstevel@tonic-gate  * Input:
1857c478bd9Sstevel@tonic-gate  *  cf     CompleteFile *  The object to be deleted.
1867c478bd9Sstevel@tonic-gate  * Output:
1877c478bd9Sstevel@tonic-gate  *  return CompleteFile *  The deleted object (always NULL).
1887c478bd9Sstevel@tonic-gate  */
_del_CompleteFile(CompleteFile * cf)1897c478bd9Sstevel@tonic-gate CompleteFile *_del_CompleteFile(CompleteFile *cf)
1907c478bd9Sstevel@tonic-gate {
1917c478bd9Sstevel@tonic-gate   if(cf) {
1927c478bd9Sstevel@tonic-gate     cf->err = _del_ErrMsg(cf->err);
1937c478bd9Sstevel@tonic-gate     cf->dr = _del_DirReader(cf->dr);
1947c478bd9Sstevel@tonic-gate     cf->home = _del_HomeDir(cf->home);
1957c478bd9Sstevel@tonic-gate     cf->path = _del_PathName(cf->path);
1967c478bd9Sstevel@tonic-gate     cf->buff = _del_PathName(cf->buff);
1977c478bd9Sstevel@tonic-gate     free(cf);
1987c478bd9Sstevel@tonic-gate   };
1997c478bd9Sstevel@tonic-gate   return NULL;
2007c478bd9Sstevel@tonic-gate }
2017c478bd9Sstevel@tonic-gate 
2027c478bd9Sstevel@tonic-gate /*.......................................................................
2037c478bd9Sstevel@tonic-gate  * Look up the possible completions of the incomplete filename that
2047c478bd9Sstevel@tonic-gate  * lies between specified indexes of a given command-line string.
2057c478bd9Sstevel@tonic-gate  *
2067c478bd9Sstevel@tonic-gate  * Input:
2077c478bd9Sstevel@tonic-gate  *  cpl   WordCompletion *  The object in which to record the completions.
2087c478bd9Sstevel@tonic-gate  *  cf      CompleteFile *  The filename-completion resource object.
2097c478bd9Sstevel@tonic-gate  *  line      const char *  The string containing the incomplete filename.
2107c478bd9Sstevel@tonic-gate  *  word_start       int    The index of the first character in line[]
2117c478bd9Sstevel@tonic-gate  *                          of the incomplete filename.
2127c478bd9Sstevel@tonic-gate  *  word_end         int    The index of the character in line[] that
2137c478bd9Sstevel@tonic-gate  *                          follows the last character of the incomplete
2147c478bd9Sstevel@tonic-gate  *                          filename.
2157c478bd9Sstevel@tonic-gate  *  escaped          int    If true, backslashes in line[] are
2167c478bd9Sstevel@tonic-gate  *                          interpreted as escaping the characters
2177c478bd9Sstevel@tonic-gate  *                          that follow them, and any spaces, tabs,
2187c478bd9Sstevel@tonic-gate  *                          backslashes, or wildcard characters in the
2197c478bd9Sstevel@tonic-gate  *                          returned suffixes will be similarly escaped.
2207c478bd9Sstevel@tonic-gate  *                          If false, backslashes will be interpreted as
2217c478bd9Sstevel@tonic-gate  *                          literal parts of the file name, and no
2227c478bd9Sstevel@tonic-gate  *                          backslashes will be added to the returned
2237c478bd9Sstevel@tonic-gate  *                          suffixes.
2247c478bd9Sstevel@tonic-gate  *  check_fn  CplCheckFn *  If not zero, this argument specifies a
2257c478bd9Sstevel@tonic-gate  *                          function to call to ask whether a given
2267c478bd9Sstevel@tonic-gate  *                          file should be included in the list
2277c478bd9Sstevel@tonic-gate  *                          of completions.
2287c478bd9Sstevel@tonic-gate  *  check_data      void *  Anonymous data to be passed to check_fn().
2297c478bd9Sstevel@tonic-gate  * Output:
2307c478bd9Sstevel@tonic-gate  *  return           int    0 - OK.
2317c478bd9Sstevel@tonic-gate  *                          1 - Error. A description of the error can be
2327c478bd9Sstevel@tonic-gate  *                                     acquired by calling _cf_last_error(cf).
2337c478bd9Sstevel@tonic-gate  */
_cf_complete_file(WordCompletion * cpl,CompleteFile * cf,const char * line,int word_start,int word_end,int escaped,CplCheckFn * check_fn,void * check_data)2347c478bd9Sstevel@tonic-gate int _cf_complete_file(WordCompletion *cpl, CompleteFile *cf,
2357c478bd9Sstevel@tonic-gate 		     const char *line, int word_start, int word_end,
2367c478bd9Sstevel@tonic-gate 		     int escaped, CplCheckFn *check_fn, void *check_data)
2377c478bd9Sstevel@tonic-gate {
2387c478bd9Sstevel@tonic-gate   const char *lptr; /* A pointer into line[] */
2397c478bd9Sstevel@tonic-gate   int nleft;        /* The number of characters still to be processed */
2407c478bd9Sstevel@tonic-gate                     /*  in line[]. */
2417c478bd9Sstevel@tonic-gate /*
2427c478bd9Sstevel@tonic-gate  * Check the arguments.
2437c478bd9Sstevel@tonic-gate  */
2447c478bd9Sstevel@tonic-gate   if(!cpl || !cf || !line || word_end < word_start) {
2457c478bd9Sstevel@tonic-gate     if(cf) {
2467c478bd9Sstevel@tonic-gate       _err_record_msg(cf->err, "_cf_complete_file: Invalid arguments",
2477c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
2487c478bd9Sstevel@tonic-gate     };
2497c478bd9Sstevel@tonic-gate     return 1;
2507c478bd9Sstevel@tonic-gate   };
2517c478bd9Sstevel@tonic-gate /*
2527c478bd9Sstevel@tonic-gate  * Clear the buffer in which the filename will be constructed.
2537c478bd9Sstevel@tonic-gate  */
2547c478bd9Sstevel@tonic-gate   _pn_clear_path(cf->path);
2557c478bd9Sstevel@tonic-gate /*
2567c478bd9Sstevel@tonic-gate  * How many characters are to be processed?
2577c478bd9Sstevel@tonic-gate  */
2587c478bd9Sstevel@tonic-gate   nleft = word_end - word_start;
2597c478bd9Sstevel@tonic-gate /*
2607c478bd9Sstevel@tonic-gate  * Get a pointer to the start of the incomplete filename.
2617c478bd9Sstevel@tonic-gate  */
2627c478bd9Sstevel@tonic-gate   lptr = line + word_start;
2637c478bd9Sstevel@tonic-gate /*
2647c478bd9Sstevel@tonic-gate  * If the first character is a tilde, then perform home-directory
2657c478bd9Sstevel@tonic-gate  * interpolation.
2667c478bd9Sstevel@tonic-gate  */
2677c478bd9Sstevel@tonic-gate   if(nleft > 0 && *lptr == '~') {
2687c478bd9Sstevel@tonic-gate     int slen;
2697c478bd9Sstevel@tonic-gate     if(!cf_read_name(cf, "User", ++lptr, --nleft, cf->usrnam, USR_LEN))
2707c478bd9Sstevel@tonic-gate       return 1;
2717c478bd9Sstevel@tonic-gate /*
2727c478bd9Sstevel@tonic-gate  * Advance over the username in the input line.
2737c478bd9Sstevel@tonic-gate  */
2747c478bd9Sstevel@tonic-gate     slen = strlen(cf->usrnam);
2757c478bd9Sstevel@tonic-gate     lptr += slen;
2767c478bd9Sstevel@tonic-gate     nleft -= slen;
2777c478bd9Sstevel@tonic-gate /*
2787c478bd9Sstevel@tonic-gate  * If we haven't hit the end of the input string then we have a complete
2797c478bd9Sstevel@tonic-gate  * username to translate to the corresponding home directory.
2807c478bd9Sstevel@tonic-gate  */
2817c478bd9Sstevel@tonic-gate     if(nleft > 0) {
2827c478bd9Sstevel@tonic-gate       if(cf_expand_home_dir(cf, cf->usrnam))
2837c478bd9Sstevel@tonic-gate 	return 1;
2847c478bd9Sstevel@tonic-gate /*
2857c478bd9Sstevel@tonic-gate  * ~user and ~ are usually followed by a directory separator to
2867c478bd9Sstevel@tonic-gate  * separate them from the file contained in the home directory.
2877c478bd9Sstevel@tonic-gate  * If the home directory is the root directory, then we don't want
2887c478bd9Sstevel@tonic-gate  * to follow the home directory by a directory separator, so we should
2897c478bd9Sstevel@tonic-gate  * skip over it so that it doesn't get copied into the filename.
2907c478bd9Sstevel@tonic-gate  */
2917c478bd9Sstevel@tonic-gate       if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 &&
2927c478bd9Sstevel@tonic-gate 	 strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
2937c478bd9Sstevel@tonic-gate 	lptr += FS_DIR_SEP_LEN;
2947c478bd9Sstevel@tonic-gate 	nleft -= FS_DIR_SEP_LEN;
2957c478bd9Sstevel@tonic-gate       };
2967c478bd9Sstevel@tonic-gate /*
2977c478bd9Sstevel@tonic-gate  * If we have reached the end of the input string, then the username
2987c478bd9Sstevel@tonic-gate  * may be incomplete, and we should attempt to complete it.
2997c478bd9Sstevel@tonic-gate  */
3007c478bd9Sstevel@tonic-gate     } else {
3017c478bd9Sstevel@tonic-gate /*
3027c478bd9Sstevel@tonic-gate  * Look up the possible completions of the username.
3037c478bd9Sstevel@tonic-gate  */
3047c478bd9Sstevel@tonic-gate       return cf_complete_username(cf, cpl, cf->usrnam, line, word_start+1,
3057c478bd9Sstevel@tonic-gate 				  word_end, escaped);
3067c478bd9Sstevel@tonic-gate     };
3077c478bd9Sstevel@tonic-gate   };
3087c478bd9Sstevel@tonic-gate /*
3097c478bd9Sstevel@tonic-gate  * Copy the rest of the path, stopping to expand $envvar expressions
3107c478bd9Sstevel@tonic-gate  * where encountered.
3117c478bd9Sstevel@tonic-gate  */
3127c478bd9Sstevel@tonic-gate   while(nleft > 0) {
3137c478bd9Sstevel@tonic-gate     int seglen;   /* The length of the next segment to be copied */
3147c478bd9Sstevel@tonic-gate /*
3157c478bd9Sstevel@tonic-gate  * Find the length of the next segment to be copied, stopping if an
3167c478bd9Sstevel@tonic-gate  * unescaped '$' is seen, or the end of the path is reached.
3177c478bd9Sstevel@tonic-gate  */
3187c478bd9Sstevel@tonic-gate     for(seglen=0; seglen < nleft; seglen++) {
3197c478bd9Sstevel@tonic-gate       int c = lptr[seglen];
3207c478bd9Sstevel@tonic-gate       if(escaped && c == '\\')
3217c478bd9Sstevel@tonic-gate 	seglen++;
3227c478bd9Sstevel@tonic-gate       else if(c == '$')
3237c478bd9Sstevel@tonic-gate 	break;
3247c478bd9Sstevel@tonic-gate /*
3257c478bd9Sstevel@tonic-gate  * We will be completing the last component of the file name,
3267c478bd9Sstevel@tonic-gate  * so whenever a directory separator is seen, assume that it
3277c478bd9Sstevel@tonic-gate  * might be the start of the last component, and mark the character
3287c478bd9Sstevel@tonic-gate  * that follows it as the start of the name that is to be completed.
3297c478bd9Sstevel@tonic-gate  */
3307c478bd9Sstevel@tonic-gate       if(nleft >= FS_DIR_SEP_LEN &&
3317c478bd9Sstevel@tonic-gate 	 strncmp(lptr + seglen, FS_DIR_SEP, FS_DIR_SEP_LEN)==0) {
3327c478bd9Sstevel@tonic-gate 	word_start = (lptr + seglen) - line + FS_DIR_SEP_LEN;
3337c478bd9Sstevel@tonic-gate       };
3347c478bd9Sstevel@tonic-gate     };
3357c478bd9Sstevel@tonic-gate /*
3367c478bd9Sstevel@tonic-gate  * We have reached either the end of the filename or the start of
3377c478bd9Sstevel@tonic-gate  * $environment_variable expression. Record the newly checked
3387c478bd9Sstevel@tonic-gate  * segment of the filename in the output filename, removing
3397c478bd9Sstevel@tonic-gate  * backslash-escapes where needed.
3407c478bd9Sstevel@tonic-gate  */
3417c478bd9Sstevel@tonic-gate     if(_pn_append_to_path(cf->path, lptr, seglen, escaped) == NULL) {
3427c478bd9Sstevel@tonic-gate       _err_record_msg(cf->err, "Insufficient memory to complete filename",
3437c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
3447c478bd9Sstevel@tonic-gate       return 1;
3457c478bd9Sstevel@tonic-gate     };
3467c478bd9Sstevel@tonic-gate     lptr += seglen;
3477c478bd9Sstevel@tonic-gate     nleft -= seglen;
3487c478bd9Sstevel@tonic-gate /*
3497c478bd9Sstevel@tonic-gate  * If the above loop finished before we hit the end of the filename,
3507c478bd9Sstevel@tonic-gate  * then this was because an unescaped $ was seen. In this case, interpolate
3517c478bd9Sstevel@tonic-gate  * the value of the environment variable that follows it into the output
3527c478bd9Sstevel@tonic-gate  * filename.
3537c478bd9Sstevel@tonic-gate  */
3547c478bd9Sstevel@tonic-gate     if(nleft > 0) {
3557c478bd9Sstevel@tonic-gate       char *value;    /* The value of the environment variable */
3567c478bd9Sstevel@tonic-gate       int vlen;       /* The length of the value string */
3577c478bd9Sstevel@tonic-gate       int nlen;       /* The length of the environment variable name */
3587c478bd9Sstevel@tonic-gate /*
3597c478bd9Sstevel@tonic-gate  * Read the name of the environment variable.
3607c478bd9Sstevel@tonic-gate  */
3617c478bd9Sstevel@tonic-gate       if(!cf_read_name(cf, "Environment", ++lptr, --nleft, cf->envnam, ENV_LEN))
3627c478bd9Sstevel@tonic-gate 	return 1;
3637c478bd9Sstevel@tonic-gate /*
3647c478bd9Sstevel@tonic-gate  * Advance over the environment variable name in the input line.
3657c478bd9Sstevel@tonic-gate  */
3667c478bd9Sstevel@tonic-gate       nlen = strlen(cf->envnam);
3677c478bd9Sstevel@tonic-gate       lptr += nlen;
3687c478bd9Sstevel@tonic-gate       nleft -= nlen;
3697c478bd9Sstevel@tonic-gate /*
3707c478bd9Sstevel@tonic-gate  * Get the value of the environment variable.
3717c478bd9Sstevel@tonic-gate  */
3727c478bd9Sstevel@tonic-gate       value = getenv(cf->envnam);
3737c478bd9Sstevel@tonic-gate       if(!value) {
3747c478bd9Sstevel@tonic-gate 	_err_record_msg(cf->err, "Unknown environment variable: ", cf->envnam,
3757c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
3767c478bd9Sstevel@tonic-gate 	return 1;
3777c478bd9Sstevel@tonic-gate       };
3787c478bd9Sstevel@tonic-gate       vlen = strlen(value);
3797c478bd9Sstevel@tonic-gate /*
3807c478bd9Sstevel@tonic-gate  * If we are at the start of the filename and the first character of the
3817c478bd9Sstevel@tonic-gate  * environment variable value is a '~', attempt home-directory
3827c478bd9Sstevel@tonic-gate  * interpolation.
3837c478bd9Sstevel@tonic-gate  */
3847c478bd9Sstevel@tonic-gate       if(cf->path->name[0] == '\0' && value[0] == '~') {
3857c478bd9Sstevel@tonic-gate 	if(!cf_read_name(cf, "User", value+1, vlen-1, cf->usrnam, USR_LEN) ||
3867c478bd9Sstevel@tonic-gate 	   cf_expand_home_dir(cf, cf->usrnam))
3877c478bd9Sstevel@tonic-gate 	  return 1;
3887c478bd9Sstevel@tonic-gate /*
3897c478bd9Sstevel@tonic-gate  * If the home directory is the root directory, and the ~usrname expression
3907c478bd9Sstevel@tonic-gate  * was followed by a directory separator, prevent the directory separator
3917c478bd9Sstevel@tonic-gate  * from being appended to the root directory by skipping it in the
3927c478bd9Sstevel@tonic-gate  * input line.
3937c478bd9Sstevel@tonic-gate  */
3947c478bd9Sstevel@tonic-gate 	if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 &&
3957c478bd9Sstevel@tonic-gate 	   strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
3967c478bd9Sstevel@tonic-gate 	  lptr += FS_DIR_SEP_LEN;
3977c478bd9Sstevel@tonic-gate 	  nleft -= FS_DIR_SEP_LEN;
3987c478bd9Sstevel@tonic-gate 	};
3997c478bd9Sstevel@tonic-gate       } else {
4007c478bd9Sstevel@tonic-gate /*
4017c478bd9Sstevel@tonic-gate  * Append the value of the environment variable to the output path.
4027c478bd9Sstevel@tonic-gate  */
4037c478bd9Sstevel@tonic-gate 	if(_pn_append_to_path(cf->path, value, strlen(value), escaped)==NULL) {
4047c478bd9Sstevel@tonic-gate 	  _err_record_msg(cf->err, "Insufficient memory to complete filename",
4057c478bd9Sstevel@tonic-gate 			  END_ERR_MSG);
4067c478bd9Sstevel@tonic-gate 	  return 1;
4077c478bd9Sstevel@tonic-gate 	};
4087c478bd9Sstevel@tonic-gate /*
4097c478bd9Sstevel@tonic-gate  * Prevent extra directory separators from being added.
4107c478bd9Sstevel@tonic-gate  */
4117c478bd9Sstevel@tonic-gate 	if(nleft >= FS_DIR_SEP_LEN &&
4127c478bd9Sstevel@tonic-gate 	   strcmp(cf->path->name, FS_ROOT_DIR) == 0 &&
4137c478bd9Sstevel@tonic-gate 	   strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
4147c478bd9Sstevel@tonic-gate 	  lptr += FS_DIR_SEP_LEN;
4157c478bd9Sstevel@tonic-gate 	  nleft -= FS_DIR_SEP_LEN;
4167c478bd9Sstevel@tonic-gate 	} else if(vlen > FS_DIR_SEP_LEN &&
4177c478bd9Sstevel@tonic-gate 		  strcmp(value + vlen - FS_DIR_SEP_LEN, FS_DIR_SEP)==0) {
4187c478bd9Sstevel@tonic-gate 	  cf->path->name[vlen-FS_DIR_SEP_LEN] = '\0';
4197c478bd9Sstevel@tonic-gate 	};
4207c478bd9Sstevel@tonic-gate       };
4217c478bd9Sstevel@tonic-gate /*
4227c478bd9Sstevel@tonic-gate  * If adding the environment variable didn't form a valid directory,
4237c478bd9Sstevel@tonic-gate  * we can't complete the line, since there is no way to separate append
4247c478bd9Sstevel@tonic-gate  * a partial filename to an environment variable reference without
4257c478bd9Sstevel@tonic-gate  * that appended part of the name being seen later as part of the
4267c478bd9Sstevel@tonic-gate  * environment variable name. Thus if the currently constructed path
4277c478bd9Sstevel@tonic-gate  * isn't a directory, quite now with no completions having been
4287c478bd9Sstevel@tonic-gate  * registered.
4297c478bd9Sstevel@tonic-gate  */
4307c478bd9Sstevel@tonic-gate       if(!_pu_path_is_dir(cf->path->name))
4317c478bd9Sstevel@tonic-gate 	return 0;
4327c478bd9Sstevel@tonic-gate /*
4337c478bd9Sstevel@tonic-gate  * For the reasons given above, if we have reached the end of the filename
4347c478bd9Sstevel@tonic-gate  * with the expansion of an environment variable, the only allowed
4357c478bd9Sstevel@tonic-gate  * completion involves the addition of a directory separator.
4367c478bd9Sstevel@tonic-gate  */
4377c478bd9Sstevel@tonic-gate       if(nleft == 0) {
4387c478bd9Sstevel@tonic-gate 	if(cpl_add_completion(cpl, line, lptr-line, word_end, FS_DIR_SEP,
4397c478bd9Sstevel@tonic-gate 			      "", "")) {
4407c478bd9Sstevel@tonic-gate 	  _err_record_msg(cf->err, cpl_last_error(cpl), END_ERR_MSG);
4417c478bd9Sstevel@tonic-gate 	  return 1;
4427c478bd9Sstevel@tonic-gate 	};
4437c478bd9Sstevel@tonic-gate 	return 0;
4447c478bd9Sstevel@tonic-gate       };
4457c478bd9Sstevel@tonic-gate     };
4467c478bd9Sstevel@tonic-gate   };
4477c478bd9Sstevel@tonic-gate /*
4487c478bd9Sstevel@tonic-gate  * Complete the filename if possible.
4497c478bd9Sstevel@tonic-gate  */
4507c478bd9Sstevel@tonic-gate   return cf_complete_entry(cf, cpl, line, word_start, word_end, escaped,
4517c478bd9Sstevel@tonic-gate 			   check_fn, check_data);
4527c478bd9Sstevel@tonic-gate }
4537c478bd9Sstevel@tonic-gate 
4547c478bd9Sstevel@tonic-gate /*.......................................................................
4557c478bd9Sstevel@tonic-gate  * Return a description of the last path-completion error that occurred.
4567c478bd9Sstevel@tonic-gate  *
4577c478bd9Sstevel@tonic-gate  * Input:
4587c478bd9Sstevel@tonic-gate  *  cf    CompleteFile *  The path-completion resource object.
4597c478bd9Sstevel@tonic-gate  * Output:
4607c478bd9Sstevel@tonic-gate  *  return  const char *  The description of the last error.
4617c478bd9Sstevel@tonic-gate  */
_cf_last_error(CompleteFile * cf)4627c478bd9Sstevel@tonic-gate const char *_cf_last_error(CompleteFile *cf)
4637c478bd9Sstevel@tonic-gate {
4647c478bd9Sstevel@tonic-gate   return cf ? _err_get_msg(cf->err) : "NULL CompleteFile argument";
4657c478bd9Sstevel@tonic-gate }
4667c478bd9Sstevel@tonic-gate 
4677c478bd9Sstevel@tonic-gate /*.......................................................................
4687c478bd9Sstevel@tonic-gate  * Lookup the home directory of the specified user, or the current user
4697c478bd9Sstevel@tonic-gate  * if no name is specified, appending it to output pathname.
4707c478bd9Sstevel@tonic-gate  *
4717c478bd9Sstevel@tonic-gate  * Input:
4727c478bd9Sstevel@tonic-gate  *  cf  CompleteFile *  The pathname completion resource object.
4737c478bd9Sstevel@tonic-gate  *  user  const char *  The username to lookup, or "" to lookup the
4747c478bd9Sstevel@tonic-gate  *                      current user.
4757c478bd9Sstevel@tonic-gate  * Output:
4767c478bd9Sstevel@tonic-gate  *  return        int    0 - OK.
4777c478bd9Sstevel@tonic-gate  *                       1 - Error.
4787c478bd9Sstevel@tonic-gate  */
cf_expand_home_dir(CompleteFile * cf,const char * user)4797c478bd9Sstevel@tonic-gate static int cf_expand_home_dir(CompleteFile *cf, const char *user)
4807c478bd9Sstevel@tonic-gate {
4817c478bd9Sstevel@tonic-gate /*
4827c478bd9Sstevel@tonic-gate  * Attempt to lookup the home directory.
4837c478bd9Sstevel@tonic-gate  */
4847c478bd9Sstevel@tonic-gate   const char *home_dir = _hd_lookup_home_dir(cf->home, user);
4857c478bd9Sstevel@tonic-gate /*
4867c478bd9Sstevel@tonic-gate  * Failed?
4877c478bd9Sstevel@tonic-gate  */
4887c478bd9Sstevel@tonic-gate   if(!home_dir) {
4897c478bd9Sstevel@tonic-gate     _err_record_msg(cf->err, _hd_last_home_dir_error(cf->home), END_ERR_MSG);
4907c478bd9Sstevel@tonic-gate     return 1;
4917c478bd9Sstevel@tonic-gate   };
4927c478bd9Sstevel@tonic-gate /*
4937c478bd9Sstevel@tonic-gate  * Append the home directory to the pathname string.
4947c478bd9Sstevel@tonic-gate  */
4957c478bd9Sstevel@tonic-gate   if(_pn_append_to_path(cf->path, home_dir, -1, 0) == NULL) {
4967c478bd9Sstevel@tonic-gate     _err_record_msg(cf->err, "Insufficient memory for home directory expansion",
4977c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
4987c478bd9Sstevel@tonic-gate     return 1;
4997c478bd9Sstevel@tonic-gate   };
5007c478bd9Sstevel@tonic-gate   return 0;
5017c478bd9Sstevel@tonic-gate }
5027c478bd9Sstevel@tonic-gate 
5037c478bd9Sstevel@tonic-gate /*.......................................................................
5047c478bd9Sstevel@tonic-gate  * Lookup and report all completions of a given username prefix.
5057c478bd9Sstevel@tonic-gate  *
5067c478bd9Sstevel@tonic-gate  * Input:
5077c478bd9Sstevel@tonic-gate  *  cf     CompleteFile *  The filename-completion resource object.
5087c478bd9Sstevel@tonic-gate  *  cpl  WordCompletion *  The object in which to record the completions.
5097c478bd9Sstevel@tonic-gate  *  prefix   const char *  The prefix of the usernames to lookup.
5107c478bd9Sstevel@tonic-gate  *  line     const char *  The command-line in which the username appears.
5117c478bd9Sstevel@tonic-gate  *  word_start      int    The index within line[] of the start of the
5127c478bd9Sstevel@tonic-gate  *                         username that is being completed.
5137c478bd9Sstevel@tonic-gate  *  word_end        int    The index within line[] of the character which
5147c478bd9Sstevel@tonic-gate  *                         follows the incomplete username.
5157c478bd9Sstevel@tonic-gate  *  escaped         int    True if the completions need to have special
5167c478bd9Sstevel@tonic-gate  *                         characters escaped.
5177c478bd9Sstevel@tonic-gate  * Output:
5187c478bd9Sstevel@tonic-gate  *  return          int    0 - OK.
5197c478bd9Sstevel@tonic-gate  *                         1 - Error.
5207c478bd9Sstevel@tonic-gate  */
cf_complete_username(CompleteFile * cf,WordCompletion * cpl,const char * prefix,const char * line,int word_start,int word_end,int escaped)5217c478bd9Sstevel@tonic-gate static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl,
5227c478bd9Sstevel@tonic-gate 				const char *prefix, const char *line,
5237c478bd9Sstevel@tonic-gate 				int word_start, int word_end, int escaped)
5247c478bd9Sstevel@tonic-gate {
5257c478bd9Sstevel@tonic-gate /*
5267c478bd9Sstevel@tonic-gate  * Set up a container of anonymous arguments to be sent to the
5277c478bd9Sstevel@tonic-gate  * username-lookup iterator.
5287c478bd9Sstevel@tonic-gate  */
5297c478bd9Sstevel@tonic-gate   CfHomeArgs args;
5307c478bd9Sstevel@tonic-gate   args.cf = cf;
5317c478bd9Sstevel@tonic-gate   args.cpl = cpl;
5327c478bd9Sstevel@tonic-gate   args.prefix_len = strlen(prefix);
5337c478bd9Sstevel@tonic-gate   args.line = line;
5347c478bd9Sstevel@tonic-gate   args.word_start = word_start;
5357c478bd9Sstevel@tonic-gate   args.word_end = word_end;
5367c478bd9Sstevel@tonic-gate   args.escaped = escaped;
5377c478bd9Sstevel@tonic-gate /*
5387c478bd9Sstevel@tonic-gate  * Iterate through the list of users, recording those which start
5397c478bd9Sstevel@tonic-gate  * with the specified prefix.
5407c478bd9Sstevel@tonic-gate  */
5417c478bd9Sstevel@tonic-gate   if(_hd_scan_user_home_dirs(cf->home, prefix, &args, cf_homedir_callback)) {
5427c478bd9Sstevel@tonic-gate     _err_record_msg(cf->err, _hd_last_home_dir_error(cf->home), END_ERR_MSG);
5437c478bd9Sstevel@tonic-gate     return 1;
5447c478bd9Sstevel@tonic-gate   };
5457c478bd9Sstevel@tonic-gate   return 0;
5467c478bd9Sstevel@tonic-gate }
5477c478bd9Sstevel@tonic-gate 
5487c478bd9Sstevel@tonic-gate /*.......................................................................
5497c478bd9Sstevel@tonic-gate  * The user/home-directory scanner callback function (see homedir.h)
5507c478bd9Sstevel@tonic-gate  * used by cf_complete_username().
5517c478bd9Sstevel@tonic-gate  */
HOME_DIR_FN(cf_homedir_callback)5527c478bd9Sstevel@tonic-gate static HOME_DIR_FN(cf_homedir_callback)
5537c478bd9Sstevel@tonic-gate {
5547c478bd9Sstevel@tonic-gate /*
5557c478bd9Sstevel@tonic-gate  * Get the file-completion resources from the anonymous data argument.
5567c478bd9Sstevel@tonic-gate  */
5577c478bd9Sstevel@tonic-gate   CfHomeArgs *args = (CfHomeArgs *) data;
5587c478bd9Sstevel@tonic-gate   WordCompletion *cpl = args->cpl;
5597c478bd9Sstevel@tonic-gate   CompleteFile *cf = args->cf;
5607c478bd9Sstevel@tonic-gate /*
5617c478bd9Sstevel@tonic-gate  * Copy the username into the pathname work buffer, adding backslash
5627c478bd9Sstevel@tonic-gate  * escapes where needed.
5637c478bd9Sstevel@tonic-gate  */
5647c478bd9Sstevel@tonic-gate   if(cf_prepare_suffix(cf, usrnam+args->prefix_len, args->escaped)) {
5657c478bd9Sstevel@tonic-gate     strncpy(errmsg, _err_get_msg(cf->err), maxerr);
5667c478bd9Sstevel@tonic-gate     errmsg[maxerr] = '\0';
5677c478bd9Sstevel@tonic-gate     return 1;
5687c478bd9Sstevel@tonic-gate   };
5697c478bd9Sstevel@tonic-gate /*
5707c478bd9Sstevel@tonic-gate  * Report the completion suffix that was copied above.
5717c478bd9Sstevel@tonic-gate  */
5727c478bd9Sstevel@tonic-gate   if(cpl_add_completion(cpl, args->line, args->word_start, args->word_end,
5737c478bd9Sstevel@tonic-gate 			cf->buff->name, FS_DIR_SEP, FS_DIR_SEP)) {
5747c478bd9Sstevel@tonic-gate     strncpy(errmsg, cpl_last_error(cpl), maxerr);
5757c478bd9Sstevel@tonic-gate     errmsg[maxerr] = '\0';
5767c478bd9Sstevel@tonic-gate     return 1;
5777c478bd9Sstevel@tonic-gate   };
5787c478bd9Sstevel@tonic-gate   return 0;
5797c478bd9Sstevel@tonic-gate }
5807c478bd9Sstevel@tonic-gate 
5817c478bd9Sstevel@tonic-gate /*.......................................................................
5827c478bd9Sstevel@tonic-gate  * Report possible completions of the filename in cf->path->name[].
5837c478bd9Sstevel@tonic-gate  *
5847c478bd9Sstevel@tonic-gate  * Input:
5857c478bd9Sstevel@tonic-gate  *  cf      CompleteFile *  The file-completion resource object.
5867c478bd9Sstevel@tonic-gate  *  cpl   WordCompletion *  The object in which to record the completions.
5877c478bd9Sstevel@tonic-gate  *  line      const char *  The input line, as received by the callback
5887c478bd9Sstevel@tonic-gate  *                          function.
5897c478bd9Sstevel@tonic-gate  *  word_start       int    The index within line[] of the start of the
5907c478bd9Sstevel@tonic-gate  *                          last component of the filename that is being
5917c478bd9Sstevel@tonic-gate  *                          completed.
5927c478bd9Sstevel@tonic-gate  *  word_end         int    The index within line[] of the character which
5937c478bd9Sstevel@tonic-gate  *                          follows the incomplete filename.
5947c478bd9Sstevel@tonic-gate  *  escaped          int    If true, escape special characters in the
5957c478bd9Sstevel@tonic-gate  *                          completion suffixes.
5967c478bd9Sstevel@tonic-gate  *  check_fn  CplCheckFn *  If not zero, this argument specifies a
5977c478bd9Sstevel@tonic-gate  *                          function to call to ask whether a given
5987c478bd9Sstevel@tonic-gate  *                          file should be included in the list
5997c478bd9Sstevel@tonic-gate  *                          of completions.
6007c478bd9Sstevel@tonic-gate  *  check_data      void *  Anonymous data to be passed to check_fn().
6017c478bd9Sstevel@tonic-gate  * Output:
6027c478bd9Sstevel@tonic-gate  *  return           int    0 - OK.
6037c478bd9Sstevel@tonic-gate  *                          1 - Error.
6047c478bd9Sstevel@tonic-gate  */
cf_complete_entry(CompleteFile * cf,WordCompletion * cpl,const char * line,int word_start,int word_end,int escaped,CplCheckFn * check_fn,void * check_data)6057c478bd9Sstevel@tonic-gate static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl,
6067c478bd9Sstevel@tonic-gate 			     const char *line, int word_start, int word_end,
6077c478bd9Sstevel@tonic-gate 			     int escaped, CplCheckFn *check_fn,
6087c478bd9Sstevel@tonic-gate 			     void *check_data)
6097c478bd9Sstevel@tonic-gate {
6107c478bd9Sstevel@tonic-gate   const char *dirpath;   /* The name of the parent directory */
6117c478bd9Sstevel@tonic-gate   int start;             /* The index of the start of the last filename */
6127c478bd9Sstevel@tonic-gate                          /*  component in the transcribed filename. */
6137c478bd9Sstevel@tonic-gate   const char *prefix;    /* The filename prefix to be completed */
6147c478bd9Sstevel@tonic-gate   int prefix_len;        /* The length of the filename prefix */
6157c478bd9Sstevel@tonic-gate   const char *file_name; /* The lastest filename being compared */
6167c478bd9Sstevel@tonic-gate   int waserr = 0;        /* True after errors */
6177c478bd9Sstevel@tonic-gate   int terminated=0;      /* True if the directory part had to be terminated */
6187c478bd9Sstevel@tonic-gate /*
6197c478bd9Sstevel@tonic-gate  * Get the pathname string and its current length.
6207c478bd9Sstevel@tonic-gate  */
6217c478bd9Sstevel@tonic-gate   char *pathname = cf->path->name;
6227c478bd9Sstevel@tonic-gate   int pathlen = strlen(pathname);
6237c478bd9Sstevel@tonic-gate /*
6247c478bd9Sstevel@tonic-gate  * Locate the start of the final component of the pathname.
6257c478bd9Sstevel@tonic-gate  */
6267c478bd9Sstevel@tonic-gate   for(start=pathlen - 1; start >= 0 &&
6277c478bd9Sstevel@tonic-gate       strncmp(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0; start--)
6287c478bd9Sstevel@tonic-gate     ;
6297c478bd9Sstevel@tonic-gate /*
6307c478bd9Sstevel@tonic-gate  * Is the parent directory the root directory?
6317c478bd9Sstevel@tonic-gate  */
6327c478bd9Sstevel@tonic-gate   if(start==0 ||
6337c478bd9Sstevel@tonic-gate      (start < 0 && strncmp(pathname, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0)) {
6347c478bd9Sstevel@tonic-gate     dirpath = FS_ROOT_DIR;
6357c478bd9Sstevel@tonic-gate     start += FS_ROOT_DIR_LEN;
6367c478bd9Sstevel@tonic-gate /*
6377c478bd9Sstevel@tonic-gate  * If we found a directory separator then the part which precedes the
6387c478bd9Sstevel@tonic-gate  * last component is the name of the directory to be opened.
6397c478bd9Sstevel@tonic-gate  */
6407c478bd9Sstevel@tonic-gate   } else if(start > 0) {
6417c478bd9Sstevel@tonic-gate /*
6427c478bd9Sstevel@tonic-gate  * The _dr_open_dir() function requires the directory name to be '\0'
6437c478bd9Sstevel@tonic-gate  * terminated, so temporarily do this by overwriting the first character
6447c478bd9Sstevel@tonic-gate  * of the directory separator.
6457c478bd9Sstevel@tonic-gate  */
6467c478bd9Sstevel@tonic-gate     pathname[start] = '\0';
6477c478bd9Sstevel@tonic-gate     dirpath = pathname;
6487c478bd9Sstevel@tonic-gate     terminated = 1;
6497c478bd9Sstevel@tonic-gate /*
6507c478bd9Sstevel@tonic-gate  * We reached the start of the pathname before finding a directory
6517c478bd9Sstevel@tonic-gate  * separator, so arrange to open the current working directory.
6527c478bd9Sstevel@tonic-gate  */
6537c478bd9Sstevel@tonic-gate   } else {
6547c478bd9Sstevel@tonic-gate     start = 0;
6557c478bd9Sstevel@tonic-gate     dirpath = FS_PWD;
6567c478bd9Sstevel@tonic-gate   };
6577c478bd9Sstevel@tonic-gate /*
6587c478bd9Sstevel@tonic-gate  * Attempt to open the directory.
6597c478bd9Sstevel@tonic-gate  */
6607c478bd9Sstevel@tonic-gate   if(_dr_open_dir(cf->dr, dirpath, NULL)) {
6617c478bd9Sstevel@tonic-gate     _err_record_msg(cf->err, "Can't open directory: ", dirpath, END_ERR_MSG);
6627c478bd9Sstevel@tonic-gate     return 1;
6637c478bd9Sstevel@tonic-gate   };
6647c478bd9Sstevel@tonic-gate /*
6657c478bd9Sstevel@tonic-gate  * If removed above, restore the directory separator and skip over it
6667c478bd9Sstevel@tonic-gate  * to the start of the filename.
6677c478bd9Sstevel@tonic-gate  */
6687c478bd9Sstevel@tonic-gate   if(terminated) {
6697c478bd9Sstevel@tonic-gate     memcpy(pathname + start, FS_DIR_SEP, FS_DIR_SEP_LEN);
6707c478bd9Sstevel@tonic-gate     start += FS_DIR_SEP_LEN;
6717c478bd9Sstevel@tonic-gate   };
6727c478bd9Sstevel@tonic-gate /*
6737c478bd9Sstevel@tonic-gate  * Get the filename prefix and its length.
6747c478bd9Sstevel@tonic-gate  */
6757c478bd9Sstevel@tonic-gate   prefix = pathname + start;
6767c478bd9Sstevel@tonic-gate   prefix_len = strlen(prefix);
6777c478bd9Sstevel@tonic-gate /*
6787c478bd9Sstevel@tonic-gate  * Traverse the directory, looking for files who's prefixes match the
6797c478bd9Sstevel@tonic-gate  * last component of the pathname.
6807c478bd9Sstevel@tonic-gate  */
6817c478bd9Sstevel@tonic-gate   while((file_name = _dr_next_file(cf->dr)) != NULL && !waserr) {
6827c478bd9Sstevel@tonic-gate     int name_len = strlen(file_name);
6837c478bd9Sstevel@tonic-gate /*
6847c478bd9Sstevel@tonic-gate  * Is the latest filename a possible completion of the filename prefix?
6857c478bd9Sstevel@tonic-gate  */
6867c478bd9Sstevel@tonic-gate     if(name_len >= prefix_len && strncmp(prefix, file_name, prefix_len)==0) {
6877c478bd9Sstevel@tonic-gate /*
6887c478bd9Sstevel@tonic-gate  * When listing all files in a directory, don't list files that start
6897c478bd9Sstevel@tonic-gate  * with '.'. This is how hidden files are denoted in UNIX.
6907c478bd9Sstevel@tonic-gate  */
6917c478bd9Sstevel@tonic-gate       if(prefix_len > 0 || file_name[0] != '.') {
6927c478bd9Sstevel@tonic-gate /*
6937c478bd9Sstevel@tonic-gate  * Copy the completion suffix into the work pathname cf->buff->name,
6947c478bd9Sstevel@tonic-gate  * adding backslash escapes if needed.
6957c478bd9Sstevel@tonic-gate  */
6967c478bd9Sstevel@tonic-gate 	if(cf_prepare_suffix(cf, file_name + prefix_len, escaped)) {
6977c478bd9Sstevel@tonic-gate 	  waserr = 1;
6987c478bd9Sstevel@tonic-gate 	} else {
6997c478bd9Sstevel@tonic-gate /*
7007c478bd9Sstevel@tonic-gate  * We want directories to be displayed with directory suffixes,
7017c478bd9Sstevel@tonic-gate  * and other fully completed filenames to be followed by spaces.
7027c478bd9Sstevel@tonic-gate  * To check the type of the file, append the current suffix
7037c478bd9Sstevel@tonic-gate  * to the path being completed, check the filetype, then restore
7047c478bd9Sstevel@tonic-gate  * the path to its original form.
7057c478bd9Sstevel@tonic-gate  */
7067c478bd9Sstevel@tonic-gate 	  const char *cont_suffix = "";  /* The suffix to add if fully */
7077c478bd9Sstevel@tonic-gate                                          /*  completed. */
7087c478bd9Sstevel@tonic-gate 	  const char *type_suffix = "";  /* The suffix to add when listing */
7097c478bd9Sstevel@tonic-gate 	  if(_pn_append_to_path(cf->path, file_name + prefix_len,
7107c478bd9Sstevel@tonic-gate 				-1, escaped) == NULL) {
7117c478bd9Sstevel@tonic-gate 	    _err_record_msg(cf->err,
7127c478bd9Sstevel@tonic-gate 			    "Insufficient memory to complete filename.",
7137c478bd9Sstevel@tonic-gate 			    END_ERR_MSG);
7147c478bd9Sstevel@tonic-gate 	    return 1;
7157c478bd9Sstevel@tonic-gate 	  };
7167c478bd9Sstevel@tonic-gate /*
7177c478bd9Sstevel@tonic-gate  * Specify suffixes according to the file type.
7187c478bd9Sstevel@tonic-gate  */
7197c478bd9Sstevel@tonic-gate 	  if(_pu_path_is_dir(cf->path->name)) {
7207c478bd9Sstevel@tonic-gate 	    cont_suffix = FS_DIR_SEP;
7217c478bd9Sstevel@tonic-gate 	    type_suffix = FS_DIR_SEP;
7227c478bd9Sstevel@tonic-gate 	  } else if(!check_fn || check_fn(check_data, cf->path->name)) {
7237c478bd9Sstevel@tonic-gate 	    cont_suffix = " ";
7247c478bd9Sstevel@tonic-gate 	  } else {
7257c478bd9Sstevel@tonic-gate 	    cf->path->name[pathlen] = '\0';
7267c478bd9Sstevel@tonic-gate 	    continue;
7277c478bd9Sstevel@tonic-gate 	  };
7287c478bd9Sstevel@tonic-gate /*
7297c478bd9Sstevel@tonic-gate  * Remove the temporarily added suffix.
7307c478bd9Sstevel@tonic-gate  */
7317c478bd9Sstevel@tonic-gate 	  cf->path->name[pathlen] = '\0';
7327c478bd9Sstevel@tonic-gate /*
7337c478bd9Sstevel@tonic-gate  * Record the latest completion.
7347c478bd9Sstevel@tonic-gate  */
7357c478bd9Sstevel@tonic-gate 	  if(cpl_add_completion(cpl, line, word_start, word_end, cf->buff->name,
7367c478bd9Sstevel@tonic-gate 				type_suffix, cont_suffix))
7377c478bd9Sstevel@tonic-gate 	    waserr = 1;
7387c478bd9Sstevel@tonic-gate 	};
7397c478bd9Sstevel@tonic-gate       };
7407c478bd9Sstevel@tonic-gate     };
7417c478bd9Sstevel@tonic-gate   };
7427c478bd9Sstevel@tonic-gate /*
7437c478bd9Sstevel@tonic-gate  * Close the directory.
7447c478bd9Sstevel@tonic-gate  */
7457c478bd9Sstevel@tonic-gate   _dr_close_dir(cf->dr);
7467c478bd9Sstevel@tonic-gate   return waserr;
7477c478bd9Sstevel@tonic-gate }
7487c478bd9Sstevel@tonic-gate 
7497c478bd9Sstevel@tonic-gate /*.......................................................................
7507c478bd9Sstevel@tonic-gate  * Read a username or environment variable name, stopping when a directory
7517c478bd9Sstevel@tonic-gate  * separator is seen, when the end of the string is reached, or the
7527c478bd9Sstevel@tonic-gate  * output buffer overflows.
7537c478bd9Sstevel@tonic-gate  *
7547c478bd9Sstevel@tonic-gate  * Input:
7557c478bd9Sstevel@tonic-gate  *  cf   CompleteFile *  The file-completion resource object.
7567c478bd9Sstevel@tonic-gate  *  type         char *  The capitalized name of the type of name being read.
7577c478bd9Sstevel@tonic-gate  *  string       char *  The string who's prefix contains the name.
7587c478bd9Sstevel@tonic-gate  *  slen          int    The number of characters in string[].
7597c478bd9Sstevel@tonic-gate  *  nambuf       char *  The output name buffer.
7607c478bd9Sstevel@tonic-gate  *  nammax        int    The longest string that will fit in nambuf[], excluding
7617c478bd9Sstevel@tonic-gate  *                       the '\0' terminator.
7627c478bd9Sstevel@tonic-gate  * Output:
7637c478bd9Sstevel@tonic-gate  *  return       char *  A pointer to nambuf on success. On error NULL is
7647c478bd9Sstevel@tonic-gate  *                       returned and a description of the error is recorded
7657c478bd9Sstevel@tonic-gate  *                       in cf->err.
7667c478bd9Sstevel@tonic-gate  */
cf_read_name(CompleteFile * cf,const char * type,const char * string,int slen,char * nambuf,int nammax)7677c478bd9Sstevel@tonic-gate static char *cf_read_name(CompleteFile *cf, const char *type,
7687c478bd9Sstevel@tonic-gate 			  const char *string, int slen,
7697c478bd9Sstevel@tonic-gate 			  char *nambuf, int nammax)
7707c478bd9Sstevel@tonic-gate {
7717c478bd9Sstevel@tonic-gate   int namlen;         /* The number of characters in nambuf[] */
7727c478bd9Sstevel@tonic-gate   const char *sptr;   /* A pointer into string[] */
7737c478bd9Sstevel@tonic-gate /*
7747c478bd9Sstevel@tonic-gate  * Work out the max number of characters that should be copied.
7757c478bd9Sstevel@tonic-gate  */
7767c478bd9Sstevel@tonic-gate   int nmax = nammax < slen ? nammax : slen;
7777c478bd9Sstevel@tonic-gate /*
7787c478bd9Sstevel@tonic-gate  * Get the environment variable name that follows the dollar.
7797c478bd9Sstevel@tonic-gate  */
7807c478bd9Sstevel@tonic-gate   for(sptr=string,namlen=0;
7817c478bd9Sstevel@tonic-gate       namlen < nmax && (slen-namlen < FS_DIR_SEP_LEN ||
7827c478bd9Sstevel@tonic-gate 			strncmp(sptr, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0);
7837c478bd9Sstevel@tonic-gate       namlen++) {
7847c478bd9Sstevel@tonic-gate     nambuf[namlen] = *sptr++;
7857c478bd9Sstevel@tonic-gate   };
7867c478bd9Sstevel@tonic-gate /*
7877c478bd9Sstevel@tonic-gate  * Did the name overflow the buffer?
7887c478bd9Sstevel@tonic-gate  */
7897c478bd9Sstevel@tonic-gate   if(namlen >= nammax) {
7907c478bd9Sstevel@tonic-gate     _err_record_msg(cf->err, type, " name too long", END_ERR_MSG);
7917c478bd9Sstevel@tonic-gate     return NULL;
7927c478bd9Sstevel@tonic-gate   };
7937c478bd9Sstevel@tonic-gate /*
7947c478bd9Sstevel@tonic-gate  * Terminate the string.
7957c478bd9Sstevel@tonic-gate  */
7967c478bd9Sstevel@tonic-gate   nambuf[namlen] = '\0';
7977c478bd9Sstevel@tonic-gate   return nambuf;
7987c478bd9Sstevel@tonic-gate }
7997c478bd9Sstevel@tonic-gate 
8007c478bd9Sstevel@tonic-gate /*.......................................................................
8017c478bd9Sstevel@tonic-gate  * Using the work buffer cf->buff, make a suitably escaped copy of a
8027c478bd9Sstevel@tonic-gate  * given completion suffix, ready to be passed to cpl_add_completion().
8037c478bd9Sstevel@tonic-gate  *
8047c478bd9Sstevel@tonic-gate  * Input:
8057c478bd9Sstevel@tonic-gate  *  cf   CompleteFile *  The file-completion resource object.
8067c478bd9Sstevel@tonic-gate  *  suffix       char *  The suffix to be copied.
8077c478bd9Sstevel@tonic-gate  *  add_escapes   int    If true, escape special characters.
8087c478bd9Sstevel@tonic-gate  * Output:
8097c478bd9Sstevel@tonic-gate  *  return        int    0 - OK.
8107c478bd9Sstevel@tonic-gate  *                       1 - Error.
8117c478bd9Sstevel@tonic-gate  */
cf_prepare_suffix(CompleteFile * cf,const char * suffix,int add_escapes)8127c478bd9Sstevel@tonic-gate static int cf_prepare_suffix(CompleteFile *cf, const char *suffix,
8137c478bd9Sstevel@tonic-gate 			     int add_escapes)
8147c478bd9Sstevel@tonic-gate {
8157c478bd9Sstevel@tonic-gate   const char *sptr; /* A pointer into suffix[] */
8167c478bd9Sstevel@tonic-gate   int nbsl;         /* The number of backslashes to add to the suffix */
8177c478bd9Sstevel@tonic-gate   int i;
8187c478bd9Sstevel@tonic-gate /*
8197c478bd9Sstevel@tonic-gate  * How long is the suffix?
8207c478bd9Sstevel@tonic-gate  */
8217c478bd9Sstevel@tonic-gate   int suffix_len = strlen(suffix);
8227c478bd9Sstevel@tonic-gate /*
8237c478bd9Sstevel@tonic-gate  * Clear the work buffer.
8247c478bd9Sstevel@tonic-gate  */
8257c478bd9Sstevel@tonic-gate   _pn_clear_path(cf->buff);
8267c478bd9Sstevel@tonic-gate /*
8277c478bd9Sstevel@tonic-gate  * Count the number of backslashes that will have to be added to
8287c478bd9Sstevel@tonic-gate  * escape spaces, tabs, backslashes and wildcard characters.
8297c478bd9Sstevel@tonic-gate  */
8307c478bd9Sstevel@tonic-gate   nbsl = 0;
8317c478bd9Sstevel@tonic-gate   if(add_escapes) {
8327c478bd9Sstevel@tonic-gate     for(sptr = suffix; *sptr; sptr++) {
8337c478bd9Sstevel@tonic-gate       switch(*sptr) {
8347c478bd9Sstevel@tonic-gate       case ' ': case '\t': case '\\': case '*': case '?': case '[':
8357c478bd9Sstevel@tonic-gate 	nbsl++;
8367c478bd9Sstevel@tonic-gate 	break;
8377c478bd9Sstevel@tonic-gate       };
8387c478bd9Sstevel@tonic-gate     };
8397c478bd9Sstevel@tonic-gate   };
8407c478bd9Sstevel@tonic-gate /*
8417c478bd9Sstevel@tonic-gate  * Arrange for the output path buffer to have sufficient room for the
8427c478bd9Sstevel@tonic-gate  * both the suffix and any backslashes that have to be inserted.
8437c478bd9Sstevel@tonic-gate  */
8447c478bd9Sstevel@tonic-gate   if(_pn_resize_path(cf->buff, suffix_len + nbsl) == NULL) {
8457c478bd9Sstevel@tonic-gate     _err_record_msg(cf->err, "Insufficient memory to complete filename",
8467c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
8477c478bd9Sstevel@tonic-gate     return 1;
8487c478bd9Sstevel@tonic-gate   };
8497c478bd9Sstevel@tonic-gate /*
8507c478bd9Sstevel@tonic-gate  * If the suffix doesn't need any escapes, copy it directly into the
8517c478bd9Sstevel@tonic-gate  * work buffer.
8527c478bd9Sstevel@tonic-gate  */
8537c478bd9Sstevel@tonic-gate   if(nbsl==0) {
8547c478bd9Sstevel@tonic-gate     strlcpy(cf->buff->name, suffix, cf->buff->dim);
8557c478bd9Sstevel@tonic-gate   } else {
8567c478bd9Sstevel@tonic-gate /*
8577c478bd9Sstevel@tonic-gate  * Make a copy with special characters escaped?
8587c478bd9Sstevel@tonic-gate  */
8597c478bd9Sstevel@tonic-gate     if(nbsl > 0) {
8607c478bd9Sstevel@tonic-gate       const char *src = suffix;
8617c478bd9Sstevel@tonic-gate       char *dst = cf->buff->name;
8627c478bd9Sstevel@tonic-gate       for(i=0; i<suffix_len; i++) {
8637c478bd9Sstevel@tonic-gate 	switch(*src) {
8647c478bd9Sstevel@tonic-gate 	case ' ': case '\t': case '\\': case '*': case '?': case '[':
8657c478bd9Sstevel@tonic-gate 	  *dst++ = '\\';
8667c478bd9Sstevel@tonic-gate 	};
8677c478bd9Sstevel@tonic-gate 	*dst++ = *src++;
8687c478bd9Sstevel@tonic-gate       };
8697c478bd9Sstevel@tonic-gate       *dst = '\0';
8707c478bd9Sstevel@tonic-gate     };
8717c478bd9Sstevel@tonic-gate   };
8727c478bd9Sstevel@tonic-gate   return 0;
8737c478bd9Sstevel@tonic-gate }
8747c478bd9Sstevel@tonic-gate 
8757c478bd9Sstevel@tonic-gate #endif  /* ifndef WITHOUT_FILE_SYSTEM */
876