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