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