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