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