1*7c478bd9Sstevel@tonic-gate /*
2*7c478bd9Sstevel@tonic-gate  * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3*7c478bd9Sstevel@tonic-gate  *
4*7c478bd9Sstevel@tonic-gate  * All rights reserved.
5*7c478bd9Sstevel@tonic-gate  *
6*7c478bd9Sstevel@tonic-gate  * Permission is hereby granted, free of charge, to any person obtaining a
7*7c478bd9Sstevel@tonic-gate  * copy of this software and associated documentation files (the
8*7c478bd9Sstevel@tonic-gate  * "Software"), to deal in the Software without restriction, including
9*7c478bd9Sstevel@tonic-gate  * without limitation the rights to use, copy, modify, merge, publish,
10*7c478bd9Sstevel@tonic-gate  * distribute, and/or sell copies of the Software, and to permit persons
11*7c478bd9Sstevel@tonic-gate  * to whom the Software is furnished to do so, provided that the above
12*7c478bd9Sstevel@tonic-gate  * copyright notice(s) and this permission notice appear in all copies of
13*7c478bd9Sstevel@tonic-gate  * the Software and that both the above copyright notice(s) and this
14*7c478bd9Sstevel@tonic-gate  * permission notice appear in supporting documentation.
15*7c478bd9Sstevel@tonic-gate  *
16*7c478bd9Sstevel@tonic-gate  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17*7c478bd9Sstevel@tonic-gate  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18*7c478bd9Sstevel@tonic-gate  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19*7c478bd9Sstevel@tonic-gate  * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20*7c478bd9Sstevel@tonic-gate  * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21*7c478bd9Sstevel@tonic-gate  * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22*7c478bd9Sstevel@tonic-gate  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23*7c478bd9Sstevel@tonic-gate  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24*7c478bd9Sstevel@tonic-gate  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25*7c478bd9Sstevel@tonic-gate  *
26*7c478bd9Sstevel@tonic-gate  * Except as contained in this notice, the name of a copyright holder
27*7c478bd9Sstevel@tonic-gate  * shall not be used in advertising or otherwise to promote the sale, use
28*7c478bd9Sstevel@tonic-gate  * or other dealings in this Software without prior written authorization
29*7c478bd9Sstevel@tonic-gate  * of the copyright holder.
30*7c478bd9Sstevel@tonic-gate  */
31*7c478bd9Sstevel@tonic-gate 
32*7c478bd9Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
33*7c478bd9Sstevel@tonic-gate 
34*7c478bd9Sstevel@tonic-gate /*
35*7c478bd9Sstevel@tonic-gate  * If file-system access is to be excluded, this module has no function,
36*7c478bd9Sstevel@tonic-gate  * so all of its code should be excluded.
37*7c478bd9Sstevel@tonic-gate  */
38*7c478bd9Sstevel@tonic-gate #ifndef WITHOUT_FILE_SYSTEM
39*7c478bd9Sstevel@tonic-gate 
40*7c478bd9Sstevel@tonic-gate #include <stdio.h>
41*7c478bd9Sstevel@tonic-gate #include <stdlib.h>
42*7c478bd9Sstevel@tonic-gate #include <string.h>
43*7c478bd9Sstevel@tonic-gate #include <errno.h>
44*7c478bd9Sstevel@tonic-gate 
45*7c478bd9Sstevel@tonic-gate #include "freelist.h"
46*7c478bd9Sstevel@tonic-gate #include "direader.h"
47*7c478bd9Sstevel@tonic-gate #include "pathutil.h"
48*7c478bd9Sstevel@tonic-gate #include "homedir.h"
49*7c478bd9Sstevel@tonic-gate #include "stringrp.h"
50*7c478bd9Sstevel@tonic-gate #include "libtecla.h"
51*7c478bd9Sstevel@tonic-gate #include "ioutil.h"
52*7c478bd9Sstevel@tonic-gate #include "expand.h"
53*7c478bd9Sstevel@tonic-gate #include "errmsg.h"
54*7c478bd9Sstevel@tonic-gate 
55*7c478bd9Sstevel@tonic-gate /*
56*7c478bd9Sstevel@tonic-gate  * Specify the number of elements to extend the files[] array by
57*7c478bd9Sstevel@tonic-gate  * when it proves to be too small. This also sets the initial size
58*7c478bd9Sstevel@tonic-gate  * of the array.
59*7c478bd9Sstevel@tonic-gate  */
60*7c478bd9Sstevel@tonic-gate #define MATCH_BLK_FACT 256
61*7c478bd9Sstevel@tonic-gate 
62*7c478bd9Sstevel@tonic-gate /*
63*7c478bd9Sstevel@tonic-gate  * A list of directory iterators is maintained using nodes of the
64*7c478bd9Sstevel@tonic-gate  * following form.
65*7c478bd9Sstevel@tonic-gate  */
66*7c478bd9Sstevel@tonic-gate typedef struct DirNode DirNode;
67*7c478bd9Sstevel@tonic-gate struct DirNode {
68*7c478bd9Sstevel@tonic-gate   DirNode *next;       /* The next directory in the list */
69*7c478bd9Sstevel@tonic-gate   DirNode *prev;       /* The node that precedes this node in the list */
70*7c478bd9Sstevel@tonic-gate   DirReader *dr;       /* The directory reader object */
71*7c478bd9Sstevel@tonic-gate };
72*7c478bd9Sstevel@tonic-gate 
73*7c478bd9Sstevel@tonic-gate typedef struct {
74*7c478bd9Sstevel@tonic-gate   FreeList *mem;       /* Memory for DirNode list nodes */
75*7c478bd9Sstevel@tonic-gate   DirNode *head;       /* The head of the list of used and unused cache nodes */
76*7c478bd9Sstevel@tonic-gate   DirNode *next;       /* The next unused node between head and tail */
77*7c478bd9Sstevel@tonic-gate   DirNode *tail;       /* The tail of the list of unused cache nodes */
78*7c478bd9Sstevel@tonic-gate } DirCache;
79*7c478bd9Sstevel@tonic-gate 
80*7c478bd9Sstevel@tonic-gate /*
81*7c478bd9Sstevel@tonic-gate  * Specify how many directory cache nodes to allocate at a time.
82*7c478bd9Sstevel@tonic-gate  */
83*7c478bd9Sstevel@tonic-gate #define DIR_CACHE_BLK 20
84*7c478bd9Sstevel@tonic-gate 
85*7c478bd9Sstevel@tonic-gate /*
86*7c478bd9Sstevel@tonic-gate  * Set the maximum length allowed for usernames.
87*7c478bd9Sstevel@tonic-gate  */
88*7c478bd9Sstevel@tonic-gate #define USR_LEN 100
89*7c478bd9Sstevel@tonic-gate 
90*7c478bd9Sstevel@tonic-gate /*
91*7c478bd9Sstevel@tonic-gate  * Set the maximum length allowed for environment variable names.
92*7c478bd9Sstevel@tonic-gate  */
93*7c478bd9Sstevel@tonic-gate #define ENV_LEN 100
94*7c478bd9Sstevel@tonic-gate 
95*7c478bd9Sstevel@tonic-gate /*
96*7c478bd9Sstevel@tonic-gate  * Set the default number of spaces place between columns when listing
97*7c478bd9Sstevel@tonic-gate  * a set of expansions.
98*7c478bd9Sstevel@tonic-gate  */
99*7c478bd9Sstevel@tonic-gate #define EF_COL_SEP 2
100*7c478bd9Sstevel@tonic-gate 
101*7c478bd9Sstevel@tonic-gate struct ExpandFile {
102*7c478bd9Sstevel@tonic-gate   ErrMsg *err;            /* The error reporting buffer */
103*7c478bd9Sstevel@tonic-gate   StringGroup *sg;        /* A list of string segments in which */
104*7c478bd9Sstevel@tonic-gate                           /*  matching filenames are stored. */
105*7c478bd9Sstevel@tonic-gate   DirCache cache;         /* The cache of directory reader objects */
106*7c478bd9Sstevel@tonic-gate   PathName *path;         /* The pathname being matched */
107*7c478bd9Sstevel@tonic-gate   HomeDir *home;          /* Home-directory lookup object */
108*7c478bd9Sstevel@tonic-gate   int files_dim;          /* The allocated dimension of result.files[] */
109*7c478bd9Sstevel@tonic-gate   char usrnam[USR_LEN+1]; /* A user name */
110*7c478bd9Sstevel@tonic-gate   char envnam[ENV_LEN+1]; /* An environment variable name */
111*7c478bd9Sstevel@tonic-gate   FileExpansion result;   /* The container used to return the results of */
112*7c478bd9Sstevel@tonic-gate                           /*  expanding a path. */
113*7c478bd9Sstevel@tonic-gate };
114*7c478bd9Sstevel@tonic-gate 
115*7c478bd9Sstevel@tonic-gate static int ef_record_pathname(ExpandFile *ef, const char *pathname,
116*7c478bd9Sstevel@tonic-gate 			      int remove_escapes);
117*7c478bd9Sstevel@tonic-gate static char *ef_cache_pathname(ExpandFile *ef, const char *pathname,
118*7c478bd9Sstevel@tonic-gate 			       int remove_escapes);
119*7c478bd9Sstevel@tonic-gate static void ef_clear_files(ExpandFile *ef);
120*7c478bd9Sstevel@tonic-gate 
121*7c478bd9Sstevel@tonic-gate static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname);
122*7c478bd9Sstevel@tonic-gate static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node);
123*7c478bd9Sstevel@tonic-gate static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen);
124*7c478bd9Sstevel@tonic-gate static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr,
125*7c478bd9Sstevel@tonic-gate 				      const char *pattern, int separate);
126*7c478bd9Sstevel@tonic-gate static int ef_matches_range(int c, const char *pattern, const char **endp);
127*7c478bd9Sstevel@tonic-gate static int ef_string_matches_pattern(const char *file, const char *pattern,
128*7c478bd9Sstevel@tonic-gate 				      int xplicit, const char *nextp);
129*7c478bd9Sstevel@tonic-gate static int ef_cmp_strings(const void *v1, const void *v2);
130*7c478bd9Sstevel@tonic-gate 
131*7c478bd9Sstevel@tonic-gate /*
132*7c478bd9Sstevel@tonic-gate  * Encapsulate the formatting information needed to layout a
133*7c478bd9Sstevel@tonic-gate  * multi-column listing of expansions.
134*7c478bd9Sstevel@tonic-gate  */
135*7c478bd9Sstevel@tonic-gate typedef struct {
136*7c478bd9Sstevel@tonic-gate   int term_width;     /* The width of the terminal (characters) */
137*7c478bd9Sstevel@tonic-gate   int column_width;   /* The number of characters within in each column. */
138*7c478bd9Sstevel@tonic-gate   int ncol;           /* The number of columns needed */
139*7c478bd9Sstevel@tonic-gate   int nline;          /* The number of lines needed */
140*7c478bd9Sstevel@tonic-gate } EfListFormat;
141*7c478bd9Sstevel@tonic-gate 
142*7c478bd9Sstevel@tonic-gate /*
143*7c478bd9Sstevel@tonic-gate  * Given the current terminal width, and a list of file expansions,
144*7c478bd9Sstevel@tonic-gate  * determine how to best use the terminal width to display a multi-column
145*7c478bd9Sstevel@tonic-gate  * listing of expansions.
146*7c478bd9Sstevel@tonic-gate  */
147*7c478bd9Sstevel@tonic-gate static void ef_plan_listing(FileExpansion *result, int term_width,
148*7c478bd9Sstevel@tonic-gate 			    EfListFormat *fmt);
149*7c478bd9Sstevel@tonic-gate 
150*7c478bd9Sstevel@tonic-gate /*
151*7c478bd9Sstevel@tonic-gate  * Display a given line of a multi-column list of file-expansions.
152*7c478bd9Sstevel@tonic-gate  */
153*7c478bd9Sstevel@tonic-gate static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum,
154*7c478bd9Sstevel@tonic-gate 			  GlWriteFn *write_fn, void *data);
155*7c478bd9Sstevel@tonic-gate 
156*7c478bd9Sstevel@tonic-gate /*.......................................................................
157*7c478bd9Sstevel@tonic-gate  * Create the resources needed to expand filenames.
158*7c478bd9Sstevel@tonic-gate  *
159*7c478bd9Sstevel@tonic-gate  * Output:
160*7c478bd9Sstevel@tonic-gate  *  return  ExpandFile *  The new object, or NULL on error.
161*7c478bd9Sstevel@tonic-gate  */
162*7c478bd9Sstevel@tonic-gate ExpandFile *new_ExpandFile(void)
163*7c478bd9Sstevel@tonic-gate {
164*7c478bd9Sstevel@tonic-gate   ExpandFile *ef;  /* The object to be returned */
165*7c478bd9Sstevel@tonic-gate /*
166*7c478bd9Sstevel@tonic-gate  * Allocate the container.
167*7c478bd9Sstevel@tonic-gate  */
168*7c478bd9Sstevel@tonic-gate   ef = (ExpandFile *) malloc(sizeof(ExpandFile));
169*7c478bd9Sstevel@tonic-gate   if(!ef) {
170*7c478bd9Sstevel@tonic-gate     errno = ENOMEM;
171*7c478bd9Sstevel@tonic-gate     return NULL;
172*7c478bd9Sstevel@tonic-gate   };
173*7c478bd9Sstevel@tonic-gate /*
174*7c478bd9Sstevel@tonic-gate  * Before attempting any operation that might fail, initialize the
175*7c478bd9Sstevel@tonic-gate  * container at least up to the point at which it can safely be passed
176*7c478bd9Sstevel@tonic-gate  * to del_ExpandFile().
177*7c478bd9Sstevel@tonic-gate  */
178*7c478bd9Sstevel@tonic-gate   ef->err = NULL;
179*7c478bd9Sstevel@tonic-gate   ef->sg = NULL;
180*7c478bd9Sstevel@tonic-gate   ef->cache.mem = NULL;
181*7c478bd9Sstevel@tonic-gate   ef->cache.head = NULL;
182*7c478bd9Sstevel@tonic-gate   ef->cache.next = NULL;
183*7c478bd9Sstevel@tonic-gate   ef->cache.tail = NULL;
184*7c478bd9Sstevel@tonic-gate   ef->path = NULL;
185*7c478bd9Sstevel@tonic-gate   ef->home = NULL;
186*7c478bd9Sstevel@tonic-gate   ef->result.files = NULL;
187*7c478bd9Sstevel@tonic-gate   ef->result.nfile = 0;
188*7c478bd9Sstevel@tonic-gate   ef->usrnam[0] = '\0';
189*7c478bd9Sstevel@tonic-gate   ef->envnam[0] = '\0';
190*7c478bd9Sstevel@tonic-gate /*
191*7c478bd9Sstevel@tonic-gate  * Allocate a place to record error messages.
192*7c478bd9Sstevel@tonic-gate  */
193*7c478bd9Sstevel@tonic-gate   ef->err = _new_ErrMsg();
194*7c478bd9Sstevel@tonic-gate   if(!ef->err)
195*7c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
196*7c478bd9Sstevel@tonic-gate /*
197*7c478bd9Sstevel@tonic-gate  * Allocate a list of string segments for storing filenames.
198*7c478bd9Sstevel@tonic-gate  */
199*7c478bd9Sstevel@tonic-gate   ef->sg = _new_StringGroup(_pu_pathname_dim());
200*7c478bd9Sstevel@tonic-gate   if(!ef->sg)
201*7c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
202*7c478bd9Sstevel@tonic-gate /*
203*7c478bd9Sstevel@tonic-gate  * Allocate a freelist for allocating directory cache nodes.
204*7c478bd9Sstevel@tonic-gate  */
205*7c478bd9Sstevel@tonic-gate   ef->cache.mem = _new_FreeList(sizeof(DirNode), DIR_CACHE_BLK);
206*7c478bd9Sstevel@tonic-gate   if(!ef->cache.mem)
207*7c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
208*7c478bd9Sstevel@tonic-gate /*
209*7c478bd9Sstevel@tonic-gate  * Allocate a pathname buffer.
210*7c478bd9Sstevel@tonic-gate  */
211*7c478bd9Sstevel@tonic-gate   ef->path = _new_PathName();
212*7c478bd9Sstevel@tonic-gate   if(!ef->path)
213*7c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
214*7c478bd9Sstevel@tonic-gate /*
215*7c478bd9Sstevel@tonic-gate  * Allocate an object for looking up home-directories.
216*7c478bd9Sstevel@tonic-gate  */
217*7c478bd9Sstevel@tonic-gate   ef->home = _new_HomeDir();
218*7c478bd9Sstevel@tonic-gate   if(!ef->home)
219*7c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
220*7c478bd9Sstevel@tonic-gate /*
221*7c478bd9Sstevel@tonic-gate  * Allocate an array for files. This will be extended later if needed.
222*7c478bd9Sstevel@tonic-gate  */
223*7c478bd9Sstevel@tonic-gate   ef->files_dim = MATCH_BLK_FACT;
224*7c478bd9Sstevel@tonic-gate   ef->result.files = (char **) malloc(sizeof(ef->result.files[0]) *
225*7c478bd9Sstevel@tonic-gate 				      ef->files_dim);
226*7c478bd9Sstevel@tonic-gate   if(!ef->result.files) {
227*7c478bd9Sstevel@tonic-gate     errno = ENOMEM;
228*7c478bd9Sstevel@tonic-gate     return del_ExpandFile(ef);
229*7c478bd9Sstevel@tonic-gate   };
230*7c478bd9Sstevel@tonic-gate   return ef;
231*7c478bd9Sstevel@tonic-gate }
232*7c478bd9Sstevel@tonic-gate 
233*7c478bd9Sstevel@tonic-gate /*.......................................................................
234*7c478bd9Sstevel@tonic-gate  * Delete a ExpandFile object.
235*7c478bd9Sstevel@tonic-gate  *
236*7c478bd9Sstevel@tonic-gate  * Input:
237*7c478bd9Sstevel@tonic-gate  *  ef     ExpandFile *  The object to be deleted.
238*7c478bd9Sstevel@tonic-gate  * Output:
239*7c478bd9Sstevel@tonic-gate  *  return ExpandFile *  The deleted object (always NULL).
240*7c478bd9Sstevel@tonic-gate  */
241*7c478bd9Sstevel@tonic-gate ExpandFile *del_ExpandFile(ExpandFile *ef)
242*7c478bd9Sstevel@tonic-gate {
243*7c478bd9Sstevel@tonic-gate   if(ef) {
244*7c478bd9Sstevel@tonic-gate     DirNode *dnode;
245*7c478bd9Sstevel@tonic-gate /*
246*7c478bd9Sstevel@tonic-gate  * Delete the string segments.
247*7c478bd9Sstevel@tonic-gate  */
248*7c478bd9Sstevel@tonic-gate     ef->sg = _del_StringGroup(ef->sg);
249*7c478bd9Sstevel@tonic-gate /*
250*7c478bd9Sstevel@tonic-gate  * Delete the cached directory readers.
251*7c478bd9Sstevel@tonic-gate  */
252*7c478bd9Sstevel@tonic-gate     for(dnode=ef->cache.head; dnode; dnode=dnode->next)
253*7c478bd9Sstevel@tonic-gate       dnode->dr = _del_DirReader(dnode->dr);
254*7c478bd9Sstevel@tonic-gate /*
255*7c478bd9Sstevel@tonic-gate  * Delete the memory from which the DirNode list was allocated, thus
256*7c478bd9Sstevel@tonic-gate  * deleting the list at the same time.
257*7c478bd9Sstevel@tonic-gate  */
258*7c478bd9Sstevel@tonic-gate     ef->cache.mem = _del_FreeList(ef->cache.mem, 1);
259*7c478bd9Sstevel@tonic-gate     ef->cache.head = ef->cache.tail = ef->cache.next = NULL;
260*7c478bd9Sstevel@tonic-gate /*
261*7c478bd9Sstevel@tonic-gate  * Delete the pathname buffer.
262*7c478bd9Sstevel@tonic-gate  */
263*7c478bd9Sstevel@tonic-gate     ef->path = _del_PathName(ef->path);
264*7c478bd9Sstevel@tonic-gate /*
265*7c478bd9Sstevel@tonic-gate  * Delete the home-directory lookup object.
266*7c478bd9Sstevel@tonic-gate  */
267*7c478bd9Sstevel@tonic-gate     ef->home = _del_HomeDir(ef->home);
268*7c478bd9Sstevel@tonic-gate /*
269*7c478bd9Sstevel@tonic-gate  * Delete the array of pointers to files.
270*7c478bd9Sstevel@tonic-gate  */
271*7c478bd9Sstevel@tonic-gate     if(ef->result.files) {
272*7c478bd9Sstevel@tonic-gate       free(ef->result.files);
273*7c478bd9Sstevel@tonic-gate       ef->result.files = NULL;
274*7c478bd9Sstevel@tonic-gate     };
275*7c478bd9Sstevel@tonic-gate /*
276*7c478bd9Sstevel@tonic-gate  * Delete the error report buffer.
277*7c478bd9Sstevel@tonic-gate  */
278*7c478bd9Sstevel@tonic-gate     ef->err = _del_ErrMsg(ef->err);
279*7c478bd9Sstevel@tonic-gate /*
280*7c478bd9Sstevel@tonic-gate  * Delete the container.
281*7c478bd9Sstevel@tonic-gate  */
282*7c478bd9Sstevel@tonic-gate     free(ef);
283*7c478bd9Sstevel@tonic-gate   };
284*7c478bd9Sstevel@tonic-gate   return NULL;
285*7c478bd9Sstevel@tonic-gate }
286*7c478bd9Sstevel@tonic-gate 
287*7c478bd9Sstevel@tonic-gate /*.......................................................................
288*7c478bd9Sstevel@tonic-gate  * Expand a pathname, converting ~user/ and ~/ patterns at the start
289*7c478bd9Sstevel@tonic-gate  * of the pathname to the corresponding home directories, replacing
290*7c478bd9Sstevel@tonic-gate  * $envvar with the value of the corresponding environment variable,
291*7c478bd9Sstevel@tonic-gate  * and then, if there are any wildcards, matching these against existing
292*7c478bd9Sstevel@tonic-gate  * filenames.
293*7c478bd9Sstevel@tonic-gate  *
294*7c478bd9Sstevel@tonic-gate  * If no errors occur, a container is returned containing the array of
295*7c478bd9Sstevel@tonic-gate  * files that resulted from the expansion. If there were no wildcards
296*7c478bd9Sstevel@tonic-gate  * in the input pathname, this will contain just the original pathname
297*7c478bd9Sstevel@tonic-gate  * after expansion of ~ and $ expressions. If there were any wildcards,
298*7c478bd9Sstevel@tonic-gate  * then the array will contain the files that matched them. Note that
299*7c478bd9Sstevel@tonic-gate  * if there were any wildcards but no existing files match them, this
300*7c478bd9Sstevel@tonic-gate  * is counted as an error and NULL is returned.
301*7c478bd9Sstevel@tonic-gate  *
302*7c478bd9Sstevel@tonic-gate  * The supported wildcards and their meanings are:
303*7c478bd9Sstevel@tonic-gate  *  *        -  Match any sequence of zero or more characters.
304*7c478bd9Sstevel@tonic-gate  *  ?        -  Match any single character.
305*7c478bd9Sstevel@tonic-gate  *  [chars]  -  Match any single character that appears in 'chars'.
306*7c478bd9Sstevel@tonic-gate  *              If 'chars' contains an expression of the form a-b,
307*7c478bd9Sstevel@tonic-gate  *              then any character between a and b, including a and b,
308*7c478bd9Sstevel@tonic-gate  *              matches. The '-' character looses its special meaning
309*7c478bd9Sstevel@tonic-gate  *              as a range specifier when it appears at the start
310*7c478bd9Sstevel@tonic-gate  *              of the sequence of characters.
311*7c478bd9Sstevel@tonic-gate  *  [^chars] -  The same as [chars] except that it matches any single
312*7c478bd9Sstevel@tonic-gate  *              character that doesn't appear in 'chars'.
313*7c478bd9Sstevel@tonic-gate  *
314*7c478bd9Sstevel@tonic-gate  * Wildcard expressions are applied to individual filename components.
315*7c478bd9Sstevel@tonic-gate  * They don't match across directory separators. A '.' character at
316*7c478bd9Sstevel@tonic-gate  * the beginning of a filename component must also be matched
317*7c478bd9Sstevel@tonic-gate  * explicitly by a '.' character in the input pathname, since these
318*7c478bd9Sstevel@tonic-gate  * are UNIX's hidden files.
319*7c478bd9Sstevel@tonic-gate  *
320*7c478bd9Sstevel@tonic-gate  * Input:
321*7c478bd9Sstevel@tonic-gate  *  ef         ExpandFile *  The pathname expansion resource object.
322*7c478bd9Sstevel@tonic-gate  *  path             char *  The path name to be expanded.
323*7c478bd9Sstevel@tonic-gate  *  pathlen           int    The length of the suffix of path[] that
324*7c478bd9Sstevel@tonic-gate  *                           constitutes the filename to be expanded,
325*7c478bd9Sstevel@tonic-gate  *                           or -1 to specify that the whole of the
326*7c478bd9Sstevel@tonic-gate  *                           path string should be used. Note that
327*7c478bd9Sstevel@tonic-gate  *                           regardless of the value of this argument,
328*7c478bd9Sstevel@tonic-gate  *                           path[] must contain a '\0' terminated
329*7c478bd9Sstevel@tonic-gate  *                           string, since this function checks that
330*7c478bd9Sstevel@tonic-gate  *                           pathlen isn't mistakenly too long.
331*7c478bd9Sstevel@tonic-gate  * Output:
332*7c478bd9Sstevel@tonic-gate  *  return  FileExpansion *  A pointer to a container within the given
333*7c478bd9Sstevel@tonic-gate  *                           ExpandFile object. This contains an array
334*7c478bd9Sstevel@tonic-gate  *                           of the pathnames that resulted from expanding
335*7c478bd9Sstevel@tonic-gate  *                           ~ and $ expressions and from matching any
336*7c478bd9Sstevel@tonic-gate  *                           wildcards, sorted into lexical order.
337*7c478bd9Sstevel@tonic-gate  *                           This container and its contents will be
338*7c478bd9Sstevel@tonic-gate  *                           recycled on subsequent calls, so if you need
339*7c478bd9Sstevel@tonic-gate  *                           to keep the results of two successive runs,
340*7c478bd9Sstevel@tonic-gate  *                           you will either have to allocate a private
341*7c478bd9Sstevel@tonic-gate  *                           copy of the array, or use two ExpandFile
342*7c478bd9Sstevel@tonic-gate  *                           objects.
343*7c478bd9Sstevel@tonic-gate  *
344*7c478bd9Sstevel@tonic-gate  *                           On error NULL is returned. A description
345*7c478bd9Sstevel@tonic-gate  *                           of the error can be acquired by calling the
346*7c478bd9Sstevel@tonic-gate  *                           ef_last_error() function.
347*7c478bd9Sstevel@tonic-gate  */
348*7c478bd9Sstevel@tonic-gate FileExpansion *ef_expand_file(ExpandFile *ef, const char *path, int pathlen)
349*7c478bd9Sstevel@tonic-gate {
350*7c478bd9Sstevel@tonic-gate   DirNode *dnode;       /* A directory-reader cache node */
351*7c478bd9Sstevel@tonic-gate   const char *dirname;  /* The name of the top level directory of the search */
352*7c478bd9Sstevel@tonic-gate   const char *pptr;     /* A pointer into path[] */
353*7c478bd9Sstevel@tonic-gate   int wild;             /* True if the path contains any wildcards */
354*7c478bd9Sstevel@tonic-gate /*
355*7c478bd9Sstevel@tonic-gate  * Check the arguments.
356*7c478bd9Sstevel@tonic-gate  */
357*7c478bd9Sstevel@tonic-gate   if(!ef || !path) {
358*7c478bd9Sstevel@tonic-gate     if(ef) {
359*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "ef_expand_file: NULL path argument",
360*7c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
361*7c478bd9Sstevel@tonic-gate     };
362*7c478bd9Sstevel@tonic-gate     errno = EINVAL;
363*7c478bd9Sstevel@tonic-gate     return NULL;
364*7c478bd9Sstevel@tonic-gate   };
365*7c478bd9Sstevel@tonic-gate /*
366*7c478bd9Sstevel@tonic-gate  * If the caller specified that the whole of path[] be matched,
367*7c478bd9Sstevel@tonic-gate  * work out the corresponding length.
368*7c478bd9Sstevel@tonic-gate  */
369*7c478bd9Sstevel@tonic-gate   if(pathlen < 0 || pathlen > strlen(path))
370*7c478bd9Sstevel@tonic-gate     pathlen = strlen(path);
371*7c478bd9Sstevel@tonic-gate /*
372*7c478bd9Sstevel@tonic-gate  * Discard previous expansion results.
373*7c478bd9Sstevel@tonic-gate  */
374*7c478bd9Sstevel@tonic-gate   ef_clear_files(ef);
375*7c478bd9Sstevel@tonic-gate /*
376*7c478bd9Sstevel@tonic-gate  * Preprocess the path, expanding ~/, ~user/ and $envvar references,
377*7c478bd9Sstevel@tonic-gate  * using ef->path as a work directory and returning a pointer to
378*7c478bd9Sstevel@tonic-gate  * a copy of the resulting pattern in the cache.
379*7c478bd9Sstevel@tonic-gate  */
380*7c478bd9Sstevel@tonic-gate   path = ef_expand_special(ef, path, pathlen);
381*7c478bd9Sstevel@tonic-gate   if(!path)
382*7c478bd9Sstevel@tonic-gate     return NULL;
383*7c478bd9Sstevel@tonic-gate /*
384*7c478bd9Sstevel@tonic-gate  * Clear the pathname buffer.
385*7c478bd9Sstevel@tonic-gate  */
386*7c478bd9Sstevel@tonic-gate   _pn_clear_path(ef->path);
387*7c478bd9Sstevel@tonic-gate /*
388*7c478bd9Sstevel@tonic-gate  * Does the pathname contain any wildcards?
389*7c478bd9Sstevel@tonic-gate  */
390*7c478bd9Sstevel@tonic-gate   for(wild=0,pptr=path; !wild && *pptr; pptr++) {
391*7c478bd9Sstevel@tonic-gate     switch(*pptr) {
392*7c478bd9Sstevel@tonic-gate     case '\\':                      /* Skip escaped characters */
393*7c478bd9Sstevel@tonic-gate       if(pptr[1])
394*7c478bd9Sstevel@tonic-gate 	pptr++;
395*7c478bd9Sstevel@tonic-gate       break;
396*7c478bd9Sstevel@tonic-gate     case '*': case '?': case '[':   /* A wildcard character? */
397*7c478bd9Sstevel@tonic-gate       wild = 1;
398*7c478bd9Sstevel@tonic-gate       break;
399*7c478bd9Sstevel@tonic-gate     };
400*7c478bd9Sstevel@tonic-gate   };
401*7c478bd9Sstevel@tonic-gate /*
402*7c478bd9Sstevel@tonic-gate  * If there are no wildcards to match, copy the current expanded
403*7c478bd9Sstevel@tonic-gate  * path into the output array, removing backslash escapes while doing so.
404*7c478bd9Sstevel@tonic-gate  */
405*7c478bd9Sstevel@tonic-gate   if(!wild) {
406*7c478bd9Sstevel@tonic-gate     if(ef_record_pathname(ef, path, 1))
407*7c478bd9Sstevel@tonic-gate       return NULL;
408*7c478bd9Sstevel@tonic-gate /*
409*7c478bd9Sstevel@tonic-gate  * Does the filename exist?
410*7c478bd9Sstevel@tonic-gate  */
411*7c478bd9Sstevel@tonic-gate     ef->result.exists = _pu_file_exists(ef->result.files[0]);
412*7c478bd9Sstevel@tonic-gate /*
413*7c478bd9Sstevel@tonic-gate  * Match wildcards against existing files.
414*7c478bd9Sstevel@tonic-gate  */
415*7c478bd9Sstevel@tonic-gate   } else {
416*7c478bd9Sstevel@tonic-gate /*
417*7c478bd9Sstevel@tonic-gate  * Only existing files that match the pattern will be returned in the
418*7c478bd9Sstevel@tonic-gate  * cache.
419*7c478bd9Sstevel@tonic-gate  */
420*7c478bd9Sstevel@tonic-gate     ef->result.exists = 1;
421*7c478bd9Sstevel@tonic-gate /*
422*7c478bd9Sstevel@tonic-gate  * Treat matching of the root-directory as a special case since it
423*7c478bd9Sstevel@tonic-gate  * isn't contained in a directory.
424*7c478bd9Sstevel@tonic-gate  */
425*7c478bd9Sstevel@tonic-gate     if(strcmp(path, FS_ROOT_DIR) == 0) {
426*7c478bd9Sstevel@tonic-gate       if(ef_record_pathname(ef, FS_ROOT_DIR, 0))
427*7c478bd9Sstevel@tonic-gate 	return NULL;
428*7c478bd9Sstevel@tonic-gate     } else {
429*7c478bd9Sstevel@tonic-gate /*
430*7c478bd9Sstevel@tonic-gate  * What should the top level directory of the search be?
431*7c478bd9Sstevel@tonic-gate  */
432*7c478bd9Sstevel@tonic-gate       if(strncmp(path, FS_ROOT_DIR, FS_ROOT_DIR_LEN) == 0) {
433*7c478bd9Sstevel@tonic-gate 	dirname = FS_ROOT_DIR;
434*7c478bd9Sstevel@tonic-gate 	if(!_pn_append_to_path(ef->path, FS_ROOT_DIR, -1, 0)) {
435*7c478bd9Sstevel@tonic-gate 	  _err_record_msg(ef->err, "Insufficient memory to record path",
436*7c478bd9Sstevel@tonic-gate 			  END_ERR_MSG);
437*7c478bd9Sstevel@tonic-gate 	  return NULL;
438*7c478bd9Sstevel@tonic-gate 	};
439*7c478bd9Sstevel@tonic-gate 	path += FS_ROOT_DIR_LEN;
440*7c478bd9Sstevel@tonic-gate       } else {
441*7c478bd9Sstevel@tonic-gate 	dirname = FS_PWD;
442*7c478bd9Sstevel@tonic-gate       };
443*7c478bd9Sstevel@tonic-gate /*
444*7c478bd9Sstevel@tonic-gate  * Open the top-level directory of the search.
445*7c478bd9Sstevel@tonic-gate  */
446*7c478bd9Sstevel@tonic-gate       dnode = ef_open_dir(ef, dirname);
447*7c478bd9Sstevel@tonic-gate       if(!dnode)
448*7c478bd9Sstevel@tonic-gate 	return NULL;
449*7c478bd9Sstevel@tonic-gate /*
450*7c478bd9Sstevel@tonic-gate  * Recursively match successive directory components of the path.
451*7c478bd9Sstevel@tonic-gate  */
452*7c478bd9Sstevel@tonic-gate       if(ef_match_relative_pathname(ef, dnode->dr, path, 0)) {
453*7c478bd9Sstevel@tonic-gate 	dnode = ef_close_dir(ef, dnode);
454*7c478bd9Sstevel@tonic-gate 	return NULL;
455*7c478bd9Sstevel@tonic-gate       };
456*7c478bd9Sstevel@tonic-gate /*
457*7c478bd9Sstevel@tonic-gate  * Cleanup.
458*7c478bd9Sstevel@tonic-gate  */
459*7c478bd9Sstevel@tonic-gate       dnode = ef_close_dir(ef, dnode);
460*7c478bd9Sstevel@tonic-gate     };
461*7c478bd9Sstevel@tonic-gate /*
462*7c478bd9Sstevel@tonic-gate  * No files matched?
463*7c478bd9Sstevel@tonic-gate  */
464*7c478bd9Sstevel@tonic-gate     if(ef->result.nfile < 1) {
465*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "No files match", END_ERR_MSG);
466*7c478bd9Sstevel@tonic-gate       return NULL;
467*7c478bd9Sstevel@tonic-gate     };
468*7c478bd9Sstevel@tonic-gate /*
469*7c478bd9Sstevel@tonic-gate  * Sort the pathnames that matched.
470*7c478bd9Sstevel@tonic-gate  */
471*7c478bd9Sstevel@tonic-gate     qsort(ef->result.files, ef->result.nfile, sizeof(ef->result.files[0]),
472*7c478bd9Sstevel@tonic-gate 	  ef_cmp_strings);
473*7c478bd9Sstevel@tonic-gate   };
474*7c478bd9Sstevel@tonic-gate /*
475*7c478bd9Sstevel@tonic-gate  * Return the result container.
476*7c478bd9Sstevel@tonic-gate  */
477*7c478bd9Sstevel@tonic-gate   return &ef->result;
478*7c478bd9Sstevel@tonic-gate }
479*7c478bd9Sstevel@tonic-gate 
480*7c478bd9Sstevel@tonic-gate /*.......................................................................
481*7c478bd9Sstevel@tonic-gate  * Attempt to recursively match the given pattern with the contents of
482*7c478bd9Sstevel@tonic-gate  * the current directory, descending sub-directories as needed.
483*7c478bd9Sstevel@tonic-gate  *
484*7c478bd9Sstevel@tonic-gate  * Input:
485*7c478bd9Sstevel@tonic-gate  *  ef      ExpandFile *  The pathname expansion resource object.
486*7c478bd9Sstevel@tonic-gate  *  dr       DirReader *  The directory reader object of the directory
487*7c478bd9Sstevel@tonic-gate  *                        to be searched.
488*7c478bd9Sstevel@tonic-gate  *  pattern const char *  The pattern to match with files in the current
489*7c478bd9Sstevel@tonic-gate  *                        directory.
490*7c478bd9Sstevel@tonic-gate  *  separate       int    When appending a filename from the specified
491*7c478bd9Sstevel@tonic-gate  *                        directory to ef->pathname, insert a directory
492*7c478bd9Sstevel@tonic-gate  *                        separator between the existing pathname and
493*7c478bd9Sstevel@tonic-gate  *                        the filename, unless separate is zero.
494*7c478bd9Sstevel@tonic-gate  * Output:
495*7c478bd9Sstevel@tonic-gate  *  return         int    0 - OK.
496*7c478bd9Sstevel@tonic-gate  *                        1 - Error.
497*7c478bd9Sstevel@tonic-gate  */
498*7c478bd9Sstevel@tonic-gate static int ef_match_relative_pathname(ExpandFile *ef, DirReader *dr,
499*7c478bd9Sstevel@tonic-gate 				       const char *pattern, int separate)
500*7c478bd9Sstevel@tonic-gate {
501*7c478bd9Sstevel@tonic-gate   const char *nextp;  /* The pointer to the character that follows the part */
502*7c478bd9Sstevel@tonic-gate                       /*  of the pattern that is to be matched with files */
503*7c478bd9Sstevel@tonic-gate                       /*  in the current directory. */
504*7c478bd9Sstevel@tonic-gate   char *file;         /* The name of the file being matched */
505*7c478bd9Sstevel@tonic-gate   int pathlen;        /* The length of ef->pathname[] on entry to this */
506*7c478bd9Sstevel@tonic-gate                       /*  function */
507*7c478bd9Sstevel@tonic-gate /*
508*7c478bd9Sstevel@tonic-gate  * Record the current length of the pathname string recorded in
509*7c478bd9Sstevel@tonic-gate  * ef->pathname[].
510*7c478bd9Sstevel@tonic-gate  */
511*7c478bd9Sstevel@tonic-gate   pathlen = strlen(ef->path->name);
512*7c478bd9Sstevel@tonic-gate /*
513*7c478bd9Sstevel@tonic-gate  * Get a pointer to the character that follows the end of the part of
514*7c478bd9Sstevel@tonic-gate  * the pattern that should be matched to files within the current directory.
515*7c478bd9Sstevel@tonic-gate  * This will either point to a directory separator, or to the '\0' terminator
516*7c478bd9Sstevel@tonic-gate  * of the pattern string.
517*7c478bd9Sstevel@tonic-gate  */
518*7c478bd9Sstevel@tonic-gate   for(nextp=pattern; *nextp && strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) != 0;
519*7c478bd9Sstevel@tonic-gate       nextp++)
520*7c478bd9Sstevel@tonic-gate     ;
521*7c478bd9Sstevel@tonic-gate /*
522*7c478bd9Sstevel@tonic-gate  * Read each file from the directory, attempting to match it to the
523*7c478bd9Sstevel@tonic-gate  * current pattern.
524*7c478bd9Sstevel@tonic-gate  */
525*7c478bd9Sstevel@tonic-gate   while((file=_dr_next_file(dr)) != NULL) {
526*7c478bd9Sstevel@tonic-gate /*
527*7c478bd9Sstevel@tonic-gate  * Does the latest file match the pattern up to nextp?
528*7c478bd9Sstevel@tonic-gate  */
529*7c478bd9Sstevel@tonic-gate     if(ef_string_matches_pattern(file, pattern, file[0]=='.', nextp)) {
530*7c478bd9Sstevel@tonic-gate /*
531*7c478bd9Sstevel@tonic-gate  * Append the new directory entry to the current matching pathname.
532*7c478bd9Sstevel@tonic-gate  */
533*7c478bd9Sstevel@tonic-gate       if((separate && _pn_append_to_path(ef->path, FS_DIR_SEP, -1, 0)==NULL) ||
534*7c478bd9Sstevel@tonic-gate 	 _pn_append_to_path(ef->path, file, -1, 0)==NULL) {
535*7c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Insufficient memory to record path",
536*7c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
537*7c478bd9Sstevel@tonic-gate 	return 1;
538*7c478bd9Sstevel@tonic-gate       };
539*7c478bd9Sstevel@tonic-gate /*
540*7c478bd9Sstevel@tonic-gate  * If we have reached the end of the pattern, record the accumulated
541*7c478bd9Sstevel@tonic-gate  * pathname in the list of matching files.
542*7c478bd9Sstevel@tonic-gate  */
543*7c478bd9Sstevel@tonic-gate       if(*nextp == '\0') {
544*7c478bd9Sstevel@tonic-gate 	if(ef_record_pathname(ef, ef->path->name, 0))
545*7c478bd9Sstevel@tonic-gate 	  return 1;
546*7c478bd9Sstevel@tonic-gate /*
547*7c478bd9Sstevel@tonic-gate  * If the matching directory entry is a subdirectory, and the
548*7c478bd9Sstevel@tonic-gate  * next character of the pattern is a directory separator,
549*7c478bd9Sstevel@tonic-gate  * recursively call the current function to scan the sub-directory
550*7c478bd9Sstevel@tonic-gate  * for matches.
551*7c478bd9Sstevel@tonic-gate  */
552*7c478bd9Sstevel@tonic-gate       } else if(_pu_path_is_dir(ef->path->name) &&
553*7c478bd9Sstevel@tonic-gate 		strncmp(nextp, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
554*7c478bd9Sstevel@tonic-gate /*
555*7c478bd9Sstevel@tonic-gate  * If the pattern finishes with the directory separator, then
556*7c478bd9Sstevel@tonic-gate  * record the pathame as matching.
557*7c478bd9Sstevel@tonic-gate  */
558*7c478bd9Sstevel@tonic-gate 	if(nextp[FS_DIR_SEP_LEN] == '\0') {
559*7c478bd9Sstevel@tonic-gate 	  if(ef_record_pathname(ef, ef->path->name, 0))
560*7c478bd9Sstevel@tonic-gate 	    return 1;
561*7c478bd9Sstevel@tonic-gate /*
562*7c478bd9Sstevel@tonic-gate  * Match files within the directory.
563*7c478bd9Sstevel@tonic-gate  */
564*7c478bd9Sstevel@tonic-gate 	} else {
565*7c478bd9Sstevel@tonic-gate 	  DirNode *subdnode = ef_open_dir(ef, ef->path->name);
566*7c478bd9Sstevel@tonic-gate 	  if(subdnode) {
567*7c478bd9Sstevel@tonic-gate 	    if(ef_match_relative_pathname(ef, subdnode->dr,
568*7c478bd9Sstevel@tonic-gate 					   nextp+FS_DIR_SEP_LEN, 1)) {
569*7c478bd9Sstevel@tonic-gate 	      subdnode = ef_close_dir(ef, subdnode);
570*7c478bd9Sstevel@tonic-gate 	      return 1;
571*7c478bd9Sstevel@tonic-gate 	    };
572*7c478bd9Sstevel@tonic-gate 	    subdnode = ef_close_dir(ef, subdnode);
573*7c478bd9Sstevel@tonic-gate 	  };
574*7c478bd9Sstevel@tonic-gate 	};
575*7c478bd9Sstevel@tonic-gate       };
576*7c478bd9Sstevel@tonic-gate /*
577*7c478bd9Sstevel@tonic-gate  * Remove the latest filename from the pathname string, so that
578*7c478bd9Sstevel@tonic-gate  * another matching file can be appended.
579*7c478bd9Sstevel@tonic-gate  */
580*7c478bd9Sstevel@tonic-gate       ef->path->name[pathlen] = '\0';
581*7c478bd9Sstevel@tonic-gate     };
582*7c478bd9Sstevel@tonic-gate   };
583*7c478bd9Sstevel@tonic-gate   return 0;
584*7c478bd9Sstevel@tonic-gate }
585*7c478bd9Sstevel@tonic-gate 
586*7c478bd9Sstevel@tonic-gate /*.......................................................................
587*7c478bd9Sstevel@tonic-gate  * Record a new matching filename.
588*7c478bd9Sstevel@tonic-gate  *
589*7c478bd9Sstevel@tonic-gate  * Input:
590*7c478bd9Sstevel@tonic-gate  *  ef        ExpandFile *  The filename-match resource object.
591*7c478bd9Sstevel@tonic-gate  *  pathname  const char *  The pathname to record.
592*7c478bd9Sstevel@tonic-gate  *  remove_escapes   int    If true, remove backslash escapes in the
593*7c478bd9Sstevel@tonic-gate  *                          recorded copy of the pathname.
594*7c478bd9Sstevel@tonic-gate  * Output:
595*7c478bd9Sstevel@tonic-gate  *  return           int     0 - OK.
596*7c478bd9Sstevel@tonic-gate  *                           1 - Error (ef->err will contain a
597*7c478bd9Sstevel@tonic-gate  *                                      description of the error).
598*7c478bd9Sstevel@tonic-gate  */
599*7c478bd9Sstevel@tonic-gate static int ef_record_pathname(ExpandFile *ef, const char *pathname,
600*7c478bd9Sstevel@tonic-gate 			      int remove_escapes)
601*7c478bd9Sstevel@tonic-gate {
602*7c478bd9Sstevel@tonic-gate   char *copy;          /* The recorded copy of pathname[] */
603*7c478bd9Sstevel@tonic-gate /*
604*7c478bd9Sstevel@tonic-gate  * Attempt to make a copy of the pathname in the cache.
605*7c478bd9Sstevel@tonic-gate  */
606*7c478bd9Sstevel@tonic-gate   copy = ef_cache_pathname(ef, pathname, remove_escapes);
607*7c478bd9Sstevel@tonic-gate   if(!copy)
608*7c478bd9Sstevel@tonic-gate     return 1;
609*7c478bd9Sstevel@tonic-gate /*
610*7c478bd9Sstevel@tonic-gate  * If there isn't room to record a pointer to the recorded pathname in the
611*7c478bd9Sstevel@tonic-gate  * array of files, attempt to extend the array.
612*7c478bd9Sstevel@tonic-gate  */
613*7c478bd9Sstevel@tonic-gate   if(ef->result.nfile + 1 > ef->files_dim) {
614*7c478bd9Sstevel@tonic-gate     int files_dim = ef->files_dim + MATCH_BLK_FACT;
615*7c478bd9Sstevel@tonic-gate     char **files = (char **) realloc(ef->result.files,
616*7c478bd9Sstevel@tonic-gate 				     files_dim * sizeof(files[0]));
617*7c478bd9Sstevel@tonic-gate     if(!files) {
618*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err,
619*7c478bd9Sstevel@tonic-gate 	     "Insufficient memory to record all of the matching filenames",
620*7c478bd9Sstevel@tonic-gate 	     END_ERR_MSG);
621*7c478bd9Sstevel@tonic-gate       errno = ENOMEM;
622*7c478bd9Sstevel@tonic-gate       return 1;
623*7c478bd9Sstevel@tonic-gate     };
624*7c478bd9Sstevel@tonic-gate     ef->result.files = files;
625*7c478bd9Sstevel@tonic-gate     ef->files_dim = files_dim;
626*7c478bd9Sstevel@tonic-gate   };
627*7c478bd9Sstevel@tonic-gate /*
628*7c478bd9Sstevel@tonic-gate  * Record a pointer to the new match.
629*7c478bd9Sstevel@tonic-gate  */
630*7c478bd9Sstevel@tonic-gate   ef->result.files[ef->result.nfile++] = copy;
631*7c478bd9Sstevel@tonic-gate   return 0;
632*7c478bd9Sstevel@tonic-gate }
633*7c478bd9Sstevel@tonic-gate 
634*7c478bd9Sstevel@tonic-gate /*.......................................................................
635*7c478bd9Sstevel@tonic-gate  * Record a pathname in the cache.
636*7c478bd9Sstevel@tonic-gate  *
637*7c478bd9Sstevel@tonic-gate  * Input:
638*7c478bd9Sstevel@tonic-gate  *  ef       ExpandFile *  The filename-match resource object.
639*7c478bd9Sstevel@tonic-gate  *  pathname       char *  The pathname to record.
640*7c478bd9Sstevel@tonic-gate  *  remove_escapes  int    If true, remove backslash escapes in the
641*7c478bd9Sstevel@tonic-gate  *                         copy of the pathname.
642*7c478bd9Sstevel@tonic-gate  * Output:
643*7c478bd9Sstevel@tonic-gate  *  return         char *  The pointer to the copy of the pathname.
644*7c478bd9Sstevel@tonic-gate  *                         On error NULL is returned and a description
645*7c478bd9Sstevel@tonic-gate  *                         of the error is left in ef->err.
646*7c478bd9Sstevel@tonic-gate  */
647*7c478bd9Sstevel@tonic-gate static char *ef_cache_pathname(ExpandFile *ef, const char *pathname,
648*7c478bd9Sstevel@tonic-gate 			       int remove_escapes)
649*7c478bd9Sstevel@tonic-gate {
650*7c478bd9Sstevel@tonic-gate   char *copy = _sg_store_string(ef->sg, pathname, remove_escapes);
651*7c478bd9Sstevel@tonic-gate   if(!copy)
652*7c478bd9Sstevel@tonic-gate     _err_record_msg(ef->err, "Insufficient memory to store pathname",
653*7c478bd9Sstevel@tonic-gate 		    END_ERR_MSG);
654*7c478bd9Sstevel@tonic-gate   return copy;
655*7c478bd9Sstevel@tonic-gate }
656*7c478bd9Sstevel@tonic-gate 
657*7c478bd9Sstevel@tonic-gate /*.......................................................................
658*7c478bd9Sstevel@tonic-gate  * Clear the results of the previous expansion operation, ready for the
659*7c478bd9Sstevel@tonic-gate  * next.
660*7c478bd9Sstevel@tonic-gate  *
661*7c478bd9Sstevel@tonic-gate  * Input:
662*7c478bd9Sstevel@tonic-gate  *  ef    ExpandFile *  The pathname expansion resource object.
663*7c478bd9Sstevel@tonic-gate  */
664*7c478bd9Sstevel@tonic-gate static void ef_clear_files(ExpandFile *ef)
665*7c478bd9Sstevel@tonic-gate {
666*7c478bd9Sstevel@tonic-gate   _clr_StringGroup(ef->sg);
667*7c478bd9Sstevel@tonic-gate   _pn_clear_path(ef->path);
668*7c478bd9Sstevel@tonic-gate   ef->result.exists = 0;
669*7c478bd9Sstevel@tonic-gate   ef->result.nfile = 0;
670*7c478bd9Sstevel@tonic-gate   _err_clear_msg(ef->err);
671*7c478bd9Sstevel@tonic-gate   return;
672*7c478bd9Sstevel@tonic-gate }
673*7c478bd9Sstevel@tonic-gate 
674*7c478bd9Sstevel@tonic-gate /*.......................................................................
675*7c478bd9Sstevel@tonic-gate  * Get a new directory reader object from the cache.
676*7c478bd9Sstevel@tonic-gate  *
677*7c478bd9Sstevel@tonic-gate  * Input:
678*7c478bd9Sstevel@tonic-gate  *  ef        ExpandFile *  The pathname expansion resource object.
679*7c478bd9Sstevel@tonic-gate  *  pathname  const char *  The pathname of the directory.
680*7c478bd9Sstevel@tonic-gate  * Output:
681*7c478bd9Sstevel@tonic-gate  *  return       DirNode *  The cache entry of the new directory reader,
682*7c478bd9Sstevel@tonic-gate  *                          or NULL on error. On error, ef->err will
683*7c478bd9Sstevel@tonic-gate  *                          contain a description of the error.
684*7c478bd9Sstevel@tonic-gate  */
685*7c478bd9Sstevel@tonic-gate static DirNode *ef_open_dir(ExpandFile *ef, const char *pathname)
686*7c478bd9Sstevel@tonic-gate {
687*7c478bd9Sstevel@tonic-gate   char *errmsg = NULL;  /* An error message from a called function */
688*7c478bd9Sstevel@tonic-gate   DirNode *node;        /* The cache node used */
689*7c478bd9Sstevel@tonic-gate /*
690*7c478bd9Sstevel@tonic-gate  * Get the directory reader cache.
691*7c478bd9Sstevel@tonic-gate  */
692*7c478bd9Sstevel@tonic-gate   DirCache *cache = &ef->cache;
693*7c478bd9Sstevel@tonic-gate /*
694*7c478bd9Sstevel@tonic-gate  * Extend the cache if there are no free cache nodes.
695*7c478bd9Sstevel@tonic-gate  */
696*7c478bd9Sstevel@tonic-gate   if(!cache->next) {
697*7c478bd9Sstevel@tonic-gate     node = (DirNode *) _new_FreeListNode(cache->mem);
698*7c478bd9Sstevel@tonic-gate     if(!node) {
699*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Insufficient memory to open a new directory",
700*7c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
701*7c478bd9Sstevel@tonic-gate       return NULL;
702*7c478bd9Sstevel@tonic-gate     };
703*7c478bd9Sstevel@tonic-gate /*
704*7c478bd9Sstevel@tonic-gate  * Initialize the cache node.
705*7c478bd9Sstevel@tonic-gate  */
706*7c478bd9Sstevel@tonic-gate     node->next = NULL;
707*7c478bd9Sstevel@tonic-gate     node->prev = NULL;
708*7c478bd9Sstevel@tonic-gate     node->dr = NULL;
709*7c478bd9Sstevel@tonic-gate /*
710*7c478bd9Sstevel@tonic-gate  * Allocate a directory reader object.
711*7c478bd9Sstevel@tonic-gate  */
712*7c478bd9Sstevel@tonic-gate     node->dr = _new_DirReader();
713*7c478bd9Sstevel@tonic-gate     if(!node->dr) {
714*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Insufficient memory to open a new directory",
715*7c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
716*7c478bd9Sstevel@tonic-gate       node = (DirNode *) _del_FreeListNode(cache->mem, node);
717*7c478bd9Sstevel@tonic-gate       return NULL;
718*7c478bd9Sstevel@tonic-gate     };
719*7c478bd9Sstevel@tonic-gate /*
720*7c478bd9Sstevel@tonic-gate  * Append the node to the cache list.
721*7c478bd9Sstevel@tonic-gate  */
722*7c478bd9Sstevel@tonic-gate     node->prev = cache->tail;
723*7c478bd9Sstevel@tonic-gate     if(cache->tail)
724*7c478bd9Sstevel@tonic-gate       cache->tail->next = node;
725*7c478bd9Sstevel@tonic-gate     else
726*7c478bd9Sstevel@tonic-gate       cache->head = node;
727*7c478bd9Sstevel@tonic-gate     cache->next = cache->tail = node;
728*7c478bd9Sstevel@tonic-gate   };
729*7c478bd9Sstevel@tonic-gate /*
730*7c478bd9Sstevel@tonic-gate  * Get the first unused node, but don't remove it from the list yet.
731*7c478bd9Sstevel@tonic-gate  */
732*7c478bd9Sstevel@tonic-gate   node = cache->next;
733*7c478bd9Sstevel@tonic-gate /*
734*7c478bd9Sstevel@tonic-gate  * Attempt to open the specified directory.
735*7c478bd9Sstevel@tonic-gate  */
736*7c478bd9Sstevel@tonic-gate   if(_dr_open_dir(node->dr, pathname, &errmsg)) {
737*7c478bd9Sstevel@tonic-gate     _err_record_msg(ef->err, errmsg, END_ERR_MSG);
738*7c478bd9Sstevel@tonic-gate     return NULL;
739*7c478bd9Sstevel@tonic-gate   };
740*7c478bd9Sstevel@tonic-gate /*
741*7c478bd9Sstevel@tonic-gate  * Now that we have successfully opened the specified directory,
742*7c478bd9Sstevel@tonic-gate  * remove the cache node from the list, and relink the list around it.
743*7c478bd9Sstevel@tonic-gate  */
744*7c478bd9Sstevel@tonic-gate   cache->next = node->next;
745*7c478bd9Sstevel@tonic-gate   if(node->prev)
746*7c478bd9Sstevel@tonic-gate     node->prev->next = node->next;
747*7c478bd9Sstevel@tonic-gate   else
748*7c478bd9Sstevel@tonic-gate     cache->head = node->next;
749*7c478bd9Sstevel@tonic-gate   if(node->next)
750*7c478bd9Sstevel@tonic-gate     node->next->prev = node->prev;
751*7c478bd9Sstevel@tonic-gate   else
752*7c478bd9Sstevel@tonic-gate     cache->tail = node->prev;
753*7c478bd9Sstevel@tonic-gate   node->next = node->prev = NULL;
754*7c478bd9Sstevel@tonic-gate /*
755*7c478bd9Sstevel@tonic-gate  * Return the successfully initialized cache node to the caller.
756*7c478bd9Sstevel@tonic-gate  */
757*7c478bd9Sstevel@tonic-gate   return node;
758*7c478bd9Sstevel@tonic-gate }
759*7c478bd9Sstevel@tonic-gate 
760*7c478bd9Sstevel@tonic-gate /*.......................................................................
761*7c478bd9Sstevel@tonic-gate  * Return a directory reader object to the cache, after first closing
762*7c478bd9Sstevel@tonic-gate  * the directory that it was managing.
763*7c478bd9Sstevel@tonic-gate  *
764*7c478bd9Sstevel@tonic-gate  * Input:
765*7c478bd9Sstevel@tonic-gate  *  ef    ExpandFile *  The pathname expansion resource object.
766*7c478bd9Sstevel@tonic-gate  *  node     DirNode *  The cache entry of the directory reader, as returned
767*7c478bd9Sstevel@tonic-gate  *                      by ef_open_dir().
768*7c478bd9Sstevel@tonic-gate  * Output:
769*7c478bd9Sstevel@tonic-gate  *  return   DirNode *  The deleted DirNode (ie. allways NULL).
770*7c478bd9Sstevel@tonic-gate  */
771*7c478bd9Sstevel@tonic-gate static DirNode *ef_close_dir(ExpandFile *ef, DirNode *node)
772*7c478bd9Sstevel@tonic-gate {
773*7c478bd9Sstevel@tonic-gate /*
774*7c478bd9Sstevel@tonic-gate  * Get the directory reader cache.
775*7c478bd9Sstevel@tonic-gate  */
776*7c478bd9Sstevel@tonic-gate   DirCache *cache = &ef->cache;
777*7c478bd9Sstevel@tonic-gate /*
778*7c478bd9Sstevel@tonic-gate  * Close the directory.
779*7c478bd9Sstevel@tonic-gate  */
780*7c478bd9Sstevel@tonic-gate   _dr_close_dir(node->dr);
781*7c478bd9Sstevel@tonic-gate /*
782*7c478bd9Sstevel@tonic-gate  * Return the node to the tail of the cache list.
783*7c478bd9Sstevel@tonic-gate  */
784*7c478bd9Sstevel@tonic-gate   node->next = NULL;
785*7c478bd9Sstevel@tonic-gate   node->prev = cache->tail;
786*7c478bd9Sstevel@tonic-gate   if(cache->tail)
787*7c478bd9Sstevel@tonic-gate     cache->tail->next = node;
788*7c478bd9Sstevel@tonic-gate   else
789*7c478bd9Sstevel@tonic-gate     cache->head = cache->tail = node;
790*7c478bd9Sstevel@tonic-gate   if(!cache->next)
791*7c478bd9Sstevel@tonic-gate     cache->next = node;
792*7c478bd9Sstevel@tonic-gate   return NULL;
793*7c478bd9Sstevel@tonic-gate }
794*7c478bd9Sstevel@tonic-gate 
795*7c478bd9Sstevel@tonic-gate /*.......................................................................
796*7c478bd9Sstevel@tonic-gate  * Return non-zero if the specified file name matches a given glob
797*7c478bd9Sstevel@tonic-gate  * pattern.
798*7c478bd9Sstevel@tonic-gate  *
799*7c478bd9Sstevel@tonic-gate  * Input:
800*7c478bd9Sstevel@tonic-gate  *  file     const char *  The file-name component to be matched to the pattern.
801*7c478bd9Sstevel@tonic-gate  *  pattern  const char *  The start of the pattern to match against file[].
802*7c478bd9Sstevel@tonic-gate  *  xplicit         int    If non-zero, the first character must be matched
803*7c478bd9Sstevel@tonic-gate  *                         explicitly (ie. not with a wildcard).
804*7c478bd9Sstevel@tonic-gate  *  nextp    const char *  The pointer to the the character following the
805*7c478bd9Sstevel@tonic-gate  *                         end of the pattern in pattern[].
806*7c478bd9Sstevel@tonic-gate  * Output:
807*7c478bd9Sstevel@tonic-gate  *  return    int          0 - Doesn't match.
808*7c478bd9Sstevel@tonic-gate  *                         1 - The file-name string matches the pattern.
809*7c478bd9Sstevel@tonic-gate  */
810*7c478bd9Sstevel@tonic-gate static int ef_string_matches_pattern(const char *file, const char *pattern,
811*7c478bd9Sstevel@tonic-gate 				      int xplicit, const char *nextp)
812*7c478bd9Sstevel@tonic-gate {
813*7c478bd9Sstevel@tonic-gate   const char *pptr = pattern; /* The pointer used to scan the pattern */
814*7c478bd9Sstevel@tonic-gate   const char *fptr = file;    /* The pointer used to scan the filename string */
815*7c478bd9Sstevel@tonic-gate /*
816*7c478bd9Sstevel@tonic-gate  * Match each character of the pattern in turn.
817*7c478bd9Sstevel@tonic-gate  */
818*7c478bd9Sstevel@tonic-gate   while(pptr < nextp) {
819*7c478bd9Sstevel@tonic-gate /*
820*7c478bd9Sstevel@tonic-gate  * Handle the next character of the pattern.
821*7c478bd9Sstevel@tonic-gate  */
822*7c478bd9Sstevel@tonic-gate     switch(*pptr) {
823*7c478bd9Sstevel@tonic-gate /*
824*7c478bd9Sstevel@tonic-gate  * A match zero-or-more characters wildcard operator.
825*7c478bd9Sstevel@tonic-gate  */
826*7c478bd9Sstevel@tonic-gate     case '*':
827*7c478bd9Sstevel@tonic-gate /*
828*7c478bd9Sstevel@tonic-gate  * Skip the '*' character in the pattern.
829*7c478bd9Sstevel@tonic-gate  */
830*7c478bd9Sstevel@tonic-gate       pptr++;
831*7c478bd9Sstevel@tonic-gate /*
832*7c478bd9Sstevel@tonic-gate  * If wildcards aren't allowed, the pattern doesn't match.
833*7c478bd9Sstevel@tonic-gate  */
834*7c478bd9Sstevel@tonic-gate       if(xplicit)
835*7c478bd9Sstevel@tonic-gate 	return 0;
836*7c478bd9Sstevel@tonic-gate /*
837*7c478bd9Sstevel@tonic-gate  * If the pattern ends with a the '*' wildcard, then the
838*7c478bd9Sstevel@tonic-gate  * rest of the filename matches this.
839*7c478bd9Sstevel@tonic-gate  */
840*7c478bd9Sstevel@tonic-gate       if(pptr >= nextp)
841*7c478bd9Sstevel@tonic-gate 	return 1;
842*7c478bd9Sstevel@tonic-gate /*
843*7c478bd9Sstevel@tonic-gate  * Using the wildcard to match successively longer sections of
844*7c478bd9Sstevel@tonic-gate  * the remaining characters of the filename, attempt to match
845*7c478bd9Sstevel@tonic-gate  * the tail of the filename against the tail of the pattern.
846*7c478bd9Sstevel@tonic-gate  */
847*7c478bd9Sstevel@tonic-gate       for( ; *fptr; fptr++) {
848*7c478bd9Sstevel@tonic-gate 	if(ef_string_matches_pattern(fptr, pptr, 0, nextp))
849*7c478bd9Sstevel@tonic-gate 	  return 1;
850*7c478bd9Sstevel@tonic-gate       };
851*7c478bd9Sstevel@tonic-gate       return 0; /* The pattern following the '*' didn't match */
852*7c478bd9Sstevel@tonic-gate       break;
853*7c478bd9Sstevel@tonic-gate /*
854*7c478bd9Sstevel@tonic-gate  * A match-one-character wildcard operator.
855*7c478bd9Sstevel@tonic-gate  */
856*7c478bd9Sstevel@tonic-gate     case '?':
857*7c478bd9Sstevel@tonic-gate /*
858*7c478bd9Sstevel@tonic-gate  * If there is a character to be matched, skip it and advance the
859*7c478bd9Sstevel@tonic-gate  * pattern pointer.
860*7c478bd9Sstevel@tonic-gate  */
861*7c478bd9Sstevel@tonic-gate       if(!xplicit && *fptr) {
862*7c478bd9Sstevel@tonic-gate         fptr++;
863*7c478bd9Sstevel@tonic-gate         pptr++;
864*7c478bd9Sstevel@tonic-gate /*
865*7c478bd9Sstevel@tonic-gate  * If we hit the end of the filename string, there is no character
866*7c478bd9Sstevel@tonic-gate  * matching the operator, so the string doesn't match.
867*7c478bd9Sstevel@tonic-gate  */
868*7c478bd9Sstevel@tonic-gate       } else {
869*7c478bd9Sstevel@tonic-gate         return 0;
870*7c478bd9Sstevel@tonic-gate       };
871*7c478bd9Sstevel@tonic-gate       break;
872*7c478bd9Sstevel@tonic-gate /*
873*7c478bd9Sstevel@tonic-gate  * A character range operator, with the character ranges enclosed
874*7c478bd9Sstevel@tonic-gate  * in matching square brackets.
875*7c478bd9Sstevel@tonic-gate  */
876*7c478bd9Sstevel@tonic-gate     case '[':
877*7c478bd9Sstevel@tonic-gate       if(xplicit || !ef_matches_range(*fptr++, ++pptr, &pptr))
878*7c478bd9Sstevel@tonic-gate         return 0;
879*7c478bd9Sstevel@tonic-gate       break;
880*7c478bd9Sstevel@tonic-gate /*
881*7c478bd9Sstevel@tonic-gate  * A backslash in the pattern prevents the following character as
882*7c478bd9Sstevel@tonic-gate  * being seen as a special character.
883*7c478bd9Sstevel@tonic-gate  */
884*7c478bd9Sstevel@tonic-gate     case '\\':
885*7c478bd9Sstevel@tonic-gate       pptr++;
886*7c478bd9Sstevel@tonic-gate       /* Note fallthrough to default */
887*7c478bd9Sstevel@tonic-gate /*
888*7c478bd9Sstevel@tonic-gate  * A normal character to be matched explicitly.
889*7c478bd9Sstevel@tonic-gate  */
890*7c478bd9Sstevel@tonic-gate     default:
891*7c478bd9Sstevel@tonic-gate       if(*fptr == *pptr) {
892*7c478bd9Sstevel@tonic-gate         fptr++;
893*7c478bd9Sstevel@tonic-gate         pptr++;
894*7c478bd9Sstevel@tonic-gate       } else {
895*7c478bd9Sstevel@tonic-gate         return 0;
896*7c478bd9Sstevel@tonic-gate       };
897*7c478bd9Sstevel@tonic-gate       break;
898*7c478bd9Sstevel@tonic-gate     };
899*7c478bd9Sstevel@tonic-gate /*
900*7c478bd9Sstevel@tonic-gate  * After passing the first character, turn off the explicit match
901*7c478bd9Sstevel@tonic-gate  * requirement.
902*7c478bd9Sstevel@tonic-gate  */
903*7c478bd9Sstevel@tonic-gate     xplicit = 0;
904*7c478bd9Sstevel@tonic-gate   };
905*7c478bd9Sstevel@tonic-gate /*
906*7c478bd9Sstevel@tonic-gate  * To get here the pattern must have been exhausted. If the filename
907*7c478bd9Sstevel@tonic-gate  * string matched, then the filename string must also have been
908*7c478bd9Sstevel@tonic-gate  * exhausted.
909*7c478bd9Sstevel@tonic-gate  */
910*7c478bd9Sstevel@tonic-gate   return *fptr == '\0';
911*7c478bd9Sstevel@tonic-gate }
912*7c478bd9Sstevel@tonic-gate 
913*7c478bd9Sstevel@tonic-gate /*.......................................................................
914*7c478bd9Sstevel@tonic-gate  * Match a character range expression terminated by an unescaped close
915*7c478bd9Sstevel@tonic-gate  * square bracket.
916*7c478bd9Sstevel@tonic-gate  *
917*7c478bd9Sstevel@tonic-gate  * Input:
918*7c478bd9Sstevel@tonic-gate  *  c               int     The character to be matched with the range
919*7c478bd9Sstevel@tonic-gate  *                          pattern.
920*7c478bd9Sstevel@tonic-gate  *  pattern  const char *   The range pattern to be matched (ie. after the
921*7c478bd9Sstevel@tonic-gate  *                          initiating '[' character).
922*7c478bd9Sstevel@tonic-gate  *  endp     const char **  On output a pointer to the character following the
923*7c478bd9Sstevel@tonic-gate  *                          range expression will be assigned to *endp.
924*7c478bd9Sstevel@tonic-gate  * Output:
925*7c478bd9Sstevel@tonic-gate  *  return          int     0 - Doesn't match.
926*7c478bd9Sstevel@tonic-gate  *                          1 - The character matched.
927*7c478bd9Sstevel@tonic-gate  */
928*7c478bd9Sstevel@tonic-gate static int ef_matches_range(int c, const char *pattern, const char **endp)
929*7c478bd9Sstevel@tonic-gate {
930*7c478bd9Sstevel@tonic-gate   const char *pptr = pattern;  /* The pointer used to scan the pattern */
931*7c478bd9Sstevel@tonic-gate   int invert = 0;              /* True to invert the sense of the match */
932*7c478bd9Sstevel@tonic-gate   int matched = 0;             /* True if the character matched the pattern */
933*7c478bd9Sstevel@tonic-gate /*
934*7c478bd9Sstevel@tonic-gate  * If the first character is a caret, the sense of the match is
935*7c478bd9Sstevel@tonic-gate  * inverted and only if the character isn't one of those in the
936*7c478bd9Sstevel@tonic-gate  * range, do we say that it matches.
937*7c478bd9Sstevel@tonic-gate  */
938*7c478bd9Sstevel@tonic-gate   if(*pptr == '^') {
939*7c478bd9Sstevel@tonic-gate     pptr++;
940*7c478bd9Sstevel@tonic-gate     invert = 1;
941*7c478bd9Sstevel@tonic-gate   };
942*7c478bd9Sstevel@tonic-gate /*
943*7c478bd9Sstevel@tonic-gate  * The hyphen is only a special character when it follows the first
944*7c478bd9Sstevel@tonic-gate  * character of the range (not including the caret).
945*7c478bd9Sstevel@tonic-gate  */
946*7c478bd9Sstevel@tonic-gate   if(*pptr == '-') {
947*7c478bd9Sstevel@tonic-gate     pptr++;
948*7c478bd9Sstevel@tonic-gate     if(c == '-') {
949*7c478bd9Sstevel@tonic-gate       *endp = pptr;
950*7c478bd9Sstevel@tonic-gate       matched = 1;
951*7c478bd9Sstevel@tonic-gate     };
952*7c478bd9Sstevel@tonic-gate /*
953*7c478bd9Sstevel@tonic-gate  * Skip other leading '-' characters since they make no sense.
954*7c478bd9Sstevel@tonic-gate  */
955*7c478bd9Sstevel@tonic-gate     while(*pptr == '-')
956*7c478bd9Sstevel@tonic-gate       pptr++;
957*7c478bd9Sstevel@tonic-gate   };
958*7c478bd9Sstevel@tonic-gate /*
959*7c478bd9Sstevel@tonic-gate  * The hyphen is only a special character when it follows the first
960*7c478bd9Sstevel@tonic-gate  * character of the range (not including the caret or a hyphen).
961*7c478bd9Sstevel@tonic-gate  */
962*7c478bd9Sstevel@tonic-gate   if(*pptr == ']') {
963*7c478bd9Sstevel@tonic-gate     pptr++;
964*7c478bd9Sstevel@tonic-gate     if(c == ']') {
965*7c478bd9Sstevel@tonic-gate       *endp = pptr;
966*7c478bd9Sstevel@tonic-gate       matched = 1;
967*7c478bd9Sstevel@tonic-gate     };
968*7c478bd9Sstevel@tonic-gate   };
969*7c478bd9Sstevel@tonic-gate /*
970*7c478bd9Sstevel@tonic-gate  * Having dealt with the characters that have special meanings at
971*7c478bd9Sstevel@tonic-gate  * the beginning of a character range expression, see if the
972*7c478bd9Sstevel@tonic-gate  * character matches any of the remaining characters of the range,
973*7c478bd9Sstevel@tonic-gate  * up until a terminating ']' character is seen.
974*7c478bd9Sstevel@tonic-gate  */
975*7c478bd9Sstevel@tonic-gate   while(!matched && *pptr && *pptr != ']') {
976*7c478bd9Sstevel@tonic-gate /*
977*7c478bd9Sstevel@tonic-gate  * Is this a range of characters signaled by the two end characters
978*7c478bd9Sstevel@tonic-gate  * separated by a hyphen?
979*7c478bd9Sstevel@tonic-gate  */
980*7c478bd9Sstevel@tonic-gate     if(*pptr == '-') {
981*7c478bd9Sstevel@tonic-gate       if(pptr[1] != ']') {
982*7c478bd9Sstevel@tonic-gate         if(c >= pptr[-1] && c <= pptr[1])
983*7c478bd9Sstevel@tonic-gate 	  matched = 1;
984*7c478bd9Sstevel@tonic-gate 	pptr += 2;
985*7c478bd9Sstevel@tonic-gate       };
986*7c478bd9Sstevel@tonic-gate /*
987*7c478bd9Sstevel@tonic-gate  * A normal character to be compared directly.
988*7c478bd9Sstevel@tonic-gate  */
989*7c478bd9Sstevel@tonic-gate     } else if(*pptr++ == c) {
990*7c478bd9Sstevel@tonic-gate       matched = 1;
991*7c478bd9Sstevel@tonic-gate     };
992*7c478bd9Sstevel@tonic-gate   };
993*7c478bd9Sstevel@tonic-gate /*
994*7c478bd9Sstevel@tonic-gate  * Find the terminating ']'.
995*7c478bd9Sstevel@tonic-gate  */
996*7c478bd9Sstevel@tonic-gate   while(*pptr && *pptr != ']')
997*7c478bd9Sstevel@tonic-gate     pptr++;
998*7c478bd9Sstevel@tonic-gate /*
999*7c478bd9Sstevel@tonic-gate  * Did we find a terminating ']'?
1000*7c478bd9Sstevel@tonic-gate  */
1001*7c478bd9Sstevel@tonic-gate   if(*pptr == ']') {
1002*7c478bd9Sstevel@tonic-gate     *endp = pptr + 1;
1003*7c478bd9Sstevel@tonic-gate     return matched ? !invert : invert;
1004*7c478bd9Sstevel@tonic-gate   };
1005*7c478bd9Sstevel@tonic-gate /*
1006*7c478bd9Sstevel@tonic-gate  * If the pattern didn't end with a ']' then it doesn't match, regardless
1007*7c478bd9Sstevel@tonic-gate  * of the value of the required sense of the match.
1008*7c478bd9Sstevel@tonic-gate  */
1009*7c478bd9Sstevel@tonic-gate   *endp = pptr;
1010*7c478bd9Sstevel@tonic-gate   return 0;
1011*7c478bd9Sstevel@tonic-gate }
1012*7c478bd9Sstevel@tonic-gate 
1013*7c478bd9Sstevel@tonic-gate /*.......................................................................
1014*7c478bd9Sstevel@tonic-gate  * This is a qsort() comparison function used to sort strings.
1015*7c478bd9Sstevel@tonic-gate  *
1016*7c478bd9Sstevel@tonic-gate  * Input:
1017*7c478bd9Sstevel@tonic-gate  *  v1, v2   void *  Pointers to the two strings to be compared.
1018*7c478bd9Sstevel@tonic-gate  * Output:
1019*7c478bd9Sstevel@tonic-gate  *  return    int    -1 -> v1 < v2.
1020*7c478bd9Sstevel@tonic-gate  *                    0 -> v1 == v2
1021*7c478bd9Sstevel@tonic-gate  *                    1 -> v1 > v2
1022*7c478bd9Sstevel@tonic-gate  */
1023*7c478bd9Sstevel@tonic-gate static int ef_cmp_strings(const void *v1, const void *v2)
1024*7c478bd9Sstevel@tonic-gate {
1025*7c478bd9Sstevel@tonic-gate   char * const *s1 = (char * const *) v1;
1026*7c478bd9Sstevel@tonic-gate   char * const *s2 = (char * const *) v2;
1027*7c478bd9Sstevel@tonic-gate   return strcmp(*s1, *s2);
1028*7c478bd9Sstevel@tonic-gate }
1029*7c478bd9Sstevel@tonic-gate 
1030*7c478bd9Sstevel@tonic-gate /*.......................................................................
1031*7c478bd9Sstevel@tonic-gate  * Preprocess a path, expanding ~/, ~user/ and $envvar references, using
1032*7c478bd9Sstevel@tonic-gate  * ef->path as a work buffer, then copy the result into a cache entry,
1033*7c478bd9Sstevel@tonic-gate  * and return a pointer to this copy.
1034*7c478bd9Sstevel@tonic-gate  *
1035*7c478bd9Sstevel@tonic-gate  * Input:
1036*7c478bd9Sstevel@tonic-gate  *  ef    ExpandFile *  The resource object of the file matcher.
1037*7c478bd9Sstevel@tonic-gate  *  pathlen      int    The length of the prefix of path[] to be expanded.
1038*7c478bd9Sstevel@tonic-gate  * Output:
1039*7c478bd9Sstevel@tonic-gate  *  return      char *  A pointer to a copy of the output path in the
1040*7c478bd9Sstevel@tonic-gate  *                      cache. On error NULL is returned, and a description
1041*7c478bd9Sstevel@tonic-gate  *                      of the error is left in ef->err.
1042*7c478bd9Sstevel@tonic-gate  */
1043*7c478bd9Sstevel@tonic-gate static char *ef_expand_special(ExpandFile *ef, const char *path, int pathlen)
1044*7c478bd9Sstevel@tonic-gate {
1045*7c478bd9Sstevel@tonic-gate   int spos;      /* The index of the start of the path segment that needs */
1046*7c478bd9Sstevel@tonic-gate                  /*  to be copied from path[] to the output pathname. */
1047*7c478bd9Sstevel@tonic-gate   int ppos;      /* The index of a character in path[] */
1048*7c478bd9Sstevel@tonic-gate   char *pptr;    /* A pointer into the output path */
1049*7c478bd9Sstevel@tonic-gate   int escaped;   /* True if the previous character was a '\' */
1050*7c478bd9Sstevel@tonic-gate   int i;
1051*7c478bd9Sstevel@tonic-gate /*
1052*7c478bd9Sstevel@tonic-gate  * Clear the pathname buffer.
1053*7c478bd9Sstevel@tonic-gate  */
1054*7c478bd9Sstevel@tonic-gate   _pn_clear_path(ef->path);
1055*7c478bd9Sstevel@tonic-gate /*
1056*7c478bd9Sstevel@tonic-gate  * We need to perform two passes, one to expand environment variables
1057*7c478bd9Sstevel@tonic-gate  * and a second to do tilde expansion. This caters for the case
1058*7c478bd9Sstevel@tonic-gate  * where an initial dollar expansion yields a tilde expression.
1059*7c478bd9Sstevel@tonic-gate  */
1060*7c478bd9Sstevel@tonic-gate   escaped = 0;
1061*7c478bd9Sstevel@tonic-gate   for(spos=ppos=0; ppos < pathlen; ppos++) {
1062*7c478bd9Sstevel@tonic-gate     int c = path[ppos];
1063*7c478bd9Sstevel@tonic-gate     if(escaped) {
1064*7c478bd9Sstevel@tonic-gate       escaped = 0;
1065*7c478bd9Sstevel@tonic-gate     } else if(c == '\\') {
1066*7c478bd9Sstevel@tonic-gate       escaped = 1;
1067*7c478bd9Sstevel@tonic-gate     } else if(c == '$') {
1068*7c478bd9Sstevel@tonic-gate       int envlen;   /* The length of the environment variable */
1069*7c478bd9Sstevel@tonic-gate       char *value;  /* The value of the environment variable */
1070*7c478bd9Sstevel@tonic-gate /*
1071*7c478bd9Sstevel@tonic-gate  * Record the preceding unrecorded part of the pathname.
1072*7c478bd9Sstevel@tonic-gate  */
1073*7c478bd9Sstevel@tonic-gate       if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0)
1074*7c478bd9Sstevel@tonic-gate 	 == NULL) {
1075*7c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Insufficient memory to expand path",
1076*7c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
1077*7c478bd9Sstevel@tonic-gate 	return NULL;
1078*7c478bd9Sstevel@tonic-gate       };
1079*7c478bd9Sstevel@tonic-gate /*
1080*7c478bd9Sstevel@tonic-gate  * Skip the dollar.
1081*7c478bd9Sstevel@tonic-gate  */
1082*7c478bd9Sstevel@tonic-gate       ppos++;
1083*7c478bd9Sstevel@tonic-gate /*
1084*7c478bd9Sstevel@tonic-gate  * Copy the environment variable name that follows the dollar into
1085*7c478bd9Sstevel@tonic-gate  * ef->envnam[], stopping if a directory separator or end of string
1086*7c478bd9Sstevel@tonic-gate  * is seen.
1087*7c478bd9Sstevel@tonic-gate  */
1088*7c478bd9Sstevel@tonic-gate       for(envlen=0; envlen<ENV_LEN && ppos < pathlen &&
1089*7c478bd9Sstevel@tonic-gate 	  strncmp(path + ppos, FS_DIR_SEP, FS_DIR_SEP_LEN); envlen++)
1090*7c478bd9Sstevel@tonic-gate 	ef->envnam[envlen] = path[ppos++];
1091*7c478bd9Sstevel@tonic-gate /*
1092*7c478bd9Sstevel@tonic-gate  * If the username overflowed the buffer, treat it as invalid (note that
1093*7c478bd9Sstevel@tonic-gate  * on most unix systems only 8 characters are allowed in a username,
1094*7c478bd9Sstevel@tonic-gate  * whereas our ENV_LEN is much bigger than that.
1095*7c478bd9Sstevel@tonic-gate  */
1096*7c478bd9Sstevel@tonic-gate       if(envlen >= ENV_LEN) {
1097*7c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Environment variable name too long",
1098*7c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
1099*7c478bd9Sstevel@tonic-gate 	return NULL;
1100*7c478bd9Sstevel@tonic-gate       };
1101*7c478bd9Sstevel@tonic-gate /*
1102*7c478bd9Sstevel@tonic-gate  * Terminate the environment variable name.
1103*7c478bd9Sstevel@tonic-gate  */
1104*7c478bd9Sstevel@tonic-gate       ef->envnam[envlen] = '\0';
1105*7c478bd9Sstevel@tonic-gate /*
1106*7c478bd9Sstevel@tonic-gate  * Lookup the value of the environment variable.
1107*7c478bd9Sstevel@tonic-gate  */
1108*7c478bd9Sstevel@tonic-gate       value = getenv(ef->envnam);
1109*7c478bd9Sstevel@tonic-gate       if(!value) {
1110*7c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "No expansion found for: $", ef->envnam,
1111*7c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
1112*7c478bd9Sstevel@tonic-gate 	return NULL;
1113*7c478bd9Sstevel@tonic-gate       };
1114*7c478bd9Sstevel@tonic-gate /*
1115*7c478bd9Sstevel@tonic-gate  * Copy the value of the environment variable into the output pathname.
1116*7c478bd9Sstevel@tonic-gate  */
1117*7c478bd9Sstevel@tonic-gate       if(_pn_append_to_path(ef->path, value, -1, 0) == NULL) {
1118*7c478bd9Sstevel@tonic-gate 	_err_record_msg(ef->err, "Insufficient memory to expand path",
1119*7c478bd9Sstevel@tonic-gate 			END_ERR_MSG);
1120*7c478bd9Sstevel@tonic-gate 	return NULL;
1121*7c478bd9Sstevel@tonic-gate       };
1122*7c478bd9Sstevel@tonic-gate /*
1123*7c478bd9Sstevel@tonic-gate  * Record the start of the uncopied tail of the input pathname.
1124*7c478bd9Sstevel@tonic-gate  */
1125*7c478bd9Sstevel@tonic-gate       spos = ppos;
1126*7c478bd9Sstevel@tonic-gate     };
1127*7c478bd9Sstevel@tonic-gate   };
1128*7c478bd9Sstevel@tonic-gate /*
1129*7c478bd9Sstevel@tonic-gate  * Record the uncopied tail of the pathname.
1130*7c478bd9Sstevel@tonic-gate  */
1131*7c478bd9Sstevel@tonic-gate   if(spos < ppos && _pn_append_to_path(ef->path, path + spos, ppos-spos, 0)
1132*7c478bd9Sstevel@tonic-gate      == NULL) {
1133*7c478bd9Sstevel@tonic-gate     _err_record_msg(ef->err, "Insufficient memory to expand path", END_ERR_MSG);
1134*7c478bd9Sstevel@tonic-gate     return NULL;
1135*7c478bd9Sstevel@tonic-gate   };
1136*7c478bd9Sstevel@tonic-gate /*
1137*7c478bd9Sstevel@tonic-gate  * If the first character of the resulting pathname is a tilde,
1138*7c478bd9Sstevel@tonic-gate  * then attempt to substitute the home directory of the specified user.
1139*7c478bd9Sstevel@tonic-gate  */
1140*7c478bd9Sstevel@tonic-gate   pptr = ef->path->name;
1141*7c478bd9Sstevel@tonic-gate   if(*pptr == '~' && path[0] != '\\') {
1142*7c478bd9Sstevel@tonic-gate     int usrlen;           /* The length of the username following the tilde */
1143*7c478bd9Sstevel@tonic-gate     const char *homedir;  /* The home directory of the user */
1144*7c478bd9Sstevel@tonic-gate     int homelen;          /* The length of the home directory string */
1145*7c478bd9Sstevel@tonic-gate     int plen;             /* The current length of the path */
1146*7c478bd9Sstevel@tonic-gate     int skip=0;           /* The number of characters to skip after the ~user */
1147*7c478bd9Sstevel@tonic-gate /*
1148*7c478bd9Sstevel@tonic-gate  * Get the current length of the output path.
1149*7c478bd9Sstevel@tonic-gate  */
1150*7c478bd9Sstevel@tonic-gate     plen = strlen(ef->path->name);
1151*7c478bd9Sstevel@tonic-gate /*
1152*7c478bd9Sstevel@tonic-gate  * Skip the tilde.
1153*7c478bd9Sstevel@tonic-gate  */
1154*7c478bd9Sstevel@tonic-gate     pptr++;
1155*7c478bd9Sstevel@tonic-gate /*
1156*7c478bd9Sstevel@tonic-gate  * Copy the optional username that follows the tilde into ef->usrnam[].
1157*7c478bd9Sstevel@tonic-gate  */
1158*7c478bd9Sstevel@tonic-gate     for(usrlen=0; usrlen<USR_LEN && *pptr &&
1159*7c478bd9Sstevel@tonic-gate 	strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN); usrlen++)
1160*7c478bd9Sstevel@tonic-gate       ef->usrnam[usrlen] = *pptr++;
1161*7c478bd9Sstevel@tonic-gate /*
1162*7c478bd9Sstevel@tonic-gate  * If the username overflowed the buffer, treat it as invalid (note that
1163*7c478bd9Sstevel@tonic-gate  * on most unix systems only 8 characters are allowed in a username,
1164*7c478bd9Sstevel@tonic-gate  * whereas our USR_LEN is much bigger than that.
1165*7c478bd9Sstevel@tonic-gate  */
1166*7c478bd9Sstevel@tonic-gate     if(usrlen >= USR_LEN) {
1167*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Username too long", END_ERR_MSG);
1168*7c478bd9Sstevel@tonic-gate       return NULL;
1169*7c478bd9Sstevel@tonic-gate     };
1170*7c478bd9Sstevel@tonic-gate /*
1171*7c478bd9Sstevel@tonic-gate  * Terminate the username string.
1172*7c478bd9Sstevel@tonic-gate  */
1173*7c478bd9Sstevel@tonic-gate     ef->usrnam[usrlen] = '\0';
1174*7c478bd9Sstevel@tonic-gate /*
1175*7c478bd9Sstevel@tonic-gate  * Lookup the home directory of the user.
1176*7c478bd9Sstevel@tonic-gate  */
1177*7c478bd9Sstevel@tonic-gate     homedir = _hd_lookup_home_dir(ef->home, ef->usrnam);
1178*7c478bd9Sstevel@tonic-gate     if(!homedir) {
1179*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, _hd_last_home_dir_error(ef->home), END_ERR_MSG);
1180*7c478bd9Sstevel@tonic-gate       return NULL;
1181*7c478bd9Sstevel@tonic-gate     };
1182*7c478bd9Sstevel@tonic-gate     homelen = strlen(homedir);
1183*7c478bd9Sstevel@tonic-gate /*
1184*7c478bd9Sstevel@tonic-gate  * ~user and ~ are usually followed by a directory separator to
1185*7c478bd9Sstevel@tonic-gate  * separate them from the file contained in the home directory.
1186*7c478bd9Sstevel@tonic-gate  * If the home directory is the root directory, then we don't want
1187*7c478bd9Sstevel@tonic-gate  * to follow the home directory by a directory separator, so we must
1188*7c478bd9Sstevel@tonic-gate  * erase it.
1189*7c478bd9Sstevel@tonic-gate  */
1190*7c478bd9Sstevel@tonic-gate     if(strcmp(homedir, FS_ROOT_DIR) == 0 &&
1191*7c478bd9Sstevel@tonic-gate        strncmp(pptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) {
1192*7c478bd9Sstevel@tonic-gate       skip = FS_DIR_SEP_LEN;
1193*7c478bd9Sstevel@tonic-gate     };
1194*7c478bd9Sstevel@tonic-gate /*
1195*7c478bd9Sstevel@tonic-gate  * If needed, increase the size of the pathname buffer to allow it
1196*7c478bd9Sstevel@tonic-gate  * to accomodate the home directory instead of the tilde expression.
1197*7c478bd9Sstevel@tonic-gate  * Note that pptr may not be valid after this call.
1198*7c478bd9Sstevel@tonic-gate  */
1199*7c478bd9Sstevel@tonic-gate     if(_pn_resize_path(ef->path, plen - usrlen - 1 - skip + homelen)==NULL) {
1200*7c478bd9Sstevel@tonic-gate       _err_record_msg(ef->err, "Insufficient memory to expand filename",
1201*7c478bd9Sstevel@tonic-gate 		      END_ERR_MSG);
1202*7c478bd9Sstevel@tonic-gate       return NULL;
1203*7c478bd9Sstevel@tonic-gate     };
1204*7c478bd9Sstevel@tonic-gate /*
1205*7c478bd9Sstevel@tonic-gate  * Move the part of the pathname that follows the tilde expression to
1206*7c478bd9Sstevel@tonic-gate  * the end of where the home directory will need to be inserted.
1207*7c478bd9Sstevel@tonic-gate  */
1208*7c478bd9Sstevel@tonic-gate     memmove(ef->path->name + homelen,
1209*7c478bd9Sstevel@tonic-gate 	    ef->path->name + 1 + usrlen + skip, plen - usrlen - 1 - skip+1);
1210*7c478bd9Sstevel@tonic-gate /*
1211*7c478bd9Sstevel@tonic-gate  * Write the home directory at the beginning of the string.
1212*7c478bd9Sstevel@tonic-gate  */
1213*7c478bd9Sstevel@tonic-gate     for(i=0; i<homelen; i++)
1214*7c478bd9Sstevel@tonic-gate       ef->path->name[i] = homedir[i];
1215*7c478bd9Sstevel@tonic-gate   };
1216*7c478bd9Sstevel@tonic-gate /*
1217*7c478bd9Sstevel@tonic-gate  * Copy the result into the cache, and return a pointer to the copy.
1218*7c478bd9Sstevel@tonic-gate  */
1219*7c478bd9Sstevel@tonic-gate   return ef_cache_pathname(ef, ef->path->name, 0);
1220*7c478bd9Sstevel@tonic-gate }
1221*7c478bd9Sstevel@tonic-gate 
1222*7c478bd9Sstevel@tonic-gate /*.......................................................................
1223*7c478bd9Sstevel@tonic-gate  * Return a description of the last path-expansion error that occurred.
1224*7c478bd9Sstevel@tonic-gate  *
1225*7c478bd9Sstevel@tonic-gate  * Input:
1226*7c478bd9Sstevel@tonic-gate  *  ef     ExpandFile *   The path-expansion resource object.
1227*7c478bd9Sstevel@tonic-gate  * Output:
1228*7c478bd9Sstevel@tonic-gate  *  return       char *   The description of the last error.
1229*7c478bd9Sstevel@tonic-gate  */
1230*7c478bd9Sstevel@tonic-gate const char *ef_last_error(ExpandFile *ef)
1231*7c478bd9Sstevel@tonic-gate {
1232*7c478bd9Sstevel@tonic-gate   return ef ? _err_get_msg(ef->err) : "NULL ExpandFile argument";
1233*7c478bd9Sstevel@tonic-gate }
1234*7c478bd9Sstevel@tonic-gate 
1235*7c478bd9Sstevel@tonic-gate /*.......................................................................
1236*7c478bd9Sstevel@tonic-gate  * Print out an array of matching files.
1237*7c478bd9Sstevel@tonic-gate  *
1238*7c478bd9Sstevel@tonic-gate  * Input:
1239*7c478bd9Sstevel@tonic-gate  *  result  FileExpansion *   The container of the sorted array of
1240*7c478bd9Sstevel@tonic-gate  *                            expansions.
1241*7c478bd9Sstevel@tonic-gate  *  fp               FILE *   The output stream to write to.
1242*7c478bd9Sstevel@tonic-gate  *  term_width        int     The width of the terminal.
1243*7c478bd9Sstevel@tonic-gate  * Output:
1244*7c478bd9Sstevel@tonic-gate  *  return            int     0 - OK.
1245*7c478bd9Sstevel@tonic-gate  *                            1 - Error.
1246*7c478bd9Sstevel@tonic-gate  */
1247*7c478bd9Sstevel@tonic-gate int ef_list_expansions(FileExpansion *result, FILE *fp, int term_width)
1248*7c478bd9Sstevel@tonic-gate {
1249*7c478bd9Sstevel@tonic-gate   return _ef_output_expansions(result, _io_write_stdio, fp, term_width);
1250*7c478bd9Sstevel@tonic-gate }
1251*7c478bd9Sstevel@tonic-gate 
1252*7c478bd9Sstevel@tonic-gate /*.......................................................................
1253*7c478bd9Sstevel@tonic-gate  * Print out an array of matching files via a callback.
1254*7c478bd9Sstevel@tonic-gate  *
1255*7c478bd9Sstevel@tonic-gate  * Input:
1256*7c478bd9Sstevel@tonic-gate  *  result  FileExpansion *  The container of the sorted array of
1257*7c478bd9Sstevel@tonic-gate  *                           expansions.
1258*7c478bd9Sstevel@tonic-gate  *  write_fn    GlWriteFn *  The function to call to write the
1259*7c478bd9Sstevel@tonic-gate  *                           expansions or 0 to discard the output.
1260*7c478bd9Sstevel@tonic-gate  *  data             void *  Anonymous data to pass to write_fn().
1261*7c478bd9Sstevel@tonic-gate  *  term_width        int    The width of the terminal.
1262*7c478bd9Sstevel@tonic-gate  * Output:
1263*7c478bd9Sstevel@tonic-gate  *  return            int    0 - OK.
1264*7c478bd9Sstevel@tonic-gate  *                           1 - Error.
1265*7c478bd9Sstevel@tonic-gate  */
1266*7c478bd9Sstevel@tonic-gate int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn,
1267*7c478bd9Sstevel@tonic-gate 			  void *data, int term_width)
1268*7c478bd9Sstevel@tonic-gate {
1269*7c478bd9Sstevel@tonic-gate   EfListFormat fmt; /* List formatting information */
1270*7c478bd9Sstevel@tonic-gate   int lnum;          /* The sequential number of the line to print next */
1271*7c478bd9Sstevel@tonic-gate /*
1272*7c478bd9Sstevel@tonic-gate  * Not enough space to list anything?
1273*7c478bd9Sstevel@tonic-gate  */
1274*7c478bd9Sstevel@tonic-gate   if(term_width < 1)
1275*7c478bd9Sstevel@tonic-gate     return 0;
1276*7c478bd9Sstevel@tonic-gate /*
1277*7c478bd9Sstevel@tonic-gate  * Do we have a callback to write via, and any expansions to be listed?
1278*7c478bd9Sstevel@tonic-gate  */
1279*7c478bd9Sstevel@tonic-gate   if(write_fn && result && result->nfile>0) {
1280*7c478bd9Sstevel@tonic-gate /*
1281*7c478bd9Sstevel@tonic-gate  * Work out how to arrange the listing into fixed sized columns.
1282*7c478bd9Sstevel@tonic-gate  */
1283*7c478bd9Sstevel@tonic-gate     ef_plan_listing(result, term_width, &fmt);
1284*7c478bd9Sstevel@tonic-gate /*
1285*7c478bd9Sstevel@tonic-gate  * Print the listing to the specified stream.
1286*7c478bd9Sstevel@tonic-gate  */
1287*7c478bd9Sstevel@tonic-gate     for(lnum=0; lnum < fmt.nline; lnum++) {
1288*7c478bd9Sstevel@tonic-gate       if(ef_format_line(result, &fmt, lnum, write_fn, data))
1289*7c478bd9Sstevel@tonic-gate 	return 1;
1290*7c478bd9Sstevel@tonic-gate     };
1291*7c478bd9Sstevel@tonic-gate   };
1292*7c478bd9Sstevel@tonic-gate   return 0;
1293*7c478bd9Sstevel@tonic-gate }
1294*7c478bd9Sstevel@tonic-gate 
1295*7c478bd9Sstevel@tonic-gate /*.......................................................................
1296*7c478bd9Sstevel@tonic-gate  * Work out how to arrange a given array of completions into a listing
1297*7c478bd9Sstevel@tonic-gate  * of one or more fixed size columns.
1298*7c478bd9Sstevel@tonic-gate  *
1299*7c478bd9Sstevel@tonic-gate  * Input:
1300*7c478bd9Sstevel@tonic-gate  *  result   FileExpansion *   The set of completions to be listed.
1301*7c478bd9Sstevel@tonic-gate  *  term_width         int     The width of the terminal. A lower limit of
1302*7c478bd9Sstevel@tonic-gate  *                             zero is quietly enforced.
1303*7c478bd9Sstevel@tonic-gate  * Input/Output:
1304*7c478bd9Sstevel@tonic-gate  *  fmt       EfListFormat *   The formatting information will be assigned
1305*7c478bd9Sstevel@tonic-gate  *                             to the members of *fmt.
1306*7c478bd9Sstevel@tonic-gate  */
1307*7c478bd9Sstevel@tonic-gate static void ef_plan_listing(FileExpansion *result, int term_width,
1308*7c478bd9Sstevel@tonic-gate 			    EfListFormat *fmt)
1309*7c478bd9Sstevel@tonic-gate {
1310*7c478bd9Sstevel@tonic-gate   int maxlen;    /* The length of the longest matching string */
1311*7c478bd9Sstevel@tonic-gate   int i;
1312*7c478bd9Sstevel@tonic-gate /*
1313*7c478bd9Sstevel@tonic-gate  * Ensure that term_width >= 0.
1314*7c478bd9Sstevel@tonic-gate  */
1315*7c478bd9Sstevel@tonic-gate   if(term_width < 0)
1316*7c478bd9Sstevel@tonic-gate     term_width = 0;
1317*7c478bd9Sstevel@tonic-gate /*
1318*7c478bd9Sstevel@tonic-gate  * Start by assuming the worst case, that either nothing will fit
1319*7c478bd9Sstevel@tonic-gate  * on the screen, or that there are no matches to be listed.
1320*7c478bd9Sstevel@tonic-gate  */
1321*7c478bd9Sstevel@tonic-gate   fmt->term_width = term_width;
1322*7c478bd9Sstevel@tonic-gate   fmt->column_width = 0;
1323*7c478bd9Sstevel@tonic-gate   fmt->nline = fmt->ncol = 0;
1324*7c478bd9Sstevel@tonic-gate /*
1325*7c478bd9Sstevel@tonic-gate  * Work out the maximum length of the matching strings.
1326*7c478bd9Sstevel@tonic-gate  */
1327*7c478bd9Sstevel@tonic-gate   maxlen = 0;
1328*7c478bd9Sstevel@tonic-gate   for(i=0; i<result->nfile; i++) {
1329*7c478bd9Sstevel@tonic-gate     int len = strlen(result->files[i]);
1330*7c478bd9Sstevel@tonic-gate     if(len > maxlen)
1331*7c478bd9Sstevel@tonic-gate       maxlen = len;
1332*7c478bd9Sstevel@tonic-gate   };
1333*7c478bd9Sstevel@tonic-gate /*
1334*7c478bd9Sstevel@tonic-gate  * Nothing to list?
1335*7c478bd9Sstevel@tonic-gate  */
1336*7c478bd9Sstevel@tonic-gate   if(maxlen == 0)
1337*7c478bd9Sstevel@tonic-gate     return;
1338*7c478bd9Sstevel@tonic-gate /*
1339*7c478bd9Sstevel@tonic-gate  * Split the available terminal width into columns of
1340*7c478bd9Sstevel@tonic-gate  * maxlen + EF_COL_SEP characters.
1341*7c478bd9Sstevel@tonic-gate  */
1342*7c478bd9Sstevel@tonic-gate   fmt->column_width = maxlen;
1343*7c478bd9Sstevel@tonic-gate   fmt->ncol = fmt->term_width / (fmt->column_width + EF_COL_SEP);
1344*7c478bd9Sstevel@tonic-gate /*
1345*7c478bd9Sstevel@tonic-gate  * If the column width is greater than the terminal width, zero columns
1346*7c478bd9Sstevel@tonic-gate  * will have been selected. Set a lower limit of one column. Leave it
1347*7c478bd9Sstevel@tonic-gate  * up to the caller how to deal with completions who's widths exceed
1348*7c478bd9Sstevel@tonic-gate  * the available terminal width.
1349*7c478bd9Sstevel@tonic-gate  */
1350*7c478bd9Sstevel@tonic-gate   if(fmt->ncol < 1)
1351*7c478bd9Sstevel@tonic-gate     fmt->ncol = 1;
1352*7c478bd9Sstevel@tonic-gate /*
1353*7c478bd9Sstevel@tonic-gate  * How many lines of output will be needed?
1354*7c478bd9Sstevel@tonic-gate  */
1355*7c478bd9Sstevel@tonic-gate   fmt->nline = (result->nfile + fmt->ncol - 1) / fmt->ncol;
1356*7c478bd9Sstevel@tonic-gate   return;
1357*7c478bd9Sstevel@tonic-gate }
1358*7c478bd9Sstevel@tonic-gate 
1359*7c478bd9Sstevel@tonic-gate /*.......................................................................
1360*7c478bd9Sstevel@tonic-gate  * Render one line of a multi-column listing of completions, using a
1361*7c478bd9Sstevel@tonic-gate  * callback function to pass the output to an arbitrary destination.
1362*7c478bd9Sstevel@tonic-gate  *
1363*7c478bd9Sstevel@tonic-gate  * Input:
1364*7c478bd9Sstevel@tonic-gate  *  result   FileExpansion *  The container of the sorted array of
1365*7c478bd9Sstevel@tonic-gate  *                            completions.
1366*7c478bd9Sstevel@tonic-gate  *  fmt       EfListFormat *  Formatting information.
1367*7c478bd9Sstevel@tonic-gate  *  lnum               int    The index of the line to print, starting
1368*7c478bd9Sstevel@tonic-gate  *                            from 0, and incrementing until the return
1369*7c478bd9Sstevel@tonic-gate  *                            value indicates that there is nothing more
1370*7c478bd9Sstevel@tonic-gate  *                            to be printed.
1371*7c478bd9Sstevel@tonic-gate  *  write_fn     GlWriteFn *  The function to call to write the line, or
1372*7c478bd9Sstevel@tonic-gate  *                            0 to discard the output.
1373*7c478bd9Sstevel@tonic-gate  *  data              void *  Anonymous data to pass to write_fn().
1374*7c478bd9Sstevel@tonic-gate  * Output:
1375*7c478bd9Sstevel@tonic-gate  *  return             int    0 - Line printed ok.
1376*7c478bd9Sstevel@tonic-gate  *                            1 - Nothing to print.
1377*7c478bd9Sstevel@tonic-gate  */
1378*7c478bd9Sstevel@tonic-gate static int ef_format_line(FileExpansion *result, EfListFormat *fmt, int lnum,
1379*7c478bd9Sstevel@tonic-gate 			  GlWriteFn *write_fn, void *data)
1380*7c478bd9Sstevel@tonic-gate {
1381*7c478bd9Sstevel@tonic-gate   int col;             /* The index of the list column being output */
1382*7c478bd9Sstevel@tonic-gate /*
1383*7c478bd9Sstevel@tonic-gate  * If the line index is out of bounds, there is nothing to be written.
1384*7c478bd9Sstevel@tonic-gate  */
1385*7c478bd9Sstevel@tonic-gate   if(lnum < 0 || lnum >= fmt->nline)
1386*7c478bd9Sstevel@tonic-gate     return 1;
1387*7c478bd9Sstevel@tonic-gate /*
1388*7c478bd9Sstevel@tonic-gate  * If no output function has been provided, return as though the line
1389*7c478bd9Sstevel@tonic-gate  * had been printed.
1390*7c478bd9Sstevel@tonic-gate  */
1391*7c478bd9Sstevel@tonic-gate   if(!write_fn)
1392*7c478bd9Sstevel@tonic-gate     return 0;
1393*7c478bd9Sstevel@tonic-gate /*
1394*7c478bd9Sstevel@tonic-gate  * Print the matches in 'ncol' columns, sorted in line order within each
1395*7c478bd9Sstevel@tonic-gate  * column.
1396*7c478bd9Sstevel@tonic-gate  */
1397*7c478bd9Sstevel@tonic-gate   for(col=0; col < fmt->ncol; col++) {
1398*7c478bd9Sstevel@tonic-gate     int m = col*fmt->nline + lnum;
1399*7c478bd9Sstevel@tonic-gate /*
1400*7c478bd9Sstevel@tonic-gate  * Is there another match to be written? Note that in general
1401*7c478bd9Sstevel@tonic-gate  * the last line of a listing will have fewer filled columns
1402*7c478bd9Sstevel@tonic-gate  * than the initial lines.
1403*7c478bd9Sstevel@tonic-gate  */
1404*7c478bd9Sstevel@tonic-gate     if(m < result->nfile) {
1405*7c478bd9Sstevel@tonic-gate       char *file = result->files[m];
1406*7c478bd9Sstevel@tonic-gate /*
1407*7c478bd9Sstevel@tonic-gate  * How long are the completion and type-suffix strings?
1408*7c478bd9Sstevel@tonic-gate  */
1409*7c478bd9Sstevel@tonic-gate       int flen = strlen(file);
1410*7c478bd9Sstevel@tonic-gate /*
1411*7c478bd9Sstevel@tonic-gate  * Write the completion string.
1412*7c478bd9Sstevel@tonic-gate  */
1413*7c478bd9Sstevel@tonic-gate       if(write_fn(data, file, flen) != flen)
1414*7c478bd9Sstevel@tonic-gate 	return 1;
1415*7c478bd9Sstevel@tonic-gate /*
1416*7c478bd9Sstevel@tonic-gate  * If another column follows the current one, pad to its start with spaces.
1417*7c478bd9Sstevel@tonic-gate  */
1418*7c478bd9Sstevel@tonic-gate       if(col+1 < fmt->ncol) {
1419*7c478bd9Sstevel@tonic-gate /*
1420*7c478bd9Sstevel@tonic-gate  * The following constant string of spaces is used to pad the output.
1421*7c478bd9Sstevel@tonic-gate  */
1422*7c478bd9Sstevel@tonic-gate 	static const char spaces[] = "                    ";
1423*7c478bd9Sstevel@tonic-gate 	static const int nspace = sizeof(spaces) - 1;
1424*7c478bd9Sstevel@tonic-gate /*
1425*7c478bd9Sstevel@tonic-gate  * Pad to the next column, using as few sub-strings of the spaces[]
1426*7c478bd9Sstevel@tonic-gate  * array as possible.
1427*7c478bd9Sstevel@tonic-gate  */
1428*7c478bd9Sstevel@tonic-gate 	int npad = fmt->column_width + EF_COL_SEP - flen;
1429*7c478bd9Sstevel@tonic-gate 	while(npad>0) {
1430*7c478bd9Sstevel@tonic-gate 	  int n = npad > nspace ? nspace : npad;
1431*7c478bd9Sstevel@tonic-gate 	  if(write_fn(data, spaces + nspace - n, n) != n)
1432*7c478bd9Sstevel@tonic-gate 	    return 1;
1433*7c478bd9Sstevel@tonic-gate 	  npad -= n;
1434*7c478bd9Sstevel@tonic-gate 	};
1435*7c478bd9Sstevel@tonic-gate       };
1436*7c478bd9Sstevel@tonic-gate     };
1437*7c478bd9Sstevel@tonic-gate   };
1438*7c478bd9Sstevel@tonic-gate /*
1439*7c478bd9Sstevel@tonic-gate  * Start a new line.
1440*7c478bd9Sstevel@tonic-gate  */
1441*7c478bd9Sstevel@tonic-gate   {
1442*7c478bd9Sstevel@tonic-gate     char s[] = "\r\n";
1443*7c478bd9Sstevel@tonic-gate     int n = strlen(s);
1444*7c478bd9Sstevel@tonic-gate     if(write_fn(data, s, n) != n)
1445*7c478bd9Sstevel@tonic-gate       return 1;
1446*7c478bd9Sstevel@tonic-gate   };
1447*7c478bd9Sstevel@tonic-gate   return 0;
1448*7c478bd9Sstevel@tonic-gate }
1449*7c478bd9Sstevel@tonic-gate 
1450*7c478bd9Sstevel@tonic-gate #endif  /* ifndef WITHOUT_FILE_SYSTEM */
1451